From a1c983251578c071fa76379664c6f582914b3704 Mon Sep 17 00:00:00 2001 From: 409 <409dev@protonmail.com> Date: Sun, 7 Sep 2025 15:09:14 +0200 Subject: [PATCH] migrate to sqlite NOTE: extension loading crashes docker (for some reason) --- Dockerfile | 12 + backend/Cargo.lock | 1 + backend/Cargo.toml | 10 +- .../20250712021357_create_warren_table.sql | 7 - .../20250712024344_add_warren_name.sql | 1 - .../20250716125144_create_users_table.sql | 9 - .../20250716135209_user_email_unique.sql | 1 - .../20250717123142_create_auth_sessions.sql | 6 - ...0250718180903_create_user_warren_table.sql | 11 - ...502_user_warrens_drop_some_permissions.sql | 1 - .../migrations/20250808160437_users_oidc.sql | 2 - .../20250810215614_create_shares_table.sql | 9 - .../20250825115342_share_permissions.sql | 21 -- ...0250825143434_shares_password_nullable.sql | 1 - .../20250825150026_shares_path_index.sql | 1 - backend/migrations/20250906174941_init.sql | 50 ++++ ...l => 20250906184417_create_admin_user.sql} | 0 backend/sqlite_extensions/uuid | Bin 0 -> 35944 bytes backend/sqlite_extensions/uuid.c | 231 ++++++++++++++++++ backend/src/bin/backend/main.rs | 11 +- backend/src/lib/config.rs | 4 - backend/src/lib/outbound/mod.rs | 2 +- backend/src/lib/outbound/postgres/mod.rs | 95 ------- .../lib/outbound/{postgres => sqlite}/auth.rs | 65 +++-- backend/src/lib/outbound/sqlite/mod.rs | 74 ++++++ .../outbound/{postgres => sqlite}/share.rs | 26 +- .../outbound/{postgres => sqlite}/warrens.rs | 38 +-- backend/warren.db | Bin 0 -> 69632 bytes compose.yaml | 19 +- frontend/components/SelectionRect.vue | 10 +- .../components/admin/AddUserWarrenDialog.vue | 69 ++++++ frontend/lib/schemas/admin.ts | 5 + 32 files changed, 536 insertions(+), 256 deletions(-) delete mode 100644 backend/migrations/20250712021357_create_warren_table.sql delete mode 100644 backend/migrations/20250712024344_add_warren_name.sql delete mode 100644 backend/migrations/20250716125144_create_users_table.sql delete mode 100644 backend/migrations/20250716135209_user_email_unique.sql delete mode 100644 backend/migrations/20250717123142_create_auth_sessions.sql delete mode 100644 backend/migrations/20250718180903_create_user_warren_table.sql delete mode 100644 backend/migrations/20250721171502_user_warrens_drop_some_permissions.sql delete mode 100644 backend/migrations/20250808160437_users_oidc.sql delete mode 100644 backend/migrations/20250810215614_create_shares_table.sql delete mode 100644 backend/migrations/20250825115342_share_permissions.sql delete mode 100644 backend/migrations/20250825143434_shares_password_nullable.sql delete mode 100644 backend/migrations/20250825150026_shares_path_index.sql create mode 100644 backend/migrations/20250906174941_init.sql rename backend/migrations/{20250829140914_create_admin_user.sql => 20250906184417_create_admin_user.sql} (100%) create mode 100755 backend/sqlite_extensions/uuid create mode 100644 backend/sqlite_extensions/uuid.c delete mode 100644 backend/src/lib/outbound/postgres/mod.rs rename backend/src/lib/outbound/{postgres => sqlite}/auth.rs (94%) create mode 100644 backend/src/lib/outbound/sqlite/mod.rs rename backend/src/lib/outbound/{postgres => sqlite}/share.rs (92%) rename backend/src/lib/outbound/{postgres => sqlite}/warrens.rs (91%) create mode 100644 backend/warren.db diff --git a/Dockerfile b/Dockerfile index 82f54ee..ad74d44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,19 @@ 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 @@ -28,6 +38,8 @@ 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/Cargo.lock b/backend/Cargo.lock index b68560b..6471dd9 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1235,6 +1235,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ + "cc", "pkg-config", "vcpkg", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d879ade..d1c20af 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -13,7 +13,7 @@ path = "src/bin/backend/main.rs" [dependencies] anyhow = "1.0.98" -argon2 = "0.5.3" +argon2 = { version = "0.5.3", features = ["std"] } axum = { version = "0.8.4", features = ["multipart", "query"] } axum-extra = { version = "0.10.1", features = ["cookie", "multipart"] } base64 = "0.22.1" @@ -29,13 +29,7 @@ regex = "1.11.1" rustix = { version = "1.0.8", features = ["fs"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" -sqlx = { version = "0.8.6", features = [ - "chrono", - "postgres", - "runtime-tokio", - "time", - "uuid", -] } +sqlx = { version = "0.8.6", features = ["chrono", "runtime-tokio", "sqlite", "time", "uuid"] } thiserror = "2.0.12" tokio = { version = "1.46.1", features = ["full"] } tokio-stream = "0.1.17" diff --git a/backend/migrations/20250712021357_create_warren_table.sql b/backend/migrations/20250712021357_create_warren_table.sql deleted file mode 100644 index 0482360..0000000 --- a/backend/migrations/20250712021357_create_warren_table.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE warrens ( - id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(), - path VARCHAR NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX idx_warrens_path ON warrens(path); diff --git a/backend/migrations/20250712024344_add_warren_name.sql b/backend/migrations/20250712024344_add_warren_name.sql deleted file mode 100644 index d0d16cb..0000000 --- a/backend/migrations/20250712024344_add_warren_name.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE warrens ADD COLUMN name VARCHAR NOT NULL; diff --git a/backend/migrations/20250716125144_create_users_table.sql b/backend/migrations/20250716125144_create_users_table.sql deleted file mode 100644 index 05badb1..0000000 --- a/backend/migrations/20250716125144_create_users_table.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE users ( - id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(), - name VARCHAR NOT NULL, - email VARCHAR NOT NULL, - hash VARCHAR NOT NULL, - admin BOOLEAN NOT NULL DEFAULT FALSE, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); diff --git a/backend/migrations/20250716135209_user_email_unique.sql b/backend/migrations/20250716135209_user_email_unique.sql deleted file mode 100644 index 6ab1046..0000000 --- a/backend/migrations/20250716135209_user_email_unique.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE (email); diff --git a/backend/migrations/20250717123142_create_auth_sessions.sql b/backend/migrations/20250717123142_create_auth_sessions.sql deleted file mode 100644 index 7210de5..0000000 --- a/backend/migrations/20250717123142_create_auth_sessions.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE auth_sessions ( - session_id VARCHAR NOT NULL PRIMARY KEY, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); diff --git a/backend/migrations/20250718180903_create_user_warren_table.sql b/backend/migrations/20250718180903_create_user_warren_table.sql deleted file mode 100644 index e56f0f8..0000000 --- a/backend/migrations/20250718180903_create_user_warren_table.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE user_warrens ( - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - warren_id UUID NOT NULL REFERENCES warrens(id) ON DELETE CASCADE, - can_create_children BOOLEAN NOT NULL, - can_list_files BOOLEAN NOT NULL, - can_read_files BOOLEAN NOT NULL, - can_modify_files BOOLEAN NOT NULL, - can_delete_files BOOLEAN NOT NULL, - can_delete_warren BOOLEAN NOT NULL, - PRIMARY KEY(user_id, warren_id) -); diff --git a/backend/migrations/20250721171502_user_warrens_drop_some_permissions.sql b/backend/migrations/20250721171502_user_warrens_drop_some_permissions.sql deleted file mode 100644 index 5299300..0000000 --- a/backend/migrations/20250721171502_user_warrens_drop_some_permissions.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE user_warrens DROP COLUMN can_create_children, DROP COLUMN can_delete_warren; diff --git a/backend/migrations/20250808160437_users_oidc.sql b/backend/migrations/20250808160437_users_oidc.sql deleted file mode 100644 index 86248d6..0000000 --- a/backend/migrations/20250808160437_users_oidc.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE users ALTER COLUMN hash DROP NOT NULL; -ALTER TABLE users ADD COLUMN oidc_sub VARCHAR UNIQUE; diff --git a/backend/migrations/20250810215614_create_shares_table.sql b/backend/migrations/20250810215614_create_shares_table.sql deleted file mode 100644 index c6f3c15..0000000 --- a/backend/migrations/20250810215614_create_shares_table.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE shares ( - id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(), - creator_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - warren_id UUID NOT NULL REFERENCES warrens(id) ON DELETE CASCADE, - path VARCHAR NOT NULL, - password_hash VARCHAR NOT NULL, - expires_at TIMESTAMP, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); diff --git a/backend/migrations/20250825115342_share_permissions.sql b/backend/migrations/20250825115342_share_permissions.sql deleted file mode 100644 index 985d10a..0000000 --- a/backend/migrations/20250825115342_share_permissions.sql +++ /dev/null @@ -1,21 +0,0 @@ -ALTER TABLE - user_warrens -ADD COLUMN - can_list_shares BOOLEAN NOT NULL DEFAULT false, -ADD COLUMN - can_create_shares BOOLEAN NOT NULL DEFAULT false, -ADD COLUMN - can_modify_shares BOOLEAN NOT NULL DEFAULT false, -ADD COLUMN - can_delete_shares BOOLEAN NOT NULL DEFAULT false; - -ALTER TABLE - user_warrens -ALTER COLUMN - can_list_shares DROP DEFAULT, -ALTER COLUMN - can_create_shares DROP DEFAULT, -ALTER COLUMN - can_modify_shares DROP DEFAULT, -ALTER COLUMN - can_delete_shares DROP DEFAULT; diff --git a/backend/migrations/20250825143434_shares_password_nullable.sql b/backend/migrations/20250825143434_shares_password_nullable.sql deleted file mode 100644 index 6b9e082..0000000 --- a/backend/migrations/20250825143434_shares_password_nullable.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE shares ALTER COLUMN password_hash DROP NOT NULL; diff --git a/backend/migrations/20250825150026_shares_path_index.sql b/backend/migrations/20250825150026_shares_path_index.sql deleted file mode 100644 index 58e1ed8..0000000 --- a/backend/migrations/20250825150026_shares_path_index.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX idx_shares_path ON shares(path); diff --git a/backend/migrations/20250906174941_init.sql b/backend/migrations/20250906174941_init.sql new file mode 100644 index 0000000..1a8d604 --- /dev/null +++ b/backend/migrations/20250906174941_init.sql @@ -0,0 +1,50 @@ +CREATE TABLE users ( + id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())), + oidc_sub TEXT UNIQUE, + name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + admin BOOLEAN NOT NULL DEFAULT FALSE, + hash TEXT, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE warrens ( + id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())), + name TEXT NOT NULL, + path TEXT NOT NULL UNIQUE, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE user_warrens ( + user_id BLOB NOT NULL REFERENCES users(id) ON DELETE CASCADE, + warren_id BLOB NOT NULL REFERENCES warrens(id) ON DELETE CASCADE, + can_list_files BOOLEAN NOT NULL, + can_read_files BOOLEAN NOT NULL, + can_modify_files BOOLEAN NOT NULL, + can_delete_files BOOLEAN NOT NULL, + can_list_shares BOOLEAN NOT NULL, + can_create_shares BOOLEAN NOT NULL, + can_modify_shares BOOLEAN NOT NULL, + can_delete_shares BOOLEAN NOT NULL, + PRIMARY KEY(user_id, warren_id) +); + +CREATE TABLE shares ( + id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())), + 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, + password_hash TEXT, + expires_at DATETIME, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_shares_path ON shares(path); + +CREATE TABLE auth_sessions ( + session_id TEXT NOT NULL PRIMARY KEY, + user_id BLOB NOT NULL REFERENCES users(id) ON DELETE CASCADE, + expires_at DATETIME NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/backend/migrations/20250829140914_create_admin_user.sql b/backend/migrations/20250906184417_create_admin_user.sql similarity index 100% rename from backend/migrations/20250829140914_create_admin_user.sql rename to backend/migrations/20250906184417_create_admin_user.sql diff --git a/backend/sqlite_extensions/uuid b/backend/sqlite_extensions/uuid new file mode 100755 index 0000000000000000000000000000000000000000..f368b5f2212473c107f7901e24ad0a83b1d3d577 GIT binary patch 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 d61bb6c..ac0324e 100644 --- a/backend/src/bin/backend/main.rs +++ b/backend/src/bin/backend/main.rs @@ -7,7 +7,7 @@ use warren::{ metrics_debug_logger::MetricsDebugLogger, notifier_debug_logger::NotifierDebugLogger, oidc::{Oidc, OidcConfig}, - postgres::{Postgres, PostgresConfig}, + sqlite::{Sqlite, SqliteConfig}, }, }; @@ -25,16 +25,15 @@ async fn main() -> anyhow::Result<()> { let metrics = MetricsDebugLogger::new(); let notifier = NotifierDebugLogger::new(); - let postgres_config = - PostgresConfig::new(config.database_url.clone(), config.database_name.clone()); - let postgres = Postgres::new(postgres_config).await?; + let sqlite_config = SqliteConfig::new(config.database_url.clone()); + let sqlite = Sqlite::new(sqlite_config).await?; let fs_config = FileSystemConfig::from_env(config.serve_dir.clone())?; let fs = FileSystem::new(fs_config)?; let fs_service = domain::warren::service::file_system::Service::new(fs, metrics, notifier); let warren_service = domain::warren::service::warren::Service::new( - postgres.clone(), + sqlite.clone(), metrics, notifier, fs_service.clone(), @@ -48,7 +47,7 @@ async fn main() -> anyhow::Result<()> { }; let auth_service = domain::warren::service::auth::Service::new( - postgres, + sqlite, metrics, notifier, config.auth, diff --git a/backend/src/lib/config.rs b/backend/src/lib/config.rs index 8598470..52031b3 100644 --- a/backend/src/lib/config.rs +++ b/backend/src/lib/config.rs @@ -6,7 +6,6 @@ use tracing::level_filters::LevelFilter; use crate::domain::warren::service::auth::AuthConfig; const DATABASE_URL_KEY: &str = "DATABASE_URL"; -const DATABASE_NAME_KEY: &str = "DATABASE_NAME"; const SERVER_ADDRESS_KEY: &str = "SERVER_ADDRESS"; const SERVER_PORT_KEY: &str = "SERVER_PORT"; @@ -28,7 +27,6 @@ pub struct Config { pub static_frontend_dir: Option, pub database_url: String, - pub database_name: String, pub log_level: LevelFilter, @@ -45,7 +43,6 @@ impl Config { let static_frontend_dir = Self::load_env(STATIC_FRONTEND_DIRECTORY).ok(); let database_url = Self::load_env(DATABASE_URL_KEY)?; - let database_name = Self::load_env(DATABASE_NAME_KEY)?; let log_level = LevelFilter::from_str(&Self::load_env(LOG_LEVEL_KEY).unwrap_or("INFO".to_string())) @@ -62,7 +59,6 @@ impl Config { static_frontend_dir, database_url, - database_name, log_level, diff --git a/backend/src/lib/outbound/mod.rs b/backend/src/lib/outbound/mod.rs index ae72e56..0aac32f 100644 --- a/backend/src/lib/outbound/mod.rs +++ b/backend/src/lib/outbound/mod.rs @@ -2,4 +2,4 @@ pub mod file_system; pub mod metrics_debug_logger; pub mod notifier_debug_logger; pub mod oidc; -pub mod postgres; +pub mod sqlite; diff --git a/backend/src/lib/outbound/postgres/mod.rs b/backend/src/lib/outbound/postgres/mod.rs deleted file mode 100644 index 727b691..0000000 --- a/backend/src/lib/outbound/postgres/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{str::FromStr as _, time::Duration}; - -use anyhow::Context as _; -use sqlx::{ - ConnectOptions as _, Connection as _, PgConnection, PgPool, - postgres::{PgConnectOptions, PgPoolOptions}, -}; -use tokio::task::JoinHandle; -pub mod auth; -pub mod share; -pub mod warrens; - -#[derive(Debug, Clone)] -pub struct PostgresConfig { - database_url: String, - database_name: String, -} - -impl PostgresConfig { - pub fn new(database_url: String, database_name: String) -> Self { - Self { - database_url, - database_name, - } - } -} - -#[derive(Debug, Clone)] -pub struct Postgres { - pool: PgPool, -} - -impl Postgres { - pub async fn new(config: PostgresConfig) -> anyhow::Result { - let opts = PgConnectOptions::from_str(&config.database_url)?.disable_statement_logging(); - - let mut connection = PgConnection::connect_with(&opts) - .await - .context("Failed to connect to the PostgreSQL database")?; - - match sqlx::query("SELECT datname FROM pg_database WHERE datname = $1") - .bind(&config.database_name) - .fetch_one(&mut connection) - .await - { - Ok(_) => (), - Err(sqlx::Error::RowNotFound) => { - sqlx::query(&format!("CREATE DATABASE {}", config.database_name)) - .execute(&mut connection) - .await?; - } - Err(e) => return Err(e.into()), - }; - - connection.close().await?; - - let pool = PgPoolOptions::new() - .connect_with(opts.database(&config.database_name)) - .await?; - sqlx::migrate!("./migrations").run(&pool).await?; - - // 3600 seconds = 1 hour - Self::start_cleanup_tasks(pool.clone(), Duration::from_secs(3600)); - - Ok(Self { pool }) - } - - pub(super) fn start_cleanup_tasks(pool: PgPool, interval: Duration) -> JoinHandle<()> { - tokio::spawn(async move { - loop { - { - let Ok(mut connection) = pool.acquire().await else { - break; - }; - - if let Ok(count) = Self::delete_expired_auth_sessions(&mut connection).await { - tracing::debug!("Removed {count} expired auth session(s)"); - } - - if let Ok(count) = Self::delete_expired_shares(&mut connection).await { - tracing::debug!("Deleted {count} expired share(s)"); - } - } - - tokio::time::sleep(interval).await; - } - - tracing::debug!("Session cleanup task stopped"); - }) - } -} - -pub(super) fn is_not_found_error(err: &sqlx::Error) -> bool { - matches!(err, sqlx::Error::RowNotFound) -} diff --git a/backend/src/lib/outbound/postgres/auth.rs b/backend/src/lib/outbound/sqlite/auth.rs similarity index 94% rename from backend/src/lib/outbound/postgres/auth.rs rename to backend/src/lib/outbound/sqlite/auth.rs index 9be74d3..bae35cf 100644 --- a/backend/src/lib/outbound/postgres/auth.rs +++ b/backend/src/lib/outbound/sqlite/auth.rs @@ -7,7 +7,7 @@ use argon2::{ }, }; use chrono::Utc; -use sqlx::{Acquire as _, PgConnection}; +use sqlx::{Acquire as _, SqliteConnection}; use uuid::Uuid; use crate::domain::warren::{ @@ -40,9 +40,9 @@ use crate::domain::warren::{ ports::{AuthRepository, WarrenService}, }; -use super::{Postgres, is_not_found_error}; +use super::{Sqlite, is_not_found_error}; -impl AuthRepository for Postgres { +impl AuthRepository for Sqlite { async fn create_user(&self, request: CreateUserRequest) -> Result { let mut connection = self .pool @@ -368,9 +368,9 @@ impl AuthRepository for Postgres { } } -impl Postgres { +impl Sqlite { pub(super) async fn delete_expired_auth_sessions( - connection: &mut PgConnection, + connection: &mut SqliteConnection, ) -> Result { let delete_count = sqlx::query( " @@ -389,7 +389,7 @@ impl Postgres { async fn create_user( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, name: &UserName, email: &UserEmail, password: &UserPassword, @@ -431,7 +431,7 @@ impl Postgres { async fn create_or_update_user( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, sub: &String, name: &UserName, email: &UserEmail, @@ -546,7 +546,7 @@ impl Postgres { async fn edit_user( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, id: &Uuid, name: &UserName, email: &UserEmail, @@ -592,7 +592,7 @@ impl Postgres { async fn delete_user_sessions( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user_id: &Uuid, ) -> Result { let rows_affected = sqlx::query( @@ -613,7 +613,7 @@ impl Postgres { async fn delete_user_from_database( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user_id: &Uuid, ) -> Result { let user: User = sqlx::query_as( @@ -635,7 +635,7 @@ impl Postgres { async fn get_user_from_id( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, id: &Uuid, ) -> Result { let user: User = sqlx::query_as( @@ -657,7 +657,7 @@ impl Postgres { async fn get_user_from_email( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, email: &UserEmail, ) -> Result { let user: User = sqlx::query_as( @@ -698,7 +698,7 @@ impl Postgres { async fn create_session( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user: &User, expiration: &SessionExpirationTime, ) -> anyhow::Result { @@ -721,7 +721,7 @@ impl Postgres { ) VALUES ( $1, $2, - TO_TIMESTAMP($3::double precision / 1000) + datetime($3, 'unixepoch') ) RETURNING * @@ -729,7 +729,7 @@ impl Postgres { ) .bind(session_id) .bind(user.id()) - .bind(expiration_time) + .bind(expiration_time / 1000) .fetch_one(&mut *tx) .await?; @@ -740,7 +740,7 @@ impl Postgres { async fn get_auth_session( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, session_id: &AuthSessionId, ) -> Result { let session: AuthSession = sqlx::query_as( @@ -762,7 +762,7 @@ impl Postgres { async fn get_user_warrens( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user_id: &Uuid, ) -> Result, sqlx::Error> { let user_warrens: Vec = sqlx::query_as( @@ -784,7 +784,7 @@ impl Postgres { async fn get_all_user_warrens( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, ) -> Result, sqlx::Error> { let user_warrens: Vec = sqlx::query_as( " @@ -802,7 +802,7 @@ impl Postgres { async fn get_user_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user_id: &Uuid, warren_id: &Uuid, ) -> Result { @@ -825,7 +825,10 @@ impl Postgres { Ok(ids) } - async fn fetch_users(&self, connection: &mut PgConnection) -> Result, sqlx::Error> { + async fn fetch_users( + &self, + connection: &mut SqliteConnection, + ) -> Result, sqlx::Error> { let users: Vec = sqlx::query_as( " SELECT @@ -844,7 +847,7 @@ impl Postgres { async fn add_user_to_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user_warren: &UserWarren, ) -> Result { let user_warren: UserWarren = sqlx::query_as( @@ -855,14 +858,22 @@ impl Postgres { can_list_files, can_read_files, can_modify_files, - can_delete_files + can_delete_files, + can_list_shares, + can_create_shares, + can_modify_shares, + can_delete_shares ) VALUES ( $1, $2, $3, $4, $5, - $6 + $6, + $7, + $8, + $9, + $10 ) RETURNING * @@ -874,6 +885,10 @@ impl Postgres { .bind(user_warren.can_read_files()) .bind(user_warren.can_modify_files()) .bind(user_warren.can_delete_files()) + .bind(user_warren.can_list_shares()) + .bind(user_warren.can_create_shares()) + .bind(user_warren.can_modify_shares()) + .bind(user_warren.can_delete_shares()) .fetch_one(connection) .await?; @@ -882,7 +897,7 @@ impl Postgres { async fn update_user_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user_warren: &UserWarren, ) -> Result { let user_warren: UserWarren = sqlx::query_as( @@ -923,7 +938,7 @@ impl Postgres { async fn remove_user_from_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, user_id: &Uuid, warren_id: &Uuid, ) -> Result { diff --git a/backend/src/lib/outbound/sqlite/mod.rs b/backend/src/lib/outbound/sqlite/mod.rs new file mode 100644 index 0000000..542f313 --- /dev/null +++ b/backend/src/lib/outbound/sqlite/mod.rs @@ -0,0 +1,74 @@ +use std::{str::FromStr as _, time::Duration}; + +use sqlx::{ + ConnectOptions as _, SqlitePool, + sqlite::{SqliteConnectOptions, SqlitePoolOptions}, +}; +use tokio::task::JoinHandle; +pub mod auth; +pub mod share; +pub mod warrens; + +#[derive(Debug, Clone)] +pub struct SqliteConfig { + database_url: String, +} + +impl SqliteConfig { + pub fn new(database_url: String) -> Self { + Self { database_url } + } +} + +#[derive(Debug, Clone)] +pub struct Sqlite { + pool: SqlitePool, +} + +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?; + sqlx::migrate!("./migrations").run(&pool).await?; + + // 3600 seconds = 1 hour + Self::start_cleanup_tasks(pool.clone(), Duration::from_secs(3600)); + + Ok(Self { pool }) + } + + pub(super) fn start_cleanup_tasks(pool: SqlitePool, interval: Duration) -> JoinHandle<()> { + tokio::spawn(async move { + loop { + { + let Ok(mut connection) = pool.acquire().await else { + break; + }; + + if let Ok(count) = Self::delete_expired_auth_sessions(&mut connection).await { + tracing::debug!("Removed {count} expired auth session(s)"); + } + + if let Ok(count) = Self::delete_expired_shares(&mut connection).await { + tracing::debug!("Deleted {count} expired share(s)"); + } + } + + tokio::time::sleep(interval).await; + } + + tracing::debug!("Session cleanup task stopped"); + }) + } +} + +pub(super) fn is_not_found_error(err: &sqlx::Error) -> bool { + matches!(err, sqlx::Error::RowNotFound) +} diff --git a/backend/src/lib/outbound/postgres/share.rs b/backend/src/lib/outbound/sqlite/share.rs similarity index 92% rename from backend/src/lib/outbound/postgres/share.rs rename to backend/src/lib/outbound/sqlite/share.rs index 9f7bb08..ed30116 100644 --- a/backend/src/lib/outbound/postgres/share.rs +++ b/backend/src/lib/outbound/sqlite/share.rs @@ -1,10 +1,10 @@ use anyhow::anyhow; use argon2::{ Argon2, PasswordHash, PasswordVerifier as _, - password_hash::{PasswordHasher as _, SaltString, rand_core::OsRng}, + password_hash::{PasswordHasher as _, SaltString}, }; use chrono::{NaiveDateTime, Utc}; -use sqlx::{Acquire as _, PgConnection}; +use sqlx::{Acquire as _, SqliteConnection}; use thiserror::Error; use uuid::Uuid; @@ -17,7 +17,7 @@ use crate::domain::warren::models::{ warren::HasWarrenId as _, }; -use super::{Postgres, is_not_found_error}; +use super::{Sqlite, is_not_found_error}; #[derive(sqlx::FromRow)] struct ShareRow { @@ -62,7 +62,7 @@ impl TryFrom for Share { } pub(super) async fn get_share( - connection: &mut PgConnection, + connection: &mut SqliteConnection, request: GetShareRequest, ) -> anyhow::Result { let share_row: ShareRow = sqlx::query_as( @@ -90,7 +90,7 @@ pub(super) async fn get_share( } pub(super) async fn list_shares( - connection: &mut PgConnection, + connection: &mut SqliteConnection, request: ListSharesRequest, ) -> anyhow::Result> { let share_rows: Vec = sqlx::query_as( @@ -126,13 +126,13 @@ pub(super) async fn list_shares( } pub(super) async fn create_share( - connection: &mut PgConnection, + connection: &mut SqliteConnection, request: CreateShareRequest, ) -> anyhow::Result { let mut tx = connection.begin().await?; let password_hash = if let Some(password) = request.base().password() { - let salt = SaltString::generate(&mut OsRng); + let salt = SaltString::generate(&mut argon2::password_hash::rand_core::OsRng); let argon2 = Argon2::default(); Some( @@ -164,7 +164,7 @@ pub(super) async fn create_share( $2, $3, $4, - TO_TIMESTAMP($5::double precision / 1000) + datetime($5, 'unixepoch') ) RETURNING * @@ -174,7 +174,7 @@ pub(super) async fn create_share( .bind(request.warren_id()) .bind(request.base().path()) .bind(password_hash) - .bind(expires_at) + .bind(expires_at.map(|v| v / 1000)) .fetch_one(&mut *tx) .await?; @@ -184,7 +184,7 @@ pub(super) async fn create_share( } pub(super) async fn delete_share( - connection: &mut PgConnection, + connection: &mut SqliteConnection, request: DeleteShareRequest, ) -> anyhow::Result { let mut tx = connection.begin().await?; @@ -209,7 +209,7 @@ pub(super) async fn delete_share( } pub(super) async fn verify_password( - connection: &mut PgConnection, + connection: &mut SqliteConnection, request: VerifySharePasswordRequest, ) -> Result { let share_row: ShareRow = sqlx::query_as( @@ -264,9 +264,9 @@ pub(super) async fn verify_password( } } -impl Postgres { +impl Sqlite { pub(super) async fn delete_expired_shares( - connection: &mut PgConnection, + connection: &mut SqliteConnection, ) -> Result { let delete_count = sqlx::query( " diff --git a/backend/src/lib/outbound/postgres/warrens.rs b/backend/src/lib/outbound/sqlite/warrens.rs similarity index 91% rename from backend/src/lib/outbound/postgres/warrens.rs rename to backend/src/lib/outbound/sqlite/warrens.rs index 2fc14bd..5001ce0 100644 --- a/backend/src/lib/outbound/postgres/warrens.rs +++ b/backend/src/lib/outbound/sqlite/warrens.rs @@ -1,5 +1,5 @@ use anyhow::{Context as _, anyhow}; -use sqlx::{Acquire as _, PgConnection}; +use sqlx::{Acquire as _, SqliteConnection}; use uuid::Uuid; use crate::domain::warren::{ @@ -21,9 +21,9 @@ use crate::domain::warren::{ ports::WarrenRepository, }; -use super::{Postgres, is_not_found_error}; +use super::{Sqlite, is_not_found_error}; -impl WarrenRepository for Postgres { +impl WarrenRepository for Sqlite { async fn create_warren( &self, request: CreateWarrenRequest, @@ -220,10 +220,10 @@ impl WarrenRepository for Postgres { } } -impl Postgres { +impl Sqlite { async fn create_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, name: &WarrenName, path: &AbsoluteFilePath, ) -> Result { @@ -254,7 +254,7 @@ impl Postgres { async fn edit_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, id: &Uuid, name: &WarrenName, path: &AbsoluteFilePath, @@ -287,7 +287,7 @@ impl Postgres { async fn delete_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, id: &Uuid, ) -> Result { let mut tx = connection.begin().await?; @@ -313,7 +313,7 @@ impl Postgres { async fn get_warren( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, id: &Uuid, ) -> Result { let warren: Warren = sqlx::query_as( @@ -335,20 +335,28 @@ impl Postgres { async fn fetch_warrens( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, ids: &[Uuid], ) -> Result, sqlx::Error> { - let warrens: Vec = sqlx::query_as::( + let mut ids_as_string = ids.into_iter().fold(String::new(), |mut acc, id| { + let encoded = hex::encode(id.as_bytes()); + acc.push_str("x'"); + acc.push_str(encoded.as_str()); + acc.push_str("',"); + acc + }); + ids_as_string.pop(); + + let warrens: Vec = sqlx::query_as::(&format!( " SELECT * FROM warrens WHERE - id = ANY($1) + id IN ({ids_as_string}) ", - ) - .bind(ids) + )) .fetch_all(&mut *connection) .await?; @@ -357,9 +365,9 @@ impl Postgres { async fn fetch_all_warrens( &self, - connection: &mut PgConnection, + connection: &mut SqliteConnection, ) -> Result, sqlx::Error> { - let warrens: Vec = sqlx::query_as::( + let warrens: Vec = sqlx::query_as::( " SELECT * diff --git a/backend/warren.db b/backend/warren.db new file mode 100644 index 0000000000000000000000000000000000000000..4fd31339ee30f75970cffd5db2d1ef6c508e0a93 GIT binary patch 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( Math.abs(rect.a.x - rect.b.x)); const height = computed(() => Math.abs(rect.a.y - rect.b.y)); function onDocumentPointerDown(e: MouseEvent) { - if (e.button !== 0) { + if (e.button !== 0 || matchMedia('(pointer:coarse)').matches) { return; } @@ -21,7 +21,7 @@ function onDocumentPointerDown(e: MouseEvent) { } function onDocumentPointerMove(e: MouseEvent) { - if (!rect.enabled) { + if (!rect.enabled || matchMedia('(pointer:coarse)').matches) { return; } @@ -39,7 +39,11 @@ function onDocumentPointerMove(e: MouseEvent) { } function onDocumentPointerUp(e: MouseEvent) { - if (e.button !== 0 || !rect.enabled) { + if ( + !rect.enabled || + e.button !== 0 || + matchMedia('(pointer:coarse)').matches + ) { return; } diff --git a/frontend/components/admin/AddUserWarrenDialog.vue b/frontend/components/admin/AddUserWarrenDialog.vue index 103626a..4130e6d 100644 --- a/frontend/components/admin/AddUserWarrenDialog.vue +++ b/frontend/components/admin/AddUserWarrenDialog.vue @@ -51,6 +51,11 @@ const form = useForm({ canReadFiles: false, canModifyFiles: false, canDeleteFiles: false, + + canListShares: false, + canCreateShares: false, + canModifyShares: false, + canDeleteShares: false, }, }); @@ -231,6 +236,70 @@ const onSubmit = form.handleSubmit(async (values) => { + + + + List shares + + + + + + + + + + Create shares + + + + + + + + + + Modify shares + + + + + + + + + + Delete shares + + + + + + diff --git a/frontend/lib/schemas/admin.ts b/frontend/lib/schemas/admin.ts index d802493..3067124 100644 --- a/frontend/lib/schemas/admin.ts +++ b/frontend/lib/schemas/admin.ts @@ -25,6 +25,11 @@ export const userWarrenSchema = object({ canReadFiles: boolean().required(), canModifyFiles: boolean().required(), canDeleteFiles: boolean().required(), + + canListShares: boolean().required(), + canCreateShares: boolean().required(), + canModifyShares: boolean().required(), + canDeleteShares: boolean().required(), }); export const createWarrenSchema = object({