Compare commits

...

5 Commits

Author SHA1 Message Date
8bddcc1b01 init 2025-08-26 16:21:16 +02:00
39d1d9a2b7 init 2025-08-11 16:59:31 +02:00
d691c1d944 init 2025-08-03 11:36:22 +02:00
5dbf0aacab pre-migration seaorm 2025-07-30 03:00:37 +02:00
be761a8c46 pre-migration seaorm 2025-07-29 20:20:29 +02:00
25 changed files with 750 additions and 221 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

201
.idea/workspace.xml generated
View File

@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="AnalysisUIOptions">
<option name="ANALYZE_INJECTED_CODE" value="false" />
<option name="SCOPE_TYPE" value="3" />
</component>
<component name="AutoImportSettings"> <component name="AutoImportSettings">
<option name="autoReloadType" value="ALL" /> <option name="autoReloadType" value="ALL" />
</component> </component>
@@ -15,42 +11,57 @@
</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="b2b598c9-ef0b-4cbc-8852-cfbc8ce3920e" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/README.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/dataSources.local.xml" beforeDir="false" />
<change afterPath="$PROJECT_DIR$/src/app/conf.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/dataSources.xml" beforeDir="false" />
<change afterPath="$PROJECT_DIR$/src/domain/models.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/dataSources/26059583-0fdb-4f6f-ad11-10388e9658c2.xml" beforeDir="false" />
<change afterPath="$PROJECT_DIR$/src/network/http.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/dataSources/26059583-0fdb-4f6f-ad11-10388e9658c2/storage_v2/_src_/schema/main.uQUzAA.meta" beforeDir="false" />
<change afterPath="$PROJECT_DIR$/src/network/http_routes/channel.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/network/http_routes/master.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/ox_speak_server.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/ox_speak_server.iml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/network/http_routes/message.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/network/http_routes/mod.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/network/http_routes/user.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/network/http_routes/websocket.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/mod.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/models/channel.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/models/link_sub_server_user.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/models/message.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/models/mod.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/models/sub_server.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/models/user.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/repositories/channel_repository.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/repositories/link_sub_server_user_repository.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/repositories/message_repository.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/repositories/mod.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/repositories/sub_server_repository.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/repositories/user_repository.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/store/store_service.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/app/app.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/app.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/app/app.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/app.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/app/conf.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/conf.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/app/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/mod.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/app/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/mod.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/domain/client.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/domain/client.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/domain/event.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/domain/event.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/domain/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/domain/mod.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/domain/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/domain/mod.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/domain/models.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/domain/models.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/domain/user.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/domain/user.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/network/http.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/http_routes/channel.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http_routes/channel.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/http_routes/master.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http_routes/master.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/http_routes/message.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http_routes/message.rs" 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/network/http_routes/sub_server.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http_routes/sub_server.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/http_routes/user.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http_routes/user.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/http_routes/websocket.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/http_routes/websocket.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/mod.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/network/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/mod.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/protocol.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/protocol.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/udp.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/udp.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/network/udp_back.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/network/udp_back.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/runtime/dispatcher.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/runtime/dispatcher.rs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/runtime/dispatcher.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/runtime/dispatcher.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/migrations/001_init.sqlite.sql" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/migrations/001_init.sqlite.sql" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/mod.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/models/channel.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/models/channel.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/models/link_sub_server_user.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/models/link_sub_server_user.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/models/message.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/models/message.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/models/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/models/mod.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/models/sub_server.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/models/sub_server.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/store/models/user.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/models/user.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/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/repositories/mod.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/session/client.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/store/session/client.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> </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" />
@@ -58,38 +69,32 @@
<option name="LAST_RESOLUTION" value="IGNORE" /> <option name="LAST_RESOLUTION" value="IGNORE" />
</component> </component>
<component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:dev" /> <component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:dev" />
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Rust File" />
</list>
</option>
</component>
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="MacroExpansionManager"> <component name="MacroExpansionManager">
<option name="directoryName" value="oBePpDlK" /> <option name="directoryName" value="j91G0RKo" />
</component> </component>
<component name="ProjectColorInfo">{ <component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 5 &quot;associatedIndex&quot;: 7
}</component> }</component>
<component name="ProjectId" id="2zaZ93S6zEp6mJe7Xq5WfvIMexv" /> <component name="ProjectId" id="31h5TOFgo6LtoeKpSInG1hXoNI7" />
<component name="ProjectViewState"> <component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent">{
&quot;keyToString&quot;: { &quot;keyToString&quot;: {
&quot;Cargo.Run.executor&quot;: &quot;Run&quot;, &quot;Cargo.Run ox_speak_server.executor&quot;: &quot;Run&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;, &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.rust.reset.selective.auto.import&quot;: &quot;true&quot;, &quot;RunOnceActivity.rust.reset.selective.auto.import&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;, &quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;, &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;, &quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;D:/Dev/ox_speak_server/src/network&quot;, &quot;last_opened_file_path&quot;: &quot;//wsl.localhost/Debian/home/Nell/linux_dev/ox_speak_server&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
@@ -98,22 +103,14 @@
&quot;org.rust.cargo.project.model.PROJECT_DISCOVERY&quot;: &quot;true&quot;, &quot;org.rust.cargo.project.model.PROJECT_DISCOVERY&quot;: &quot;true&quot;,
&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;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;terminal&quot;, &quot;to.speed.mode.migration.done&quot;: &quot;true&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}</component> }</component>
<component name="RecentsManager"> <component name="RunManager" selected="Cargo.Run ox_speak_server">
<key name="CopyFile.RECENT_KEYS"> <configuration name="Run ox_speak_server" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<recent name="D:\Dev\ox_speak_server\src\network" /> <option name="buildProfileId" value="dev" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\Dev\ox_speak_server\src\store" />
<recent name="D:\Dev\ox_speak_server\src\store\repositories" />
</key>
</component>
<component name="RunManager">
<configuration name="Run" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package ox_speak_server --bin ox_speak_server" /> <option name="command" value="run --package ox_speak_server --bin ox_speak_server" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs /> <envs />
@@ -130,84 +127,44 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" /> <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method> </method>
</configuration> </configuration>
<configuration name="Test ox_speak_server" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test --workspace" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component> </component>
<component name="RustProjectSettings"> <component name="RustProjectSettings">
<option name="toolchainHomeDirectory" value="$USER_HOME$/.cargo/bin" /> <option name="toolchainHomeDirectory" value="$PROJECT_DIR$/../../.cargo/bin" />
</component> </component>
<component name="TaskManager"> <component name="TaskManager">
<task active="true" id="Default" summary="Default task"> <task active="true" id="Default" summary="Default task">
<changelist id="ca698286-778f-4335-97c8-da35a666c986" name="Changes" comment="" /> <changelist id="b2b598c9-ef0b-4cbc-8852-cfbc8ce3920e" name="Changes" comment="" />
<created>1751970990022</created> <created>1755963473950</created>
<option name="number" value="Default" /> <option name="number" value="Default" />
<option name="presentableId" value="Default" /> <option name="presentableId" value="Default" />
<updated>1751970990022</updated> <updated>1755963473950</updated>
<workItem from="1751970991653" duration="7327000" /> <workItem from="1755963475439" duration="2760000" />
<workItem from="1752016714585" duration="1095000" /> <workItem from="1756139974994" duration="110000" />
<workItem from="1752076174877" duration="11015000" />
<workItem from="1752189013977" duration="4750000" />
<workItem from="1752224469499" duration="14591000" />
<workItem from="1752305152715" duration="19879000" />
<workItem from="1752391851097" duration="12061000" />
<workItem from="1752446764168" duration="8166000" />
<workItem from="1752484190578" duration="17709000" />
<workItem from="1752591656862" duration="429000" />
<workItem from="1752593250094" duration="11969000" />
<workItem from="1752694022400" duration="6212000" />
<workItem from="1752741840195" duration="2946000" />
<workItem from="1752833798325" duration="5366000" />
<workItem from="1752917416027" duration="1192000" />
<workItem from="1752931843330" duration="2938000" />
<workItem from="1752997629708" duration="9070000" />
<workItem from="1753107912894" duration="595000" />
<workItem from="1753225761416" duration="1356000" />
<workItem from="1753282037526" duration="5207000" />
<workItem from="1753397680991" duration="1782000" />
<workItem from="1753399490773" duration="3189000" />
<workItem from="1753436756029" duration="16895000" />
<workItem from="1753521176318" duration="17811000" />
<workItem from="1753601912332" duration="5843000" />
<workItem from="1753718175508" duration="8774000" />
<workItem from="1753800817354" duration="3570000" />
<workItem from="1753804571241" duration="59000" />
<workItem from="1753804642657" duration="236000" />
<workItem from="1753804898179" duration="625000" />
<workItem from="1753805533139" duration="2909000" />
</task> </task>
<task id="LOCAL-00001" summary="init">
<option name="closed" value="true" />
<created>1752591904243</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1752591904243</updated>
</task>
<task id="LOCAL-00002" summary="init">
<option name="closed" value="true" />
<created>1752591989113</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1752591989113</updated>
</task>
<option name="localTasksCounter" value="3" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>
<component name="Vcs.Log.Tabs.Properties"> <component name="XSLT-Support.FileAssociations.UIState">
<option name="TAB_STATES"> <expand />
<map> <select />
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="init" />
<option name="LAST_COMMIT_MESSAGE" value="init" />
</component> </component>
</project> </project>

