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 f368b5f..0000000 Binary files a/backend/sqlite_extensions/uuid and /dev/null differ diff --git a/backend/sqlite_extensions/uuid.c b/backend/sqlite_extensions/uuid.c deleted file mode 100644 index 9732e3c..0000000 --- a/backend/sqlite_extensions/uuid.c +++ /dev/null @@ -1,231 +0,0 @@ -/* -** 2019-10-23 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This SQLite extension implements functions that handling RFC-4122 UUIDs -** Three SQL functions are implemented: -** -** uuid() - generate a version 4 UUID as a string -** uuid_str(X) - convert a UUID X into a well-formed UUID string -** uuid_blob(X) - convert a UUID X into a 16-byte blob -** -** The output from uuid() and uuid_str(X) are always well-formed RFC-4122 -** UUID strings in this format: -** -** xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx -** -** All of the 'x', 'M', and 'N' values are lower-case hexadecimal digits. -** The M digit indicates the "version". For uuid()-generated UUIDs, the -** version is always "4" (a random UUID). The upper three bits of N digit -** are the "variant". This library only supports variant 1 (indicated -** by values of N between '8' and 'b') as those are overwhelming the most -** common. Other variants are for legacy compatibility only. -** -** The output of uuid_blob(X) is always a 16-byte blob. The UUID input -** string is converted in network byte order (big-endian) in accordance -** with RFC-4122 specifications for variant-1 UUIDs. Note that network -** byte order is *always* used, even if the input self-identifies as a -** variant-2 UUID. -** -** The input X to the uuid_str() and uuid_blob() functions can be either -** a string or a BLOB. If it is a BLOB it must be exactly 16 bytes in -** length or else a NULL is returned. If the input is a string it must -** consist of 32 hexadecimal digits, upper or lower case, optionally -** surrounded by {...} and with optional "-" characters interposed in the -** middle. The flexibility of input is inspired by the PostgreSQL -** implementation of UUID functions that accept in all of the following -** formats: -** -** A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11 -** {a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11} -** a0eebc999c0b4ef8bb6d6bb9bd380a11 -** a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11 -** {a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11} -** -** If any of the above inputs are passed into uuid_str(), the output will -** always be in the canonical RFC-4122 format: -** -** a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 -** -** If the X input string has too few or too many digits or contains -** stray characters other than {, }, or -, then NULL is returned. -*/ -#include "sqlite3ext.h" -SQLITE_EXTENSION_INIT1 -#include -#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 4fd3133..0000000 Binary files a/backend/warren.db and /dev/null differ diff --git a/compose.yaml b/compose.yaml index 1aae5ed..0c9c5b6 100644 --- a/compose.yaml +++ b/compose.yaml @@ -11,7 +11,7 @@ services: environment: - 'SERVER_ADDRESS=0.0.0.0' - 'SERVER_PORT=8080' - - 'DATABASE_URL=sqlite:///var/lib/warren/warren.db' + - 'DATABASE_URL=sqlite:///var/lib/warren/data/warren.db' - 'SERVE_DIRECTORY=/serve' - 'CORS_ALLOW_ORIGIN=http://localhost:8081' - 'LOG_LEVEL=debug' @@ -19,7 +19,7 @@ services: - 'ZIP_READ_BUFFER_BYTES=4096' volumes: - './backend/serve:/serve:rw' - - './backend/warren.db:/var/lib/warren/warren.db:rw' + - './backend/data:/var/lib/warren/data:rw' networks: warren-net: name: 'warren-net'