Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(common): improve database trait api #44

Merged
merged 1 commit into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ readme.workspace = true
license.workspace = true
edition.workspace = true

[features]
mock = ["mockall"]

[lib]
name = "common"
path = "src/lib.rs"
doctest = false

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
axum.workspace = true
http.workspace = true
Expand All @@ -27,6 +31,8 @@ serde_with.workspace = true
time.workspace = true
tracing.workspace = true
uuid = { workspace = true, features = ["serde"] }
mockall = { workspace = true, optional = true }

[dev-dependencies]
testdir.workspace = true
mockall = { workspace = true }
51 changes: 20 additions & 31 deletions crates/common/src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use std::sync::Arc;

use anyhow::Result;
use async_trait::async_trait;
use std::error::Error;
use time::OffsetDateTime;

use crate::auth::user::User;
Expand All @@ -30,57 +32,44 @@ pub mod reference;
pub mod tag;
pub mod user;

pub type ArcDynDatabase = Arc<dyn Database + Send + Sync>;

#[cfg_attr(any(test, feature = "mock"), mockall::automock)]
#[async_trait]
pub trait Database {
/// Initialize the database and run required migrations
async fn setup(&mut self) -> Result<(), Box<dyn Error>>;

/// List registered user accounts
async fn get_users(&self) -> Result<Vec<User>, Box<dyn Error>>;
async fn get_users(&self) -> Result<Vec<User>>;

/// Create a new user account
async fn create_user(&self, user: &User) -> Result<(), Box<dyn Error>>;
async fn create_user(&self, user: &User) -> Result<()>;

/// Get user by user_id
async fn get_user(&self, user_id: &str) -> Result<User, Box<dyn Error>>;
async fn get_user(&self, user_id: &str) -> Result<User>;

/// Partial update a single user account
async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box<dyn Error>>;
async fn update_nickname(&self, nickname: &str) -> Result<(), Box<dyn Error>>;
async fn update_names(
&self,
firstname: &str,
lastname: &str,
user_id: &str,
) -> Result<(), Box<dyn Error>>;
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<dyn Error>>;
async fn enable_user(&self, user_id: &str) -> Result<(), Box<dyn Error>>;
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<Vec<MediaItem>, Box<dyn Error>>;
async fn get_media_items(&self, user_id: &str) -> Result<Vec<MediaItem>>;
async fn create_media_item(
&self,
user_id: &str,
name: &str,
date_taken: OffsetDateTime,
) -> Result<String, Box<dyn Error>>;
async fn get_media_item(&self, media_id: &str) -> Result<MediaItem, Box<dyn Error>>;
) -> Result<String>;
async fn get_media_item(&self, media_id: &str) -> Result<MediaItem>;
async fn add_reference(
&self,
user_id: &str,
media_id: &str,
reference: &Reference,
) -> Result<String, Box<dyn Error>>;
) -> Result<String>;

async fn update_reference(
&self,
reference_id: &str,
reference: &Reference,
) -> Result<(), Box<dyn Error>>;
async fn update_reference(&self, reference_id: &str, reference: &Reference) -> Result<()>;

async fn remove_reference(
&self,
media_id: &str,
reference_id: &str,
) -> Result<(), Box<dyn Error>>;
async fn remove_reference(&self, media_id: &str, reference_id: &str) -> Result<()>;
}
17 changes: 7 additions & 10 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<D> {
pub config: Configuration,
pub struct ApplicationState {
pub config: Arc<Configuration>,
pub plugins: HashMap<PluginId, PluginFactoryRef>,
pub router: Option<Router>,
pub database: D,
pub database: ArcDynDatabase,
}

impl<D> ApplicationState<D>
where
D: Database,
{
pub fn new(config: Configuration, database: D) -> Self {
impl ApplicationState {
pub fn new(config: Arc<Configuration>, database: ArcDynDatabase) -> Self {
Self {
config,
plugins: HashMap::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ path = "src/lib.rs"
doctest = false

[dependencies]
anyhow.workspace = true
common.workspace = true
async-trait.workspace = true
tracing.workspace = true
Expand Down
79 changes: 31 additions & 48 deletions crates/database/src/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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<Self> {
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<dyn Error>> {
// run migrations from `migrations` directory
sqlx::migrate!("./migrations").run(&self.pool).await?;

Ok(())
}

async fn get_users(&self) -> Result<Vec<User>, Box<dyn Error>> {
async fn get_users(&self) -> Result<Vec<User>> {
let query = "SELECT uuid, email, password, lastname, firstname FROM users";

let res = sqlx::query(query);
Expand All @@ -76,7 +72,7 @@ impl Database for PostgresDatabase {
Ok(users)
}

async fn create_user(&self, user: &User) -> Result<(), Box<dyn Error>> {
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);
Expand All @@ -92,11 +88,11 @@ impl Database for PostgresDatabase {
Ok(())
}

async fn get_user(&self, _user_id: &str) -> Result<User, Box<dyn Error>> {
Err("Not implemented".into())
async fn get_user(&self, _user_id: &str) -> Result<User> {
unimplemented!()
}

async fn update_email(&self, email: &str, user_id: &str) -> Result<(), Box<dyn Error>> {
async fn update_email(&self, email: &str, user_id: &str) -> Result<()> {
let query = "UPDATE users SET email = $1 WHERE uuid = $2";

sqlx::query(query)
Expand All @@ -108,28 +104,23 @@ impl Database for PostgresDatabase {
Ok(())
}

async fn update_nickname(&self, _nickname: &str) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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<dyn Error>> {
Err("Not implemented".into())
async fn disable_user(&self, _user_id: &str) -> Result<()> {
unimplemented!()
}
async fn enable_user(&self, _user_id: &str) -> Result<(), Box<dyn Error>> {
Err("Not implemented".into())
async fn enable_user(&self, _user_id: &str) -> Result<()> {
unimplemented!()
}

async fn get_media_items(&self, _user_id: &str) -> Result<Vec<MediaItem>, Box<dyn Error>> {
Err("Not implemented".into())
async fn get_media_items(&self, _user_id: &str) -> Result<Vec<MediaItem>> {
unimplemented!()
}

/// Creates a new media item if it doesn't exist and returns the media_id
Expand All @@ -138,7 +129,7 @@ impl Database for PostgresDatabase {
user_id: &str,
name: &str,
date_taken: OffsetDateTime,
) -> Result<String, Box<dyn Error>> {
) -> Result<String> {
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?;
Expand All @@ -164,31 +155,23 @@ impl Database for PostgresDatabase {

Ok("".to_string())
}
async fn get_media_item(&self, _media_id: &str) -> Result<MediaItem, Box<dyn Error>> {
Err("Not implemented".into())
async fn get_media_item(&self, _media_id: &str) -> Result<MediaItem> {
unimplemented!()
}
async fn add_reference(
&self,
_user_id: &str,
_media_id: &str,
_reference: &Reference,
) -> Result<String, Box<dyn Error>> {
Err("Not implemented".into())
) -> Result<String> {
unimplemented!()
}

async fn update_reference(
&self,
_reference_id: &str,
_reference: &Reference,
) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
Err("Not implemented".into())
async fn remove_reference(&self, _media_id: &str, _reference_id: &str) -> Result<()> {
unimplemented!()
}
}
Loading
Loading