16
Cargo.lock generated
View File

@@ -1432,12 +1432,12 @@ dependencies = [
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.10" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -1776,9 +1776,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.46.1" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -1791,7 +1791,7 @@ dependencies = [
"slab", "slab",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -1969,9 +1969,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.17.0" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
dependencies = [ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
"js-sys", "js-sys",

View File

@@ -17,9 +17,9 @@ debug = true
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
parking_lot = "0.12" parking_lot = "0.12"
tokio = { version = "1.46", features = ["full"] } tokio = { version = "1.47", features = ["full"] }
strum = {version = "0.27", features = ["derive"] } strum = {version = "0.27", features = ["derive"] }
uuid = {version = "1.17", features = ["v4", "serde"] } uuid = {version = "1.18", features = ["v4", "serde"] }
event-listener = "5.4" event-listener = "5.4"
dashmap = "6.1" dashmap = "6.1"
bytes = "1.10" bytes = "1.10"

View File

@@ -2,3 +2,5 @@ créer un fichier de migration sqlx :
```shell ```shell
sqlx migrate add --source src/store/migrations migration_name sqlx migrate add --source src/store/migrations migration_name
``` ```
# Example

View File

@@ -1,11 +1,9 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use axum::{ use axum::{extract::{ws::WebSocket, ws::WebSocketUpgrade, State}, response::Response, Json, Router, routing::{get, post}, middleware};
extract::{ws::WebSocket, ws::WebSocketUpgrade, State}, use axum::body::Body;
response::Response, use axum::http::{HeaderValue, Request, StatusCode};
Json, Router, use axum::middleware::Next;
routing::{get, post}
};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use crate::domain::event::EventBus; use crate::domain::event::EventBus;
use crate::network::http_routes::master; use crate::network::http_routes::master;
@@ -46,7 +44,8 @@ impl HttpServer {
fn create_router(&self) -> Router { fn create_router(&self) -> Router {
let api_route = Router::new() let api_route = Router::new()
.nest("/master", master::create_router()); .nest("/master", master::create_router())
.layer(middleware::from_fn(app_only_middleware));
Router::new() Router::new()
.nest("/api", api_route) .nest("/api", api_route)
@@ -68,3 +67,23 @@ impl HttpServer {
}) })
} }
} }
// Middlewares
async fn app_only_middleware(request: Request<Body>, next: Next) -> Result<Response, StatusCode> {
let headers = request.headers();
let expected_client = HeaderValue::from_static("ox_speak");
let expected_version = HeaderValue::from_static("1.0");
let expected_user_agent = HeaderValue::from_static("OxSpeak/1.0");
if headers.get("X-Client-Type") != Some(&expected_client) ||
headers.get("X-Protocol-Version") != Some(&expected_version) ||
headers.get("User-Agent") != Some(&expected_user_agent) {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body("Invalid client".into())
.unwrap());
}
Ok(next.run(request).await)
}

