This commit is contained in:
2025-08-03 11:36:22 +02:00
parent 5dbf0aacab
commit d691c1d944
14 changed files with 2153 additions and 81 deletions

18
.idea/dataSources.local.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="RR-251.26927.79">
<data-source name="db.sqlite" uuid="26059583-0fdb-4f6f-ad11-10388e9658c2">
<database-info product="SQLite" version="3.45.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.45.1.0" dbms="SQLITE" exact-version="3.45.1" exact-driver-version="3.45">
<identifier-quote-string>&quot;</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
<secret-storage>master_key</secret-storage>
<auth-provider>no-auth</auth-provider>
<schema-mapping>
<introspection-scope>
<node kind="schema" qname="@" />
</introspection-scope>
</schema-mapping>
</data-source>
</component>
</project>

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="db.sqlite" uuid="26059583-0fdb-4f6f-ad11-10388e9658c2">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db.sqlite</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
#n:main
!<md> [0, 0, null, null, -2147483648, -2147483648]

37
.idea/workspace.xml generated
View File

@@ -15,7 +15,18 @@
</cargoProject> </cargoProject>
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="ca698286-778f-4335-97c8-da35a666c986" name="Changes" comment="init" /> <list default="true" id="ca698286-778f-4335-97c8-da35a666c986" name="Changes" comment="init">
<change afterPath="$PROJECT_DIR$/src/network/http_routes/sub_server.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/http_routes/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http_routes/mod.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/repositories/channel_repository.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/repositories/channel_repository.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/repositories/link_sub_server_user_repository.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/repositories/link_sub_server_user_repository.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/repositories/message_repository.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/repositories/message_repository.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/repositories/sub_server_repository.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/repositories/sub_server_repository.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/repositories/user_repository.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/repositories/user_repository.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/store_service.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/store_service.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/utils/shared_store.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/shared_store.rs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -68,8 +79,13 @@
&quot;org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon&quot;: &quot;&quot;, &quot;org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon&quot;: &quot;&quot;,
&quot;org.rust.first.attach.projects&quot;: &quot;true&quot;, &quot;org.rust.first.attach.projects&quot;: &quot;true&quot;,
&quot;run.code.analysis.last.selected.profile&quot;: &quot;pProject Default&quot;, &quot;run.code.analysis.last.selected.profile&quot;: &quot;pProject Default&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;terminal&quot;, &quot;settings.editor.selected.configurable&quot;: &quot;ml.llm.LLMProjectConfigurable&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;sqlite&quot;
]
} }
}</component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
@@ -141,6 +157,19 @@
<workItem from="1753804642657" duration="236000" /> <workItem from="1753804642657" duration="236000" />
<workItem from="1753804898179" duration="625000" /> <workItem from="1753804898179" duration="625000" />
<workItem from="1753805533139" duration="2956000" /> <workItem from="1753805533139" duration="2956000" />
<workItem from="1753868593668" duration="550000" />
<workItem from="1753869165302" duration="389000" />
<workItem from="1753869606067" duration="448000" />
<workItem from="1753870068458" duration="3339000" />
<workItem from="1753975758350" duration="2576000" />
<workItem from="1754003476744" duration="2622000" />
<workItem from="1754006134163" duration="1746000" />
<workItem from="1754007911864" duration="414000" />
<workItem from="1754039613630" duration="3435000" />
<workItem from="1754131509073" duration="779000" />
<workItem from="1754132326149" duration="52000" />
<workItem from="1754132390785" duration="5048000" />
<workItem from="1754211612647" duration="2131000" />
</task> </task>
<task id="LOCAL-00001" summary="init"> <task id="LOCAL-00001" summary="init">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -199,4 +228,8 @@
<MESSAGE value="init" /> <MESSAGE value="init" />
<option name="LAST_COMMIT_MESSAGE" value="init" /> <option name="LAST_COMMIT_MESSAGE" value="init" />
</component> </component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />
<select />
</component>
</project> </project>

View File

@@ -3,3 +3,4 @@ pub mod channel;
pub mod message; pub mod message;
pub mod websocket; pub mod websocket;
pub mod master; pub mod master;
mod sub_server;

View File

View File

