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/.gitignore
|
||||
backend/data
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@@ -13,19 +13,9 @@ COPY frontend/ ./
|
||||
RUN npm run generate
|
||||
|
||||
|
||||
FROM alpine:3 AS sqlite-extension-compiler
|
||||
WORKDIR /var/lib/warren
|
||||
|
||||
RUN apk add sqlite-libs sqlite-dev build-base
|
||||
COPY backend/sqlite_extensions sqlite_extensions
|
||||
RUN gcc -g -fPIC -shared sqlite_extensions/uuid.c -o sqlite_extensions/uuid
|
||||
|
||||
|
||||
FROM rust:alpine AS backend-builder
|
||||
WORKDIR /usr/src/warren
|
||||
|
||||
RUN apk add sqlite sqlite-dev build-base
|
||||
|
||||
COPY backend/Cargo.toml backend/Cargo.lock ./
|
||||
RUN mkdir -p src/bin/backend && mkdir src/lib && echo "fn main() {}" > src/bin/backend/main.rs && echo "" > src/lib/lib.rs
|
||||
RUN apk add --no-cache pkgconfig openssl openssl-dev libc-dev openssl-libs-static
|
||||
@@ -38,8 +28,6 @@ RUN cargo build --release
|
||||
FROM alpine:3
|
||||
WORKDIR /var/lib/warren
|
||||
|
||||
COPY --from=sqlite-extension-compiler /var/lib/warren/sqlite_extensions/uuid /var/lib/warren/sqlite_extensions/uuid
|
||||
|
||||
COPY --from=backend-builder /usr/src/warren/target/release/warren_backend /usr/bin/warren
|
||||
COPY --from=frontend-builder /usr/src/warren/dist ./frontend
|
||||
|
||||
|
||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
target
|
||||
serve
|
||||
.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"
|
||||
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
|
||||
@@ -39,5 +39,5 @@ tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
url = "2.5.4"
|
||||
uuid = { version = "1.17.0", features = ["serde"] }
|
||||
uuid = { version = "1.17.0", features = ["serde", "v4"] }
|
||||
zip = "4.5.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
CREATE TABLE users (
|
||||
id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())),
|
||||
id BLOB NOT NULL PRIMARY KEY,
|
||||
oidc_sub TEXT UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
@@ -10,7 +10,7 @@ CREATE TABLE users (
|
||||
);
|
||||
|
||||
CREATE TABLE warrens (
|
||||
id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())),
|
||||
id BLOB NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
@@ -31,7 +31,7 @@ CREATE TABLE user_warrens (
|
||||
);
|
||||
|
||||
CREATE TABLE shares (
|
||||
id BLOB NOT NULL PRIMARY KEY DEFAULT (uuid_blob(uuid())),
|
||||
id BLOB NOT NULL PRIMARY KEY,
|
||||
creator_id BLOB NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
warren_id BLOB NOT NULL REFERENCES warrens(id) ON DELETE CASCADE,
|
||||
path TEXT NOT NULL,
|
||||
@@ -48,3 +48,8 @@ CREATE TABLE auth_sessions (
|
||||
expires_at DATETIME NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE application_options (
|
||||
key TEXT NOT NULL PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
let option_service =
|
||||
domain::warren::service::option::Service::new(sqlite.clone(), metrics, notifier);
|
||||
|
||||
let auth_service = domain::warren::service::auth::Service::new(
|
||||
sqlite,
|
||||
metrics,
|
||||
notifier,
|
||||
config.auth,
|
||||
oidc_service,
|
||||
);
|
||||
option_service,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let server_config = HttpServerConfig::new(
|
||||
&config.server_address,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod auth_session;
|
||||
pub mod file;
|
||||
pub mod option;
|
||||
pub mod share;
|
||||
pub mod user;
|
||||
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,
|
||||
email: UserEmail,
|
||||
password: UserPassword,
|
||||
bypass_registration_flag: bool,
|
||||
admin: bool,
|
||||
}
|
||||
|
||||
impl RegisterUserRequest {
|
||||
@@ -17,6 +19,23 @@ impl RegisterUserRequest {
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
bypass_registration_flag: false,
|
||||
admin: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_bypass_flag(
|
||||
name: UserName,
|
||||
email: UserEmail,
|
||||
password: UserPassword,
|
||||
admin: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
bypass_registration_flag: true,
|
||||
admin,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +50,19 @@ impl RegisterUserRequest {
|
||||
pub fn password(&self) -> &UserPassword {
|
||||
&self.password
|
||||
}
|
||||
|
||||
pub fn admin(&self) -> bool {
|
||||
self.admin
|
||||
}
|
||||
|
||||
pub fn bypass_registration_flag(&self) -> bool {
|
||||
self.bypass_registration_flag
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegisterUserRequest> for CreateUserRequest {
|
||||
fn from(value: RegisterUserRequest) -> Self {
|
||||
Self::new(value.name, value.email, value.password, false)
|
||||
Self::new(value.name, value.email, value.password, value.admin)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_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,
|
||||
TouchError, TouchRequest,
|
||||
},
|
||||
option::{
|
||||
DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError,
|
||||
GetOptionRequest, GetOptionResponse, OptionType, SetOptionError, SetOptionRequest,
|
||||
SetOptionResponse,
|
||||
},
|
||||
share::{
|
||||
CreateShareBaseRequest, CreateShareError, CreateShareRequest, CreateShareResponse,
|
||||
DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest,
|
||||
@@ -337,3 +342,18 @@ pub trait AuthService: Clone + Send + Sync + 'static {
|
||||
warren_service: &WS,
|
||||
) -> impl Future<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::{
|
||||
auth_session::requests::FetchAuthSessionResponse,
|
||||
file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse},
|
||||
option::{DeleteOptionResponse, GetOptionResponse, OptionType, SetOptionResponse},
|
||||
share::{
|
||||
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
||||
ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse,
|
||||
@@ -219,3 +220,15 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
||||
response: &DeleteShareResponse,
|
||||
) -> impl Future<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,
|
||||
TouchError, TouchRequest,
|
||||
},
|
||||
option::{
|
||||
DeleteOptionError, DeleteOptionRequest, DeleteOptionResponse, GetOptionError,
|
||||
GetOptionRequest, GetOptionResponse, OptionType, SetOptionError, SetOptionRequest,
|
||||
SetOptionResponse,
|
||||
},
|
||||
share::{
|
||||
CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError,
|
||||
DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, ListSharesError,
|
||||
@@ -188,3 +193,18 @@ pub trait AuthRepository: Clone + Send + Sync + 'static {
|
||||
request: FetchUserWarrenRequest,
|
||||
) -> impl Future<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,
|
||||
option::{GetOptionError, GetOptionRequest, OptionKey, SetOptionRequest},
|
||||
share::{
|
||||
CreateShareBaseRequest, CreateShareError, CreateShareResponse,
|
||||
DeleteShareError, DeleteShareRequest, DeleteShareResponse, ListSharesError,
|
||||
@@ -24,7 +25,7 @@ use crate::{
|
||||
ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse, ListUsersError,
|
||||
ListUsersRequest, LoginUserError, LoginUserOidcError, LoginUserOidcRequest,
|
||||
LoginUserOidcResponse, LoginUserRequest, LoginUserResponse, RegisterUserError,
|
||||
RegisterUserRequest, User,
|
||||
RegisterUserRequest, User, UserEmail, UserName, UserPassword,
|
||||
},
|
||||
user_warren::{
|
||||
UserWarren,
|
||||
@@ -46,7 +47,10 @@ use crate::{
|
||||
WarrenTouchResponse,
|
||||
},
|
||||
},
|
||||
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService},
|
||||
ports::{
|
||||
AuthMetrics, AuthNotifier, AuthRepository, AuthService, OptionService,
|
||||
WarrenService,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -97,54 +101,100 @@ impl AuthConfig {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Service<R, M, N, OIDC>
|
||||
pub struct Service<R, M, N, OIDC, O>
|
||||
where
|
||||
R: AuthRepository,
|
||||
M: AuthMetrics,
|
||||
N: AuthNotifier,
|
||||
OIDC: OidcService,
|
||||
O: OptionService,
|
||||
{
|
||||
repository: R,
|
||||
metrics: M,
|
||||
notifier: N,
|
||||
oidc: Option<OIDC>,
|
||||
option_service: O,
|
||||
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
|
||||
R: AuthRepository,
|
||||
M: AuthMetrics,
|
||||
N: AuthNotifier,
|
||||
OIDC: OidcService,
|
||||
O: OptionService,
|
||||
{
|
||||
pub fn new(
|
||||
pub async fn new(
|
||||
repository: R,
|
||||
metrics: M,
|
||||
notifier: N,
|
||||
config: AuthConfig,
|
||||
oidc: Option<OIDC>,
|
||||
) -> Self {
|
||||
Self {
|
||||
option_service: O,
|
||||
) -> anyhow::Result<Self> {
|
||||
let service = Self {
|
||||
repository,
|
||||
metrics,
|
||||
notifier,
|
||||
config,
|
||||
oidc,
|
||||
}
|
||||
option_service,
|
||||
};
|
||||
|
||||
service.init().await?;
|
||||
|
||||
Ok(service)
|
||||
}
|
||||
|
||||
pub fn oidc(&self) -> Option<&OIDC> {
|
||||
self.oidc.as_ref()
|
||||
}
|
||||
|
||||
async fn init(&self) -> anyhow::Result<()> {
|
||||
self.create_admin_user_if_init().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_admin_user_if_init(&self) -> anyhow::Result<()> {
|
||||
const CREATED_ADMIN_USER_KEY: &str = "CREATED_ADMIN_USER";
|
||||
|
||||
let key = OptionKey::new(CREATED_ADMIN_USER_KEY)?;
|
||||
let request = GetOptionRequest::new(key.clone());
|
||||
match self.option_service.get_option::<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
|
||||
R: AuthRepository,
|
||||
M: AuthMetrics,
|
||||
N: AuthNotifier,
|
||||
OIDC: OidcService,
|
||||
O: OptionService,
|
||||
{
|
||||
async fn create_warren<WS: WarrenService>(
|
||||
&self,
|
||||
@@ -253,7 +303,7 @@ where
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
R: AuthRepository,
|
||||
M: AuthMetrics,
|
||||
N: AuthNotifier,
|
||||
OIDC: OidcService,
|
||||
O: OptionService,
|
||||
{
|
||||
/// A helper to get a [UserWarren], [User] and the underlying request from an [AuthRequest]
|
||||
async fn get_session_data_and_user_warren<T, E>(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod auth;
|
||||
pub mod file_system;
|
||||
pub mod option;
|
||||
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::{
|
||||
oidc::ports::OidcMetrics,
|
||||
warren::ports::{AuthMetrics, FileSystemMetrics, WarrenMetrics},
|
||||
warren::ports::{AuthMetrics, FileSystemMetrics, OptionMetrics, WarrenMetrics},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -452,3 +452,26 @@ impl OidcMetrics for MetricsDebugLogger {
|
||||
tracing::debug!("[Metrics] OIDC get user info failed");
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionMetrics for MetricsDebugLogger {
|
||||
async fn record_option_get_success(&self) {
|
||||
tracing::debug!("[Metrics] Get option succeeded");
|
||||
}
|
||||
async fn record_option_get_failure(&self) {
|
||||
tracing::debug!("[Metrics] Get option failed");
|
||||
}
|
||||
|
||||
async fn record_option_set_success(&self) {
|
||||
tracing::debug!("[Metrics] Set option succeeded");
|
||||
}
|
||||
async fn record_option_set_failure(&self) {
|
||||
tracing::debug!("[Metrics] Set option failed");
|
||||
}
|
||||
|
||||
async fn record_option_delete_success(&self) {
|
||||
tracing::debug!("[Metrics] Delete option succeeded");
|
||||
}
|
||||
async fn record_option_delete_failure(&self) {
|
||||
tracing::debug!("[Metrics] Delete option failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::domain::{
|
||||
models::{
|
||||
auth_session::requests::FetchAuthSessionResponse,
|
||||
file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse},
|
||||
option::{DeleteOptionResponse, GetOptionResponse, OptionType, SetOptionResponse},
|
||||
share::{
|
||||
CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse,
|
||||
ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse,
|
||||
@@ -22,7 +23,7 @@ use crate::domain::{
|
||||
WarrenRmResponse, WarrenSaveResponse, WarrenTouchResponse,
|
||||
},
|
||||
},
|
||||
ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier},
|
||||
ports::{AuthNotifier, FileSystemNotifier, OptionNotifier, WarrenNotifier},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -515,3 +516,25 @@ impl OidcNotifier for NotifierDebugLogger {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionNotifier for NotifierDebugLogger {
|
||||
async fn got_option<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
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user = self
|
||||
.create_user(
|
||||
@@ -72,7 +72,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user = self
|
||||
.create_or_update_user(
|
||||
@@ -93,7 +93,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user = self
|
||||
.edit_user(
|
||||
@@ -115,7 +115,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
self.delete_user_from_database(&mut connection, request.user_id())
|
||||
.await
|
||||
@@ -136,7 +136,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user = self
|
||||
.get_user_from_email(&mut connection, request.email())
|
||||
@@ -166,7 +166,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let session = self
|
||||
.create_session(&mut connection, request.user(), request.expiration())
|
||||
@@ -184,7 +184,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let session = self
|
||||
.get_auth_session(&mut connection, request.session_id())
|
||||
@@ -212,7 +212,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user_warren = self
|
||||
.add_user_to_warren(&mut connection, request.user_warren())
|
||||
@@ -230,7 +230,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user_warren = self
|
||||
.update_user_warren(&mut connection, request.user_warren())
|
||||
@@ -248,7 +248,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user_warren = self
|
||||
.remove_user_from_warren(&mut connection, request.user_id(), request.warren_id())
|
||||
@@ -272,7 +272,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user_warrens = self
|
||||
.get_user_warrens(&mut connection, request.user_id())
|
||||
@@ -290,7 +290,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let user_warrens = self
|
||||
.get_all_user_warrens(&mut connection)
|
||||
@@ -308,7 +308,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
self.get_user_warren(&mut connection, request.user_id(), request.warren_id())
|
||||
.await
|
||||
@@ -326,7 +326,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let users = self
|
||||
.fetch_users(&mut connection)
|
||||
@@ -345,7 +345,7 @@ impl AuthRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let users = self
|
||||
.fetch_users(&mut connection)
|
||||
@@ -402,6 +402,7 @@ impl Sqlite {
|
||||
|
||||
let user: User = sqlx::query_as(
|
||||
"INSERT INTO users (
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
hash,
|
||||
@@ -411,12 +412,14 @@ impl Sqlite {
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4
|
||||
$4,
|
||||
$5
|
||||
)
|
||||
RETURNING
|
||||
*
|
||||
",
|
||||
)
|
||||
.bind(Uuid::new_v4())
|
||||
.bind(name)
|
||||
.bind(email)
|
||||
.bind(password_hash)
|
||||
|
||||
@@ -6,6 +6,7 @@ use sqlx::{
|
||||
};
|
||||
use tokio::task::JoinHandle;
|
||||
pub mod auth;
|
||||
pub mod options;
|
||||
pub mod share;
|
||||
pub mod warrens;
|
||||
|
||||
@@ -29,10 +30,6 @@ impl Sqlite {
|
||||
pub async fn new(config: SqliteConfig) -> anyhow::Result<Self> {
|
||||
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?;
|
||||
|
||||
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(
|
||||
"
|
||||
INSERT INTO shares (
|
||||
id,
|
||||
creator_id,
|
||||
warren_id,
|
||||
path,
|
||||
@@ -164,12 +165,14 @@ pub(super) async fn create_share(
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
datetime($5, 'unixepoch')
|
||||
$5,
|
||||
datetime($6, 'unixepoch')
|
||||
)
|
||||
RETURNING
|
||||
*
|
||||
",
|
||||
)
|
||||
.bind(Uuid::new_v4())
|
||||
.bind(request.creator_id())
|
||||
.bind(request.warren_id())
|
||||
.bind(request.base().path())
|
||||
|
||||
@@ -32,7 +32,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let warren = self
|
||||
.create_warren(&mut connection, request.name(), request.path())
|
||||
@@ -47,7 +47,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let warren = self
|
||||
.edit_warren(
|
||||
@@ -70,7 +70,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let warren = self
|
||||
.delete_warren(&mut connection, request.id())
|
||||
@@ -88,7 +88,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let warrens = self
|
||||
.fetch_warrens(&mut connection, request.ids())
|
||||
@@ -106,7 +106,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let warrens = self
|
||||
.fetch_all_warrens(&mut connection)
|
||||
@@ -121,7 +121,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let warren = self
|
||||
.get_warren(&mut connection, request.id())
|
||||
@@ -144,7 +144,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
super::share::get_share(&mut connection, request)
|
||||
.await
|
||||
@@ -159,7 +159,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
super::share::create_share(&mut connection, request)
|
||||
.await
|
||||
@@ -177,7 +177,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
let path = request.path().clone();
|
||||
|
||||
@@ -195,7 +195,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
super::share::delete_share(&mut connection, request)
|
||||
.await
|
||||
@@ -211,7 +211,7 @@ impl WarrenRepository for Sqlite {
|
||||
.pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("Failed to get a PostgreSQL connection")?;
|
||||
.context("Failed to get a Sqlite connection")?;
|
||||
|
||||
super::share::verify_password(&mut connection, request)
|
||||
.await
|
||||
@@ -232,16 +232,19 @@ impl Sqlite {
|
||||
let warren: Warren = sqlx::query_as(
|
||||
"
|
||||
INSERT INTO warrens (
|
||||
id,
|
||||
name,
|
||||
path
|
||||
) VALUES (
|
||||
$1,
|
||||
$2
|
||||
$2,
|
||||
$3
|
||||
)
|
||||
RETURNING
|
||||
*
|
||||
",
|
||||
)
|
||||
.bind(Uuid::new_v4())
|
||||
.bind(name)
|
||||
.bind(path)
|
||||
.fetch_one(&mut *tx)
|
||||
|
||||
Binary file not shown.
@@ -11,7 +11,7 @@ services:
|
||||
environment:
|
||||
- 'SERVER_ADDRESS=0.0.0.0'
|
||||
- 'SERVER_PORT=8080'
|
||||
- 'DATABASE_URL=sqlite:///var/lib/warren/warren.db'
|
||||
- 'DATABASE_URL=sqlite:///var/lib/warren/data/warren.db'
|
||||
- 'SERVE_DIRECTORY=/serve'
|
||||
- 'CORS_ALLOW_ORIGIN=http://localhost:8081'
|
||||
- 'LOG_LEVEL=debug'
|
||||
@@ -19,7 +19,7 @@ services:
|
||||
- 'ZIP_READ_BUFFER_BYTES=4096'
|
||||
volumes:
|
||||
- './backend/serve:/serve:rw'
|
||||
- './backend/warren.db:/var/lib/warren/warren.db:rw'
|
||||
- './backend/data:/var/lib/warren/data:rw'
|
||||
networks:
|
||||
warren-net:
|
||||
name: 'warren-net'
|
||||
|
||||
Reference in New Issue
Block a user