View File

@@ -0,0 +1,8 @@
use axum::Router;
use axum::routing::post;
use crate::network::http::HttpState;
pub fn create_router() -> Router<HttpState> {
Router::new()
// .route("/auth/", post(join_master_server))
}

View File

@@ -10,9 +10,37 @@ use crate::network::http::HttpState;
pub fn create_router() -> Router<HttpState> { pub fn create_router() -> Router<HttpState> {
Router::new() Router::new()
.route("/auth/", post(join_master_server)) .route("/auth/", post(join))
} }
pub async fn join_master_server(State(state): State<HttpState>) -> Json<HashMap<String, String>> { pub async fn challenge(State(state): State<HttpState>) -> Json<HashMap<String, String>> {
todo!("join master server") // permet de sécuriser les échanges entre le client et le serveur pour qu'elle soit propre à l'application
// l'idée est d'éviter que les système comme les navigateurs, curl ... puisse accéder à l'application
todo!("challenge master server")
}
pub async fn join(State(state): State<HttpState>) -> Json<HashMap<String, String>> {
// Cette page est systématiquement appelé à la première connexion du client
// dans le payload de la requête, on aura les info client
// public key
// dans la réponse, on aura le status
// success, error, password_needed
// match determine_server_state().await {
// ServerState::FirstSetup => {
// // Logique de création du super admin
// // Validation des champs requis (password, username)
// }
// ServerState::RequiresPassword => {
// // Logique d'authentification
// // Validation du mot de passe serveur
// }
// ServerState::Ready => {
// // Logique de connexion normale
// // Juste la public key suffit
// }
// }
todo!("join master server")
} }

