From 5e160ae11c575f2331fd0c7bd0bf26139c415b9d Mon Sep 17 00:00:00 2001 From: Antoine Pelletier Date: Tue, 29 Sep 2026 09:47:27 +0200 Subject: [PATCH] wip --- Cargo.lock | 426 +++++++++++++++- Cargo.toml | 4 + README.md | 3 +- config.example.yml | 12 +- .../20260929180000_associations_focus.sql | 41 ++ db/schema.sql | 29 +- frontend/src/components/AppContent.vue | 19 +- ...{CoursesCalendar.vue => TasksCalendar.vue} | 48 +- frontend/src/components/calendar/TodoList.vue | 185 +++++++ .../course/AssociationDeadlinesTab.vue | 190 ++++++++ .../src/components/course/CourseFilesTab.vue | 23 +- .../src/components/course/CourseTasksTab.vue | 58 ++- .../src/components/course/TaskFormDialog.vue | 71 ++- .../components/courses/CourseFormDialog.vue | 35 +- .../components/schedule/SlotFormDialog.vue | 46 +- .../components/timetable/WeekTimetable.vue | 39 +- .../ui/alert-dialog/AlertDialogContent.vue | 2 +- .../components/ui/dialog/DialogContent.vue | 2 +- frontend/src/lib/api.d.ts | 274 ++++++++++- frontend/src/locales/en.yml | 89 +++- frontend/src/locales/fr.yml | 89 +++- frontend/src/router/index.ts | 57 ++- frontend/src/router/navigation.ts | 8 +- frontend/src/services/api/course-files.ts | 9 +- frontend/src/services/api/courses.ts | 12 +- frontend/src/services/api/tasks.ts | 95 +++- frontend/src/services/focus.ts | 204 ++++++++ frontend/src/utils/calendar.ts | 2 +- frontend/src/utils/routes.ts | 43 ++ frontend/src/utils/types.ts | 6 + frontend/src/views/CalendarView.vue | 132 +++-- frontend/src/views/CourseView.vue | 59 ++- frontend/src/views/CoursesView.vue | 48 +- frontend/src/views/HomeView.vue | 2 +- frontend/src/views/LectureNoteView.vue | 18 +- frontend/src/views/PomodoroView.vue | 458 +++++++++++++++++- src/api/helpers.rs | 1 + src/api/models.rs | 20 +- src/api/tasks.rs | 77 ++- src/core/controller/course_files.rs | 99 +++- src/core/controller/courses.rs | 147 +++++- src/core/controller/focus.rs | 183 +++++++ src/core/controller/mod.rs | 10 +- src/core/controller/tasks.rs | 30 +- src/core/models/course.rs | 46 +- src/core/models/course_file.rs | 5 +- src/core/models/focus.rs | 45 ++ src/core/models/mod.rs | 1 + src/core/models/tag.rs | 2 + src/core/models/task.rs | 16 +- .../repositories/course_files_repository.rs | 6 +- src/core/repositories/courses_repository.rs | 13 +- src/core/repositories/mod.rs | 1 + src/core/repositories/storage_repository.rs | 3 + src/core/repositories/tasks_repository.rs | 6 + src/core/repositories/time_tracker.rs | 21 + src/main.rs | 20 +- src/services/database/course_files.rs | 14 +- src/services/database/courses.rs | 57 ++- src/services/database/tags.rs | 4 +- src/services/database/tasks.rs | 42 +- src/services/mod.rs | 1 + src/services/storage.rs | 26 + src/services/timetagger.rs | 135 ++++++ src/utils/config.rs | 15 +- 65 files changed, 3569 insertions(+), 315 deletions(-) create mode 100644 db/migrations/20260929180000_associations_focus.sql rename frontend/src/components/calendar/{CoursesCalendar.vue => TasksCalendar.vue} (82%) create mode 100644 frontend/src/components/calendar/TodoList.vue create mode 100644 frontend/src/components/course/AssociationDeadlinesTab.vue create mode 100644 frontend/src/services/focus.ts create mode 100644 frontend/src/utils/routes.ts create mode 100644 src/core/controller/focus.rs create mode 100644 src/core/models/focus.rs create mode 100644 src/core/repositories/time_tracker.rs create mode 100644 src/services/timetagger.rs diff --git a/Cargo.lock b/Cargo.lock index 064b86b..c500dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,6 +226,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -310,6 +327,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -678,8 +704,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -689,8 +717,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", + "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -851,6 +882,23 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", ] [[package]] @@ -859,13 +907,21 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", "tokio", "tower-service", + "tracing", ] [[package]] @@ -1013,6 +1069,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + [[package]] name = "itoa" version = "1.0.18" @@ -1111,6 +1173,12 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + [[package]] name = "matchers" version = "0.2.0" @@ -1189,7 +1257,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -1397,6 +1465,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -1420,7 +1544,18 @@ checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -1430,7 +1565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1442,6 +1577,21 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1497,6 +1647,58 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "ron" version = "0.12.2" @@ -1524,7 +1726,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -1541,6 +1743,12 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustix" version = "1.1.5" @@ -1554,6 +1762,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1717,7 +1960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1728,7 +1971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1764,7 +2007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1927,7 +2170,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.7", "rsa", "serde", "sha1", @@ -1966,7 +2209,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.7", "serde", "serde_json", "sha2", @@ -2035,6 +2278,7 @@ dependencies = [ "axum", "chrono", "config", + "reqwest", "schemars", "serde", "serde_json", @@ -2080,6 +2324,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -2195,6 +2442,16 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.19" @@ -2287,8 +2544,10 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", + "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -2365,6 +2624,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typeid" version = "1.0.3" @@ -2422,6 +2687,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -2458,6 +2729,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2483,6 +2763,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.126" @@ -2515,6 +2805,35 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.6.1" @@ -2590,7 +2909,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2608,13 +2936,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2623,42 +2967,90 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index 89732ad..83f9fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,10 @@ axum = { version = "0.8.9", features = ["macros"] } chrono = { version = "0.4.45", features = ["serde"] } config = "0.15.23" schemars = { version = "0.9.0", features = ["chrono04"] } +reqwest = { version = "0.12.28", default-features = false, features = [ + "json", + "rustls-tls", +] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" sqlx = { version = "0.8.6", features = [ diff --git a/README.md b/README.md index 1b01aa6..e9e33cd 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,8 @@ key, keep it up to date. Every value can also come from the environment │ ├── services/ implementations of the repositories (postgres/sqlx) │ └── utils/ configuration ├── LaTeX_pdf/ template of the lecture notes pdf (pandoc + pdflatex) -├── storage/ files of the courses, stands in for the NAS (gitignored) +├── storage/ files of the courses and associations, one folder each, +│ stands in for the NAS (gitignored) ├── db/ │ ├── migrations/ dbmate migrations │ ├── schema.sql dump regenerated by dbmate, do not edit by hand diff --git a/config.example.yml b/config.example.yml index b41c677..006f100 100644 --- a/config.example.yml +++ b/config.example.yml @@ -14,7 +14,8 @@ postgres: # Directory containing the built frontend (frontend/dist after `npm run build`) frontend_dir: frontend/dist -# Where the files of the courses are stored (lecture notes and their pdf...). +# Where the files of the courses and associations are stored (lecture notes and their +# pdf...), one folder each: Cours/CS-477 Advanced OS/Notes/..., Associations/... # A local directory standing in for the NAS for now. storage_dir: storage @@ -27,3 +28,12 @@ notes: # Shown in the header of the pdf, both optional student_name: Jane Doe student_id: "123456" + +# Optional: the focus sessions of the pomodoro are also counted in TimeTagger, tagged +# with the course code, the tags and the name of the task. Without it, the time is only +# counted in the app. +# timetagger: +# # https://timetagger.app/api/v2 by default, or the api of your own server +# url: https://timetagger.app/api/v2 +# # Api token, from the account page of TimeTagger +# token: ... diff --git a/db/migrations/20260929180000_associations_focus.sql b/db/migrations/20260929180000_associations_focus.sql new file mode 100644 index 0000000..4727d7e --- /dev/null +++ b/db/migrations/20260929180000_associations_focus.sql @@ -0,0 +1,41 @@ +-- migrate:up + +-- Associations are managed like courses: same tasks, files and slots, without grades. +ALTER TABLE courses ADD COLUMN kind TEXT NOT NULL DEFAULT 'course' + CHECK (kind IN ('course', 'association')); +ALTER TABLE courses ALTER COLUMN kind DROP DEFAULT; + +-- Folder of the course in the storage (the NAS), relative to its root, named after the +-- course: `Cours/CS-477 Advanced operating systems`. Moved when the course is renamed. +-- The existing folders were named after the ids and are not moved by this migration. +ALTER TABLE courses ADD COLUMN folder TEXT; +UPDATE courses SET folder = 'Cours/' || trim(regexp_replace( + regexp_replace(COALESCE(code || ' ', '') || "name", '[/\\:*?"<>|]', '-', 'g'), + '\s+', ' ', 'g')); +ALTER TABLE courses ALTER COLUMN folder SET NOT NULL; +ALTER TABLE courses ADD CONSTRAINT courses_folder_key UNIQUE (folder); + +-- Path of a file in the folder of its course, without extension: `Notes/Lecture 1 Notes` +-- (the note is `Notes/Lecture 1 Notes.md` and its pdf `Notes/Lecture 1 Notes.pdf`) +ALTER TABLE course_files ADD COLUMN "path" TEXT; +UPDATE course_files SET "path" = 'Notes/' || trim(regexp_replace( + regexp_replace("name", '[/\\:*?"<>|]', '-', 'g'), '\s+', ' ', 'g')); +ALTER TABLE course_files ALTER COLUMN "path" SET NOT NULL; +ALTER TABLE course_files ADD CONSTRAINT course_files_course_id_path_key UNIQUE (course_id, "path"); + +-- Time spent working on a task, counted by the pomodoro +ALTER TABLE tasks ADD COLUMN time_spent INTEGER NOT NULL DEFAULT 0 CHECK (time_spent >= 0); + +-- Kinds of the slots and tasks of the associations +INSERT INTO tags ("name", color, scopes, slug) VALUES + ('{"fr": "Réunion", "en": "Meeting"}', '#db2777', '{slot,task}', 'meeting'), + ('{"fr": "Événement", "en": "Event"}', '#4f46e5', '{slot,task}', 'event'); + +-- migrate:down +DELETE FROM courses WHERE kind = 'association'; +DELETE FROM time_slots WHERE tag_id IN (SELECT id FROM tags WHERE slug IN ('meeting', 'event')); +DELETE FROM tags WHERE slug IN ('meeting', 'event'); +ALTER TABLE tasks DROP COLUMN time_spent; +ALTER TABLE course_files DROP COLUMN "path"; +ALTER TABLE courses DROP COLUMN folder; +ALTER TABLE courses DROP COLUMN kind; diff --git a/db/schema.sql b/db/schema.sql index 19ce074..4a2d0fa 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -31,6 +31,7 @@ CREATE TABLE public.course_files ( created_at timestamp with time zone DEFAULT now() CONSTRAINT lecture_notes_created_at_not_null NOT NULL, updated_at timestamp with time zone DEFAULT now() CONSTRAINT lecture_notes_updated_at_not_null NOT NULL, kind text NOT NULL, + path text NOT NULL, CONSTRAINT course_files_kind_check CHECK ((kind = ANY (ARRAY['note'::text, 'upload'::text]))) ); @@ -64,7 +65,10 @@ CREATE TABLE public.courses ( name text NOT NULL, code text, teachers text[] DEFAULT '{}'::text[] NOT NULL, - main_page_url text + main_page_url text, + kind text NOT NULL, + folder text NOT NULL, + CONSTRAINT courses_kind_check CHECK ((kind = ANY (ARRAY['course'::text, 'association'::text]))) ); @@ -201,7 +205,9 @@ CREATE TABLE public.tasks ( description text, links text[] DEFAULT '{}'::text[] NOT NULL, deadline timestamp with time zone, - done boolean DEFAULT false NOT NULL + done boolean DEFAULT false NOT NULL, + time_spent integer DEFAULT 0 NOT NULL, + CONSTRAINT tasks_time_spent_check CHECK ((time_spent >= 0)) ); @@ -303,6 +309,14 @@ ALTER TABLE ONLY public.tasks ALTER COLUMN id SET DEFAULT nextval('public.tasks_ ALTER TABLE ONLY public.time_slots ALTER COLUMN id SET DEFAULT nextval('public.time_slots_id_seq'::regclass); +-- +-- Name: course_files course_files_course_id_path_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.course_files + ADD CONSTRAINT course_files_course_id_path_key UNIQUE (course_id, path); + + -- -- Name: course_files course_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -311,6 +325,14 @@ ALTER TABLE ONLY public.course_files ADD CONSTRAINT course_files_pkey PRIMARY KEY (id); +-- +-- Name: courses courses_folder_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.courses + ADD CONSTRAINT courses_folder_key UNIQUE (folder); + + -- -- Name: courses courses_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -505,4 +527,5 @@ ALTER TABLE ONLY public.time_slots INSERT INTO public.schema_migrations (version) VALUES ('20260928120000'), ('20260928150000'), - ('20260929090000'); + ('20260929090000'), + ('20260929180000'); diff --git a/frontend/src/components/AppContent.vue b/frontend/src/components/AppContent.vue index 926e0c1..857b963 100644 --- a/frontend/src/components/AppContent.vue +++ b/frontend/src/components/AppContent.vue @@ -12,11 +12,14 @@ import { import { Separator } from '@/components/ui/separator' import { SidebarInset, SidebarTrigger } from '@/components/ui/sidebar' import ThemeToggle from '@/components/ThemeToggle.vue' +import { formatDuration, useFocus } from '@/services/focus' import { locales, setLocale } from '@/services/i18n' import { useI18n } from 'vue-i18n' +import { Pause, Timer } from '@lucide/vue' const route = useRoute() const { locale: currentLocale } = useI18n() +const { session, elapsed, isRunning } = useFocus() const selectedLocale = computed( () => locales.find((l) => l.lang === currentLocale.value) ?? locales[0], @@ -30,7 +33,21 @@ const selectedLocale = computed(

{{ route.meta.title ? $t(route.meta.title) : '' }}

- + + + + + {{ formatDuration(elapsed) }} + + +
    -
  • +
  • +
diff --git a/frontend/src/components/calendar/TodoList.vue b/frontend/src/components/calendar/TodoList.vue new file mode 100644 index 0000000..c0da214 --- /dev/null +++ b/frontend/src/components/calendar/TodoList.vue @@ -0,0 +1,185 @@ + + + diff --git a/frontend/src/components/course/AssociationDeadlinesTab.vue b/frontend/src/components/course/AssociationDeadlinesTab.vue new file mode 100644 index 0000000..b0b09f3 --- /dev/null +++ b/frontend/src/components/course/AssociationDeadlinesTab.vue @@ -0,0 +1,190 @@ + + + diff --git a/frontend/src/components/course/CourseFilesTab.vue b/frontend/src/components/course/CourseFilesTab.vue index a57363d..755965a 100644 --- a/frontend/src/components/course/CourseFilesTab.vue +++ b/frontend/src/components/course/CourseFilesTab.vue @@ -18,14 +18,15 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { useCourseFiles, useLectureNoteMutations } from '@/services/api/course-files' import { useTags } from '@/services/api/tags' import { formatDate } from '@/utils/dates' -import type { CourseFile } from '@/utils/types' +import { noteRoute } from '@/utils/routes' +import type { Course, CourseFile } from '@/utils/types' -const props = defineProps<{ courseId: number }>() +const props = defineProps<{ course: Course }>() const { t, locale } = useI18n() const { tagsById } = useTags() -const { data: files, isPending, isError, refetch } = useCourseFiles(() => props.courseId) -const { remove } = useLectureNoteMutations(() => props.courseId) +const { data: files, isPending, isError, refetch } = useCourseFiles(() => props.course.id) +const { remove } = useLectureNoteMutations(() => props.course.id) const deleteOpen = ref(false) const deletedFile = ref() @@ -50,7 +51,7 @@ async function confirmDelete() {
- {{ $t('files.title') }} + {{ $t(`files.title.${course.kind}`) }} {{ $t('files.description') }}
@@ -67,7 +68,7 @@ async function confirmDelete() { {{ $t('files.coming-soon') }}