From bf81bf7da02da7b04192f3c39bbfd3b8e610e200 Mon Sep 17 00:00:00 2001
From: Patrick Willner <50421879+heat1q@users.noreply.github.com>
Date: Fri, 27 Oct 2023 11:47:15 +0200
Subject: [PATCH] refactor(common): improve database trait api
---
Cargo.lock | 3 +
crates/common/Cargo.toml | 1 +
crates/common/src/database/mod.rs | 50 ++++-----
crates/common/src/lib.rs | 17 ++--
crates/database/Cargo.toml | 1 +
crates/database/src/postgres.rs | 79 ++++++---------
crates/database/src/sqlite.rs | 117 +++++++++-------------
crates/media/Cargo.toml | 2 +
crates/media/src/api/router.rs | 38 +++----
crates/media/src/api/routes/get_media.rs | 10 +-
crates/media/src/api/routes/post_media.rs | 17 ++--
crates/media/src/repository.rs | 16 +--
src/lib.rs | 30 +++---
src/plugin/plugin_manager.rs | 11 +-
14 files changed, 174 insertions(+), 218 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 1767f85..c4c31a7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -625,6 +625,7 @@ dependencies = [
name = "common"
version = "0.6.0"
dependencies = [
+ "anyhow",
"async-trait",
"axum",
"http",
@@ -883,6 +884,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
name = "database"
version = "0.6.0"
dependencies = [
+ "anyhow",
"async-trait",
"common",
"pretty_assertions",
@@ -1790,6 +1792,7 @@ dependencies = [
name = "media"
version = "0.6.0"
dependencies = [
+ "anyhow",
"axum",
"common",
"database",
diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml
index 2316765..64d8d2b 100644
--- a/crates/common/Cargo.toml
+++ b/crates/common/Cargo.toml
@@ -16,6 +16,7 @@ path = "src/lib.rs"
doctest = false
[dependencies]
+anyhow.workspace = true
async-trait.workspace = true
axum.workspace = true
http.workspace = true
diff --git a/crates/common/src/database/mod.rs b/crates/common/src/database/mod.rs
index 321db81..b906d12 100644
--- a/crates/common/src/database/mod.rs
+++ b/crates/common/src/database/mod.rs
@@ -15,8 +15,10 @@
* along with this program. If not, see .
*/
+use std::sync::Arc;
+
+use anyhow::Result;
use async_trait::async_trait;
-use std::error::Error;
use time::OffsetDateTime;
use crate::auth::user::User;
@@ -30,57 +32,43 @@ pub mod reference;
pub mod tag;
pub mod user;
+pub type ArcDynDatabase = Arc;
+
#[async_trait]
pub trait Database {
- /// Initialize the database and run required migrations
- async fn setup(&mut self) -> Result<(), Box>;
-
/// List registered user accounts
- async fn get_users(&self) -> Result, Box>;
+ async fn get_users(&self) -> Result>;
/// Create a new user account
- async fn create_user(&self, user: &User) -> Result<(), Box>;
+ async fn create_user(&self, user: &User) -> Result<()>;
/// Get user by user_id
- async fn get_user(&self, user_id: &str) -> Result>;
+ async fn get_user(&self, user_id: &str) -> Result;
/// Partial update a single user account
- async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box>;
- async fn update_nickname(&self, nickname: &str) -> Result<(), Box>;
- async fn update_names(
- &self,
- firstname: &str,
- lastname: &str,
- user_id: &str,
- ) -> Result<(), Box>;
+ async fn update_email(&self, email: &str, user_id: &str) -> Result<()>;
+ async fn update_nickname(&self, nickname: &str) -> Result<()>;
+ async fn update_names(&self, firstname: &str, lastname: &str, user_id: &str) -> Result<()>;
- async fn disable_user(&self, user_id: &str) -> Result<(), Box>;
- async fn enable_user(&self, user_id: &str) -> Result<(), Box>;
+ async fn disable_user(&self, user_id: &str) -> Result<()>;
+ async fn enable_user(&self, user_id: &str) -> Result<()>;
- async fn get_media_items(&self, user_id: &str) -> Result, Box>;
+ async fn get_media_items(&self, user_id: &str) -> Result>;
async fn create_media_item(
&self,
user_id: &str,
name: &str,
date_taken: OffsetDateTime,
- ) -> Result>;
- async fn get_media_item(&self, media_id: &str) -> Result>;
+ ) -> Result;
+ async fn get_media_item(&self, media_id: &str) -> Result;
async fn add_reference(
&self,
user_id: &str,
media_id: &str,
reference: &Reference,
- ) -> Result>;
+ ) -> Result;
- async fn update_reference(
- &self,
- reference_id: &str,
- reference: &Reference,
- ) -> Result<(), Box>;
+ async fn update_reference(&self, reference_id: &str, reference: &Reference) -> Result<()>;
- async fn remove_reference(
- &self,
- media_id: &str,
- reference_id: &str,
- ) -> Result<(), Box>;
+ async fn remove_reference(&self, media_id: &str, reference_id: &str) -> Result<()>;
}
diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs
index 74b59d3..c500b0d 100644
--- a/crates/common/src/lib.rs
+++ b/crates/common/src/lib.rs
@@ -18,11 +18,11 @@
//! This crate offers shared data models for [Photos.network](https://photos.network) core application.
//!
-use std::collections::HashMap;
+use std::{collections::HashMap, sync::Arc};
use axum::Router;
use config::configuration::Configuration;
-use database::Database;
+use database::ArcDynDatabase;
use photos_network_plugin::{PluginFactoryRef, PluginId};
pub mod auth;
@@ -35,18 +35,15 @@ pub mod model {
/// Aggregates the applications configuration, its loaded plugins and the router for all REST APIs
#[derive(Clone)]
-pub struct ApplicationState {
- pub config: Configuration,
+pub struct ApplicationState {
+ pub config: Arc,
pub plugins: HashMap,
pub router: Option,
- pub database: D,
+ pub database: ArcDynDatabase,
}
-impl ApplicationState
-where
- D: Database,
-{
- pub fn new(config: Configuration, database: D) -> Self {
+impl ApplicationState {
+ pub fn new(config: Arc, database: ArcDynDatabase) -> Self {
Self {
config,
plugins: HashMap::new(),
diff --git a/crates/database/Cargo.toml b/crates/database/Cargo.toml
index b38d323..7508668 100644
--- a/crates/database/Cargo.toml
+++ b/crates/database/Cargo.toml
@@ -16,6 +16,7 @@ path = "src/lib.rs"
doctest = false
[dependencies]
+anyhow.workspace = true
common.workspace = true
async-trait.workspace = true
tracing.workspace = true
diff --git a/crates/database/src/postgres.rs b/crates/database/src/postgres.rs
index d42ba7a..70a5d26 100644
--- a/crates/database/src/postgres.rs
+++ b/crates/database/src/postgres.rs
@@ -17,6 +17,7 @@
//! This crate offers a database abstraction for [Photos.network](https://photos.network) core application.
//!
+use anyhow::Result;
use async_trait::async_trait;
use common::auth::user::User;
use common::database::media_item::MediaItem;
@@ -25,7 +26,6 @@ use common::database::Database;
use sqlx::types::time::OffsetDateTime;
use sqlx::PgPool;
use sqlx::Row;
-use std::error::Error;
use tracing::info;
use uuid::Uuid;
@@ -35,23 +35,19 @@ pub struct PostgresDatabase {
}
impl PostgresDatabase {
- pub async fn new(db_url: &str) -> Self {
- let pool = PgPool::connect(db_url).await.unwrap();
+ pub async fn new(db_url: &str) -> Result {
+ let pool = PgPool::connect(db_url).await?;
- PostgresDatabase { pool }
+ // run migrations from `migrations` directory
+ sqlx::migrate!("./migrations").run(&pool).await?;
+
+ Ok(PostgresDatabase { pool })
}
}
#[async_trait]
impl Database for PostgresDatabase {
- async fn setup(&mut self) -> Result<(), Box> {
- // run migrations from `migrations` directory
- sqlx::migrate!("./migrations").run(&self.pool).await?;
-
- Ok(())
- }
-
- async fn get_users(&self) -> Result, Box> {
+ async fn get_users(&self) -> Result> {
let query = "SELECT uuid, email, password, lastname, firstname FROM users";
let res = sqlx::query(query);
@@ -76,7 +72,7 @@ impl Database for PostgresDatabase {
Ok(users)
}
- async fn create_user(&self, user: &User) -> Result<(), Box> {
+ async fn create_user(&self, user: &User) -> Result<()> {
let query = "INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)";
let id = Uuid::new_v4().hyphenated().to_string();
info!("create new user with id `{}`.", id);
@@ -92,11 +88,11 @@ impl Database for PostgresDatabase {
Ok(())
}
- async fn get_user(&self, _user_id: &str) -> Result> {
- Err("Not implemented".into())
+ async fn get_user(&self, _user_id: &str) -> Result {
+ unimplemented!()
}
- async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box> {
+ async fn update_email(&self, email: &str, user_id: &str) -> Result<()> {
let query = "UPDATE users SET email = $1 WHERE uuid = $2";
sqlx::query(query)
@@ -108,28 +104,23 @@ impl Database for PostgresDatabase {
Ok(())
}
- async fn update_nickname(&self, _nickname: &str) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn update_nickname(&self, _nickname: &str) -> Result<()> {
+ unimplemented!()
}
- async fn update_names(
- &self,
- _firstname: &str,
- _lastname: &str,
- _user_id: &str,
- ) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn update_names(&self, _firstname: &str, _lastname: &str, _user_id: &str) -> Result<()> {
+ unimplemented!()
}
- async fn disable_user(&self, _user_id: &str) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn disable_user(&self, _user_id: &str) -> Result<()> {
+ unimplemented!()
}
- async fn enable_user(&self, _user_id: &str) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn enable_user(&self, _user_id: &str) -> Result<()> {
+ unimplemented!()
}
- async fn get_media_items(&self, _user_id: &str) -> Result, Box> {
- Err("Not implemented".into())
+ async fn get_media_items(&self, _user_id: &str) -> Result> {
+ unimplemented!()
}
/// Creates a new media item if it doesn't exist and returns the media_id
@@ -138,7 +129,7 @@ impl Database for PostgresDatabase {
user_id: &str,
name: &str,
date_taken: OffsetDateTime,
- ) -> Result> {
+ ) -> Result {
let query = "SELECT COUNT(*) FROM media WHERE owner is $1 and taken_at like $2";
let res = sqlx::query(query).bind(user_id).bind(date_taken);
let rows = res.fetch_all(&self.pool).await?;
@@ -164,31 +155,23 @@ impl Database for PostgresDatabase {
Ok("".to_string())
}
- async fn get_media_item(&self, _media_id: &str) -> Result> {
- Err("Not implemented".into())
+ async fn get_media_item(&self, _media_id: &str) -> Result {
+ unimplemented!()
}
async fn add_reference(
&self,
_user_id: &str,
_media_id: &str,
_reference: &Reference,
- ) -> Result> {
- Err("Not implemented".into())
+ ) -> Result {
+ unimplemented!()
}
- async fn update_reference(
- &self,
- _reference_id: &str,
- _reference: &Reference,
- ) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn update_reference(&self, _reference_id: &str, _reference: &Reference) -> Result<()> {
+ unimplemented!()
}
- async fn remove_reference(
- &self,
- _media_id: &str,
- _reference_id: &str,
- ) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn remove_reference(&self, _media_id: &str, _reference_id: &str) -> Result<()> {
+ unimplemented!()
}
}
diff --git a/crates/database/src/sqlite.rs b/crates/database/src/sqlite.rs
index 2d39d68..72e242b 100644
--- a/crates/database/src/sqlite.rs
+++ b/crates/database/src/sqlite.rs
@@ -17,6 +17,7 @@
//! This crate offers a database abstraction for [Photos.network](https://photos.network) core application.
//!
+use anyhow::Result;
use async_trait::async_trait;
use common::auth::user::User;
use common::database::media_item::MediaItem;
@@ -26,7 +27,6 @@ use sqlx::sqlite::SqliteQueryResult;
use sqlx::types::time::OffsetDateTime;
use sqlx::Row;
use sqlx::SqlitePool;
-use std::error::Error;
use std::i64;
use tracing::error;
use tracing::info;
@@ -38,23 +38,19 @@ pub struct SqliteDatabase {
}
impl SqliteDatabase {
- pub async fn new(db_url: &str) -> Self {
- let pool = SqlitePool::connect(db_url).await.unwrap();
+ pub async fn new(db_url: &str) -> Result {
+ let pool = SqlitePool::connect(db_url).await?;
- SqliteDatabase { pool }
+ // run migrations from `migrations` directory
+ sqlx::migrate!("./migrations").run(&pool).await?;
+
+ Ok(SqliteDatabase { pool })
}
}
#[async_trait]
impl Database for SqliteDatabase {
- async fn setup(&mut self) -> Result<(), Box> {
- // run migrations from `migrations` directory
- sqlx::migrate!("./migrations").run(&self.pool).await?;
-
- Ok(())
- }
-
- async fn get_users(&self) -> Result, Box> {
+ async fn get_users(&self) -> Result> {
let query = "SELECT uuid, email, password, lastname, firstname FROM users";
let res = sqlx::query(query);
@@ -79,7 +75,7 @@ impl Database for SqliteDatabase {
Ok(users)
}
- async fn create_user(&self, user: &User) -> Result<(), Box> {
+ async fn create_user(&self, user: &User) -> Result<()> {
let query = "INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)";
let id = Uuid::new_v4().hyphenated().to_string();
info!("create new user with id `{}`.", id);
@@ -95,11 +91,11 @@ impl Database for SqliteDatabase {
Ok(())
}
- async fn get_user(&self, _user_id: &str) -> Result> {
- Err("Not implemented".into())
+ async fn get_user(&self, _user_id: &str) -> Result {
+ unimplemented!()
}
- async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box> {
+ async fn update_email(&self, email: &str, user_id: &str) -> Result<()> {
let query = "UPDATE users SET email = $1 WHERE uuid = $2";
sqlx::query(query)
@@ -111,35 +107,30 @@ impl Database for SqliteDatabase {
Ok(())
}
- async fn update_nickname(&self, _nickname: &str) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn update_nickname(&self, _nickname: &str) -> Result<()> {
+ unimplemented!()
}
- async fn update_names(
- &self,
- _firstname: &str,
- _lastname: &str,
- _user_id: &str,
- ) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn update_names(&self, _firstname: &str, _lastname: &str, _user_id: &str) -> Result<()> {
+ unimplemented!()
}
- async fn disable_user(&self, _user_id: &str) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn disable_user(&self, _user_id: &str) -> Result<()> {
+ unimplemented!()
}
- async fn enable_user(&self, _user_id: &str) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn enable_user(&self, _user_id: &str) -> Result<()> {
+ unimplemented!()
}
- async fn get_media_items(&self, _user_id: &str) -> Result, Box> {
- Err("Not implemented".into())
+ async fn get_media_items(&self, _user_id: &str) -> Result> {
+ unimplemented!()
}
async fn create_media_item(
&self,
user_id: &str,
name: &str,
date_taken: OffsetDateTime,
- ) -> Result> {
+ ) -> Result {
struct Item {
uuid: String,
}
@@ -188,8 +179,8 @@ impl Database for SqliteDatabase {
};
}
- async fn get_media_item(&self, _media_id: &str) -> Result> {
- Err("Not implemented".into())
+ async fn get_media_item(&self, _media_id: &str) -> Result {
+ unimplemented!()
}
async fn add_reference(
@@ -197,7 +188,7 @@ impl Database for SqliteDatabase {
user_id: &str,
media_id: &str,
reference: &Reference,
- ) -> Result> {
+ ) -> Result {
let query = "INSERT INTO reference (uuid, media, owner, filepath, filename, size) VALUES ($1, $2, $3, $4, $5, $6)";
let id = Uuid::new_v4().hyphenated().to_string();
let _res: SqliteQueryResult = sqlx::query(query)
@@ -213,20 +204,12 @@ impl Database for SqliteDatabase {
Ok(id)
}
- async fn update_reference(
- &self,
- _reference_id: &str,
- _reference: &Reference,
- ) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn update_reference(&self, _reference_id: &str, _reference: &Reference) -> Result<()> {
+ unimplemented!()
}
- async fn remove_reference(
- &self,
- _media_id: &str,
- _reference_id: &str,
- ) -> Result<(), Box> {
- Err("Not implemented".into())
+ async fn remove_reference(&self, _media_id: &str, _reference_id: &str) -> Result<()> {
+ unimplemented!()
}
}
@@ -238,12 +221,12 @@ mod tests {
use time::format_description::well_known::Rfc3339;
#[sqlx::test]
- async fn create_user_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn create_user_should_succeed(pool: SqlitePool) -> Result<()> {
// given
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/create_user_should_succeed.sqlite",
)
- .await;
+ .await?;
// when
for i in 0..3 {
@@ -273,12 +256,12 @@ mod tests {
}
#[sqlx::test]
- async fn create_already_existing_user_should_fail(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn create_already_existing_user_should_fail(pool: SqlitePool) -> Result<()> {
// given
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/create_already_existing_user_should_fail.sqlite",
)
- .await;
+ .await?;
// when
let uuid = uuid::Uuid::new_v4().hyphenated().to_string();
@@ -310,7 +293,7 @@ mod tests {
}
#[sqlx::test]
- async fn update_email_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn update_email_should_succeed(pool: SqlitePool) -> Result<()> {
// given
sqlx::query("INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)")
.bind("570DC079-664A-4496-BAA3-668C445A447")
@@ -322,13 +305,13 @@ mod tests {
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/update_email_should_succeed.sqlite",
)
- .await;
+ .await?;
// when
let result = db
.update_email(
- "security@photos.network".into(),
- "570DC079-664A-4496-BAA3-668C445A447".into(),
+ "security@photos.network",
+ "570DC079-664A-4496-BAA3-668C445A447",
)
.await;
@@ -343,7 +326,7 @@ mod tests {
}
#[sqlx::test]
- async fn update_email_to_existing_should_fail(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn update_email_to_existing_should_fail(pool: SqlitePool) -> Result<()> {
// given
sqlx::query("INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)")
.bind("570DC079-664A-4496-BAA3-668C445A447")
@@ -364,13 +347,13 @@ mod tests {
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/update_email_to_existing_should_fail.sqlite",
)
- .await;
+ .await?;
// when
let result = db
.update_email(
- "security@photos.network".into(),
- "570DC079-664A-4496-BAA3-668C445A447".into(),
+ "security@photos.network",
+ "570DC079-664A-4496-BAA3-668C445A447",
)
.await;
@@ -387,7 +370,7 @@ mod tests {
}
#[sqlx::test]
- async fn get_users_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn get_users_should_succeed(pool: SqlitePool) -> Result<()> {
// given
sqlx::query("INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)")
.bind("570DC079-664A-4496-BAA3-668C445A447")
@@ -399,7 +382,7 @@ mod tests {
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/get_users_should_succeed.sqlite",
)
- .await;
+ .await?;
// when
let users = db.get_users().await.unwrap();
@@ -416,7 +399,7 @@ mod tests {
//noinspection DuplicatedCode
#[sqlx::test]
- async fn create_media_item_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn create_media_item_should_succeed(pool: SqlitePool) -> Result<()> {
// given
let user_id = "570DC079-664A-4496-BAA3-668C445A447";
// create fake user - used as FOREIGN KEY in media
@@ -430,7 +413,7 @@ mod tests {
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/create_media_item_should_succeed.sqlite",
)
- .await;
+ .await?;
let name = "DSC_1234";
let date_taken = OffsetDateTime::now_utc();
@@ -446,7 +429,7 @@ mod tests {
//noinspection DuplicatedCode
#[sqlx::test]
- async fn create_media_item_should_return_existing_uuid(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn create_media_item_should_return_existing_uuid(pool: SqlitePool) -> Result<()> {
// given
let user_id = "570DC079-664A-4496-BAA3-668C445A447";
@@ -476,7 +459,7 @@ mod tests {
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/create_media_item_should_return_existing_uuid.sqlite",
)
- .await;
+ .await?;
// when
let media_item_result = db.create_media_item(user_id, name, taken_at).await;
@@ -490,7 +473,7 @@ mod tests {
//noinspection DuplicatedCode
#[sqlx::test]
- async fn add_reference_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn add_reference_should_succeed(pool: SqlitePool) -> Result<()> {
// given
let user_id = "570DC079-664A-4496-BAA3-668C445A447";
let media_id = "ef9ac799-02f3-4b3f-9d96-7576be0434e6";
@@ -517,7 +500,7 @@ mod tests {
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/database/sqlite/tests/add_reference_should_succeed.sqlite",
)
- .await;
+ .await?;
let filename = "DSC_1234.jpg";
let dir: PathBuf = testdir!();
diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml
index 55b58aa..9205ca8 100644
--- a/crates/media/Cargo.toml
+++ b/crates/media/Cargo.toml
@@ -22,6 +22,8 @@ database.workspace = true
time.workspace = true
+anyhow.workspace = true
+
tracing.workspace = true
tokio = { workspace = true, features = ["full"] }
diff --git a/crates/media/src/api/router.rs b/crates/media/src/api/router.rs
index fa53a32..9321fb6 100644
--- a/crates/media/src/api/router.rs
+++ b/crates/media/src/api/router.rs
@@ -31,18 +31,17 @@ use crate::repository::{MediaRepository, MediaRepositoryState};
use axum::routing::{delete, get, patch, post};
use axum::Router;
use common::ApplicationState;
-use database::sqlite::SqliteDatabase;
use std::sync::Arc;
pub struct MediaApi {}
impl MediaApi {
- pub async fn routes(state: ApplicationState) -> Router
+ pub async fn routes(state: &ApplicationState) -> Router
where
S: Send + Sync + Clone,
{
let media_repository: MediaRepository =
- MediaRepository::new(state.database.clone(), state.config.clone()).await;
+ MediaRepository::new(Arc::clone(&state.database), Arc::clone(&state.config)).await;
let repository_state: MediaRepositoryState = Arc::new(media_repository);
Router::new()
@@ -99,6 +98,7 @@ mod tests {
http::{self, Request, StatusCode},
};
use common::config::configuration::Configuration;
+ use database::sqlite::SqliteDatabase;
use serde_json::json;
use sqlx::SqlitePool;
use tower::ServiceExt;
@@ -106,13 +106,13 @@ mod tests {
#[sqlx::test]
async fn get_media_with_query_success(pool: SqlitePool) {
// given
- let state: ApplicationState = ApplicationState {
- config: Configuration::empty(),
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty().into(),
plugins: HashMap::new(),
router: None,
- database: SqliteDatabase { pool },
+ database: Arc::new(SqliteDatabase { pool }),
};
- let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let app = Router::new().nest("/", MediaApi::routes(&state).await);
// when
let response = app
@@ -139,13 +139,13 @@ mod tests {
#[sqlx::test]
async fn get_media_without_query_success(pool: SqlitePool) {
// given
- let state: ApplicationState = ApplicationState {
- config: Configuration::empty(),
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty().into(),
plugins: HashMap::new(),
router: None,
- database: SqliteDatabase { pool },
+ database: Arc::new(SqliteDatabase { pool }),
};
- let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let app = Router::new().nest("/", MediaApi::routes(&state).await);
// when
let response = app
@@ -172,13 +172,13 @@ mod tests {
#[sqlx::test]
async fn post_media_without_user_fail(pool: SqlitePool) {
// given
- let state: ApplicationState = ApplicationState {
- config: Configuration::empty(),
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty().into(),
plugins: HashMap::new(),
router: None,
- database: SqliteDatabase { pool },
+ database: Arc::new(SqliteDatabase { pool }),
};
- let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let app = Router::new().nest("/", MediaApi::routes(&state).await);
// when
let response = app
@@ -211,13 +211,13 @@ mod tests {
#[allow(dead_code)]
async fn post_media_success(pool: SqlitePool) {
// given
- let state: ApplicationState = ApplicationState {
- config: Configuration::empty(),
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty().into(),
plugins: HashMap::new(),
router: None,
- database: SqliteDatabase { pool },
+ database: Arc::new(SqliteDatabase { pool }),
};
- let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let app = Router::new().nest("/", MediaApi::routes(&state).await);
// when
let response = app
diff --git a/crates/media/src/api/routes/get_media.rs b/crates/media/src/api/routes/get_media.rs
index 9060981..80dba9e 100644
--- a/crates/media/src/api/routes/get_media.rs
+++ b/crates/media/src/api/routes/get_media.rs
@@ -65,7 +65,7 @@ pub(crate) async fn get_media(
#[cfg(test)]
mod tests {
- use std::collections::HashMap;
+ use std::{collections::HashMap, sync::Arc};
use axum::Router;
use common::{config::configuration::Configuration, ApplicationState};
@@ -81,14 +81,14 @@ mod tests {
#[sqlx::test]
async fn get_media_unauthorized_should_not_fail(pool: SqlitePool) {
// given
- let state: ApplicationState = ApplicationState {
- config: Configuration::empty(),
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty().into(),
plugins: HashMap::new(),
router: None,
- database: SqliteDatabase { pool },
+ database: Arc::new(SqliteDatabase { pool }),
};
- let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let app = Router::new().nest("/", MediaApi::routes(&state).await);
// when
let response = app
diff --git a/crates/media/src/api/routes/post_media.rs b/crates/media/src/api/routes/post_media.rs
index b8a2185..1941411 100644
--- a/crates/media/src/api/routes/post_media.rs
+++ b/crates/media/src/api/routes/post_media.rs
@@ -109,6 +109,7 @@ pub(crate) async fn post_media(
mod tests {
use std::collections::HashMap;
use std::io;
+ use std::sync::Arc;
use axum::Router;
use common::{config::configuration::Configuration, ApplicationState};
@@ -131,14 +132,14 @@ mod tests {
#[sqlx::test]
async fn post_media_unauthorized_should_fail(pool: SqlitePool) {
// given
- let state: ApplicationState = ApplicationState {
- config: Configuration::empty(),
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty().into(),
plugins: HashMap::new(),
router: None,
- database: SqliteDatabase { pool },
+ database: Arc::new(SqliteDatabase { pool }),
};
- let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let app = Router::new().nest("/", MediaApi::routes(&state).await);
// when
let response = app
@@ -160,13 +161,13 @@ mod tests {
#[ignore]
async fn post_media_authorized_without_name_field(pool: SqlitePool) {
// given
- let state: ApplicationState = ApplicationState {
- config: Configuration::empty(),
+ let state: ApplicationState = ApplicationState {
+ config: Configuration::empty().into(),
plugins: HashMap::new(),
router: None,
- database: SqliteDatabase { pool },
+ database: Arc::new(SqliteDatabase { pool }),
};
- let app = Router::new().nest("/", MediaApi::routes(state).await);
+ let app = Router::new().nest("/", MediaApi::routes(&state).await);
let data = media_item_form_data().await.unwrap();
// when
diff --git a/crates/media/src/repository.rs b/crates/media/src/repository.rs
index 33bd9f3..8003067 100644
--- a/crates/media/src/repository.rs
+++ b/crates/media/src/repository.rs
@@ -17,11 +17,11 @@
use crate::data::error::DataAccessError;
use crate::data::media_item::MediaItem;
+use anyhow::Result;
use axum::async_trait;
use common::config::configuration::Configuration;
use common::database::reference::Reference;
-use common::database::Database;
-use database::sqlite::SqliteDatabase;
+use common::database::ArcDynDatabase;
use std::path::Path;
use std::sync::Arc;
use time::OffsetDateTime;
@@ -31,8 +31,8 @@ use uuid::Uuid;
#[allow(dead_code)]
pub struct MediaRepository {
- pub(crate) database: SqliteDatabase,
- pub(crate) config: Configuration,
+ pub(crate) database: ArcDynDatabase,
+ pub(crate) config: Arc,
}
pub type MediaRepositoryState = Arc;
@@ -65,7 +65,7 @@ pub trait MediaRepositoryTrait {
}
impl MediaRepository {
- pub async fn new(database: SqliteDatabase, config: Configuration) -> Self {
+ pub async fn new(database: ArcDynDatabase, config: Arc) -> Self {
Self { database, config }
}
}
@@ -174,7 +174,7 @@ mod tests {
//noinspection DuplicatedCode
#[sqlx::test(migrations = "../database/migrations")]
- async fn get_media_items_should_succeed(pool: SqlitePool) -> sqlx::Result<()> {
+ async fn get_media_items_should_succeed(pool: SqlitePool) -> Result<()> {
// given
let user_id = "605EE8BE-BAF2-4499-B8D4-BA8C74E8B242";
sqlx::query("INSERT INTO users (uuid, email, password, lastname, firstname) VALUES ($1, $2, $3, $4, $5)")
@@ -195,8 +195,8 @@ mod tests {
let db = SqliteDatabase::new(
"target/sqlx/test-dbs/media/repository/tests/get_media_items_should_succeed.sqlite",
)
- .await;
- let repository = MediaRepository::new(db, Configuration::empty()).await;
+ .await?;
+ let repository = MediaRepository::new(Arc::new(db), Configuration::empty().into()).await;
// when
let result = repository
diff --git a/src/lib.rs b/src/lib.rs
index 3a9a7af..adbd3ed 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,16 +29,17 @@
use std::fs::{self, OpenOptions};
use std::net::SocketAddr;
+use std::sync::Arc;
use abi_stable::external_types::crossbeam_channel;
use abi_stable::std_types::RResult::{RErr, ROk};
use accounts::api::router::AccountsApi;
-use anyhow::Result;
+use anyhow::{Context, Result};
use axum::extract::DefaultBodyLimit;
use axum::routing::{get, head};
use axum::{Json, Router};
use common::auth::user::User;
-use common::database::Database;
+use common::database::ArcDynDatabase;
use common::ApplicationState;
use database::sqlite::SqliteDatabase;
use media::api::router::MediaApi;
@@ -95,7 +96,8 @@ pub async fn start_server() -> Result<()> {
fs::create_dir_all("plugins")?;
// read config file
- let configuration = Configuration::new(CONFIG_PATH).expect("Could not parse configuration!");
+ let configuration =
+ Arc::new(Configuration::new(CONFIG_PATH).context("Could not parse configuration!")?);
debug!("Configuration: {}", configuration);
// init database
@@ -106,14 +108,11 @@ pub async fn start_server() -> Result<()> {
.create_new(true)
.open("data/core.sqlite3");
- let mut db = SqliteDatabase::new("data/core.sqlite3").await;
+ // TODO: which db?
+ let db: ArcDynDatabase = Arc::new(SqliteDatabase::new("data/core.sqlite3").await?);
- {
- let _ = db.setup().await;
- }
-
- let users = db.get_users().await;
- if users.unwrap().is_empty() {
+ let users = db.get_users().await?;
+ if users.is_empty() {
info!("No user found, create a default admin user. Please check `data/credentials.txt` for details.");
let default_user = "photo@photos.network";
let default_pass = "unsecure";
@@ -134,12 +133,11 @@ pub async fn start_server() -> Result<()> {
updated_at: None,
last_login: None,
};
- let _ = db.clone().create_user(&user).await;
+ let _ = db.create_user(&user).await;
}
// init application state
- //let mut app_state = ApplicationState::::new(configuration.clone(), db);
- let mut app_state = ApplicationState::::new(configuration.clone(), db);
+ let mut app_state = ApplicationState::new(Arc::clone(&configuration), db);
let cfg = ServerConfig {
listen_addr: configuration.internal_url.to_owned(),
@@ -168,7 +166,7 @@ pub async fn start_server() -> Result<()> {
.route("/", head(status))
// Media items
- .nest("/", MediaApi::routes(app_state.clone()).await)
+ .nest("/", MediaApi::routes(&app_state).await)
// OAuth 2.0 Authentication
.nest("/", AuthenticationManager::routes())
@@ -196,7 +194,7 @@ pub async fn start_server() -> Result<()> {
// initialize plugin manager
let mut plugin_manager = PluginManager::new(
- configuration.clone(),
+ Arc::clone(&configuration),
PLUGIN_PATH.to_string(),
&mut app_state,
)?;
@@ -252,7 +250,7 @@ pub async fn start_server() -> Result<()> {
axum::Server::bind(&addr)
.serve(router.into_make_service())
.await
- .unwrap();
+ .context("start server")?;
Ok(())
}
diff --git a/src/plugin/plugin_manager.rs b/src/plugin/plugin_manager.rs
index 4bb7cfe..a877b83 100644
--- a/src/plugin/plugin_manager.rs
+++ b/src/plugin/plugin_manager.rs
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-use std::path::PathBuf;
+use std::{path::PathBuf, sync::Arc};
use abi_stable::library::{lib_header_from_path, LibrarySuffix, RawLibrary};
@@ -23,21 +23,20 @@ use anyhow::Result;
use common::{config::configuration::Configuration, ApplicationState};
use core_extensions::SelfOps;
-use database::sqlite::SqliteDatabase;
use photos_network_plugin::{PluginFactoryRef, PluginId};
use tracing::{debug, error, info};
pub struct PluginManager<'a> {
- config: Configuration,
+ config: Arc,
path: String,
- state: &'a mut ApplicationState,
+ state: &'a mut ApplicationState,
}
impl<'a> PluginManager<'a> {
pub fn new(
- config: Configuration,
+ config: Arc,
path: String,
- state: &'a mut ApplicationState,
+ state: &'a mut ApplicationState,
) -> Result {
Ok(Self {
config,