From 6fa26b3ddbde4f59da04de41bf0efc343286165b Mon Sep 17 00:00:00 2001 From: 409 <409dev@protonmail.com> Date: Sun, 7 Sep 2025 17:19:16 +0200 Subject: [PATCH] remove sqlite extensions to fix docker issue UUIDs are now generated in the backend before insertion --- .dockerignore | 1 + Dockerfile | 12 - backend/.gitignore | 1 + backend/Cargo.lock | 1 + backend/Cargo.toml | 2 +- backend/migrations/20250906174941_init.sql | 11 +- .../20250906184417_create_admin_user.sql | 11 - backend/sqlite_extensions/uuid | Bin 35944 -> 0 bytes backend/sqlite_extensions/uuid.c | 231 ------------------ backend/src/bin/backend/main.rs | 7 +- backend/src/lib/domain/warren/models/mod.rs | 1 + .../lib/domain/warren/models/option/mod.rs | 74 ++++++ .../warren/models/option/requests/delete.rs | 47 ++++ .../warren/models/option/requests/get.rs | 57 +++++ .../warren/models/option/requests/mod.rs | 6 + .../warren/models/option/requests/set.rs | 83 +++++++ .../warren/models/user/requests/register.rs | 29 ++- .../src/lib/domain/warren/ports/metrics.rs | 11 + backend/src/lib/domain/warren/ports/mod.rs | 20 ++ .../src/lib/domain/warren/ports/notifier.rs | 13 + .../src/lib/domain/warren/ports/repository.rs | 20 ++ backend/src/lib/domain/warren/service/auth.rs | 73 +++++- backend/src/lib/domain/warren/service/mod.rs | 1 + .../src/lib/domain/warren/service/option.rs | 90 +++++++ .../src/lib/outbound/metrics_debug_logger.rs | 25 +- .../src/lib/outbound/notifier_debug_logger.rs | 25 +- backend/src/lib/outbound/sqlite/auth.rs | 35 +-- backend/src/lib/outbound/sqlite/mod.rs | 5 +- backend/src/lib/outbound/sqlite/options.rs | 134 ++++++++++ backend/src/lib/outbound/sqlite/share.rs | 5 +- backend/src/lib/outbound/sqlite/warrens.rs | 27 +- backend/warren.db | Bin 69632 -> 0 bytes compose.yaml | 4 +- 33 files changed, 754 insertions(+), 308 deletions(-) delete mode 100644 backend/migrations/20250906184417_create_admin_user.sql delete mode 100755 backend/sqlite_extensions/uuid delete mode 100644 backend/sqlite_extensions/uuid.c create mode 100644 backend/src/lib/domain/warren/models/option/mod.rs create mode 100644 backend/src/lib/domain/warren/models/option/requests/delete.rs create mode 100644 backend/src/lib/domain/warren/models/option/requests/get.rs create mode 100644 backend/src/lib/domain/warren/models/option/requests/mod.rs create mode 100644 backend/src/lib/domain/warren/models/option/requests/set.rs create mode 100644 backend/src/lib/domain/warren/service/option.rs create mode 100644 backend/src/lib/outbound/sqlite/options.rs delete mode 100644 backend/warren.db diff --git a/.dockerignore b/.dockerignore index 8b346a2..a286678 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,3 +12,4 @@ frontend/node_modules backend/target backend/.gitignore +backend/data diff --git a/Dockerfile b/Dockerfile index ad74d44..82f54ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,19 +13,9 @@ COPY frontend/ ./ RUN npm run generate -FROM alpine:3 AS sqlite-extension-compiler -WORKDIR /var/lib/warren - -RUN apk add sqlite-libs sqlite-dev build-base -COPY backend/sqlite_extensions sqlite_extensions -RUN gcc -g -fPIC -shared sqlite_extensions/uuid.c -o sqlite_extensions/uuid - - FROM rust:alpine AS backend-builder WORKDIR /usr/src/warren -RUN apk add sqlite sqlite-dev build-base - COPY backend/Cargo.toml backend/Cargo.lock ./ RUN mkdir -p src/bin/backend && mkdir src/lib && echo "fn main() {}" > src/bin/backend/main.rs && echo "" > src/lib/lib.rs RUN apk add --no-cache pkgconfig openssl openssl-dev libc-dev openssl-libs-static @@ -38,8 +28,6 @@ RUN cargo build --release FROM alpine:3 WORKDIR /var/lib/warren -COPY --from=sqlite-extension-compiler /var/lib/warren/sqlite_extensions/uuid /var/lib/warren/sqlite_extensions/uuid - COPY --from=backend-builder /usr/src/warren/target/release/warren_backend /usr/bin/warren COPY --from=frontend-builder /usr/src/warren/dist ./frontend diff --git a/backend/.gitignore b/backend/.gitignore index 1fb42cc..afc3d77 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ target serve .env +data diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 6471dd9..28c5ab0 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2721,6 +2721,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d1c20af..be508b3 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -39,5 +39,5 @@ tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] } tracing = "0.1.41" tracing-subscriber = "0.3.19" url = "2.5.4" -uuid = { version = "1.17.0", features = ["serde"] } +uuid = { version = "1.17.0", features = ["serde", "v4"] } zip = "4.5.0" diff --git a/backend/migrations/20250906174941_init.sql b/backend/migrations/20250906174941_init.sql index 1a8d604..0138dd8 100644 --- a/backend/migrations/20250906174941_init.sql +++ b/backend/migrations/20250906174941_init.sql @@ -1,5 +1,5 @@ CREATE TABLE users ( - id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())), + id BLOB NOT NULL PRIMARY KEY, oidc_sub TEXT UNIQUE, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, @@ -10,7 +10,7 @@ CREATE TABLE users ( ); CREATE TABLE warrens ( - id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())), + id BLOB NOT NULL PRIMARY KEY, name TEXT NOT NULL, path TEXT NOT NULL UNIQUE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP @@ -31,7 +31,7 @@ CREATE TABLE user_warrens ( ); CREATE TABLE shares ( - id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())), + id BLOB NOT NULL PRIMARY KEY, creator_id BLOB NOT NULL REFERENCES users(id) ON DELETE CASCADE, warren_id BLOB NOT NULL REFERENCES warrens(id) ON DELETE CASCADE, path TEXT NOT NULL, @@ -48,3 +48,8 @@ CREATE TABLE auth_sessions ( expires_at DATETIME NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); + +CREATE TABLE application_options ( + key TEXT NOT NULL PRIMARY KEY, + value TEXT NOT NULL +); diff --git a/backend/migrations/20250906184417_create_admin_user.sql b/backend/migrations/20250906184417_create_admin_user.sql deleted file mode 100644 index c3b2983..0000000 --- a/backend/migrations/20250906184417_create_admin_user.sql +++ /dev/null @@ -1,11 +0,0 @@ -INSERT INTO users ( - name, - email, - hash, - admin -) VALUES ( - 'admin', - 'admin@example.com', - '$argon2id$v=19$m=19456,t=2,p=1$H1WsElL4921/WD5oPkY7JQ$aHudNG8z0ns3pRULfuDpuEkxPUbGxq9AHC4QGyt5odc', - true -); diff --git a/backend/sqlite_extensions/uuid b/backend/sqlite_extensions/uuid deleted file mode 100755 index f368b5f2212473c107f7901e24ad0a83b1d3d577..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35944 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 -#include -#include - -#if !defined(SQLITE_ASCII) && !defined(SQLITE_EBCDIC) -#define SQLITE_ASCII 1 -#endif - -/* -** Translate a single byte of Hex into an integer. -** This routine only works if h really is a valid hexadecimal -** character: 0..9a..fA..F -*/ -static unsigned char sqlite3UuidHexToInt(int h) { - assert((h >= '0' && h <= '9') || (h >= 'a' && h <= 'f') || - (h >= 'A' && h <= 'F')); -#ifdef SQLITE_ASCII - h += 9 * (1 & (h >> 6)); -#endif -#ifdef SQLITE_EBCDIC - h += 9 * (1 & ~(h >> 4)); -#endif - return (unsigned char)(h & 0xf); -} - -/* -** Convert a 16-byte BLOB into a well-formed RFC-4122 UUID. The output -** buffer zStr should be at least 37 bytes in length. The output will -** be zero-terminated. -*/ -static void sqlite3UuidBlobToStr(const unsigned char *aBlob, /* Input blob */ - unsigned char *zStr /* Write the answer here */ -) { - static const char zDigits[] = "0123456789abcdef"; - int i, k; - unsigned char x; - k = 0; - for (i = 0, k = 0x550; i < 16; i++, k = k >> 1) { - if (k & 1) { - zStr[0] = '-'; - zStr++; - } - x = aBlob[i]; - zStr[0] = zDigits[x >> 4]; - zStr[1] = zDigits[x & 0xf]; - zStr += 2; - } - *zStr = 0; -} - -/* -** Attempt to parse a zero-terminated input string zStr into a binary -** UUID. Return 0 on success, or non-zero if the input string is not -** parsable. -*/ -static int sqlite3UuidStrToBlob(const unsigned char *zStr, /* Input string */ - unsigned char *aBlob /* Write results here */ -) { - int i; - if (zStr[0] == '{') - zStr++; - for (i = 0; i < 16; i++) { - if (zStr[0] == '-') - zStr++; - if (isxdigit(zStr[0]) && isxdigit(zStr[1])) { - aBlob[i] = (sqlite3UuidHexToInt(zStr[0]) << 4) + - sqlite3UuidHexToInt(zStr[1]); - zStr += 2; - } else { - return 1; - } - } - if (zStr[0] == '}') - zStr++; - return zStr[0] != 0; -} - -/* -** Render sqlite3_value pIn as a 16-byte UUID blob. Return a pointer -** to the blob, or NULL if the input is not well-formed. -*/ -static const unsigned char * -sqlite3UuidInputToBlob(sqlite3_value *pIn, /* Input text */ - unsigned char *pBuf /* output buffer */ -) { - switch (sqlite3_value_type(pIn)) { - case SQLITE_TEXT: { - const unsigned char *z = sqlite3_value_text(pIn); - if (sqlite3UuidStrToBlob(z, pBuf)) - return 0; - return pBuf; - } - case SQLITE_BLOB: { - int n = sqlite3_value_bytes(pIn); - return n == 16 ? sqlite3_value_blob(pIn) : 0; - } - default: { - return 0; - } - } -} - -/* Implementation of uuid() */ -static void sqlite3UuidFunc(sqlite3_context *context, int argc, - sqlite3_value **argv) { - unsigned char aBlob[16]; - unsigned char zStr[37]; - (void)argc; - (void)argv; - sqlite3_randomness(16, aBlob); - aBlob[6] = (aBlob[6] & 0x0f) + 0x40; - aBlob[8] = (aBlob[8] & 0x3f) + 0x80; - sqlite3UuidBlobToStr(aBlob, zStr); - sqlite3_result_text(context, (char *)zStr, 36, SQLITE_TRANSIENT); -} - -/* Implementation of uuid_str() */ -static void sqlite3UuidStrFunc(sqlite3_context *context, int argc, - sqlite3_value **argv) { - unsigned char aBlob[16]; - unsigned char zStr[37]; - const unsigned char *pBlob; - (void)argc; - pBlob = sqlite3UuidInputToBlob(argv[0], aBlob); - if (pBlob == 0) - return; - sqlite3UuidBlobToStr(pBlob, zStr); - sqlite3_result_text(context, (char *)zStr, 36, SQLITE_TRANSIENT); -} - -/* Implementation of uuid_blob() */ -static void sqlite3UuidBlobFunc(sqlite3_context *context, int argc, - sqlite3_value **argv) { - unsigned char aBlob[16]; - const unsigned char *pBlob; - (void)argc; - pBlob = sqlite3UuidInputToBlob(argv[0], aBlob); - if (pBlob == 0) - return; - sqlite3_result_blob(context, pBlob, 16, SQLITE_TRANSIENT); -} - -#ifdef _WIN32 -__declspec(dllexport) -#endif -int sqlite3_uuid_init( - sqlite3 *db, - char **pzErrMsg, - const sqlite3_api_routines *pApi -){ - int rc = SQLITE_OK; - SQLITE_EXTENSION_INIT2(pApi); - (void)pzErrMsg; /* Unused parameter */ - rc = sqlite3_create_function(db, "uuid", 0, SQLITE_UTF8 | SQLITE_INNOCUOUS, - 0, sqlite3UuidFunc, 0, 0); - if (rc == SQLITE_OK) { - rc = sqlite3_create_function(db, "uuid_str", 1, - SQLITE_UTF8 | SQLITE_INNOCUOUS | - SQLITE_DETERMINISTIC, - 0, sqlite3UuidStrFunc, 0, 0); - } - if (rc == SQLITE_OK) { - rc = sqlite3_create_function(db, "uuid_blob", 1, - SQLITE_UTF8 | SQLITE_INNOCUOUS | - SQLITE_DETERMINISTIC, - 0, sqlite3UuidBlobFunc, 0, 0); - } - return rc; -} diff --git a/backend/src/bin/backend/main.rs b/backend/src/bin/backend/main.rs index ac0324e..00d1a74 100644 --- a/backend/src/bin/backend/main.rs +++ b/backend/src/bin/backend/main.rs @@ -46,13 +46,18 @@ async fn main() -> anyhow::Result<()> { None }; + let option_service = + domain::warren::service::option::Service::new(sqlite.clone(), metrics, notifier); + let auth_service = domain::warren::service::auth::Service::new( sqlite, metrics, notifier, config.auth, oidc_service, - ); + option_service, + ) + .await?; let server_config = HttpServerConfig::new( &config.server_address, diff --git a/backend/src/lib/domain/warren/models/mod.rs b/backend/src/lib/domain/warren/models/mod.rs index 05951b6..0233e3a 100644 --- a/backend/src/lib/domain/warren/models/mod.rs +++ b/backend/src/lib/domain/warren/models/mod.rs @@ -1,5 +1,6 @@ pub mod auth_session; pub mod file; +pub mod option; pub mod share; pub mod user; pub mod user_warren; diff --git a/backend/src/lib/domain/warren/models/option/mod.rs b/backend/src/lib/domain/warren/models/option/mod.rs new file mode 100644 index 0000000..13e5731 --- /dev/null +++ b/backend/src/lib/domain/warren/models/option/mod.rs @@ -0,0 +1,74 @@ +mod requests; +use derive_more::Display; +pub use requests::*; +use thiserror::Error; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)] +pub struct OptionKey(String); + +#[derive(Debug, Error)] +pub enum OptionKeyError { + #[error("An OptionKey must not be empty")] + Empty, +} + +impl OptionKey { + pub fn new(raw: &str) -> Result { + let raw = raw.trim(); + + if raw.is_empty() { + return Err(OptionKeyError::Empty); + } + + Ok(Self(raw.to_string())) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +#[derive(Clone, Debug)] +pub struct OptionValue(T) +where + T: OptionType; + +impl OptionValue +where + T: OptionType, +{ + pub fn new(value: T) -> Self { + Self(value) + } + + pub fn inner(&self) -> &T { + &self.0 + } + + pub fn get_inner(self) -> T { + self.0 + } +} + +pub trait OptionType: std::fmt::Debug + Clone + Send + Sync { + type Error: std::fmt::Debug; + + fn parse(raw: &str) -> Result; + fn to_string(&self) -> String; +} + +impl OptionType for bool { + type Error = anyhow::Error; + + fn parse(raw: &str) -> Result { + Ok(match raw.to_lowercase().as_str() { + "true" => true, + "false" => false, + _ => anyhow::bail!("Expected 'true' or 'false': {raw}"), + }) + } + + fn to_string(&self) -> String { + if *self { "true" } else { "false" }.to_string() + } +} diff --git a/backend/src/lib/domain/warren/models/option/requests/delete.rs b/backend/src/lib/domain/warren/models/option/requests/delete.rs new file mode 100644 index 0000000..4ba7ce1 --- /dev/null +++ b/backend/src/lib/domain/warren/models/option/requests/delete.rs @@ -0,0 +1,47 @@ +use thiserror::Error; + +use crate::domain::warren::models::option::OptionKey; + +#[derive(Clone, Debug)] +pub struct DeleteOptionRequest { + key: OptionKey, +} + +impl DeleteOptionRequest { + pub fn new(key: OptionKey) -> Self { + Self { key } + } + + pub fn key(&self) -> &OptionKey { + &self.key + } +} + +impl From for OptionKey { + fn from(value: DeleteOptionRequest) -> Self { + value.key + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeleteOptionResponse { + key: OptionKey, +} + +impl DeleteOptionResponse { + pub fn new(key: OptionKey) -> Self { + Self { key } + } + + pub fn key(&self) -> &OptionKey { + &self.key + } +} + +#[derive(Debug, Error)] +pub enum DeleteOptionError { + #[error("Could not find option with key: {0}")] + NotFound(OptionKey), + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/option/requests/get.rs b/backend/src/lib/domain/warren/models/option/requests/get.rs new file mode 100644 index 0000000..2281676 --- /dev/null +++ b/backend/src/lib/domain/warren/models/option/requests/get.rs @@ -0,0 +1,57 @@ +use thiserror::Error; + +use crate::domain::warren::models::option::{OptionKey, OptionType}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GetOptionRequest { + key: OptionKey, +} + +impl GetOptionRequest { + pub fn new(key: OptionKey) -> Self { + Self { key } + } + + pub fn key(&self) -> &OptionKey { + &self.key + } +} + +impl From for OptionKey { + fn from(value: GetOptionRequest) -> Self { + value.key + } +} + +#[derive(Clone, Debug)] +pub struct GetOptionResponse { + key: OptionKey, + value: T, +} + +impl GetOptionResponse +where + T: OptionType, +{ + pub fn new(key: OptionKey, value: T) -> Self { + Self { key, value } + } + + pub fn key(&self) -> &OptionKey { + &self.key + } + + pub fn value(&self) -> &T { + &self.value + } +} + +#[derive(Debug, Error)] +pub enum GetOptionError { + #[error("Could not find option with key: {0}")] + NotFound(OptionKey), + #[error("Could not parse the option value with the specified type")] + Parse, + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/option/requests/mod.rs b/backend/src/lib/domain/warren/models/option/requests/mod.rs new file mode 100644 index 0000000..6d6c5d6 --- /dev/null +++ b/backend/src/lib/domain/warren/models/option/requests/mod.rs @@ -0,0 +1,6 @@ +mod delete; +mod get; +mod set; +pub use delete::*; +pub use get::*; +pub use set::*; diff --git a/backend/src/lib/domain/warren/models/option/requests/set.rs b/backend/src/lib/domain/warren/models/option/requests/set.rs new file mode 100644 index 0000000..a733bb1 --- /dev/null +++ b/backend/src/lib/domain/warren/models/option/requests/set.rs @@ -0,0 +1,83 @@ +use thiserror::Error; + +use crate::domain::warren::models::option::{OptionKey, OptionType, OptionValue}; + +#[derive(Clone, Debug)] +pub struct SetOptionRequest +where + T: OptionType, +{ + key: OptionKey, + value: OptionValue, +} + +impl SetOptionRequest +where + T: OptionType, +{ + pub fn new(key: OptionKey, value: T) -> Self { + Self { + key, + value: OptionValue::new(value), + } + } + + pub fn key(&self) -> &OptionKey { + &self.key + } + + pub fn value(&self) -> &OptionValue { + &self.value + } + + pub fn unpack(self) -> (OptionKey, OptionValue) { + (self.key, self.value) + } +} + +impl From> for OptionKey +where + T: OptionType, +{ + fn from(value: SetOptionRequest) -> Self { + value.key + } +} + +impl From> for OptionValue +where + T: OptionType, +{ + fn from(value: SetOptionRequest) -> Self { + value.value + } +} + +#[derive(Clone, Debug)] +pub struct SetOptionResponse { + key: OptionKey, + value: T, +} + +impl SetOptionResponse +where + T: OptionType, +{ + pub fn new(key: OptionKey, value: T) -> Self { + Self { key, value } + } + + pub fn key(&self) -> &OptionKey { + &self.key + } + + pub fn value(&self) -> &T { + &self.value + } +} + +#[derive(Debug, Error)] +pub enum SetOptionError { + #[error(transparent)] + Unknown(#[from] anyhow::Error), +} diff --git a/backend/src/lib/domain/warren/models/user/requests/register.rs b/backend/src/lib/domain/warren/models/user/requests/register.rs index 7998aee..99901d1 100644 --- a/backend/src/lib/domain/warren/models/user/requests/register.rs +++ b/backend/src/lib/domain/warren/models/user/requests/register.rs @@ -9,6 +9,8 @@ pub struct RegisterUserRequest { name: UserName, email: UserEmail, password: UserPassword, + bypass_registration_flag: bool, + admin: bool, } impl RegisterUserRequest { @@ -17,6 +19,23 @@ impl RegisterUserRequest { name, email, password, + bypass_registration_flag: false, + admin: false, + } + } + + pub fn new_bypass_flag( + name: UserName, + email: UserEmail, + password: UserPassword, + admin: bool, + ) -> Self { + Self { + name, + email, + password, + bypass_registration_flag: true, + admin, } } @@ -31,11 +50,19 @@ impl RegisterUserRequest { pub fn password(&self) -> &UserPassword { &self.password } + + pub fn admin(&self) -> bool { + self.admin + } + + pub fn bypass_registration_flag(&self) -> bool { + self.bypass_registration_flag + } } impl From for CreateUserRequest { fn from(value: RegisterUserRequest) -> Self { - Self::new(value.name, value.email, value.password, false) + Self::new(value.name, value.email, value.password, value.admin) } } diff --git a/backend/src/lib/domain/warren/ports/metrics.rs b/backend/src/lib/domain/warren/ports/metrics.rs index c63567c..a17f9b0 100644 --- a/backend/src/lib/domain/warren/ports/metrics.rs +++ b/backend/src/lib/domain/warren/ports/metrics.rs @@ -185,3 +185,14 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static { fn record_auth_share_deletion_success(&self) -> impl Future + Send; fn record_auth_share_deletion_failure(&self) -> impl Future + Send; } + +pub trait OptionMetrics: Clone + Send + Sync + 'static { + fn record_option_get_success(&self) -> impl Future + Send; + fn record_option_get_failure(&self) -> impl Future + Send; + + fn record_option_set_success(&self) -> impl Future + Send; + fn record_option_set_failure(&self) -> impl Future + Send; + + fn record_option_delete_success(&self) -> impl Future + Send; + fn record_option_delete_failure(&self) -> impl Future + Send; +} diff --git a/backend/src/lib/domain/warren/ports/mod.rs b/backend/src/lib/domain/warren/ports/mod.rs index 1308d89..6e27ad5 100644 --- a/backend/src/lib/domain/warren/ports/mod.rs +++ b/backend/src/lib/domain/warren/ports/mod.rs @@ -20,6 +20,11 @@ use super::models::{ RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest, }, + option::{ + DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError, + GetOptionRequest, GetOptionResponse, OptionType, SetOptionError, SetOptionRequest, + SetOptionResponse, + }, share::{ CreateShareBaseRequest, CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, @@ -337,3 +342,18 @@ pub trait AuthService: Clone + Send + Sync + 'static { warren_service: &WS, ) -> impl Future>> + Send; } + +pub trait OptionService: Clone + Send + Sync + 'static { + fn get_option( + &self, + request: GetOptionRequest, + ) -> impl Future, GetOptionError>> + Send; + fn set_option( + &self, + request: SetOptionRequest, + ) -> impl Future, SetOptionError>> + Send; + fn delete_option( + &self, + request: DeleteOptionRequest, + ) -> impl Future> + Send; +} diff --git a/backend/src/lib/domain/warren/ports/notifier.rs b/backend/src/lib/domain/warren/ports/notifier.rs index 1951fc7..dd257e7 100644 --- a/backend/src/lib/domain/warren/ports/notifier.rs +++ b/backend/src/lib/domain/warren/ports/notifier.rs @@ -3,6 +3,7 @@ use uuid::Uuid; use crate::domain::warren::models::{ auth_session::requests::FetchAuthSessionResponse, file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse}, + option::{DeleteOptionResponse, GetOptionResponse, OptionType, SetOptionResponse}, share::{ CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse, ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse, @@ -219,3 +220,15 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static { response: &DeleteShareResponse, ) -> impl Future + Send; } + +pub trait OptionNotifier: Clone + Send + Sync + 'static { + fn got_option( + &self, + response: &GetOptionResponse, + ) -> impl Future + Send; + fn set_option( + &self, + response: &SetOptionResponse, + ) -> impl Future + Send; + fn deleted_option(&self, response: &DeleteOptionResponse) -> impl Future + Send; +} diff --git a/backend/src/lib/domain/warren/ports/repository.rs b/backend/src/lib/domain/warren/ports/repository.rs index 59b3c1c..a2c5ab6 100644 --- a/backend/src/lib/domain/warren/ports/repository.rs +++ b/backend/src/lib/domain/warren/ports/repository.rs @@ -12,6 +12,11 @@ use crate::domain::warren::models::{ RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest, }, + option::{ + DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError, + GetOptionRequest, GetOptionResponse, OptionType, SetOptionError, SetOptionRequest, + SetOptionResponse, + }, share::{ CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, ListSharesError, @@ -188,3 +193,18 @@ pub trait AuthRepository: Clone + Send + Sync + 'static { request: FetchUserWarrenRequest, ) -> impl Future> + Send; } + +pub trait OptionRepository: Clone + Send + Sync + 'static { + fn get_option( + &self, + request: GetOptionRequest, + ) -> impl Future, GetOptionError>> + Send; + fn set_option( + &self, + request: SetOptionRequest, + ) -> impl Future, SetOptionError>> + Send; + fn delete_option( + &self, + request: DeleteOptionRequest, + ) -> impl Future> + Send; +} diff --git a/backend/src/lib/domain/warren/service/auth.rs b/backend/src/lib/domain/warren/service/auth.rs index d2c8175..2f568aa 100644 --- a/backend/src/lib/domain/warren/service/auth.rs +++ b/backend/src/lib/domain/warren/service/auth.rs @@ -12,6 +12,7 @@ use crate::{ }, }, file::FileStream, + option::{GetOptionError, GetOptionRequest, OptionKey, SetOptionRequest}, share::{ CreateShareBaseRequest, CreateShareError, CreateShareResponse, DeleteShareError, DeleteShareRequest, DeleteShareResponse, ListSharesError, @@ -24,7 +25,7 @@ use crate::{ ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse, ListUsersError, ListUsersRequest, LoginUserError, LoginUserOidcError, LoginUserOidcRequest, LoginUserOidcResponse, LoginUserRequest, LoginUserResponse, RegisterUserError, - RegisterUserRequest, User, + RegisterUserRequest, User, UserEmail, UserName, UserPassword, }, user_warren::{ UserWarren, @@ -46,7 +47,10 @@ use crate::{ WarrenTouchResponse, }, }, - ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService}, + ports::{ + AuthMetrics, AuthNotifier, AuthRepository, AuthService, OptionService, + WarrenService, + }, }, }, }; @@ -97,54 +101,100 @@ impl AuthConfig { } #[derive(Debug, Clone)] -pub struct Service +pub struct Service where R: AuthRepository, M: AuthMetrics, N: AuthNotifier, OIDC: OidcService, + O: OptionService, { repository: R, metrics: M, notifier: N, oidc: Option, + option_service: O, config: AuthConfig, } -impl Service +impl Service where R: AuthRepository, M: AuthMetrics, N: AuthNotifier, OIDC: OidcService, + O: OptionService, { - pub fn new( + pub async fn new( repository: R, metrics: M, notifier: N, config: AuthConfig, oidc: Option, - ) -> Self { - Self { + option_service: O, + ) -> anyhow::Result { + let service = Self { repository, metrics, notifier, config, oidc, - } + option_service, + }; + + service.init().await?; + + Ok(service) } pub fn oidc(&self) -> Option<&OIDC> { self.oidc.as_ref() } + + async fn init(&self) -> anyhow::Result<()> { + self.create_admin_user_if_init().await?; + + Ok(()) + } + + async fn create_admin_user_if_init(&self) -> anyhow::Result<()> { + const CREATED_ADMIN_USER_KEY: &str = "CREATED_ADMIN_USER"; + + let key = OptionKey::new(CREATED_ADMIN_USER_KEY)?; + let request = GetOptionRequest::new(key.clone()); + match self.option_service.get_option::(request).await { + // If the option is already set and true we don't have to do anything anymore + Ok(opt) if *opt.value() => return Ok(()), + Err(e) => match e { + // The option is not yet set so we proceed with the admin user creation + GetOptionError::NotFound(_) => (), + _ => return Err(e.into()), + }, + // The option was set but it was false so we proceed with the admin user creation + _ => (), + } + + let name = UserName::new("admin")?; + let email = UserEmail::new("admin@example.com")?; + let password = UserPassword::new("admin1234567")?; + let request = RegisterUserRequest::new_bypass_flag(name, email, password, true); + + self.register_user(request).await?; + self.option_service + .set_option(SetOptionRequest::new(key, true)) + .await?; + + Ok(()) + } } -impl AuthService for Service +impl AuthService for Service where R: AuthRepository, M: AuthMetrics, N: AuthNotifier, OIDC: OidcService, + O: OptionService, { async fn create_warren( &self, @@ -253,7 +303,7 @@ where } async fn register_user(&self, request: RegisterUserRequest) -> Result { - if !self.config.allow_registration { + if !self.config.allow_registration && !request.bypass_registration_flag() { self.metrics.record_user_registration_failure().await; return Err(RegisterUserError::Disabled); } @@ -972,12 +1022,13 @@ where } } -impl Service +impl Service where R: AuthRepository, M: AuthMetrics, N: AuthNotifier, OIDC: OidcService, + O: OptionService, { /// A helper to get a [UserWarren], [User] and the underlying request from an [AuthRequest] async fn get_session_data_and_user_warren( diff --git a/backend/src/lib/domain/warren/service/mod.rs b/backend/src/lib/domain/warren/service/mod.rs index ae9858f..a926d28 100644 --- a/backend/src/lib/domain/warren/service/mod.rs +++ b/backend/src/lib/domain/warren/service/mod.rs @@ -1,3 +1,4 @@ pub mod auth; pub mod file_system; +pub mod option; pub mod warren; diff --git a/backend/src/lib/domain/warren/service/option.rs b/backend/src/lib/domain/warren/service/option.rs new file mode 100644 index 0000000..b30e8c5 --- /dev/null +++ b/backend/src/lib/domain/warren/service/option.rs @@ -0,0 +1,90 @@ +use crate::domain::warren::{ + models::option::{ + DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError, + GetOptionRequest, GetOptionResponse, OptionType, SetOptionError, SetOptionRequest, + SetOptionResponse, + }, + ports::{OptionMetrics, OptionNotifier, OptionRepository, OptionService}, +}; + +#[derive(Debug, Clone)] +pub struct Service +where + R: OptionRepository, + M: OptionMetrics, + N: OptionNotifier, +{ + repository: R, + metrics: M, + notifier: N, +} + +impl Service +where + R: OptionRepository, + M: OptionMetrics, + N: OptionNotifier, +{ + pub fn new(repository: R, metrics: M, notifier: N) -> Self { + Self { + repository, + metrics, + notifier, + } + } +} + +impl OptionService for Service +where + R: OptionRepository, + M: OptionMetrics, + N: OptionNotifier, +{ + async fn get_option( + &self, + request: GetOptionRequest, + ) -> Result, GetOptionError> { + let result = self.repository.get_option(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics.record_option_get_success().await; + self.notifier.got_option(response).await; + } else { + self.metrics.record_option_get_failure().await; + } + + result + } + + async fn set_option( + &self, + request: SetOptionRequest, + ) -> Result, SetOptionError> { + let result = self.repository.set_option(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics.record_option_set_success().await; + self.notifier.set_option(response).await; + } else { + self.metrics.record_option_set_failure().await; + } + + result + } + + async fn delete_option( + &self, + request: DeleteOptionRequest, + ) -> Result { + let result = self.repository.delete_option(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics.record_option_delete_success().await; + self.notifier.deleted_option(response).await; + } else { + self.metrics.record_option_delete_failure().await; + } + + result + } +} diff --git a/backend/src/lib/outbound/metrics_debug_logger.rs b/backend/src/lib/outbound/metrics_debug_logger.rs index 1ed7b5f..a5b31b9 100644 --- a/backend/src/lib/outbound/metrics_debug_logger.rs +++ b/backend/src/lib/outbound/metrics_debug_logger.rs @@ -1,6 +1,6 @@ use crate::domain::{ oidc::ports::OidcMetrics, - warren::ports::{AuthMetrics, FileSystemMetrics, WarrenMetrics}, + warren::ports::{AuthMetrics, FileSystemMetrics, OptionMetrics, WarrenMetrics}, }; #[derive(Debug, Clone, Copy)] @@ -452,3 +452,26 @@ impl OidcMetrics for MetricsDebugLogger { tracing::debug!("[Metrics] OIDC get user info failed"); } } + +impl OptionMetrics for MetricsDebugLogger { + async fn record_option_get_success(&self) { + tracing::debug!("[Metrics] Get option succeeded"); + } + async fn record_option_get_failure(&self) { + tracing::debug!("[Metrics] Get option failed"); + } + + async fn record_option_set_success(&self) { + tracing::debug!("[Metrics] Set option succeeded"); + } + async fn record_option_set_failure(&self) { + tracing::debug!("[Metrics] Set option failed"); + } + + async fn record_option_delete_success(&self) { + tracing::debug!("[Metrics] Delete option succeeded"); + } + async fn record_option_delete_failure(&self) { + tracing::debug!("[Metrics] Delete option failed"); + } +} diff --git a/backend/src/lib/outbound/notifier_debug_logger.rs b/backend/src/lib/outbound/notifier_debug_logger.rs index aa49796..2612a99 100644 --- a/backend/src/lib/outbound/notifier_debug_logger.rs +++ b/backend/src/lib/outbound/notifier_debug_logger.rs @@ -9,6 +9,7 @@ use crate::domain::{ models::{ auth_session::requests::FetchAuthSessionResponse, file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse}, + option::{DeleteOptionResponse, GetOptionResponse, OptionType, SetOptionResponse}, share::{ CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse, ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse, @@ -22,7 +23,7 @@ use crate::domain::{ WarrenRmResponse, WarrenSaveResponse, WarrenTouchResponse, }, }, - ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier}, + ports::{AuthNotifier, FileSystemNotifier, OptionNotifier, WarrenNotifier}, }, }; @@ -515,3 +516,25 @@ impl OidcNotifier for NotifierDebugLogger { ); } } + +impl OptionNotifier for NotifierDebugLogger { + async fn got_option(&self, response: &GetOptionResponse) { + tracing::debug!( + "[Notifier] Got option {}: {}", + response.key().to_string(), + response.value().to_string(), + ); + } + + async fn set_option(&self, response: &SetOptionResponse) { + tracing::debug!( + "[Notifier] Set option {} to {}", + response.key().to_string(), + response.value().to_string(), + ); + } + + async fn deleted_option(&self, response: &DeleteOptionResponse) { + tracing::debug!("[Notifier] Deleted option {}", response.key().to_string()); + } +} diff --git a/backend/src/lib/outbound/sqlite/auth.rs b/backend/src/lib/outbound/sqlite/auth.rs index bae35cf..47fd427 100644 --- a/backend/src/lib/outbound/sqlite/auth.rs +++ b/backend/src/lib/outbound/sqlite/auth.rs @@ -48,7 +48,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user = self .create_user( @@ -72,7 +72,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user = self .create_or_update_user( @@ -93,7 +93,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user = self .edit_user( @@ -115,7 +115,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; self.delete_user_from_database(&mut connection, request.user_id()) .await @@ -136,7 +136,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user = self .get_user_from_email(&mut connection, request.email()) @@ -166,7 +166,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let session = self .create_session(&mut connection, request.user(), request.expiration()) @@ -184,7 +184,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let session = self .get_auth_session(&mut connection, request.session_id()) @@ -212,7 +212,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user_warren = self .add_user_to_warren(&mut connection, request.user_warren()) @@ -230,7 +230,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user_warren = self .update_user_warren(&mut connection, request.user_warren()) @@ -248,7 +248,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user_warren = self .remove_user_from_warren(&mut connection, request.user_id(), request.warren_id()) @@ -272,7 +272,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user_warrens = self .get_user_warrens(&mut connection, request.user_id()) @@ -290,7 +290,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let user_warrens = self .get_all_user_warrens(&mut connection) @@ -308,7 +308,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; self.get_user_warren(&mut connection, request.user_id(), request.warren_id()) .await @@ -326,7 +326,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let users = self .fetch_users(&mut connection) @@ -345,7 +345,7 @@ impl AuthRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let users = self .fetch_users(&mut connection) @@ -402,6 +402,7 @@ impl Sqlite { let user: User = sqlx::query_as( "INSERT INTO users ( + id, name, email, hash, @@ -411,12 +412,14 @@ impl Sqlite { $1, $2, $3, - $4 + $4, + $5 ) RETURNING * ", ) + .bind(Uuid::new_v4()) .bind(name) .bind(email) .bind(password_hash) diff --git a/backend/src/lib/outbound/sqlite/mod.rs b/backend/src/lib/outbound/sqlite/mod.rs index 542f313..3584370 100644 --- a/backend/src/lib/outbound/sqlite/mod.rs +++ b/backend/src/lib/outbound/sqlite/mod.rs @@ -6,6 +6,7 @@ use sqlx::{ }; use tokio::task::JoinHandle; pub mod auth; +pub mod options; pub mod share; pub mod warrens; @@ -29,10 +30,6 @@ impl Sqlite { pub async fn new(config: SqliteConfig) -> anyhow::Result { let opts = SqliteConnectOptions::from_str(&config.database_url)? .create_if_missing(true) - .extension_with_entrypoint( - "/var/lib/warren/sqlite_extensions/uuid", - "sqlite3_uuid_init", - ) .disable_statement_logging(); let pool = SqlitePoolOptions::new().connect_with(opts).await?; diff --git a/backend/src/lib/outbound/sqlite/options.rs b/backend/src/lib/outbound/sqlite/options.rs new file mode 100644 index 0000000..ad05bd6 --- /dev/null +++ b/backend/src/lib/outbound/sqlite/options.rs @@ -0,0 +1,134 @@ +use anyhow::Context; +use sqlx::FromRow; + +use crate::domain::warren::{ + models::option::{ + DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError, + GetOptionRequest, GetOptionResponse, OptionKey, OptionType, SetOptionError, + SetOptionRequest, SetOptionResponse, + }, + ports::OptionRepository, +}; + +use super::{Sqlite, is_not_found_error}; + +#[derive(Debug, FromRow)] +struct OptionRow { + key: String, + value: String, +} + +impl OptionRepository for Sqlite { + async fn get_option( + &self, + request: GetOptionRequest, + ) -> Result, GetOptionError> { + let mut connection = self + .pool + .acquire() + .await + .context("Failed to get a Sqlite connection")?; + + let key: OptionKey = request.into(); + + let row: OptionRow = sqlx::query_as( + " + SELECT + key, + value + FROM + application_options + WHERE + key = $1", + ) + .bind(key.as_str()) + .fetch_one(&mut *connection) + .await + .map_err(|e| { + if is_not_found_error(&e) { + GetOptionError::NotFound(key) + } else { + GetOptionError::Unknown(e.into()) + } + })?; + + let parsed = T::parse(&row.value).map_err(|_| GetOptionError::Parse)?; + + Ok(GetOptionResponse::new( + OptionKey::new(&row.key).unwrap(), + parsed, + )) + } + + async fn set_option( + &self, + request: SetOptionRequest, + ) -> Result, SetOptionError> { + let mut connection = self + .pool + .acquire() + .await + .context("Failed to get a Sqlite connection")?; + + let (key, value) = request.unpack(); + + sqlx::query_as::<_, OptionRow>( + " + INSERT INTO application_options ( + key, + value + ) VALUES ( + $1, + $2 + ) + RETURNING + key, + value + ", + ) + .bind(key.as_str()) + .bind(value.inner().to_string()) + .fetch_one(&mut *connection) + .await + .map_err(|e| SetOptionError::Unknown(e.into()))?; + + Ok(SetOptionResponse::new(key, value.get_inner())) + } + + async fn delete_option( + &self, + request: DeleteOptionRequest, + ) -> Result { + let mut connection = self + .pool + .acquire() + .await + .context("Failed to get a Sqlite connection")?; + + let key: OptionKey = request.into(); + + sqlx::query_as::<_, OptionRow>( + " + DELETE FROM + application_options + WHERE + key = $1 + RETURNING + key, + value + ", + ) + .bind(key.as_str()) + .fetch_one(&mut *connection) + .await + .map_err(|e| { + if is_not_found_error(&e) { + DeleteOptionError::NotFound(key.clone()) + } else { + DeleteOptionError::Unknown(e.into()) + } + })?; + + Ok(DeleteOptionResponse::new(key)) + } +} diff --git a/backend/src/lib/outbound/sqlite/share.rs b/backend/src/lib/outbound/sqlite/share.rs index ed30116..9e211ef 100644 --- a/backend/src/lib/outbound/sqlite/share.rs +++ b/backend/src/lib/outbound/sqlite/share.rs @@ -154,6 +154,7 @@ pub(super) async fn create_share( let share: ShareRow = sqlx::query_as( " INSERT INTO shares ( + id, creator_id, warren_id, path, @@ -164,12 +165,14 @@ pub(super) async fn create_share( $2, $3, $4, - datetime($5, 'unixepoch') + $5, + datetime($6, 'unixepoch') ) RETURNING * ", ) + .bind(Uuid::new_v4()) .bind(request.creator_id()) .bind(request.warren_id()) .bind(request.base().path()) diff --git a/backend/src/lib/outbound/sqlite/warrens.rs b/backend/src/lib/outbound/sqlite/warrens.rs index 5001ce0..38b7cff 100644 --- a/backend/src/lib/outbound/sqlite/warrens.rs +++ b/backend/src/lib/outbound/sqlite/warrens.rs @@ -32,7 +32,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let warren = self .create_warren(&mut connection, request.name(), request.path()) @@ -47,7 +47,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let warren = self .edit_warren( @@ -70,7 +70,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let warren = self .delete_warren(&mut connection, request.id()) @@ -88,7 +88,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let warrens = self .fetch_warrens(&mut connection, request.ids()) @@ -106,7 +106,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let warrens = self .fetch_all_warrens(&mut connection) @@ -121,7 +121,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let warren = self .get_warren(&mut connection, request.id()) @@ -144,7 +144,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; super::share::get_share(&mut connection, request) .await @@ -159,7 +159,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; super::share::create_share(&mut connection, request) .await @@ -177,7 +177,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; let path = request.path().clone(); @@ -195,7 +195,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; super::share::delete_share(&mut connection, request) .await @@ -211,7 +211,7 @@ impl WarrenRepository for Sqlite { .pool .acquire() .await - .context("Failed to get a PostgreSQL connection")?; + .context("Failed to get a Sqlite connection")?; super::share::verify_password(&mut connection, request) .await @@ -232,16 +232,19 @@ impl Sqlite { let warren: Warren = sqlx::query_as( " INSERT INTO warrens ( + id, name, path ) VALUES ( $1, - $2 + $2, + $3 ) RETURNING * ", ) + .bind(Uuid::new_v4()) .bind(name) .bind(path) .fetch_one(&mut *tx) diff --git a/backend/warren.db b/backend/warren.db deleted file mode 100644 index 4fd31339ee30f75970cffd5db2d1ef6c508e0a93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69632 zcmeI*eP|o!9S86`$&w=Z<(#HNGa1?aVb@kI#iu)+WSN;pv1P}LY$vvilf7^|-JR;2 z^;VtKC?T-fN!GyVM#o^;27_(1-Pp?B3V~%CgLP#u9V>K+VU=3Nv=tdbNf9yG( ztVpuul(-D+eh=$(ch7To&-3{`k92p^S&OF=dP!r``C?Wsu>#dg(KL08Whsj4A&-OP zu{pTNM91a~d8Qkt+fDXRM=un*ng3F~&O=ns=b7tWKkoTdkJ#hsywG*2E9oBVjJdw$ z?shFY54k?rZX;L@0SG_<0{<6*bsOXI4i3_nCQ5QDqsisciezYpq33f((@gKgLOhm? zv&q=;M4W9(Vuw8L+B%Z1vdQ?FBs(`x9!rS?dum~3Hnwn(YO~e;jGlMatt3Gyqj-8Ar;$)W-vBinlWSp$7RSJ4hGbHlvHAxDSoSBW+ zw?dMYq9&I#b=zdJv8mWnBFRoHEs%XBC3EG)WNh}7#~1B&dS?e|O3$fU#ds`3-XtYb z!+dq#oVsfw1)F9M&h|LH!+o??&8n*v$ykxi5*FmriZ#j9;AZ9~<7e2GEM_gOY(wT6 zzV($Zm$$EvzA{`5hm~ZFyPE=FEi>h}Xf}w~D?L;$>#CH>6PD2K+eg< zqL$k+qE?pnF*WPm+-6A03bJ9G%NJE?MK-L|?W4b0iTeopaJSR@NMF04TfS1zdhWU0 z;cNiRCRt!xZZW+e-Eq`IVWXwqa>yEjApRM=jRjg z*j!T=A~{GeQfoQdWy$7MJ$-)X&8S*NBOPhST~!NHYs+m)V|D6Uzq&_xcHUC0K5SD-RJQ|I-TB=1MS*OW4xtc<6PI;vdiT?e3-uEsJ5S4)2TfjXlgaJg}dz9 zIXSCs)2mv$8oM+%b9%{qX}#aq&VbwLjU8@BQmc%_*CsrH1Men#u$JAuzGQcKy4EVa7Jwvbv@)-%n#cO_*ttLL^f)cQ;DR4lP*f;Yah zl?&>9ym9R!YCfmaJML{4wJN71RG(Zv$Gx-i)vQim$Gx*w`-S0H_t7qIAV6QXI)p^t zbt_UjB(y0Aa0elof>>IBf~Mm8z_0%BoS$HXr#|X<9{7%4Vsg zl1_SUt@xWqP#)hSHm7$n&@Nc57*eoxiJcrt9x#t4yUD}G#3<%jGQt1>2tWV=5P$## zAOHafKmY;|fWY~#T;-9cLyDQs@L!);DJ{hImtKg~>0!@}!tQwR$%4(bN6%iLyF3 zJ$ix58A4%UDUmKu7RvF}%BiK)bmg&V?8HRq^z`|Xm{*l;z8|p7BJ=Zqn)wGs{xCoQ z0uX=z1Rwwb2tWV=5P$##Ag~Vv4%z+g){7I?`Tre?xw8){h$=t;0uX=z1Rwwb2tWV= z5P$##Ah1QiZl~PV`9Hb;A9w$6@xcNJKmY;|fB*y_009U<00Izzz#bHM&_3ecavOqm z|NqMr^D=X757r4qh5!U0009U<00Izz00bZa0SN3pftbzV9~czUf4KeiFqvcqg%4kS z^2?qx^L@{i|9%e!sgmwEhc! z^LBxaUmp9!FZk>9KX1MDqhWf7%-etdzd!|$i>bs50cPjYSfy}#D;fAjNys(0TBp+XRV00bZa0SG_<0uX=z1Rwwb2s8+o z_y6PkzX1f35P$##AOHafKmY;|fB*y_0D*ldfb;)-7*tdV0uX=z1Rwwb2tWV=5P$## zAkZRUe*fRbbWqGX8DW3`1Rwwb2tWV=5P$##AOHaf>|=q8Hs@NmUl7!2NR4ojkfw%| zU^uL#MOhAqLLpv@h+$QTC{aO3aq?&=sHEkzEQC}cDst)3bSj+|#HiYOM?TN-Vt|VV zxKTDJjB%r50#{!YsV}-nTYKk4Uekh6RZ+sx(Nu~TL?I<}!YI$nK_Q$TO^Kq)r8Q0n z30hc?Qz0!BQbnE%Dtsgy<)b_7o#V#1(3lu$*!$)p^YecjQ>K_3WP||%5P$##AOHaf iKmY;|fB*y_0D<>TAl|XoP4D(