@@ -2,6 +2,7 @@ use std::sync::Arc;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use uuid::Uuid; use uuid::Uuid;
use crate::store::models::channel::Channel; use crate::store::models::channel::Channel;
use crate::store::models::Message;
use crate::store::store_service::StoreService; use crate::store::store_service::StoreService;
use crate::utils::shared_store::SharedArcMap; use crate::utils::shared_store::SharedArcMap;
@@ -71,7 +72,7 @@ impl ChannelRepository {
} }
pub async fn delete(&self, id: Uuid) -> Result<bool, sqlx::Error> { pub async fn delete(&self, id: Uuid) -> Result<bool, sqlx::Error> {
let rows_affected = sqlx::query("DELETE FROM sub_server WHERE id = ?") let rows_affected = sqlx::query("DELETE FROM channel WHERE id = ?")
.bind(&id) .bind(&id)
.execute(&self.store.db) .execute(&self.store.db)
.await? .await?
@@ -84,17 +85,13 @@ impl ChannelRepository {
Ok(false) Ok(false)
} }
} }
}
// Pour initialiser le cache depuis la DB impl ChannelRepository {
pub async fn load_all_from_db(&self) -> Result<(), sqlx::Error> { // getters (db)
let servers: Vec<Channel> = sqlx::query_as("SELECT * FROM sub_server") pub async fn db_all(&self) -> Result<Vec<Channel>, sqlx::Error> {
sqlx::query_as("SELECT * FROM channel")
.fetch_all(&self.store.db) .fetch_all(&self.store.db)
.await?; .await
for server in servers {
self.store.channels.insert(server.id, server);
}
Ok(())
} }
} }

View File

@@ -63,5 +63,13 @@ impl LinkSubServerUserRepository {
Ok(through) Ok(through)
} }
}
impl LinkSubServerUserRepository {
// getters (db)
pub async fn db_all(&self) -> Result<Vec<LinkSubServerUser>, sqlx::Error> {
sqlx::query_as("SELECT * FROM sub_server_users")
.fetch_all(&self.store.db)
.await
}
} }

View File

@@ -18,8 +18,7 @@ impl MessageRepository {
} }
impl MessageRepository { impl MessageRepository {
// getters // getters (caches)
} }
impl MessageRepository { impl MessageRepository {
@@ -50,7 +49,6 @@ impl MessageRepository {
.bind(&message.id) .bind(&message.id)
.execute(&self.store.db) .execute(&self.store.db)
.await?; .await?;
Ok(()) Ok(())
} }
@@ -68,3 +66,12 @@ impl MessageRepository {
} }
} }
} }
impl MessageRepository {
// getters (db)
pub async fn db_all(&self) -> Result<Vec<Message>, sqlx::Error> {
sqlx::query_as("SELECT * FROM message")
.fetch_all(&self.store.db)
.await
}
}

View File

@@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use uuid::Uuid; use uuid::Uuid;
use crate::store::models::Channel;
use crate::store::models::sub_server::SubServer; use crate::store::models::sub_server::SubServer;
use crate::store::store_service::StoreService; use crate::store::store_service::StoreService;
use crate::utils::shared_store::SharedArcMap; use crate::utils::shared_store::SharedArcMap;
@@ -79,17 +80,13 @@ impl SubServerRepository {
Ok(false) Ok(false)
} }
} }
}
// Pour initialiser le cache depuis la DB impl SubServerRepository {
pub async fn load_all_from_db(&self) -> Result<(), sqlx::Error> { // getters (db)
let servers: Vec<SubServer> = sqlx::query_as("SELECT * FROM sub_server") pub async fn db_all(&self) -> Result<Vec<SubServer>, sqlx::Error> {
sqlx::query_as("SELECT * FROM sub_server")
.fetch_all(&self.store.db) .fetch_all(&self.store.db)
.await?; .await
for server in servers {
self.store.sub_servers.insert(server.id, server);
}
Ok(())
} }
} }

View File