View File

@@ -0,0 +1,8 @@
use axum::Router;
use axum::routing::post;
use crate::network::http::HttpState;
pub fn create_router() -> Router<HttpState> {
Router::new()
// .route("/auth/", post(join_master_server))
}

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

@@ -0,0 +1,9 @@
use axum::Router;
use axum::routing::post;
use crate::network::http::HttpState;
pub fn create_router() -> Router<HttpState> {
Router::new()
// .route("/auth/", post(join_master_server))
}

View File

@@ -0,0 +1,8 @@
use axum::Router;
use axum::routing::post;
use crate::network::http::HttpState;
pub fn create_router() -> Router<HttpState> {
Router::new()
// .route("/auth/", post(join_master_server))
}

View File

@@ -0,0 +1,8 @@
use axum::Router;
use axum::routing::post;
use crate::network::http::HttpState;
pub fn create_router() -> Router<HttpState> {
Router::new()
// .route("/auth/", post(join_master_server))
}

View File

@@ -1,3 +1,4 @@
pub mod models; pub mod models;
pub mod repositories; pub mod repositories;
pub mod store_service; pub mod store_service;
mod session;

View File

@@ -1,3 +1,4 @@
use std::hash::{Hash, Hasher};
// use chrono::{DateTime, Utc}; // use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
// use uuid::Uuid; // use uuid::Uuid;
@@ -30,3 +31,17 @@ impl Channel {
} }
} }
} }
impl PartialEq for Channel {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Channel {}
impl Hash for Channel {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}

View File

@@ -1,8 +1,10 @@
// L'application peut avoir plusieurs sous servers // L'application peut avoir plusieurs sous servers
use std::hash::{Hash, Hasher};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use crate::store::models::Channel;
#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)] #[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
pub struct SubServer { pub struct SubServer {
@@ -22,3 +24,17 @@ impl SubServer {
} }
} }
} }
impl PartialEq for SubServer {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for SubServer {}
impl Hash for SubServer {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}

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(())
} }
} }

