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
This commit is contained in:
2025-11-02 10:22:38 +01:00
parent 7ea91da20a
commit a5a88c7f33
23 changed files with 1122 additions and 107 deletions

514
Cargo.lock generated
View File

@@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 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]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.21" version = "0.2.21"
@@ -35,6 +44,43 @@ dependencies = [
"password-hash", "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]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.89" version = "0.1.89"
@@ -204,6 +250,9 @@ name = "bytes"
version = "1.10.1" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "cc" name = "cc"
@@ -250,6 +299,16 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 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]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.7" version = "0.8.7"
@@ -344,6 +403,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "data-encoding"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.10" version = "0.7.10"
@@ -362,6 +427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde_core",
] ]
[[package]] [[package]]
@@ -427,6 +493,7 @@ dependencies = [
"ed25519", "ed25519",
"serde", "serde",
"sha2", "sha2",
"signature",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -1065,6 +1132,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
"async-nats",
"async-trait", "async-trait",
"axum", "axum",
"axum-extra", "axum-extra",
@@ -1074,6 +1142,7 @@ dependencies = [
"headers", "headers",
"jsonwebtoken", "jsonwebtoken",
"libertas_core", "libertas_core",
"libertas_infra",
"once_cell", "once_cell",
"rand_core 0.9.3", "rand_core 0.9.3",
"serde", "serde",
@@ -1093,7 +1162,36 @@ dependencies = [
"bytes", "bytes",
"chrono", "chrono",
"futures", "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", "uuid",
] ]
@@ -1202,6 +1300,30 @@ dependencies = [
"version_check", "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]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.6" version = "0.4.6"
@@ -1270,6 +1392,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "p256" name = "p256"
version = "0.13.2" version = "0.13.2"
@@ -1359,6 +1487,26 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 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]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.16"
@@ -1398,6 +1546,12 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.4" version = "0.1.4"
@@ -1503,6 +1657,35 @@ dependencies = [
"bitflags", "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]] [[package]]
name = "rfc6979" name = "rfc6979"
version = "0.4.0" version = "0.4.0"
@@ -1513,6 +1696,20 @@ dependencies = [
"subtle", "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]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.8" version = "0.9.8"
@@ -1542,6 +1739,72 @@ dependencies = [
"semver", "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]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -1554,6 +1817,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 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]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -1574,6 +1846,29 @@ dependencies = [
"zeroize", "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]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.27"
@@ -1623,6 +1918,15 @@ dependencies = [
"serde_core", "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]] [[package]]
name = "serde_path_to_error" name = "serde_path_to_error"
version = "0.1.20" version = "0.1.20"
@@ -1634,6 +1938,17 @@ dependencies = [
"serde_core", "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]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -1683,6 +1998,18 @@ dependencies = [
"libc", "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]] [[package]]
name = "signature" name = "signature"
version = "2.2.0" version = "2.2.0"
@@ -1701,7 +2028,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
"thiserror", "thiserror 2.0.17",
"time", "time",
] ]
@@ -1790,7 +2117,7 @@ dependencies = [
"serde_json", "serde_json",
"sha2", "sha2",
"smallvec", "smallvec",
"thiserror", "thiserror 2.0.17",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing", "tracing",
@@ -1874,7 +2201,7 @@ dependencies = [
"smallvec", "smallvec",
"sqlx-core", "sqlx-core",
"stringprep", "stringprep",
"thiserror", "thiserror 2.0.17",
"tracing", "tracing",
"uuid", "uuid",
"whoami", "whoami",
@@ -1913,7 +2240,7 @@ dependencies = [
"smallvec", "smallvec",
"sqlx-core", "sqlx-core",
"stringprep", "stringprep",
"thiserror", "thiserror 2.0.17",
"tracing", "tracing",
"uuid", "uuid",
"whoami", "whoami",
@@ -1939,7 +2266,7 @@ dependencies = [
"serde", "serde",
"serde_urlencoded", "serde_urlencoded",
"sqlx-core", "sqlx-core",
"thiserror", "thiserror 2.0.17",
"tracing", "tracing",
"url", "url",
"uuid", "uuid",
@@ -1996,13 +2323,33 @@ dependencies = [
"syn", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.17" version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [ 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]] [[package]]
@@ -2100,6 +2447,16 @@ dependencies = [
"syn", "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]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.17" version = "0.1.17"
@@ -2111,6 +2468,40 @@ dependencies = [
"tokio", "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]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@@ -2171,6 +2562,16 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.19.0"
@@ -2204,6 +2605,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.7" version = "2.5.7"
@@ -2312,6 +2719,24 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "whoami" name = "whoami"
version = "1.6.1" version = "1.6.1"
@@ -2390,6 +2815,15 @@ dependencies = [
"windows-targets 0.48.5", "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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.60.2" version = "0.60.2"
@@ -2423,6 +2857,22 @@ dependencies = [
"windows_x86_64_msvc 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 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]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.53.5" version = "0.53.5"
@@ -2433,7 +2883,7 @@ dependencies = [
"windows_aarch64_gnullvm 0.53.1", "windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1", "windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 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_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1", "windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.53.1" version = "0.53.1"
@@ -2458,6 +2914,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.53.1" version = "0.53.1"
@@ -2470,12 +2932,24 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.53.1" version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.53.1" version = "0.53.1"
@@ -2488,6 +2962,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.53.1" version = "0.53.1"
@@ -2500,6 +2980,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 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]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.53.1" version = "0.53.1"
@@ -2512,6 +2998,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 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]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.53.1" version = "0.53.1"
@@ -2524,6 +3016,12 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 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]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.53.1" version = "0.53.1"

View File

@@ -1,3 +1,3 @@
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["libertas_api", "libertas_core"] members = ["libertas_api", "libertas_core", "libertas_infra", "libertas_worker"]

View File

@@ -8,6 +8,7 @@ axum = { version = "0.8.6", features = ["multipart"] }
tokio = { version = "1.48.0", features = ["full"] } tokio = { version = "1.48.0", features = ["full"] }
libertas_core = { path = "../libertas_core" } libertas_core = { path = "../libertas_core" }
libertas_infra = { path = "../libertas_infra" }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
sqlx = { version = "0.8.6", features = [ sqlx = { version = "0.8.6", features = [
"runtime-tokio", "runtime-tokio",
@@ -30,3 +31,4 @@ rand_core = { version = "0.9.3", features = ["std"] }
sha2 = "0.10.9" sha2 = "0.10.9"
futures = "0.3.31" futures = "0.3.31"
bytes = "1.10.1" bytes = "1.10.1"
async-nats = "0.44.2"

View File

@@ -1,25 +1,7 @@
use libertas_core::error::CoreResult; use libertas_core::{
use serde::Deserialize; config::{Config, DatabaseConfig, DatabaseType},
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 fn load_config() -> CoreResult<Config> { pub fn load_config() -> CoreResult<Config> {
Ok(Config { Ok(Config {
@@ -30,5 +12,6 @@ pub fn load_config() -> CoreResult<Config> {
server_address: "127.0.0.1:8080".to_string(), server_address: "127.0.0.1:8080".to_string(),
jwt_secret: "super_secret_jwt_key".to_string(), jwt_secret: "super_secret_jwt_key".to_string(),
media_library_path: "media_library".to_string(), media_library_path: "media_library".to_string(),
broker_url: "nats://localhost:4222".to_string(),
}) })
} }

View File

@@ -1,14 +1,14 @@
use std::sync::Arc; use std::sync::Arc;
use libertas_core::{ use libertas_core::{
config::Config,
error::{CoreError, CoreResult}, 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::{ use crate::{
config::{Config, DatabaseConfig, DatabaseType},
repositories::user_repository::{PostgresUserRepository, SqliteUserRepository},
security::{Argon2Hasher, JwtGenerator}, security::{Argon2Hasher, JwtGenerator},
services::{ services::{
album_service::AlbumServiceImpl, media_service::MediaServiceImpl, album_service::AlbumServiceImpl, media_service::MediaServiceImpl,
@@ -17,13 +17,12 @@ use crate::{
state::AppState, state::AppState,
}; };
#[derive(Clone)]
enum DatabasePool {
Postgres(Pool<Postgres>),
Sqlite(Pool<Sqlite>),
}
pub async fn build_app_state(config: Config) -> CoreResult<AppState> { pub async fn build_app_state(config: Config) -> CoreResult<AppState> {
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 db_pool = build_database_pool(&config.database).await?;
let user_repo = build_user_repository(&config.database, db_pool.clone()).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<AppState> {
let tokenizer = Arc::new(JwtGenerator::new(config.jwt_secret.clone())); let tokenizer = Arc::new(JwtGenerator::new(config.jwt_secret.clone()));
let user_service = Arc::new(UserServiceImpl::new(user_repo, hasher, tokenizer.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)); let album_service = Arc::new(AlbumServiceImpl::new(album_repo, media_repo));
Ok(AppState { Ok(AppState {
@@ -42,64 +45,6 @@ pub async fn build_app_state(config: Config) -> CoreResult<AppState> {
media_service, media_service,
album_service, album_service,
token_generator: tokenizer, token_generator: tokenizer,
nats_client,
}) })
} }
async fn build_database_pool(db_config: &DatabaseConfig) -> CoreResult<DatabasePool> {
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<Arc<dyn UserRepository>> {
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<Arc<dyn libertas_core::repositories::MediaRepository>> {
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<Arc<dyn libertas_core::repositories::AlbumRepository>> {
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(),
)),
}
}

View File

@@ -4,26 +4,35 @@ use async_trait::async_trait;
use chrono::Datelike; use chrono::Datelike;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use libertas_core::{ use libertas_core::{
config::Config,
error::{CoreError, CoreResult}, error::{CoreError, CoreResult},
models::Media, models::Media,
repositories::MediaRepository, repositories::MediaRepository,
schema::UploadMediaData, schema::UploadMediaData,
services::MediaService, services::MediaService,
}; };
use serde_json::json;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use tokio::{fs, io::AsyncWriteExt}; use tokio::{fs, io::AsyncWriteExt};
use uuid::Uuid; use uuid::Uuid;
use crate::config::Config;
pub struct MediaServiceImpl { pub struct MediaServiceImpl {
repo: Arc<dyn MediaRepository>, repo: Arc<dyn MediaRepository>,
config: Config, config: Config,
nats_client: async_nats::Client,
} }
impl MediaServiceImpl { impl MediaServiceImpl {
pub fn new(repo: Arc<dyn MediaRepository>, config: Config) -> Self { pub fn new(
Self { repo, config } repo: Arc<dyn MediaRepository>,
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?; 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) Ok(media_model)
} }

View File

@@ -10,4 +10,5 @@ pub struct AppState {
pub media_service: Arc<dyn MediaService>, pub media_service: Arc<dyn MediaService>,
pub album_service: Arc<dyn AlbumService>, pub album_service: Arc<dyn AlbumService>,
pub token_generator: Arc<dyn TokenGenerator>, pub token_generator: Arc<dyn TokenGenerator>,
pub nats_client: async_nats::Client,
} }

View File

@@ -11,3 +11,11 @@ chrono = "0.4.42"
futures = "0.3.31" futures = "0.3.31"
thiserror = "2.0.17" thiserror = "2.0.17"
uuid = "1.18.1" uuid = "1.18.1"
sqlx = { version = "0.8.6", features = [
"runtime-tokio",
"postgres",
"uuid",
"chrono",
"sqlite",
] }
serde = { version = "1.0.228", features = ["derive"] }

View File

@@ -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<Config> {
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(),
})
}

View File

@@ -23,8 +23,8 @@ pub enum CoreError {
#[error("Authentication failed: {0}")] #[error("Authentication failed: {0}")]
Auth(String), Auth(String),
#[error("An unknown error occurred")] #[error("An unknown error occurred: {0}")]
Unknown, Unknown(String),
} }
pub type CoreResult<T> = Result<T, CoreError>; pub type CoreResult<T> = Result<T, CoreError>;

View File

@@ -1,5 +1,7 @@
pub mod config;
pub mod error; pub mod error;
pub mod models; pub mod models;
pub mod plugins;
pub mod repositories; pub mod repositories;
pub mod schema; pub mod schema;
pub mod services; pub mod services;

View File

@@ -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<dyn MediaRepository>,
pub album_repo: Arc<dyn AlbumRepository>,
pub user_repo: Arc<dyn UserRepository>,
}
#[async_trait]
pub trait MediaProcessorPlugin: Send + Sync {
fn name(&self) -> &'static str;
async fn process(&self, media: &Media, context: &PluginContext) -> CoreResult<PluginData>;
}

1
libertas_infra/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

16
libertas_infra/Cargo.toml Normal file
View File

@@ -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"] }

View File

@@ -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<Postgres>),
Sqlite(Pool<Sqlite>),
}
pub async fn build_database_pool(db_config: &DatabaseConfig) -> CoreResult<DatabasePool> {
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<Arc<dyn UserRepository>> {
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<Arc<dyn libertas_core::repositories::MediaRepository>> {
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<Arc<dyn libertas_core::repositories::AlbumRepository>> {
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(),
)),
}
}

View File

@@ -0,0 +1,2 @@
pub mod factory;
pub mod repositories;

View File

@@ -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<Option<Album>> {
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<Vec<Album>> {
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<Uuid> 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(())
}
}

View File

@@ -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<Option<Media>> {
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<Option<Media>> {
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<Vec<Media>> {
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()))
}
}

View File

@@ -0,0 +1,3 @@
pub mod album_repository;
pub mod media_repository;
pub mod user_repository;

View File

@@ -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<Option<User>> {
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<Option<User>> {
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<Option<User>> {
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<Option<User>> {
println!("SQLITE REPO: Finding user by email");
Ok(None)
}
async fn find_by_username(&self, _username: &str) -> CoreResult<Option<User>> {
println!("SQLITE REPO: Finding user by username");
Ok(None)
}
async fn find_by_id(&self, _id: Uuid) -> CoreResult<Option<User>> {
println!("SQLITE REPO: Finding user by id");
Ok(None)
}
}

View File

@@ -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"] }

View File

@@ -0,0 +1,17 @@
use libertas_core::{
config::{Config, DatabaseConfig, DatabaseType},
error::CoreResult,
};
pub fn load_config() -> CoreResult<Config> {
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(),
})
}

View File

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