@@ -2,19 +2,18 @@ use std::sync::Arc;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use uuid::Uuid; use uuid::Uuid;
use crate::store::models::user::User; use crate::store::models::user::User;
use crate::store::store_service::StoreService;
use crate::utils::shared_store::SharedArcMap; use crate::utils::shared_store::SharedArcMap;
#[derive(Clone)] #[derive(Clone)]
pub struct UserRepository { pub struct UserRepository {
db: SqlitePool, store: StoreService,
cache: SharedArcMap<Uuid, User>
} }
impl UserRepository { impl UserRepository {
pub fn new(db: SqlitePool, cache: SharedArcMap<Uuid, User>) -> Self { pub fn new(store: StoreService) -> Self {
Self { Self {
db, store
cache
} }
} }
} }
@@ -22,11 +21,11 @@ impl UserRepository {
impl UserRepository { impl UserRepository {
// getters // getters
pub async fn all(&self) -> Vec<Arc<User>> { pub async fn all(&self) -> Vec<Arc<User>> {
self.cache.values().collect() self.store.users.values().collect()
} }
pub async fn get(&self, id: Uuid) -> Option<Arc<User>> { pub async fn get(&self, id: Uuid) -> Option<Arc<User>> {
self.cache.get(&id) self.store.users.get(&id)
} }
} }
@@ -40,12 +39,12 @@ impl UserRepository {
.bind(&user.username) .bind(&user.username)
.bind(&user.pub_key) .bind(&user.pub_key)
.bind(&user.created_at) .bind(&user.created_at)
.execute(&self.db) .execute(&self.store.db)
.await?; .await?;
// ajouter au cache // ajouter au cache
let arc_server = Arc::new(user.clone()); let arc_server = Arc::new(user.clone());
self.cache.insert_arc(user.id, arc_server.clone()); self.store.users.insert_arc(user.id, arc_server.clone());
Ok(arc_server) Ok(arc_server)
} }
@@ -57,11 +56,11 @@ impl UserRepository {
.bind(&user.username) .bind(&user.username)
.bind(&user.pub_key) .bind(&user.pub_key)
.bind(&user.id) .bind(&user.id)
.execute(&self.db) .execute(&self.store.db)
.await?; .await?;
// Mettre à jour le cache // Mettre à jour le cache
self.cache.insert(user.id, user.clone()); self.store.users.insert(user.id, user.clone());
Ok(()) Ok(())
} }
@@ -69,28 +68,24 @@ impl UserRepository {
pub async fn delete(&self, id: Uuid) -> Result<bool, sqlx::Error> { pub async fn delete(&self, id: Uuid) -> Result<bool, sqlx::Error> {
let rows_affected = sqlx::query("DELETE FROM user WHERE id = ?") let rows_affected = sqlx::query("DELETE FROM user WHERE id = ?")
.bind(&id) .bind(&id)
.execute(&self.db) .execute(&self.store.db)
.await? .await?
.rows_affected(); .rows_affected();
if rows_affected > 0 { if rows_affected > 0 {
self.cache.remove(&id); self.store.users.remove(&id);
Ok(true) Ok(true)
} else { } else {
Ok(false) Ok(false)
} }
} }
}
// Pour initialiser le cache depuis la DB impl UserRepository {
pub async fn load_all_from_db(&self) -> Result<(), sqlx::Error> { // getters (db)
let servers: Vec<User> = sqlx::query_as("SELECT * FROM user") pub async fn db_all(&self) -> Result<Vec<User>, sqlx::Error> {
.fetch_all(&self.db) sqlx::query_as("SELECT * FROM user")
.await?; .fetch_all(&self.store.db)
.await
for server in servers {
self.cache.insert(server.id, server);
}
Ok(())
} }
} }

View File