213
src/store/session/client.rs Normal file
View File

@@ -0,0 +1,213 @@
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::net::SocketAddr;
use std::sync::{Arc, Weak};
use std::time::Instant;
use axum::extract::ws::WebSocket;
use uuid::Uuid;
use crate::store::models::{Channel, SubServer};
use crate::store::models::user::User;
use crate::utils::shared_store::{SharedArcHashSet, SharedArcMap, SharedArcVec};
/// Représente un client connecté au serveur (WebSocket + optionnel UDP)
///
/// Chaque client a une connexion WebSocket obligatoire et peut avoir une connexion UDP
/// pour la voix s'il rejoint un channel vocal.
#[derive(Debug)]
pub struct Client {
// ===== CHAMPS IMMUTABLES =====
pub session_id: Uuid,
pub user: User,
pub websocket: WebSocket,
pub created_at: Instant,
// ===== CHAMPS MUTABLES =====
pub udp_socket: Option<SocketAddr>,
pub voice_channel: Option<Channel>,
// ===== RÉFÉRENCES INTERNES =====
/// Référence faible vers le manager pour l'auto-nettoyage
manager: Option<Weak<ClientManager>>,
}
impl Client {
/// Crée un nouveau client
pub fn new(user: User, websocket: WebSocket) -> Self {
Self {
session_id: Uuid::new_v4(),
user,
websocket,
created_at: Instant::now(),
udp_socket: None,
voice_channel: None,
manager: None,
}
}
// ===== MÉTHODES MUTABLES INTERNES =====
// Ces méthodes sont pub(crate) - elles ne doivent être appelées que via ClientManager::modify_client()
/// Configure l'adresse UDP (méthode interne)
pub(crate) fn set_udp_socket(&mut self, udp_socket: SocketAddr) {
self.udp_socket = Some(udp_socket);
}
/// Supprime l'adresse UDP (méthode interne)
pub(crate) fn remove_udp_socket(&mut self) {
self.udp_socket = None;
}
/// Configure la référence vers le manager (appelé par le manager)
pub(crate) fn set_manager(&mut self, manager: Weak<ClientManager>) {
self.manager = Some(manager);
}
}
impl PartialEq for Client {
fn eq(&self, other: &Self) -> bool {
self.session_id == other.session_id
}
}
impl Eq for Client {}
impl Hash for Client {
fn hash<H: Hasher>(&self, state: &mut H) {
self.session_id.hash(state);
}
}
/// Nettoyage automatique quand le client est détruit
impl Drop for Client {
fn drop(&mut self) {
if let Some(manager) = self.manager.as_ref().and_then(|w| w.upgrade()) {
manager.cleanup_client_index(&self);
}
}
}
// =========================================================================
// CLIENT MANAGER
// =========================================================================
/// Gestionnaire centralisé de tous les clients connectés
///
/// Maintient les clients et leurs index pour des recherches efficaces.
/// Thread-safe via SharedArcMap.
pub struct ClientManager {
/// Map principale des clients par session_id
clients: SharedArcMap<Uuid, Client>,
// ===== INDEX POUR RECHERCHES RAPIDES =====
/// Index des clients par channel_id
voice_channel_clients: SharedArcMap<Channel, SharedArcHashSet<Client>>, // channel_id -> HashSet<session_id>
/// Index des clients par sub_server_id
sub_server_clients: SharedArcMap<SubServer, SharedArcHashSet<Client>>, // sub_server_id -> Vec<session_id>
/// Index des clients par adresse UDP
udp_clients: SharedArcMap<SocketAddr, String>, // udp_address -> session_id
}
impl ClientManager {
/// Crée un nouveau gestionnaire de clients
pub fn new() -> Self {
Self {
clients: SharedArcMap::new(),
voice_channel_clients: SharedArcMap::new(),
sub_server_clients: SharedArcMap::new(),
udp_clients: SharedArcMap::new(),
}
}
// ===== GESTION DES CLIENTS =====
/// Ajoute un nouveau client au gestionnaire
pub fn add_client(self: &Arc<Self>, mut client: Client) -> Arc<Client> {
let session_id = client.session_id.clone();
// Lier le client au manager pour l'auto-nettoyage
client.set_manager(Arc::downgrade(self));
// Stocker et retourner
let arc_client = Arc::new(client);
self.clients.insert_arc(session_id, arc_client.clone());
arc_client
}
pub fn join_sub_server(&self, client: &Arc<Client>, sub_server: &SubServer) {
let clients = self.sub_server_clients.get(sub_server).unwrap_or_else(|| {
let new_clients = Arc::new(SharedArcHashSet::new());
self.sub_server_clients.insert_arc(sub_server.clone(), new_clients.clone());
new_clients
});
clients.insert_arc(client.clone());
}
/// Obtient un client par son session_id
pub fn get_client(&self, session_id: &Uuid) -> Option<Arc<Client>> {
self.clients.get(session_id)
}
/// Obtient tous les clients connectés
pub fn get_all_clients(&self) -> Vec<Arc<Client>> {
self.clients.values().collect()
}
// ===== OPÉRATIONS DE MODIFICATION =====
/// Modifie un client via une closure thread-safe
///
/// Cette méthode garantit que la modification est atomique et que
/// les index restent cohérents avec l'état du client.
///
/// # Arguments
/// * `session_id` - L'ID de session du client à modifier
/// * `f` - La closure qui recevra une référence mutable vers le client
///
/// # Returns
/// `true` si le client a été trouvé et modifié, `false` sinon
pub fn modify_client<F>(&self, client: &Client, f: F) -> bool
where
F: FnOnce(&mut Client),
{
// Convertir &str en String pour les clés
todo!()
}
}
impl ClientManager {
// Delete methods
/// Supprime tous les index d'un client
pub fn cleanup_client_index(&self, client: &Client) {
if let Some(channel) = client.voice_channel.as_ref() {
self.remove_client_from_voice_channel(channel, client);
}
}
/// Supprime un client du gestionnaire
pub fn remove_client(&self, session_id: &Uuid) {
// Convertir &str en String pour les clés
if let Some(client) = self.clients.get(session_id) {
// Nettoyer tous les index
self.cleanup_client_index(&client);
// Supprimer le client de la liste principale
self.clients.remove(&session_id);
}
}
pub fn remove_client_from_voice_channel(&self, voice_channel: &Channel, client: &Client) {
if let Some(channel) = self.voice_channel_clients.get(voice_channel) {
channel.remove(client);
}
}
pub fn remove_client_from_sub_servers(&self, client: &Client) {
for (sub_server, clients) in self.sub_server_clients.iter() {
clients.remove(client);
}
}
}

