From 43f36b81a93aa063d681f296a0864aad321723d4 Mon Sep 17 00:00:00 2001 From: fluo10 Date: Tue, 13 May 2025 08:54:15 +0900 Subject: [PATCH] Implement server entity --- Cargo.lock | 3 + Cargo.toml | 1 + progress-pile-client/Cargo.toml | 2 +- progress-pile-core/src/error.rs | 2 + progress-pile-server/Cargo.toml | 5 ++ .../src/entity/access_token.rs | 2 +- progress-pile-server/src/entity/mod.rs | 54 +++++--------- .../src/entity/progress_category.rs | 45 ++++++++---- .../src/entity/progress_entry.rs | 73 +++++++++++++------ progress-pile-server/src/entity/user.rs | 4 +- progress-pile-server/src/error.rs | 14 ---- progress-pile-server/src/global/database.rs | 57 +++++++++++++++ progress-pile-server/src/global/mod.rs | 15 ++++ progress-pile-server/src/graphql/mutation.rs | 3 +- progress-pile-server/src/graphql/query.rs | 8 +- progress-pile-server/src/lib.rs | 4 +- 16 files changed, 189 insertions(+), 103 deletions(-) delete mode 100644 progress-pile-server/src/error.rs create mode 100644 progress-pile-server/src/global/database.rs create mode 100644 progress-pile-server/src/global/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b428e24..177e2ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2314,12 +2314,15 @@ dependencies = [ "chrono", "clap", "progress-pile-core", + "progress-pile-migration", "rand 0.9.1", "sea-orm", "serde", + "tempfile", "thiserror 2.0.12", "tokio", "toml", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7439aec..2f9dd96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ progress-pile-core = { path = "progress-pile-core" } progress-pile-migration = { path = "progress-pile-migration", default-features = false } progress-pile-server.path = "progress-pile-server" serde = { version = "1.0", features = ["derive"] } +tempfile = "3.20.0" thiserror = "2.0.12" tokio = "1.44.2" toml = "0.8.22" diff --git a/progress-pile-client/Cargo.toml b/progress-pile-client/Cargo.toml index 84adc71..987f64c 100644 --- a/progress-pile-client/Cargo.toml +++ b/progress-pile-client/Cargo.toml @@ -23,4 +23,4 @@ toml.workspace = true uuid.workspace = true [dev-dependencies] -tempfile = "3.20.0" +tempfile.workspace = true diff --git a/progress-pile-core/src/error.rs b/progress-pile-core/src/error.rs index 05657ca..da0ec2d 100644 --- a/progress-pile-core/src/error.rs +++ b/progress-pile-core/src/error.rs @@ -4,6 +4,8 @@ pub enum Error { Io(#[from] std::io::Error), #[error("Parse int error")] ParseInt(#[from] std::num::ParseIntError), + #[error("Password hash error")] + PasswordHash(String), #[error("Deserialize toml error")] TomlDe(#[from] toml::de::Error), #[error("Serialize toml error")] diff --git a/progress-pile-server/Cargo.toml b/progress-pile-server/Cargo.toml index ee61c65..475b00b 100644 --- a/progress-pile-server/Cargo.toml +++ b/progress-pile-server/Cargo.toml @@ -11,11 +11,16 @@ async-graphql.workspace = true axum = "0.8.4" clap = {workspace = true, features = ["derive"]} progress-pile-core = {workspace = true} +progress-pile-migration = { workspace = true, features = ["server"] } chrono = {workspace = true} sea-orm = { workspace = true, features = ["sqlx-postgres"] } serde.workspace = true thiserror.workspace = true tokio.workspace = true toml.workspace = true +uuid.workspace = true rand = "0.9.1" async-graphql-axum = "7.0.16" + +[dev-dependencies] +tempfile.workspace = true \ No newline at end of file diff --git a/progress-pile-server/src/entity/access_token.rs b/progress-pile-server/src/entity/access_token.rs index 5c3d53e..1dfd2c1 100644 --- a/progress-pile-server/src/entity/access_token.rs +++ b/progress-pile-server/src/entity/access_token.rs @@ -8,7 +8,7 @@ use sea_orm::entity::{ }; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel,)] #[sea_orm(table_name = "access_token")] pub struct Model { #[sea_orm(primary_key)] diff --git a/progress-pile-server/src/entity/mod.rs b/progress-pile-server/src/entity/mod.rs index 6582504..9e7685e 100644 --- a/progress-pile-server/src/entity/mod.rs +++ b/progress-pile-server/src/entity/mod.rs @@ -45,68 +45,48 @@ mod tests { use std::time::Duration; use chrono::{offset, FixedOffset, Local, TimeZone}; use sea_orm::{entity::*, query::*, ConnectOptions, Database}; - use progress_pile_migration_server::{Migrator, MigratorTrait}; - use crate::entity::*; + use progress_pile_migration::{ServerMigrator, MigratorTrait}; + use crate::{entity::*, global::GLOBAL}; #[tokio::test] async fn check_database_connection() { - DATABASE_CONNECTION.init_test().await; - let db = DATABASE_CONNECTION.get().unwrap(); + + let db = GLOBAL.get_or_init_temporary_database().await; assert!(db.ping().await.is_ok()); } #[tokio::test] async fn check_insert_entity() { - DATABASE_CONNECTION.init_test().await; - let db = DATABASE_CONNECTION.get().unwrap(); + let db = GLOBAL.get_or_init_temporary_database().await; - - let local_date_time = Local::now(); - let offset_date_time = local_date_time.with_timezone(local_date_time.offset()); + let timestamp = Local::now().fixed_offset(); let user = UserActiveModel{ login_name: Set("admin".to_owned()), password_hash: Set("admin".to_owned()), - created_at: Set(offset_date_time), - updated_at: Set(offset_date_time), - ..Default::default() - }.insert(db) - .await.unwrap(); + created_at: Set(timestamp), + updated_at: Set(timestamp), + ..UserActiveModel::new() + }.insert(db).await.unwrap(); - let record_tag = RecordTagActiveModel{ + let access_token = AccessTokenActiveModel{ user_id: Set(user.id), - name: Set("test".to_owned()), ..Default::default() - }.insert(db) - .await.unwrap(); + }.insert(db).await.unwrap(); - let record_header = RecordHeaderActiveModel{ + let progress_category = ProgressCategoryActiveModel{ user_id: Set(user.id), - created_at: Set(offset_date_time), - updated_at: Set(offset_date_time), - recorded_at: Set(offset_date_time), - comment: Set("".to_owned()), + name: Set("test_category".to_string()), ..Default::default() }.insert(db) .await.unwrap(); - RecordDetailActiveModel { - record_header_id: Set(record_header.id), - record_tag_id: Set(record_tag.id), - count: Set(1), - ..Default::default() - }.insert(db) - .await.unwrap(); - RecordDetailActiveModel { - record_header_id: Set(record_header.id), - record_tag_id: Set(record_tag.id), - count: Set(2), + ProgressEntryActiveModel { + user_id: Set(user.id), + progress_category_id: Set(progress_category.id), ..Default::default() }.insert(db) .await.unwrap(); - - Migrator::reset(db).await.unwrap(); - db.clone().close().await.unwrap(); } } \ No newline at end of file diff --git a/progress-pile-server/src/entity/progress_category.rs b/progress-pile-server/src/entity/progress_category.rs index 8c5708d..25501c1 100644 --- a/progress-pile-server/src/entity/progress_category.rs +++ b/progress-pile-server/src/entity/progress_category.rs @@ -1,25 +1,29 @@ use async_graphql::*; -use sea_orm::entity::prelude::*; +use chrono::Local; +use sea_orm::{entity::prelude::*, ActiveValue::Set}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)] -#[sea_orm(table_name = "record_tag")] -#[graphql(concrete(name = "RecordTag", params()))] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel,)] +#[sea_orm(table_name = "progress_category")] pub struct Model { - #[sea_orm(primary_key)] - #[serde(skip_deserializing)] - pub id: i32, - #[sea_orm(indexed)] - #[serde(skip_deserializing)] + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] pub user_id: i32, #[sea_orm(indexed)] pub name: String, + #[sea_orm(indexed)] + pub created_at: DateTimeWithTimeZone, + #[sea_orm(indexed)] + pub updated_at: DateTimeWithTimeZone, + #[sea_orm(indexed)] + pub deleted_at: Option, } #[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] pub enum Relation { - #[sea_orm(has_many = "super::record_detail::Entity")] - RecordDetail, + #[sea_orm(has_many = "super::progress_entry::Entity")] + ProgressEntry, #[sea_orm( belongs_to = "super::user::Entity", from = "Column::UserId", @@ -27,10 +31,10 @@ pub enum Relation { )] User, } - -impl Related for Entity { + +impl Related for Entity { fn to() -> RelationDef { - Relation::RecordDetail.def() + Relation::ProgressEntry.def() } } @@ -39,5 +43,16 @@ impl Related for Entity { Relation::User.def() } } +impl ActiveModelBehavior for ActiveModel {} -impl ActiveModelBehavior for ActiveModel {} \ No newline at end of file +impl ActiveModel { + pub fn new() -> Self { + let timestamp: DateTimeWithTimeZone = Local::now().fixed_offset(); + Self{ + id: Set(Uuid::new_v4()), + created_at: Set(timestamp), + updated_at: Set(timestamp), + ..Default::default() + } + } +} \ No newline at end of file diff --git a/progress-pile-server/src/entity/progress_entry.rs b/progress-pile-server/src/entity/progress_entry.rs index 18c20f5..d834bc4 100644 --- a/progress-pile-server/src/entity/progress_entry.rs +++ b/progress-pile-server/src/entity/progress_entry.rs @@ -1,46 +1,71 @@ use async_graphql::*; -use sea_orm::entity::prelude::*; +use chrono::Local; +use sea_orm::{entity::prelude::*, ActiveValue::Set}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)] -#[sea_orm(table_name = "record_detail")] -#[graphql(concrete(name = "RecordDetail", params()))] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "progress_entry")] pub struct Model { - #[sea_orm(primary_key)] - #[serde(skip_deserializing)] - pub record_header_id: i32, - #[sea_orm(primary_key)] - #[serde(skip_deserializing)] - pub record_tag_id: i32, - pub count: i32, + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub user_id: i32, + #[sea_orm(indexed)] + pub progress_category_id: Uuid, + #[sea_orm(indexed)] + pub created_at: DateTimeWithTimeZone, + #[sea_orm(indexed)] + pub updated_at: DateTimeWithTimeZone, + #[sea_orm(indexed)] + pub deleted_at: Option, + #[sea_orm(indexed)] + pub progressed_at: DateTimeWithTimeZone, + pub quantity: i32, + pub note: String, } #[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] pub enum Relation { #[sea_orm( - belongs_to = "super::record_header::Entity", - from = "Column::RecordHeaderId", - to = "super::record_header::Column::Id" + belongs_to = "super::progress_category::Entity", + from = "Column::ProgressCategoryId", + to = "super::progress_category::Column::Id" )] - RecordHeader, + ProgressCategory, #[sea_orm( - belongs_to = "super::record_tag::Entity", - from = "Column::RecordTagId", - to = "super::record_tag::Column::Id" + belongs_to = "super::user::Entity", + from = "Column::UserId", + to = "super::user::Column::Id" )] - RecordTag, + User, } -impl Related for Entity { + +impl Related for Entity { fn to() -> RelationDef { - Relation::RecordHeader.def() + Relation::ProgressCategory.def() } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { - Relation::RecordTag.def() + Relation::User.def() } } -impl ActiveModelBehavior for ActiveModel {} \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} + +impl ActiveModel { + pub fn new() -> Self { + let timestamp: DateTimeWithTimeZone = Local::now().fixed_offset(); + Self{ + id: Set(Uuid::new_v4()), + created_at: Set(timestamp), + updated_at: Set(timestamp), + progressed_at: Set(timestamp), + quantity: Set(1), + note: Set("".to_string()), + ..Default::default() + } + } +} \ No newline at end of file diff --git a/progress-pile-server/src/entity/user.rs b/progress-pile-server/src/entity/user.rs index 5a44888..7f02d45 100644 --- a/progress-pile-server/src/entity/user.rs +++ b/progress-pile-server/src/entity/user.rs @@ -4,12 +4,10 @@ use sea_orm::{entity::prelude::*, ActiveValue::Set}; use serde::{Deserialize, Serialize}; use crate::error::Error; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, SimpleObject, Deserialize)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel,)] #[sea_orm(table_name = "user")] -#[graphql(concrete(name = "User", params()))] pub struct Model { #[sea_orm(primary_key)] - #[serde(skip_deserializing)] pub id: i32, #[sea_orm(unique, indexed)] pub login_name: String, diff --git a/progress-pile-server/src/error.rs b/progress-pile-server/src/error.rs deleted file mode 100644 index c1339a2..0000000 --- a/progress-pile-server/src/error.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Uninitialized OnceCell: {0}")] - UninitializedOnceCell(String), - #[error("Parse int error")] - ParseInt(#[from] std::num::ParseIntError), - #[error("Argon2 Password hash error: {0}")] - PasswordHash(String), - #[error("Parse toml error")] - TomlDe(#[from] toml::de::Error), - #[error("Missing config value: ({0})")] - MissingConfig(String) - -} diff --git a/progress-pile-server/src/global/database.rs b/progress-pile-server/src/global/database.rs new file mode 100644 index 0000000..cb451d0 --- /dev/null +++ b/progress-pile-server/src/global/database.rs @@ -0,0 +1,57 @@ +use progress_pile_migration::{ServerMigrator, MigratorTrait}; +use sea_orm::{ConnectOptions, Database, DatabaseConnection}; +use crate::error::Error; +use tokio::sync::OnceCell; +use progress_pile_core::global::GlobalDatabase; + +use super::Global; + +impl GlobalDatabase for Global { + fn get_database(&self) -> Option<&DatabaseConnection> { + self.database.get() + } + async fn get_or_try_init_database(&self) -> Result<&DatabaseConnection, Error> { + todo!() + } + + async fn get_or_try_init_database_with_connect_options(&self, options: T) -> Result<&DatabaseConnection, Error> where + T: Into, + { + Ok(self.database.get_or_try_init(|| async { + let db = Database::connect(options).await?; + ServerMigrator::up(&db, None).await?; + Ok::(db) + }).await?) + } +} + +#[cfg(test)] +pub mod tests { + use std::sync::LazyLock; + + use crate::global::GLOBAL; + + use super::*; + + pub static TEST_DATABASE_URL: LazyLock = LazyLock::new(|| { + let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path(); + temp_path.disable_cleanup(true); + let url = "sqlite://".to_string() + temp_path.as_os_str().to_str().unwrap() + "?mode=rwc"; + println!("{}", &url); + url + }); + + impl Global { + pub async fn get_or_init_temporary_database(&self) -> &DatabaseConnection { + + self.get_or_try_init_database_with_connect_options(&*TEST_DATABASE_URL).await.unwrap() + } + } + + #[tokio::test] + async fn connect_database () { + let db = GLOBAL.get_or_init_temporary_database().await; + assert!(db.ping().await.is_ok()); + } + +} \ No newline at end of file diff --git a/progress-pile-server/src/global/mod.rs b/progress-pile-server/src/global/mod.rs new file mode 100644 index 0000000..c6ccff0 --- /dev/null +++ b/progress-pile-server/src/global/mod.rs @@ -0,0 +1,15 @@ +use crate::config::ServerConfig; +use sea_orm::DatabaseConnection; +use tokio::sync::OnceCell; + + +mod database; + +pub static GLOBAL: Global = Global{ + config: OnceCell::const_new(), + database: OnceCell::const_new(), +}; +pub struct Global { + config: OnceCell, + database: OnceCell, +} diff --git a/progress-pile-server/src/graphql/mutation.rs b/progress-pile-server/src/graphql/mutation.rs index 12f1d04..3faacf7 100644 --- a/progress-pile-server/src/graphql/mutation.rs +++ b/progress-pile-server/src/graphql/mutation.rs @@ -1,5 +1,4 @@ use async_graphql::*; -use progress_pile_core::entity::UserModel; use crate::{auth::try_hash_password, entity::UserActiveModel}; @@ -10,7 +9,7 @@ impl Mutation { async fn login(&self, username:String, password: String) -> Result { todo!() } - async fn create_user(&self, username:String, password: String) -> Result { + async fn create_user(&self, username:String, password: String) -> Result { todo!() } } \ No newline at end of file diff --git a/progress-pile-server/src/graphql/query.rs b/progress-pile-server/src/graphql/query.rs index e1af075..6f794c8 100644 --- a/progress-pile-server/src/graphql/query.rs +++ b/progress-pile-server/src/graphql/query.rs @@ -13,10 +13,10 @@ pub struct Query; #[Object] impl Query { - pub async fn user(&self, user_name: String) -> Result> { - Ok(UserEntity::find_by_name(&user_name).await?) + pub async fn user(&self, user_name: String) -> Result> { + todo!() } - pub async fn users(&self) -> Result> { - Ok(UserEntity::find_all().await?) + pub async fn users(&self) -> Result> { + todo!() } } \ No newline at end of file diff --git a/progress-pile-server/src/lib.rs b/progress-pile-server/src/lib.rs index dcfcd13..e9de2a2 100644 --- a/progress-pile-server/src/lib.rs +++ b/progress-pile-server/src/lib.rs @@ -2,8 +2,9 @@ mod args; mod auth; mod config; pub mod entity; -pub mod error; +pub mod global; pub mod graphql; +pub use progress_pile_core::error; pub use args::Args; use async_graphql::{EmptySubscription, Schema}; @@ -13,7 +14,6 @@ use async_graphql_axum::{ }; use axum::{routing::get, Router}; use crate::graphql::build_service; -use progress_pile_core::entity as entity; pub fn build_app() -> Router {