From a5a88c7f33ac1cf9583d73ec8251cc0b261a27ec Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 2 Nov 2025 10:22:38 +0100 Subject: [PATCH] feat: expand workspace to include libertas_infra and libertas_worker feat(libertas_api): add dependency on libertas_infra and async-nats refactor(libertas_api): consolidate config loading and add broker_url refactor(libertas_api): integrate NATS client into app state and services feat(libertas_core): introduce config module for database and server settings fix(libertas_core): enhance error handling with detailed messages feat(libertas_infra): create infrastructure layer with database repositories feat(libertas_infra): implement Postgres repositories for media and albums feat(libertas_worker): add worker service to process media jobs via NATS --- Cargo.lock | 514 +++++++++++++++++- Cargo.toml | 2 +- libertas_api/Cargo.toml | 2 + libertas_api/src/config.rs | 27 +- libertas_api/src/factory.rs | 85 +-- libertas_api/src/services/media_service.rs | 23 +- libertas_api/src/state.rs | 1 + libertas_core/Cargo.toml | 8 + libertas_core/src/config.rs | 37 ++ libertas_core/src/error.rs | 4 +- libertas_core/src/lib.rs | 2 + libertas_core/src/plugins.rs | 26 + libertas_infra/.gitignore | 1 + libertas_infra/Cargo.toml | 16 + libertas_infra/src/factory.rs | 75 +++ libertas_infra/src/lib.rs | 2 + .../src/repositories/album_repository.rs | 91 ++++ .../src/repositories/media_repository.rs | 93 ++++ libertas_infra/src/repositories/mod.rs | 3 + .../src/repositories/user_repository.rs | 96 ++++ libertas_worker/Cargo.toml | 24 + libertas_worker/src/config.rs | 17 + libertas_worker/src/main.rs | 80 +++ 23 files changed, 1122 insertions(+), 107 deletions(-) create mode 100644 libertas_core/src/config.rs create mode 100644 libertas_core/src/plugins.rs create mode 100644 libertas_infra/.gitignore create mode 100644 libertas_infra/Cargo.toml create mode 100644 libertas_infra/src/factory.rs create mode 100644 libertas_infra/src/lib.rs create mode 100644 libertas_infra/src/repositories/album_repository.rs create mode 100644 libertas_infra/src/repositories/media_repository.rs create mode 100644 libertas_infra/src/repositories/mod.rs create mode 100644 libertas_infra/src/repositories/user_repository.rs create mode 100644 libertas_worker/Cargo.toml create mode 100644 libertas_worker/src/config.rs create mode 100644 libertas_worker/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 024400d..3221aba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -35,6 +44,43 @@ dependencies = [ "password-hash", ] +[[package]] +name = "async-nats" +version = "0.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f834a80c3ab6109b9c8f5ca6661a578cf31e088e831b6ce07c6b23cca04f6742" +dependencies = [ + "base64", + "bytes", + "futures-util", + "memchr", + "nkeys", + "nuid", + "once_cell", + "pin-project", + "portable-atomic", + "rand", + "regex", + "ring", + "rustls-native-certs", + "rustls-pemfile", + "rustls-webpki 0.102.8", + "serde", + "serde_json", + "serde_nanos", + "serde_repr", + "thiserror 1.0.69", + "time", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-util", + "tokio-websockets", + "tracing", + "tryhard", + "url", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -204,6 +250,9 @@ name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -250,6 +299,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -344,6 +403,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "der" version = "0.7.10" @@ -362,6 +427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -427,6 +493,7 @@ dependencies = [ "ed25519", "serde", "sha2", + "signature", "subtle", "zeroize", ] @@ -1065,6 +1132,7 @@ version = "0.1.0" dependencies = [ "anyhow", "argon2", + "async-nats", "async-trait", "axum", "axum-extra", @@ -1074,6 +1142,7 @@ dependencies = [ "headers", "jsonwebtoken", "libertas_core", + "libertas_infra", "once_cell", "rand_core 0.9.3", "serde", @@ -1093,7 +1162,36 @@ dependencies = [ "bytes", "chrono", "futures", - "thiserror", + "serde", + "sqlx", + "thiserror 2.0.17", + "uuid", +] + +[[package]] +name = "libertas_infra" +version = "0.1.0" +dependencies = [ + "async-trait", + "libertas_core", + "sqlx", + "uuid", +] + +[[package]] +name = "libertas_worker" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-nats", + "bytes", + "futures-util", + "libertas_core", + "libertas_infra", + "serde", + "serde_json", + "sqlx", + "tokio", "uuid", ] @@ -1202,6 +1300,30 @@ dependencies = [ "version_check", ] +[[package]] +name = "nkeys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879011babc47a1c7fdf5a935ae3cfe94f34645ca0cac1c7f6424b36fc743d1bf" +dependencies = [ + "data-encoding", + "ed25519", + "ed25519-dalek", + "getrandom 0.2.16", + "log", + "rand", + "signatory", +] + +[[package]] +name = "nuid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc895af95856f929163a0aa20c26a78d26bfdc839f51b9d5aa7a5b79e52b7e83" +dependencies = [ + "rand", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1270,6 +1392,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + [[package]] name = "p256" version = "0.13.2" @@ -1359,6 +1487,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1398,6 +1546,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "potential_utf" version = "0.1.4" @@ -1503,6 +1657,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rfc6979" version = "0.4.0" @@ -1513,6 +1696,20 @@ dependencies = [ "subtle", ] +[[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.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rsa" version = "0.9.8" @@ -1542,6 +1739,72 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.23.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1554,6 +1817,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1574,6 +1846,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1623,6 +1918,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_nanos" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93142f0367a4cc53ae0fead1bcda39e85beccfad3dcd717656cacab94b12985" +dependencies = [ + "serde", +] + [[package]] name = "serde_path_to_error" version = "0.1.20" @@ -1634,6 +1938,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1683,6 +1998,18 @@ dependencies = [ "libc", ] +[[package]] +name = "signatory" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31" +dependencies = [ + "pkcs8", + "rand_core 0.6.4", + "signature", + "zeroize", +] + [[package]] name = "signature" version = "2.2.0" @@ -1701,7 +2028,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 2.0.17", "time", ] @@ -1790,7 +2117,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -1874,7 +2201,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.17", "tracing", "uuid", "whoami", @@ -1913,7 +2240,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.17", "tracing", "uuid", "whoami", @@ -1939,7 +2266,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.17", "tracing", "url", "uuid", @@ -1996,13 +2323,33 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2100,6 +2447,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -2111,6 +2468,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-websockets" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591660438b3038dd04d16c938271c79e7e06260ad2ea2885a4861bfb238605d" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-sink", + "http", + "httparse", + "rand", + "ring", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "webpki-roots 0.26.11", +] + [[package]] name = "tower" version = "0.5.2" @@ -2171,6 +2562,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tryhard" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fe58ebd5edd976e0fe0f8a14d2a04b7c81ef153ea9a54eebc42e67c2c23b4e5" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "typenum" version = "1.19.0" @@ -2204,6 +2605,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[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.7" @@ -2312,6 +2719,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.4", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.6.1" @@ -2390,6 +2815,15 @@ dependencies = [ "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]] name = "windows-sys" version = "0.60.2" @@ -2423,6 +2857,22 @@ dependencies = [ "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 0.52.6", + "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]] name = "windows-targets" version = "0.53.5" @@ -2433,7 +2883,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", @@ -2446,6 +2896,12 @@ 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_gnullvm" version = "0.53.1" @@ -2458,6 +2914,12 @@ 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_aarch64_msvc" version = "0.53.1" @@ -2470,12 +2932,24 @@ 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_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[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_gnullvm" version = "0.53.1" @@ -2488,6 +2962,12 @@ 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_i686_msvc" version = "0.53.1" @@ -2500,6 +2980,12 @@ 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_gnu" version = "0.53.1" @@ -2512,6 +2998,12 @@ 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_gnullvm" version = "0.53.1" @@ -2524,6 +3016,12 @@ 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 = "windows_x86_64_msvc" version = "0.53.1" diff --git a/Cargo.toml b/Cargo.toml index d834181..395014e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "3" -members = ["libertas_api", "libertas_core"] +members = ["libertas_api", "libertas_core", "libertas_infra", "libertas_worker"] diff --git a/libertas_api/Cargo.toml b/libertas_api/Cargo.toml index 752619a..6a6bf82 100644 --- a/libertas_api/Cargo.toml +++ b/libertas_api/Cargo.toml @@ -8,6 +8,7 @@ axum = { version = "0.8.6", features = ["multipart"] } tokio = { version = "1.48.0", features = ["full"] } libertas_core = { path = "../libertas_core" } +libertas_infra = { path = "../libertas_infra" } serde = { version = "1.0.228", features = ["derive"] } sqlx = { version = "0.8.6", features = [ "runtime-tokio", @@ -30,3 +31,4 @@ rand_core = { version = "0.9.3", features = ["std"] } sha2 = "0.10.9" futures = "0.3.31" bytes = "1.10.1" +async-nats = "0.44.2" diff --git a/libertas_api/src/config.rs b/libertas_api/src/config.rs index c9ef7d9..b57de04 100644 --- a/libertas_api/src/config.rs +++ b/libertas_api/src/config.rs @@ -1,25 +1,7 @@ -use libertas_core::error::CoreResult; -use serde::Deserialize; - -#[derive(Deserialize, Clone)] -pub enum DatabaseType { - Postgres, - Sqlite, -} - -#[derive(Deserialize, Clone)] -pub struct DatabaseConfig { - pub db_type: DatabaseType, - pub url: String, -} - -#[derive(Deserialize, Clone)] -pub struct Config { - pub database: DatabaseConfig, - pub server_address: String, - pub jwt_secret: String, - pub media_library_path: String, -} +use libertas_core::{ + config::{Config, DatabaseConfig, DatabaseType}, + error::CoreResult, +}; pub fn load_config() -> CoreResult { Ok(Config { @@ -30,5 +12,6 @@ pub fn load_config() -> CoreResult { server_address: "127.0.0.1:8080".to_string(), jwt_secret: "super_secret_jwt_key".to_string(), media_library_path: "media_library".to_string(), + broker_url: "nats://localhost:4222".to_string(), }) } diff --git a/libertas_api/src/factory.rs b/libertas_api/src/factory.rs index 5b40fbd..66de256 100644 --- a/libertas_api/src/factory.rs +++ b/libertas_api/src/factory.rs @@ -1,14 +1,14 @@ use std::sync::Arc; use libertas_core::{ + config::Config, error::{CoreError, CoreResult}, - repositories::UserRepository, }; -use sqlx::{Pool, Postgres, Sqlite}; +use libertas_infra::factory::{ + build_album_repository, build_database_pool, build_media_repository, build_user_repository, +}; use crate::{ - config::{Config, DatabaseConfig, DatabaseType}, - repositories::user_repository::{PostgresUserRepository, SqliteUserRepository}, security::{Argon2Hasher, JwtGenerator}, services::{ album_service::AlbumServiceImpl, media_service::MediaServiceImpl, @@ -17,13 +17,12 @@ use crate::{ state::AppState, }; -#[derive(Clone)] -enum DatabasePool { - Postgres(Pool), - Sqlite(Pool), -} - pub async fn build_app_state(config: Config) -> CoreResult { + let nats_client = async_nats::connect(&config.broker_url) + .await + .map_err(|e| CoreError::Config(format!("Failed to connect to NATS: {}", e)))?; + println!("API connected to NATS at {}", config.broker_url); + let db_pool = build_database_pool(&config.database).await?; let user_repo = build_user_repository(&config.database, db_pool.clone()).await?; @@ -34,7 +33,11 @@ pub async fn build_app_state(config: Config) -> CoreResult { let tokenizer = Arc::new(JwtGenerator::new(config.jwt_secret.clone())); let user_service = Arc::new(UserServiceImpl::new(user_repo, hasher, tokenizer.clone())); - let media_service = Arc::new(MediaServiceImpl::new(media_repo.clone(), config.clone())); + let media_service = Arc::new(MediaServiceImpl::new( + media_repo.clone(), + config.clone(), + nats_client.clone(), + )); let album_service = Arc::new(AlbumServiceImpl::new(album_repo, media_repo)); Ok(AppState { @@ -42,64 +45,6 @@ pub async fn build_app_state(config: Config) -> CoreResult { media_service, album_service, token_generator: tokenizer, + nats_client, }) } - -async fn build_database_pool(db_config: &DatabaseConfig) -> CoreResult { - match db_config.db_type { - DatabaseType::Postgres => { - let pool = sqlx::postgres::PgPoolOptions::new() - .max_connections(50) - .connect(&db_config.url) - .await - .map_err(|e| CoreError::Database(e.to_string()))?; - Ok(DatabasePool::Postgres(pool)) - } - DatabaseType::Sqlite => { - let pool = sqlx::sqlite::SqlitePoolOptions::new() - .max_connections(2) - .connect(&db_config.url) - .await - .map_err(|e| CoreError::Database(e.to_string()))?; - Ok(DatabasePool::Sqlite(pool)) - } - } -} - -async fn build_user_repository( - _db_config: &DatabaseConfig, - pool: DatabasePool, -) -> CoreResult> { - match pool { - DatabasePool::Postgres(pg_pool) => Ok(Arc::new(PostgresUserRepository::new(pg_pool))), - DatabasePool::Sqlite(sqlite_pool) => Ok(Arc::new(SqliteUserRepository::new(sqlite_pool))), - } -} - -async fn build_media_repository( - _db_config: &DatabaseConfig, - pool: DatabasePool, -) -> CoreResult> { - match pool { - DatabasePool::Postgres(pg_pool) => Ok(Arc::new( - crate::repositories::media_repository::PostgresMediaRepository::new(pg_pool), - )), - DatabasePool::Sqlite(_sqlite_pool) => Err(CoreError::Database( - "Sqlite media repository not implemented".to_string(), - )), - } -} - -async fn build_album_repository( - _db_config: &DatabaseConfig, - pool: DatabasePool, -) -> CoreResult> { - match pool { - DatabasePool::Postgres(pg_pool) => Ok(Arc::new( - crate::repositories::album_repository::PostgresAlbumRepository::new(pg_pool), - )), - DatabasePool::Sqlite(_sqlite_pool) => Err(CoreError::Database( - "Sqlite album repository not implemented".to_string(), - )), - } -} diff --git a/libertas_api/src/services/media_service.rs b/libertas_api/src/services/media_service.rs index 346dde0..e40bb7f 100644 --- a/libertas_api/src/services/media_service.rs +++ b/libertas_api/src/services/media_service.rs @@ -4,26 +4,35 @@ use async_trait::async_trait; use chrono::Datelike; use futures::stream::StreamExt; use libertas_core::{ + config::Config, error::{CoreError, CoreResult}, models::Media, repositories::MediaRepository, schema::UploadMediaData, services::MediaService, }; +use serde_json::json; use sha2::{Digest, Sha256}; use tokio::{fs, io::AsyncWriteExt}; use uuid::Uuid; -use crate::config::Config; - pub struct MediaServiceImpl { repo: Arc, config: Config, + nats_client: async_nats::Client, } impl MediaServiceImpl { - pub fn new(repo: Arc, config: Config) -> Self { - Self { repo, config } + pub fn new( + repo: Arc, + config: Config, + nats_client: async_nats::Client, + ) -> Self { + Self { + repo, + config, + nats_client, + } } } @@ -89,6 +98,12 @@ impl MediaService for MediaServiceImpl { self.repo.create(&media_model).await?; + let job_payload = json!({ "media_id": media_model.id }); + self.nats_client + .publish("media.new".to_string(), job_payload.to_string().into()) + .await + .map_err(|e| CoreError::Unknown(format!("Failed to publish NATS job: {}", e)))?; + Ok(media_model) } diff --git a/libertas_api/src/state.rs b/libertas_api/src/state.rs index 6537dcf..4f2a7bd 100644 --- a/libertas_api/src/state.rs +++ b/libertas_api/src/state.rs @@ -10,4 +10,5 @@ pub struct AppState { pub media_service: Arc, pub album_service: Arc, pub token_generator: Arc, + pub nats_client: async_nats::Client, } diff --git a/libertas_core/Cargo.toml b/libertas_core/Cargo.toml index eed3d0e..eec4097 100644 --- a/libertas_core/Cargo.toml +++ b/libertas_core/Cargo.toml @@ -11,3 +11,11 @@ chrono = "0.4.42" futures = "0.3.31" thiserror = "2.0.17" uuid = "1.18.1" +sqlx = { version = "0.8.6", features = [ + "runtime-tokio", + "postgres", + "uuid", + "chrono", + "sqlite", +] } +serde = { version = "1.0.228", features = ["derive"] } diff --git a/libertas_core/src/config.rs b/libertas_core/src/config.rs new file mode 100644 index 0000000..ad80543 --- /dev/null +++ b/libertas_core/src/config.rs @@ -0,0 +1,37 @@ +use serde::Deserialize; + +use crate::error::CoreResult; + +#[derive(Deserialize, Clone)] +pub enum DatabaseType { + Postgres, + Sqlite, +} + +#[derive(Deserialize, Clone)] +pub struct DatabaseConfig { + pub db_type: DatabaseType, + pub url: String, +} + +#[derive(Deserialize, Clone)] +pub struct Config { + pub database: DatabaseConfig, + pub server_address: String, + pub jwt_secret: String, + pub media_library_path: String, + pub broker_url: String, +} + +pub fn load_config() -> CoreResult { + Ok(Config { + database: DatabaseConfig { + db_type: DatabaseType::Postgres, + url: "postgres://postgres:postgres@localhost:5432/libertas_db".to_string(), + }, + server_address: "127.0.0.1:8080".to_string(), + jwt_secret: "super_secret_jwt_key".to_string(), + media_library_path: "media_library".to_string(), + broker_url: "amqp://guest:guest@localhost:5672/".to_string(), + }) +} diff --git a/libertas_core/src/error.rs b/libertas_core/src/error.rs index bc40939..d964e45 100644 --- a/libertas_core/src/error.rs +++ b/libertas_core/src/error.rs @@ -23,8 +23,8 @@ pub enum CoreError { #[error("Authentication failed: {0}")] Auth(String), - #[error("An unknown error occurred")] - Unknown, + #[error("An unknown error occurred: {0}")] + Unknown(String), } pub type CoreResult = Result; diff --git a/libertas_core/src/lib.rs b/libertas_core/src/lib.rs index e8f91e6..999c907 100644 --- a/libertas_core/src/lib.rs +++ b/libertas_core/src/lib.rs @@ -1,5 +1,7 @@ +pub mod config; pub mod error; pub mod models; +pub mod plugins; pub mod repositories; pub mod schema; pub mod services; diff --git a/libertas_core/src/plugins.rs b/libertas_core/src/plugins.rs new file mode 100644 index 0000000..550c604 --- /dev/null +++ b/libertas_core/src/plugins.rs @@ -0,0 +1,26 @@ +use std::sync::Arc; + +use async_trait::async_trait; + +use crate::{ + error::CoreResult, + models::Media, + repositories::{AlbumRepository, MediaRepository, UserRepository}, +}; + +pub struct PluginData { + pub message: String, +} + +pub struct PluginContext { + pub media_repo: Arc, + pub album_repo: Arc, + pub user_repo: Arc, +} + +#[async_trait] +pub trait MediaProcessorPlugin: Send + Sync { + fn name(&self) -> &'static str; + + async fn process(&self, media: &Media, context: &PluginContext) -> CoreResult; +} diff --git a/libertas_infra/.gitignore b/libertas_infra/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/libertas_infra/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/libertas_infra/Cargo.toml b/libertas_infra/Cargo.toml new file mode 100644 index 0000000..a11f87d --- /dev/null +++ b/libertas_infra/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libertas_infra" +version = "0.1.0" +edition = "2024" + +[dependencies] +libertas_core = { path = "../libertas_core" } +sqlx = { version = "0.8.6", features = [ + "runtime-tokio", + "postgres", + "uuid", + "chrono", + "sqlite", +] } +async-trait = "0.1.89" +uuid = { version = "1.18.1", features = ["v4"] } diff --git a/libertas_infra/src/factory.rs b/libertas_infra/src/factory.rs new file mode 100644 index 0000000..0139588 --- /dev/null +++ b/libertas_infra/src/factory.rs @@ -0,0 +1,75 @@ +use std::sync::Arc; + +use libertas_core::{ + config::{DatabaseConfig, DatabaseType}, + error::{CoreError, CoreResult}, + repositories::UserRepository, +}; +use sqlx::{Pool, Postgres, Sqlite}; + +use crate::repositories::user_repository::{PostgresUserRepository, SqliteUserRepository}; + +#[derive(Clone)] +pub enum DatabasePool { + Postgres(Pool), + Sqlite(Pool), +} + +pub async fn build_database_pool(db_config: &DatabaseConfig) -> CoreResult { + match db_config.db_type { + DatabaseType::Postgres => { + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(50) + .connect(&db_config.url) + .await + .map_err(|e| CoreError::Database(e.to_string()))?; + Ok(DatabasePool::Postgres(pool)) + } + DatabaseType::Sqlite => { + let pool = sqlx::sqlite::SqlitePoolOptions::new() + .max_connections(2) + .connect(&db_config.url) + .await + .map_err(|e| CoreError::Database(e.to_string()))?; + Ok(DatabasePool::Sqlite(pool)) + } + } +} + +pub async fn build_user_repository( + _db_config: &DatabaseConfig, + pool: DatabasePool, +) -> CoreResult> { + match pool { + DatabasePool::Postgres(pg_pool) => Ok(Arc::new(PostgresUserRepository::new(pg_pool))), + DatabasePool::Sqlite(sqlite_pool) => Ok(Arc::new(SqliteUserRepository::new(sqlite_pool))), + } +} + +pub async fn build_media_repository( + _db_config: &DatabaseConfig, + pool: DatabasePool, +) -> CoreResult> { + match pool { + DatabasePool::Postgres(pg_pool) => Ok(Arc::new( + crate::repositories::media_repository::PostgresMediaRepository::new(pg_pool), + )), + DatabasePool::Sqlite(_sqlite_pool) => Err(CoreError::Database( + "Sqlite media repository not implemented".to_string(), + )), + } +} + +pub async fn build_album_repository( + _db_config: &DatabaseConfig, + pool: DatabasePool, +) -> CoreResult> { + match pool { + DatabasePool::Postgres(pg_pool) => Ok(Arc::new( + crate::repositories::album_repository::PostgresAlbumRepository::new(pg_pool), + )), + DatabasePool::Sqlite(_sqlite_pool) => Err(CoreError::Database( + "Sqlite album repository not implemented".to_string(), + )), + } +} diff --git a/libertas_infra/src/lib.rs b/libertas_infra/src/lib.rs new file mode 100644 index 0000000..12c05bb --- /dev/null +++ b/libertas_infra/src/lib.rs @@ -0,0 +1,2 @@ +pub mod factory; +pub mod repositories; diff --git a/libertas_infra/src/repositories/album_repository.rs b/libertas_infra/src/repositories/album_repository.rs new file mode 100644 index 0000000..46fd502 --- /dev/null +++ b/libertas_infra/src/repositories/album_repository.rs @@ -0,0 +1,91 @@ +use async_trait::async_trait; +use libertas_core::{ + error::{CoreError, CoreResult}, + models::Album, + repositories::AlbumRepository, +}; +use sqlx::PgPool; +use uuid::Uuid; + +#[derive(Clone)] +pub struct PostgresAlbumRepository { + pool: PgPool, +} + +impl PostgresAlbumRepository { + pub fn new(pool: PgPool) -> Self { + Self { pool } + } +} + +#[async_trait] +impl AlbumRepository for PostgresAlbumRepository { + async fn create(&self, album: Album) -> CoreResult<()> { + sqlx::query!( + r#" + INSERT INTO albums (id, owner_id, name, description, is_public, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7) + "#, + album.id, + album.owner_id, + album.name, + album.description, + album.is_public, + album.created_at, + album.updated_at + ) + .execute(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string()))?; + + Ok(()) + } + + async fn find_by_id(&self, id: Uuid) -> CoreResult> { + sqlx::query_as!( + Album, + r#" + SELECT id, owner_id, name, description, is_public, created_at, updated_at + FROM albums + WHERE id = $1 + "#, + id + ) + .fetch_optional(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } + + async fn list_by_user(&self, user_id: Uuid) -> CoreResult> { + sqlx::query_as!( + Album, + r#" + SELECT id, owner_id, name, description, is_public, created_at, updated_at + FROM albums + WHERE owner_id = $1 + "#, + user_id + ) + .fetch_all(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } + + async fn add_media_to_album(&self, album_id: Uuid, media_ids: &[Uuid]) -> CoreResult<()> { + // Use sqlx's `unnest` feature to pass the Vec efficiently + sqlx::query!( + r#" + INSERT INTO album_media (album_id, media_id) + SELECT $1, media_id FROM unnest($2::uuid[]) as media_id + ON CONFLICT (album_id, media_id) DO NOTHING + "#, + album_id, + media_ids + ) + .execute(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string()))?; + + Ok(()) + } +} diff --git a/libertas_infra/src/repositories/media_repository.rs b/libertas_infra/src/repositories/media_repository.rs new file mode 100644 index 0000000..7983b73 --- /dev/null +++ b/libertas_infra/src/repositories/media_repository.rs @@ -0,0 +1,93 @@ +use async_trait::async_trait; +use libertas_core::{ + error::{CoreError, CoreResult}, + models::Media, + repositories::MediaRepository, +}; +use sqlx::PgPool; +use uuid::Uuid; + +#[derive(Clone)] +pub struct PostgresMediaRepository { + pool: PgPool, +} + +impl PostgresMediaRepository { + pub fn new(pool: PgPool) -> Self { + Self { pool } + } +} + +#[async_trait] +impl MediaRepository for PostgresMediaRepository { + async fn create(&self, media: &Media) -> CoreResult<()> { + sqlx::query!( + r#" + INSERT INTO media (id, owner_id, storage_path, original_filename, mime_type, hash, created_at, width, height) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + "#, + media.id, + media.owner_id, + media.storage_path, + media.original_filename, + media.mime_type, + media.hash, + media.created_at, + media.width, + media.height + ) + .execute(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string()))?; + + Ok(()) + } + + async fn find_by_hash(&self, hash: &str) -> CoreResult> { + sqlx::query_as!( + Media, + r#" + SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at, + extracted_location, width, height + FROM media + WHERE hash = $1 + "#, + hash + ) + .fetch_optional(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } + + async fn find_by_id(&self, id: Uuid) -> CoreResult> { + sqlx::query_as!( + Media, + r#" + SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at, + extracted_location, width, height + FROM media + WHERE id = $1 + "#, + id + ) + .fetch_optional(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } + + async fn list_by_user(&self, user_id: Uuid) -> CoreResult> { + sqlx::query_as!( + Media, + r#" + SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at, + extracted_location, width, height + FROM media + WHERE owner_id = $1 + "#, + user_id + ) + .fetch_all(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } +} diff --git a/libertas_infra/src/repositories/mod.rs b/libertas_infra/src/repositories/mod.rs new file mode 100644 index 0000000..f1f652d --- /dev/null +++ b/libertas_infra/src/repositories/mod.rs @@ -0,0 +1,3 @@ +pub mod album_repository; +pub mod media_repository; +pub mod user_repository; diff --git a/libertas_infra/src/repositories/user_repository.rs b/libertas_infra/src/repositories/user_repository.rs new file mode 100644 index 0000000..b18cc0e --- /dev/null +++ b/libertas_infra/src/repositories/user_repository.rs @@ -0,0 +1,96 @@ +use async_trait::async_trait; +use libertas_core::{ + error::{CoreError, CoreResult}, + models::User, + repositories::UserRepository, +}; +use sqlx::{PgPool, SqlitePool, types::Uuid}; + +#[derive(Clone)] +pub struct PostgresUserRepository { + pool: PgPool, +} + +impl PostgresUserRepository { + pub fn new(pool: PgPool) -> Self { + Self { pool } + } +} + +#[derive(Clone)] +pub struct SqliteUserRepository { + _pool: SqlitePool, +} + +impl SqliteUserRepository { + pub fn new(pool: SqlitePool) -> Self { + Self { _pool: pool } + } +} + +#[async_trait] +impl UserRepository for PostgresUserRepository { + async fn create(&self, user: User) -> CoreResult<()> { + sqlx::query!( + r#" + INSERT INTO users (id, username, email, hashed_password, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6) + "#, + user.id, + user.username, + user.email, + user.hashed_password, + user.created_at, + user.updated_at + ) + .execute(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string()))?; + + Ok(()) + } + + async fn find_by_email(&self, email: &str) -> CoreResult> { + sqlx::query_as!(User, "SELECT * FROM users WHERE email = $1", email) + .fetch_optional(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } + + async fn find_by_username(&self, username: &str) -> CoreResult> { + sqlx::query_as!(User, "SELECT * FROM users WHERE username = $1", username) + .fetch_optional(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } + + async fn find_by_id(&self, id: Uuid) -> CoreResult> { + sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id) + .fetch_optional(&self.pool) + .await + .map_err(|e| CoreError::Database(e.to_string())) + } +} + +#[async_trait] +impl UserRepository for SqliteUserRepository { + async fn create(&self, _user: User) -> CoreResult<()> { + println!("SQLITE REPO: Creating user"); + Ok(()) + } + + async fn find_by_email(&self, _email: &str) -> CoreResult> { + println!("SQLITE REPO: Finding user by email"); + Ok(None) + } + + async fn find_by_username(&self, _username: &str) -> CoreResult> { + println!("SQLITE REPO: Finding user by username"); + Ok(None) + } + + async fn find_by_id(&self, _id: Uuid) -> CoreResult> { + println!("SQLITE REPO: Finding user by id"); + Ok(None) + } +} diff --git a/libertas_worker/Cargo.toml b/libertas_worker/Cargo.toml new file mode 100644 index 0000000..87adee7 --- /dev/null +++ b/libertas_worker/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "libertas_worker" +version = "0.1.0" +edition = "2024" + +[dependencies] +libertas_core = { path = "../libertas_core" } +libertas_infra = { path = "../libertas_infra" } + +anyhow = "1.0.100" +async-nats = "0.44.2" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +tokio = { version = "1.48.0", features = ["full"] } +sqlx = { version = "0.8.6", features = [ + "runtime-tokio", + "postgres", + "uuid", + "chrono", + "sqlite", +] } +futures-util = "0.3.31" +bytes = "1.10.1" +uuid = { version = "1.18.1", features = ["v4", "serde"] } diff --git a/libertas_worker/src/config.rs b/libertas_worker/src/config.rs new file mode 100644 index 0000000..b57de04 --- /dev/null +++ b/libertas_worker/src/config.rs @@ -0,0 +1,17 @@ +use libertas_core::{ + config::{Config, DatabaseConfig, DatabaseType}, + error::CoreResult, +}; + +pub fn load_config() -> CoreResult { + Ok(Config { + database: DatabaseConfig { + db_type: DatabaseType::Postgres, + url: "postgres://postgres:postgres@localhost:5432/libertas_db".to_string(), + }, + server_address: "127.0.0.1:8080".to_string(), + jwt_secret: "super_secret_jwt_key".to_string(), + media_library_path: "media_library".to_string(), + broker_url: "nats://localhost:4222".to_string(), + }) +} diff --git a/libertas_worker/src/main.rs b/libertas_worker/src/main.rs new file mode 100644 index 0000000..1f18bf2 --- /dev/null +++ b/libertas_worker/src/main.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use futures_util::StreamExt; +use libertas_core::plugins::PluginContext; +use libertas_infra::factory::{ + build_album_repository, build_database_pool, build_media_repository, build_user_repository, +}; +use serde::Deserialize; +use uuid::Uuid; + +use crate::config::load_config; + +pub mod config; + +#[derive(Deserialize)] +struct MediaJob { + media_id: Uuid, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + println!("Starting libertas worker..."); + + let config = load_config()?; + + let db_pool = build_database_pool(&config.database).await?; + println!("Worker connected to database."); + + let media_repo = build_media_repository(&config.database, db_pool.clone()).await?; + let album_repo = build_album_repository(&config.database, db_pool.clone()).await?; + let user_repo = build_user_repository(&config.database, db_pool.clone()).await?; + + // 3. Create the abstracted PluginContext + let context = Arc::new(PluginContext { + media_repo, + album_repo, + user_repo, + }); + println!("Plugin context created."); + + let nats_client = async_nats::connect(&config.broker_url).await?; + + println!("Connected to NATS server at {}", config.broker_url); + + let mut subscriber = nats_client + .queue_subscribe("media.new", "media_processors".to_string()) + .await?; + println!("Subscribed to 'media.new' queue"); + + while let Some(msg) = subscriber.next().await { + let context = context.clone(); + tokio::spawn(async move { + if let Err(e) = process_job(msg, context).await { + eprintln!("Job failed: {}", e); + } + }); + } + + Ok(()) +} + +async fn process_job(msg: async_nats::Message, context: Arc) -> anyhow::Result<()> { + let job: MediaJob = serde_json::from_slice(&msg.payload)?; + + let media = context + .media_repo + .find_by_id(job.media_id) + .await? + .ok_or_else(|| anyhow::anyhow!("Media not found: {}", job.media_id))?; + + println!("Processing media: {}", media.original_filename); + + // 3. Pass to the (future) PluginManager + // plugin_manager.process(&media, &context).await?; + + // For now, we'll just print a success message + println!("Successfully processed job for media_id: {}", media.id); + + Ok(()) +}