1
src/store/session/mod.rs Normal file
View File

@@ -0,0 +1 @@
mod client;

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

@@ -244,6 +244,71 @@ impl<T> SharedVec<T> {
self.inner.store(Arc::new(new_vec)); self.inner.store(Arc::new(new_vec));
} }
/// Supprime l'élément à l'index donné et le retourne.
/// Retourne `None` si l'index est hors limites.
///
/// Exemple : `let removed = clients.delete(2); // Supprime le 3e client`
pub fn delete(&self, index: usize) -> Option<T>
where
T: Clone,
{
let current = self.inner.load_full();
if index >= current.len() {
return None;
}
let mut new_vec = current.as_ref().clone();
let removed_item = new_vec.remove(index);
self.inner.store(Arc::new(new_vec));
Some(removed_item)
}
/// Supprime et retourne le dernier élément du vecteur.
/// Retourne `None` si le vecteur est vide.
///
/// Exemple : `let last_client = clients.pop(); // Supprime le dernier client`
pub fn pop(&self) -> Option<T>
where
T: Clone,
{
let current = self.inner.load_full();
if current.is_empty() {
return None;
}
let mut new_vec = current.as_ref().clone();
let popped_item = new_vec.pop();
self.inner.store(Arc::new(new_vec));
popped_item
}
/// Supprime tous les éléments qui correspondent au prédicat donné.
/// Retourne le nombre d'éléments supprimés.
///
/// Exemple : `let removed = clients.delete_matching(|c| c.is_disconnected());`
pub fn delete_matching<F>(&self, predicate: F) -> usize
where
T: Clone,
F: Fn(&T) -> bool,
{
let current = self.inner.load_full();
let mut new_vec = Vec::with_capacity(current.len());
let mut removed_count = 0;
for item in current.iter() {
if predicate(item) {
removed_count += 1;
} else {
new_vec.push(item.clone());
}
}
if removed_count > 0 {
self.inner.store(Arc::new(new_vec));
}
removed_count
}
/// Clear optimisé /// Clear optimisé
/// ///
/// Exemple : `clients.clear(); // Vide la liste` /// Exemple : `clients.clear(); // Vide la liste`
@@ -707,6 +772,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)
@@ -820,6 +917,85 @@ impl<T> SharedArcVec<T> {
self.inner.store(Arc::new(Vec::new())); self.inner.store(Arc::new(Vec::new()));
} }
/// Supprime l'élément à l'index donné et le retourne.
/// Retourne `None` si l'index est hors limites.
pub fn delete(&self, index: usize) -> Option<Arc<T>> {
let current = self.inner.load_full();
if index >= current.len() {
return None;
}
let mut new_vec = current.as_ref().clone();
let removed_item = new_vec.remove(index);
self.inner.store(Arc::new(new_vec));
Some(removed_item)
}
/// Supprime et retourne le dernier élément du vecteur.
/// Retourne `None` si le vecteur est vide.
pub fn pop(&self) -> Option<Arc<T>> {
let current = self.inner.load_full();
if current.is_empty() {
return None;
}
let mut new_vec = current.as_ref().clone();
let popped_item = new_vec.pop();
self.inner.store(Arc::new(new_vec));
popped_item
}
/// Supprime tous les éléments qui correspondent au prédicat donné.
/// Retourne le nombre d'éléments supprimés.
pub fn delete_matching<F>(&self, predicate: F) -> usize
where
F: Fn(&T) -> bool,
{
let current = self.inner.load_full();
let mut new_vec = Vec::with_capacity(current.len());
let mut removed_count = 0;
for item in current.iter() {
if predicate(item.as_ref()) {
removed_count += 1;
} else {
new_vec.push(item.clone());
}
}
if removed_count > 0 {
self.inner.store(Arc::new(new_vec));
}
removed_count
}
/// Supprime le premier élément trouvé qui correspond au prédicat.
/// Retourne `Some(Arc<T>)` si un élément a été trouvé et supprimé, `None` sinon.
pub fn delete_first_matching<F>(&self, predicate: F) -> Option<Arc<T>>
where
F: Fn(&T) -> bool,
{
let current = self.inner.load_full();
let mut found_index = None;
for (index, item) in current.iter().enumerate() {
if predicate(item.as_ref()) {
found_index = Some(index);
break;
}
}
if let Some(index) = found_index {
let mut new_vec = current.as_ref().clone();
let removed_item = new_vec.remove(index);
self.inner.store(Arc::new(new_vec));
Some(removed_item)
} else {
None
}
}
/// Iteration retournant Arc<T> /// Iteration retournant Arc<T>
pub fn iter(&self) -> impl Iterator<Item = Arc<T>> + '_ { pub fn iter(&self) -> impl Iterator<Item = Arc<T>> + '_ {
let vec_ref = self.inner.load_full(); let vec_ref = self.inner.load_full();
@@ -1059,8 +1235,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> {