remove sqlite extensions to fix docker issue
UUIDs are now generated in the backend before insertion
This commit is contained in:
@@ -12,3 +12,4 @@ frontend/node_modules
|
|||||||
|
|
||||||
backend/target
|
backend/target
|
||||||
backend/.gitignore
|
backend/.gitignore
|
||||||
|
backend/data
|
||||||
|
|||||||
12
Dockerfile
12
Dockerfile
@@ -13,19 +13,9 @@ COPY frontend/ ./
|
|||||||
RUN npm run generate
|
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
|
FROM rust:alpine AS backend-builder
|
||||||
WORKDIR /usr/src/warren
|
WORKDIR /usr/src/warren
|
||||||
|
|
||||||
RUN apk add sqlite sqlite-dev build-base
|
|
||||||
|
|
||||||
COPY backend/Cargo.toml backend/Cargo.lock ./
|
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 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
|
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
|
FROM alpine:3
|
||||||
WORKDIR /var/lib/warren
|
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=backend-builder /usr/src/warren/target/release/warren_backend /usr/bin/warren
|
||||||
COPY --from=frontend-builder /usr/src/warren/dist ./frontend
|
COPY --from=frontend-builder /usr/src/warren/dist ./frontend
|
||||||
|
|
||||||
|
|||||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
target
|
target
|
||||||
serve
|
serve
|
||||||
.env
|
.env
|
||||||
|
data
|
||||||
|
|||||||
1
backend/Cargo.lock
generated
1
backend/Cargo.lock
generated
@@ -2721,6 +2721,7 @@ version = "1.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom 0.3.3",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|||||||
@@ -39,5 +39,5 @@ tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
|
|||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
uuid = { version = "1.17.0", features = ["serde"] }
|
uuid = { version = "1.17.0", features = ["serde", "v4"] }
|
||||||
zip = "4.5.0"
|
zip = "4.5.0"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())),
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
oidc_sub TEXT UNIQUE,
|
oidc_sub TEXT UNIQUE,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
@@ -10,7 +10,7 @@ CREATE TABLE users (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE warrens (
|
CREATE TABLE warrens (
|
||||||
id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())),
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
path TEXT NOT NULL UNIQUE,
|
path TEXT NOT NULL UNIQUE,
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
@@ -31,7 +31,7 @@ CREATE TABLE user_warrens (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE shares (
|
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,
|
creator_id BLOB NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
warren_id BLOB NOT NULL REFERENCES warrens(id) ON DELETE CASCADE,
|
warren_id BLOB NOT NULL REFERENCES warrens(id) ON DELETE CASCADE,
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -48,3 +48,8 @@ CREATE TABLE auth_sessions (
|
|||||||
expires_at DATETIME NOT NULL,
|
expires_at DATETIME NOT NULL,
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE application_options (
|
||||||
|
key TEXT NOT NULL PRIMARY KEY,
|
||||||
|
value TEXT NOT 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
|
|
||||||
);
|
|
||||||
Binary file not shown.
@@ -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 <assert.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -46,13 +46,18 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let option_service =
|
||||||
|
domain::warren::service::option::Service::new(sqlite.clone(), metrics, notifier);
|
||||||
|
|
||||||
let auth_service = domain::warren::service::auth::Service::new(
|
let auth_service = domain::warren::service::auth::Service::new(
|
||||||
sqlite,
|
sqlite,
|
||||||
metrics,
|
metrics,
|
||||||
notifier,
|
notifier,
|
||||||
config.auth,
|
config.auth,
|
||||||
oidc_service,
|
oidc_service,
|
||||||
);
|
option_service,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let server_config = HttpServerConfig::new(
|
let server_config = HttpServerConfig::new(
|
||||||
&config.server_address,
|
&config.server_address,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod auth_session;
|
pub mod auth_session;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
pub mod option;
|
||||||
pub mod share;
|
pub mod share;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod user_warren;
|
pub mod user_warren;
|
||||||
|
|||||||
74
backend/src/lib/domain/warren/models/option/mod.rs
Normal file
74
backend/src/lib/domain/warren/models/option/mod.rs
Normal file
@@ -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<Self, OptionKeyError> {
|
||||||
|
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>(T)
|
||||||
|
where
|
||||||
|
T: OptionType;
|
||||||
|
|
||||||
|
impl<T> OptionValue<T>
|
||||||
|
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<Self, Self::Error>;
|
||||||
|
fn to_string(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OptionType for bool {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn parse(raw: &str) -> Result<Self, Self::Error> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DeleteOptionRequest> 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),
|
||||||
|
}
|
||||||
57
backend/src/lib/domain/warren/models/option/requests/get.rs
Normal file
57
backend/src/lib/domain/warren/models/option/requests/get.rs
Normal file
@@ -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<GetOptionRequest> for OptionKey {
|
||||||
|
fn from(value: GetOptionRequest) -> Self {
|
||||||
|
value.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GetOptionResponse<T: OptionType> {
|
||||||
|
key: OptionKey,
|
||||||
|
value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> GetOptionResponse<T>
|
||||||
|
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),
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mod delete;
|
||||||
|
mod get;
|
||||||
|
mod set;
|
||||||
|
pub use delete::*;
|
||||||
|
pub use get::*;
|
||||||
|
pub use set::*;
|
||||||
83
backend/src/lib/domain/warren/models/option/requests/set.rs
Normal file
83
backend/src/lib/domain/warren/models/option/requests/set.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::domain::warren::models::option::{OptionKey, OptionType, OptionValue};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SetOptionRequest<T>
|
||||||
|
where
|
||||||
|
T: OptionType,
|
||||||
|
{
|
||||||
|
key: OptionKey,
|
||||||
|
value: OptionValue<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SetOptionRequest<T>
|
||||||
|
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<T> {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unpack(self) -> (OptionKey, OptionValue<T>) {
|
||||||
|
(self.key, self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<SetOptionRequest<T>> for OptionKey
|
||||||
|
where
|
||||||
|
T: OptionType,
|
||||||
|
{
|
||||||
|
fn from(value: SetOptionRequest<T>) -> Self {
|
||||||
|
value.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<SetOptionRequest<T>> for OptionValue<T>
|
||||||
|
where
|
||||||
|
T: OptionType,
|
||||||
|
{
|
||||||
|
fn from(value: SetOptionRequest<T>) -> Self {
|
||||||
|
value.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SetOptionResponse<T: OptionType> {
|
||||||
|
key: OptionKey,
|
||||||
|
value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SetOptionResponse<T>
|
||||||
|
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),
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ pub struct RegisterUserRequest {
|
|||||||
name: UserName,
|
name: UserName,
|
||||||
email: UserEmail,
|
email: UserEmail,
|
||||||
password: UserPassword,
|
password: UserPassword,
|
||||||
|
bypass_registration_flag: bool,
|
||||||
|
admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegisterUserRequest {
|
impl RegisterUserRequest {
|
||||||
@@ -17,6 +19,23 @@ impl RegisterUserRequest {
|
|||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
password,
|
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 {
|
pub fn password(&self) -> &UserPassword {
|
||||||
&self.password
|
&self.password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn admin(&self) -> bool {
|
||||||
|
self.admin
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bypass_registration_flag(&self) -> bool {
|
||||||
|
self.bypass_registration_flag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RegisterUserRequest> for CreateUserRequest {
|
impl From<RegisterUserRequest> for CreateUserRequest {
|
||||||
fn from(value: RegisterUserRequest) -> Self {
|
fn from(value: RegisterUserRequest) -> Self {
|
||||||
Self::new(value.name, value.email, value.password, false)
|
Self::new(value.name, value.email, value.password, value.admin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,3 +185,14 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static {
|
|||||||
fn record_auth_share_deletion_success(&self) -> impl Future<Output = ()> + Send;
|
fn record_auth_share_deletion_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
fn record_auth_share_deletion_failure(&self) -> impl Future<Output = ()> + Send;
|
fn record_auth_share_deletion_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait OptionMetrics: Clone + Send + Sync + 'static {
|
||||||
|
fn record_option_get_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_option_get_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_option_set_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_option_set_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_option_delete_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_option_delete_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ use super::models::{
|
|||||||
RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse,
|
RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse,
|
||||||
TouchError, TouchRequest,
|
TouchError, TouchRequest,
|
||||||
},
|
},
|
||||||
|
option::{
|
||||||
|
DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError,
|
||||||
|
GetOptionRequest, GetOptionResponse, OptionType, SetOptionError, SetOptionRequest,
|
||||||
|
SetOptionResponse,
|
||||||
|
},
|
||||||
share::{
|
share::{
|
||||||
CreateShareBaseRequest, CreateShareError, CreateShareRequest, CreateShareResponse,
|
CreateShareBaseRequest, CreateShareError, CreateShareRequest, CreateShareResponse,
|
||||||
DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest,
|
DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest,
|
||||||
@@ -337,3 +342,18 @@ pub trait AuthService: Clone + Send + Sync + 'static {
|
|||||||
warren_service: &WS,
|
warren_service: &WS,
|
||||||
) -> impl Future<Output = Result<DeleteShareResponse, AuthError<DeleteShareError>>> + Send;
|
) -> impl Future<Output = Result<DeleteShareResponse, AuthError<DeleteShareError>>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait OptionService: Clone + Send + Sync + 'static {
|
||||||
|
fn get_option<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: GetOptionRequest,
|
||||||
|
) -> impl Future<Output = Result<GetOptionResponse<T>, GetOptionError>> + Send;
|
||||||
|
fn set_option<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: SetOptionRequest<T>,
|
||||||
|
) -> impl Future<Output = Result<SetOptionResponse<T>, SetOptionError>> + Send;
|
||||||
|
fn delete_option(
|
||||||
|
&self,
|
||||||
|
request: DeleteOptionRequest,
|
||||||
|
) -> impl Future<Output = Result<DeleteOptionResponse, DeleteOptionError>> + Send;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use uuid::Uuid;
|
|||||||
use crate::domain::warren::models::{
|
use crate::domain::warren::models::{
|
||||||
auth_session::requests::FetchAuthSessionResponse,
|
auth_session::requests::FetchAuthSessionResponse,
|
||||||
file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse},
|
file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse},
|
||||||
|
option::{DeleteOptionResponse, GetOptionResponse, OptionType, SetOptionResponse},
|
||||||
share::{
|
share::{
|
||||||
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
||||||
ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse,
|
ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse,
|
||||||
@@ -219,3 +220,15 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
|||||||
response: &DeleteShareResponse,
|
response: &DeleteShareResponse,
|
||||||
) -> impl Future<Output = ()> + Send;
|
) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait OptionNotifier: Clone + Send + Sync + 'static {
|
||||||
|
fn got_option<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
response: &GetOptionResponse<T>,
|
||||||
|
) -> impl Future<Output = ()> + Send;
|
||||||
|
fn set_option<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
response: &SetOptionResponse<T>,
|
||||||
|
) -> impl Future<Output = ()> + Send;
|
||||||
|
fn deleted_option(&self, response: &DeleteOptionResponse) -> impl Future<Output = ()> + Send;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ use crate::domain::warren::models::{
|
|||||||
RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse,
|
RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse,
|
||||||
TouchError, TouchRequest,
|
TouchError, TouchRequest,
|
||||||
},
|
},
|
||||||
|
option::{
|
||||||
|
DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError,
|
||||||
|
GetOptionRequest, GetOptionResponse, OptionType, SetOptionError, SetOptionRequest,
|
||||||
|
SetOptionResponse,
|
||||||
|
},
|
||||||
share::{
|
share::{
|
||||||
CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError,
|
CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError,
|
||||||
DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, ListSharesError,
|
DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, ListSharesError,
|
||||||
@@ -188,3 +193,18 @@ pub trait AuthRepository: Clone + Send + Sync + 'static {
|
|||||||
request: FetchUserWarrenRequest,
|
request: FetchUserWarrenRequest,
|
||||||
) -> impl Future<Output = Result<UserWarren, FetchUserWarrenError>> + Send;
|
) -> impl Future<Output = Result<UserWarren, FetchUserWarrenError>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait OptionRepository: Clone + Send + Sync + 'static {
|
||||||
|
fn get_option<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: GetOptionRequest,
|
||||||
|
) -> impl Future<Output = Result<GetOptionResponse<T>, GetOptionError>> + Send;
|
||||||
|
fn set_option<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: SetOptionRequest<T>,
|
||||||
|
) -> impl Future<Output = Result<SetOptionResponse<T>, SetOptionError>> + Send;
|
||||||
|
fn delete_option(
|
||||||
|
&self,
|
||||||
|
request: DeleteOptionRequest,
|
||||||
|
) -> impl Future<Output = Result<DeleteOptionResponse, DeleteOptionError>> + Send;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
file::FileStream,
|
file::FileStream,
|
||||||
|
option::{GetOptionError, GetOptionRequest, OptionKey, SetOptionRequest},
|
||||||
share::{
|
share::{
|
||||||
CreateShareBaseRequest, CreateShareError, CreateShareResponse,
|
CreateShareBaseRequest, CreateShareError, CreateShareResponse,
|
||||||
DeleteShareError, DeleteShareRequest, DeleteShareResponse, ListSharesError,
|
DeleteShareError, DeleteShareRequest, DeleteShareResponse, ListSharesError,
|
||||||
@@ -24,7 +25,7 @@ use crate::{
|
|||||||
ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse, ListUsersError,
|
ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse, ListUsersError,
|
||||||
ListUsersRequest, LoginUserError, LoginUserOidcError, LoginUserOidcRequest,
|
ListUsersRequest, LoginUserError, LoginUserOidcError, LoginUserOidcRequest,
|
||||||
LoginUserOidcResponse, LoginUserRequest, LoginUserResponse, RegisterUserError,
|
LoginUserOidcResponse, LoginUserRequest, LoginUserResponse, RegisterUserError,
|
||||||
RegisterUserRequest, User,
|
RegisterUserRequest, User, UserEmail, UserName, UserPassword,
|
||||||
},
|
},
|
||||||
user_warren::{
|
user_warren::{
|
||||||
UserWarren,
|
UserWarren,
|
||||||
@@ -46,7 +47,10 @@ use crate::{
|
|||||||
WarrenTouchResponse,
|
WarrenTouchResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService},
|
ports::{
|
||||||
|
AuthMetrics, AuthNotifier, AuthRepository, AuthService, OptionService,
|
||||||
|
WarrenService,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -97,54 +101,100 @@ impl AuthConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Service<R, M, N, OIDC>
|
pub struct Service<R, M, N, OIDC, O>
|
||||||
where
|
where
|
||||||
R: AuthRepository,
|
R: AuthRepository,
|
||||||
M: AuthMetrics,
|
M: AuthMetrics,
|
||||||
N: AuthNotifier,
|
N: AuthNotifier,
|
||||||
OIDC: OidcService,
|
OIDC: OidcService,
|
||||||
|
O: OptionService,
|
||||||
{
|
{
|
||||||
repository: R,
|
repository: R,
|
||||||
metrics: M,
|
metrics: M,
|
||||||
notifier: N,
|
notifier: N,
|
||||||
oidc: Option<OIDC>,
|
oidc: Option<OIDC>,
|
||||||
|
option_service: O,
|
||||||
config: AuthConfig,
|
config: AuthConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R, M, N, OIDC> Service<R, M, N, OIDC>
|
impl<R, M, N, OIDC, O> Service<R, M, N, OIDC, O>
|
||||||
where
|
where
|
||||||
R: AuthRepository,
|
R: AuthRepository,
|
||||||
M: AuthMetrics,
|
M: AuthMetrics,
|
||||||
N: AuthNotifier,
|
N: AuthNotifier,
|
||||||
OIDC: OidcService,
|
OIDC: OidcService,
|
||||||
|
O: OptionService,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub async fn new(
|
||||||
repository: R,
|
repository: R,
|
||||||
metrics: M,
|
metrics: M,
|
||||||
notifier: N,
|
notifier: N,
|
||||||
config: AuthConfig,
|
config: AuthConfig,
|
||||||
oidc: Option<OIDC>,
|
oidc: Option<OIDC>,
|
||||||
) -> Self {
|
option_service: O,
|
||||||
Self {
|
) -> anyhow::Result<Self> {
|
||||||
|
let service = Self {
|
||||||
repository,
|
repository,
|
||||||
metrics,
|
metrics,
|
||||||
notifier,
|
notifier,
|
||||||
config,
|
config,
|
||||||
oidc,
|
oidc,
|
||||||
}
|
option_service,
|
||||||
|
};
|
||||||
|
|
||||||
|
service.init().await?;
|
||||||
|
|
||||||
|
Ok(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn oidc(&self) -> Option<&OIDC> {
|
pub fn oidc(&self) -> Option<&OIDC> {
|
||||||
self.oidc.as_ref()
|
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::<bool>(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<R, M, N, OIDC> AuthService for Service<R, M, N, OIDC>
|
impl<R, M, N, OIDC, O> AuthService for Service<R, M, N, OIDC, O>
|
||||||
where
|
where
|
||||||
R: AuthRepository,
|
R: AuthRepository,
|
||||||
M: AuthMetrics,
|
M: AuthMetrics,
|
||||||
N: AuthNotifier,
|
N: AuthNotifier,
|
||||||
OIDC: OidcService,
|
OIDC: OidcService,
|
||||||
|
O: OptionService,
|
||||||
{
|
{
|
||||||
async fn create_warren<WS: WarrenService>(
|
async fn create_warren<WS: WarrenService>(
|
||||||
&self,
|
&self,
|
||||||
@@ -253,7 +303,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn register_user(&self, request: RegisterUserRequest) -> Result<User, RegisterUserError> {
|
async fn register_user(&self, request: RegisterUserRequest) -> Result<User, RegisterUserError> {
|
||||||
if !self.config.allow_registration {
|
if !self.config.allow_registration && !request.bypass_registration_flag() {
|
||||||
self.metrics.record_user_registration_failure().await;
|
self.metrics.record_user_registration_failure().await;
|
||||||
return Err(RegisterUserError::Disabled);
|
return Err(RegisterUserError::Disabled);
|
||||||
}
|
}
|
||||||
@@ -972,12 +1022,13 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R, M, N, OIDC> Service<R, M, N, OIDC>
|
impl<R, M, N, OIDC, O> Service<R, M, N, OIDC, O>
|
||||||
where
|
where
|
||||||
R: AuthRepository,
|
R: AuthRepository,
|
||||||
M: AuthMetrics,
|
M: AuthMetrics,
|
||||||
N: AuthNotifier,
|
N: AuthNotifier,
|
||||||
OIDC: OidcService,
|
OIDC: OidcService,
|
||||||
|
O: OptionService,
|
||||||
{
|
{
|
||||||
/// A helper to get a [UserWarren], [User] and the underlying request from an [AuthRequest]
|
/// A helper to get a [UserWarren], [User] and the underlying request from an [AuthRequest]
|
||||||
async fn get_session_data_and_user_warren<T, E>(
|
async fn get_session_data_and_user_warren<T, E>(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod file_system;
|
pub mod file_system;
|
||||||
|
pub mod option;
|
||||||
pub mod warren;
|
pub mod warren;
|
||||||
|
|||||||
90
backend/src/lib/domain/warren/service/option.rs
Normal file
90
backend/src/lib/domain/warren/service/option.rs
Normal file
@@ -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<R, M, N>
|
||||||
|
where
|
||||||
|
R: OptionRepository,
|
||||||
|
M: OptionMetrics,
|
||||||
|
N: OptionNotifier,
|
||||||
|
{
|
||||||
|
repository: R,
|
||||||
|
metrics: M,
|
||||||
|
notifier: N,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, M, N> Service<R, M, N>
|
||||||
|
where
|
||||||
|
R: OptionRepository,
|
||||||
|
M: OptionMetrics,
|
||||||
|
N: OptionNotifier,
|
||||||
|
{
|
||||||
|
pub fn new(repository: R, metrics: M, notifier: N) -> Self {
|
||||||
|
Self {
|
||||||
|
repository,
|
||||||
|
metrics,
|
||||||
|
notifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, M, N> OptionService for Service<R, M, N>
|
||||||
|
where
|
||||||
|
R: OptionRepository,
|
||||||
|
M: OptionMetrics,
|
||||||
|
N: OptionNotifier,
|
||||||
|
{
|
||||||
|
async fn get_option<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: GetOptionRequest,
|
||||||
|
) -> Result<GetOptionResponse<T>, 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<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: SetOptionRequest<T>,
|
||||||
|
) -> Result<SetOptionResponse<T>, 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<DeleteOptionResponse, DeleteOptionError> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
oidc::ports::OidcMetrics,
|
oidc::ports::OidcMetrics,
|
||||||
warren::ports::{AuthMetrics, FileSystemMetrics, WarrenMetrics},
|
warren::ports::{AuthMetrics, FileSystemMetrics, OptionMetrics, WarrenMetrics},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -452,3 +452,26 @@ impl OidcMetrics for MetricsDebugLogger {
|
|||||||
tracing::debug!("[Metrics] OIDC get user info failed");
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::domain::{
|
|||||||
models::{
|
models::{
|
||||||
auth_session::requests::FetchAuthSessionResponse,
|
auth_session::requests::FetchAuthSessionResponse,
|
||||||
file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse},
|
file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse},
|
||||||
|
option::{DeleteOptionResponse, GetOptionResponse, OptionType, SetOptionResponse},
|
||||||
share::{
|
share::{
|
||||||
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
||||||
ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse,
|
ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse,
|
||||||
@@ -22,7 +23,7 @@ use crate::domain::{
|
|||||||
WarrenRmResponse, WarrenSaveResponse, WarrenTouchResponse,
|
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<T: OptionType>(&self, response: &GetOptionResponse<T>) {
|
||||||
|
tracing::debug!(
|
||||||
|
"[Notifier] Got option {}: {}",
|
||||||
|
response.key().to_string(),
|
||||||
|
response.value().to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_option<T: OptionType>(&self, response: &SetOptionResponse<T>) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user = self
|
let user = self
|
||||||
.create_user(
|
.create_user(
|
||||||
@@ -72,7 +72,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user = self
|
let user = self
|
||||||
.create_or_update_user(
|
.create_or_update_user(
|
||||||
@@ -93,7 +93,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user = self
|
let user = self
|
||||||
.edit_user(
|
.edit_user(
|
||||||
@@ -115,7 +115,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.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())
|
self.delete_user_from_database(&mut connection, request.user_id())
|
||||||
.await
|
.await
|
||||||
@@ -136,7 +136,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user = self
|
let user = self
|
||||||
.get_user_from_email(&mut connection, request.email())
|
.get_user_from_email(&mut connection, request.email())
|
||||||
@@ -166,7 +166,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let session = self
|
let session = self
|
||||||
.create_session(&mut connection, request.user(), request.expiration())
|
.create_session(&mut connection, request.user(), request.expiration())
|
||||||
@@ -184,7 +184,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let session = self
|
let session = self
|
||||||
.get_auth_session(&mut connection, request.session_id())
|
.get_auth_session(&mut connection, request.session_id())
|
||||||
@@ -212,7 +212,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user_warren = self
|
let user_warren = self
|
||||||
.add_user_to_warren(&mut connection, request.user_warren())
|
.add_user_to_warren(&mut connection, request.user_warren())
|
||||||
@@ -230,7 +230,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user_warren = self
|
let user_warren = self
|
||||||
.update_user_warren(&mut connection, request.user_warren())
|
.update_user_warren(&mut connection, request.user_warren())
|
||||||
@@ -248,7 +248,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user_warren = self
|
let user_warren = self
|
||||||
.remove_user_from_warren(&mut connection, request.user_id(), request.warren_id())
|
.remove_user_from_warren(&mut connection, request.user_id(), request.warren_id())
|
||||||
@@ -272,7 +272,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user_warrens = self
|
let user_warrens = self
|
||||||
.get_user_warrens(&mut connection, request.user_id())
|
.get_user_warrens(&mut connection, request.user_id())
|
||||||
@@ -290,7 +290,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let user_warrens = self
|
let user_warrens = self
|
||||||
.get_all_user_warrens(&mut connection)
|
.get_all_user_warrens(&mut connection)
|
||||||
@@ -308,7 +308,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.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())
|
self.get_user_warren(&mut connection, request.user_id(), request.warren_id())
|
||||||
.await
|
.await
|
||||||
@@ -326,7 +326,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let users = self
|
let users = self
|
||||||
.fetch_users(&mut connection)
|
.fetch_users(&mut connection)
|
||||||
@@ -345,7 +345,7 @@ impl AuthRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let users = self
|
let users = self
|
||||||
.fetch_users(&mut connection)
|
.fetch_users(&mut connection)
|
||||||
@@ -402,6 +402,7 @@ impl Sqlite {
|
|||||||
|
|
||||||
let user: User = sqlx::query_as(
|
let user: User = sqlx::query_as(
|
||||||
"INSERT INTO users (
|
"INSERT INTO users (
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
hash,
|
hash,
|
||||||
@@ -411,12 +412,14 @@ impl Sqlite {
|
|||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
$3,
|
$3,
|
||||||
$4
|
$4,
|
||||||
|
$5
|
||||||
)
|
)
|
||||||
RETURNING
|
RETURNING
|
||||||
*
|
*
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
|
.bind(Uuid::new_v4())
|
||||||
.bind(name)
|
.bind(name)
|
||||||
.bind(email)
|
.bind(email)
|
||||||
.bind(password_hash)
|
.bind(password_hash)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use sqlx::{
|
|||||||
};
|
};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod options;
|
||||||
pub mod share;
|
pub mod share;
|
||||||
pub mod warrens;
|
pub mod warrens;
|
||||||
|
|
||||||
@@ -29,10 +30,6 @@ impl Sqlite {
|
|||||||
pub async fn new(config: SqliteConfig) -> anyhow::Result<Self> {
|
pub async fn new(config: SqliteConfig) -> anyhow::Result<Self> {
|
||||||
let opts = SqliteConnectOptions::from_str(&config.database_url)?
|
let opts = SqliteConnectOptions::from_str(&config.database_url)?
|
||||||
.create_if_missing(true)
|
.create_if_missing(true)
|
||||||
.extension_with_entrypoint(
|
|
||||||
"/var/lib/warren/sqlite_extensions/uuid",
|
|
||||||
"sqlite3_uuid_init",
|
|
||||||
)
|
|
||||||
.disable_statement_logging();
|
.disable_statement_logging();
|
||||||
|
|
||||||
let pool = SqlitePoolOptions::new().connect_with(opts).await?;
|
let pool = SqlitePoolOptions::new().connect_with(opts).await?;
|
||||||
|
|||||||
134
backend/src/lib/outbound/sqlite/options.rs
Normal file
134
backend/src/lib/outbound/sqlite/options.rs
Normal file
@@ -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<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: GetOptionRequest,
|
||||||
|
) -> Result<GetOptionResponse<T>, 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<T: OptionType>(
|
||||||
|
&self,
|
||||||
|
request: SetOptionRequest<T>,
|
||||||
|
) -> Result<SetOptionResponse<T>, 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<DeleteOptionResponse, DeleteOptionError> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -154,6 +154,7 @@ pub(super) async fn create_share(
|
|||||||
let share: ShareRow = sqlx::query_as(
|
let share: ShareRow = sqlx::query_as(
|
||||||
"
|
"
|
||||||
INSERT INTO shares (
|
INSERT INTO shares (
|
||||||
|
id,
|
||||||
creator_id,
|
creator_id,
|
||||||
warren_id,
|
warren_id,
|
||||||
path,
|
path,
|
||||||
@@ -164,12 +165,14 @@ pub(super) async fn create_share(
|
|||||||
$2,
|
$2,
|
||||||
$3,
|
$3,
|
||||||
$4,
|
$4,
|
||||||
datetime($5, 'unixepoch')
|
$5,
|
||||||
|
datetime($6, 'unixepoch')
|
||||||
)
|
)
|
||||||
RETURNING
|
RETURNING
|
||||||
*
|
*
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
|
.bind(Uuid::new_v4())
|
||||||
.bind(request.creator_id())
|
.bind(request.creator_id())
|
||||||
.bind(request.warren_id())
|
.bind(request.warren_id())
|
||||||
.bind(request.base().path())
|
.bind(request.base().path())
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let warren = self
|
let warren = self
|
||||||
.create_warren(&mut connection, request.name(), request.path())
|
.create_warren(&mut connection, request.name(), request.path())
|
||||||
@@ -47,7 +47,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let warren = self
|
let warren = self
|
||||||
.edit_warren(
|
.edit_warren(
|
||||||
@@ -70,7 +70,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let warren = self
|
let warren = self
|
||||||
.delete_warren(&mut connection, request.id())
|
.delete_warren(&mut connection, request.id())
|
||||||
@@ -88,7 +88,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let warrens = self
|
let warrens = self
|
||||||
.fetch_warrens(&mut connection, request.ids())
|
.fetch_warrens(&mut connection, request.ids())
|
||||||
@@ -106,7 +106,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let warrens = self
|
let warrens = self
|
||||||
.fetch_all_warrens(&mut connection)
|
.fetch_all_warrens(&mut connection)
|
||||||
@@ -121,7 +121,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let warren = self
|
let warren = self
|
||||||
.get_warren(&mut connection, request.id())
|
.get_warren(&mut connection, request.id())
|
||||||
@@ -144,7 +144,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
super::share::get_share(&mut connection, request)
|
super::share::get_share(&mut connection, request)
|
||||||
.await
|
.await
|
||||||
@@ -159,7 +159,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
super::share::create_share(&mut connection, request)
|
super::share::create_share(&mut connection, request)
|
||||||
.await
|
.await
|
||||||
@@ -177,7 +177,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
let path = request.path().clone();
|
let path = request.path().clone();
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
super::share::delete_share(&mut connection, request)
|
super::share::delete_share(&mut connection, request)
|
||||||
.await
|
.await
|
||||||
@@ -211,7 +211,7 @@ impl WarrenRepository for Sqlite {
|
|||||||
.pool
|
.pool
|
||||||
.acquire()
|
.acquire()
|
||||||
.await
|
.await
|
||||||
.context("Failed to get a PostgreSQL connection")?;
|
.context("Failed to get a Sqlite connection")?;
|
||||||
|
|
||||||
super::share::verify_password(&mut connection, request)
|
super::share::verify_password(&mut connection, request)
|
||||||
.await
|
.await
|
||||||
@@ -232,16 +232,19 @@ impl Sqlite {
|
|||||||
let warren: Warren = sqlx::query_as(
|
let warren: Warren = sqlx::query_as(
|
||||||
"
|
"
|
||||||
INSERT INTO warrens (
|
INSERT INTO warrens (
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
path
|
path
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1,
|
$1,
|
||||||
$2
|
$2,
|
||||||
|
$3
|
||||||
)
|
)
|
||||||
RETURNING
|
RETURNING
|
||||||
*
|
*
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
|
.bind(Uuid::new_v4())
|
||||||
.bind(name)
|
.bind(name)
|
||||||
.bind(path)
|
.bind(path)
|
||||||
.fetch_one(&mut *tx)
|
.fetch_one(&mut *tx)
|
||||||
|
|||||||
Binary file not shown.
@@ -11,7 +11,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- 'SERVER_ADDRESS=0.0.0.0'
|
- 'SERVER_ADDRESS=0.0.0.0'
|
||||||
- 'SERVER_PORT=8080'
|
- 'SERVER_PORT=8080'
|
||||||
- 'DATABASE_URL=sqlite:///var/lib/warren/warren.db'
|
- 'DATABASE_URL=sqlite:///var/lib/warren/data/warren.db'
|
||||||
- 'SERVE_DIRECTORY=/serve'
|
- 'SERVE_DIRECTORY=/serve'
|
||||||
- 'CORS_ALLOW_ORIGIN=http://localhost:8081'
|
- 'CORS_ALLOW_ORIGIN=http://localhost:8081'
|
||||||
- 'LOG_LEVEL=debug'
|
- 'LOG_LEVEL=debug'
|
||||||
@@ -19,7 +19,7 @@ services:
|
|||||||
- 'ZIP_READ_BUFFER_BYTES=4096'
|
- 'ZIP_READ_BUFFER_BYTES=4096'
|
||||||
volumes:
|
volumes:
|
||||||
- './backend/serve:/serve:rw'
|
- './backend/serve:/serve:rw'
|
||||||
- './backend/warren.db:/var/lib/warren/warren.db:rw'
|
- './backend/data:/var/lib/warren/data:rw'
|
||||||
networks:
|
networks:
|
||||||
warren-net:
|
warren-net:
|
||||||
name: 'warren-net'
|
name: 'warren-net'
|
||||||
|
|||||||
Reference in New Issue
Block a user