@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
use sqlx::{AnyPool, SqlitePool}; use sqlx::{AnyPool, SqlitePool};
@@ -28,9 +29,9 @@ pub struct StoreService {
pub sub_servers: SharedArcMap<Uuid, SubServer>, pub sub_servers: SharedArcMap<Uuid, SubServer>,
pub channels: SharedArcMap<Uuid, Channel>, pub channels: SharedArcMap<Uuid, Channel>,
pub sub_server_users: SharedArcVec<LinkSubServerUser>, pub sub_server_users: SharedArcVec<LinkSubServerUser>,
} }
impl StoreService { impl StoreService {
pub async fn new(database_url: &str) -> Result<Self, sqlx::Error> { pub async fn new(database_url: &str) -> Result<Self, sqlx::Error> {
let connection_url = Self::normalize_database_url(database_url); let connection_url = Self::normalize_database_url(database_url);
@@ -38,9 +39,9 @@ impl StoreService {
let db = SqlitePool::connect(&connection_url).await?; let db = SqlitePool::connect(&connection_url).await?;
sqlx::migrate!("./src/store/migrations").run(&db).await?; // sqlx::migrate!("./src/store/migrations").run(&db).await?;
let service = Self { let mut service = Self {
db, db,
users: SharedArcMap::new(), users: SharedArcMap::new(),
sub_servers: SharedArcMap::new(), sub_servers: SharedArcMap::new(),
@@ -55,43 +56,56 @@ impl StoreService {
} }
async fn load_all_caches(&self) -> Result<(), sqlx::Error> { async fn load_all_caches(&self) -> Result<(), sqlx::Error> {
// Users let sub_server_rep = SubServerRepository::new(self.clone());
let users: Vec<User> = sqlx::query_as("SELECT * FROM user") let user_rep = UserRepository::new(self.clone());
.fetch_all(&self.db) let channel_rep = ChannelRepository::new(self.clone());
.await?; let link_sub_server_user_rep = LinkSubServerUserRepository::new(self.clone());
for user in users {
self.users.insert(user.id, user);
}
// SubServers // sub_servers
let sub_servers: Vec<SubServer> = sqlx::query_as("SELECT * FROM sub_server") let sub_servers = sub_server_rep.db_all().await?;
.fetch_all(&self.db) self.sub_servers.insert_batch_with_key(sub_servers, |sub_server| sub_server.id);
.await?;
for sub_server in sub_servers { // Users
self.sub_servers.insert(sub_server.id, sub_server); let users = user_rep.db_all().await?;
} self.users.insert_batch_with_key(users, |user| user.id);
// Channels // Channels
let channels: Vec<Channel> = sqlx::query_as("SELECT * FROM channel") let channels = channel_rep.db_all().await?;
.fetch_all(&self.db) self.channels.insert_batch_with_key(channels, |channel| channel.id);
.await?;
for channel in channels {
self.channels.insert(channel.id, channel);
}
// Relations N-N // Relations N-N
let relations: Vec<LinkSubServerUser> = sqlx::query_as("SELECT * FROM sub_server_user") let relations: Vec<LinkSubServerUser> = link_sub_server_user_rep.db_all().await?;
.fetch_all(&self.db) self.sub_server_users.extend(relations);
.await?;
for relation in relations {
self.sub_server_users.push(relation);
}
Ok(()) Ok(())
} }
} }
impl StoreService {
// Getters repositories
pub fn user_repository(&self) -> UserRepository {
UserRepository::new(self.clone())
}
pub fn sub_server_repository(&self) -> SubServerRepository {
SubServerRepository::new(self.clone())
}
pub fn channel_repository(&self) -> ChannelRepository {
ChannelRepository::new(self.clone())
}
pub fn message_repository(&self) -> MessageRepository {
MessageRepository::new(self.clone())
}
pub fn sub_server_user_repository(&self) -> LinkSubServerUserRepository {
LinkSubServerUserRepository::new(self.clone())
}
}
impl StoreService { impl StoreService {
// ===== HELPERS ===== // ===== HELPERS =====
/// ✅ Normalise l'URL pour supporter différentes bases de données /// ✅ Normalise l'URL pour supporter différentes bases de données

View File

@@ -707,6 +707,38 @@ where
let map_ref = self.inner.load_full(); let map_ref = self.inner.load_full();
map_ref.values().cloned().collect::<Vec<_>>().into_iter() map_ref.values().cloned().collect::<Vec<_>>().into_iter()
} }
/// Insert une collection entière de paires clé-valeur
pub fn insert_batch<I>(&self, items: I)
where
I: IntoIterator<Item = (K, V)>,
{
let current = self.inner.load_full();
let mut new_map = current.as_ref().clone();
for (key, value) in items {
new_map.insert(key, value);
}
self.inner.store(Arc::new(new_map));
}
/// Insert une collection de valeurs avec une fonction pour extraire la clé
pub fn insert_batch_with_key<I, F>(&self, items: I, key_fn: F)
where
I: IntoIterator<Item = V>,
F: Fn(&V) -> K,
{
let current = self.inner.load_full();
let mut new_map = current.as_ref().clone();
for value in items {
let key = key_fn(&value);
new_map.insert(key, value);
}
self.inner.store(Arc::new(new_map));
}
} }
// Clone gratuit (juste Arc::clone) // Clone gratuit (juste Arc::clone)
@@ -1059,8 +1091,41 @@ where
let map_ref = self.inner.load_full(); let map_ref = self.inner.load_full();
map_ref.iter().map(|(k, v)| (k.clone(), v.clone())).collect::<Vec<_>>().into_iter() map_ref.iter().map(|(k, v)| (k.clone(), v.clone())).collect::<Vec<_>>().into_iter()
} }
/// Insert une collection entière de paires clé-valeur
pub fn insert_batch<I>(&self, items: I)
where
I: IntoIterator<Item = (K, V)>,
{
let current = self.inner.load_full();
let mut new_map = current.as_ref().clone();
for (key, value) in items {
new_map.insert(key, Arc::new(value));
}
self.inner.store(Arc::new(new_map));
}
/// Insert une collection de valeurs avec une fonction pour extraire la clé
pub fn insert_batch_with_key<I, F>(&self, items: I, key_fn: F)
where
I: IntoIterator<Item = V>,
F: Fn(&V) -> K,
{
let current = self.inner.load_full();
let mut new_map = current.as_ref().clone();
for value in items {
let key = key_fn(&value);
new_map.insert(key, Arc::new(value));
}
self.inner.store(Arc::new(new_map));
}
} }
// ===== IMPLÉMENTATIONS CLONE ===== // ===== IMPLÉMENTATIONS CLONE =====
impl<T> Clone for SharedArcVec<T> { impl<T> Clone for SharedArcVec<T> {