Update global database connection

This commit is contained in:
fluo10 2025-05-12 08:49:51 +09:00
parent 47da8152e6
commit 7f8000ef42
16 changed files with 415 additions and 58 deletions

2
Cargo.lock generated
View file

@ -2287,6 +2287,7 @@ dependencies = [
"iana-time-zone",
"log",
"sea-orm",
"sea-orm-migration",
"serde",
"thiserror 2.0.12",
"tokio",
@ -2313,6 +2314,7 @@ dependencies = [
"clap",
"progress-pile-core",
"rand 0.9.1",
"sea-orm",
"serde",
"thiserror 2.0.12",
"tokio",

View file

@ -20,19 +20,23 @@ mod tests {
use super::*;
use chrono::Local;
use sea_orm::entity::*;
use progress_pile_core::global::GlobalDatabase;
use sea_orm::{entity::*, DatabaseConnection};
use progress_pile_migration::{ClientMigrator, MigratorTrait};
use uuid::Uuid;
use crate::database_connection::DATABASE_CONNECTION;
use crate::error::Error;
async fn get_or_try_init_test_database() -> Result<&'static DatabaseConnection, Error> {
Ok(GlobalDatabase::get_or_try_init("sqlite::memory:", ClientMigrator).await?)
}
#[tokio::test]
async fn check_insert_entity() {
let db = DATABASE_CONNECTION.get_or_init("sqlite::memory:").await;
ClientMigrator::up(db, None).await.unwrap();
let local_date_time = Local::now();
let offset_date_time = local_date_time.with_timezone(local_date_time.offset());
let db = get_or_try_init_test_database().await.unwrap();
let category = ProgressCategoryActiveModel{
name: Set("test_category".to_owned()),
..ProgressCategoryActiveModel::new()
@ -42,8 +46,10 @@ mod tests {
progress_category_id: Set(category.id),
..ProgressEntryActiveModel::new()
}.insert(db).await.unwrap();
ClientMigrator::reset(db).await.unwrap();
//db.clone().close().await.unwrap();
}
#[tokio::test]
async fn connect_database () {
let db = get_or_try_init_test_database().await.unwrap();
assert!(db.ping().await.is_ok());
}
}

View file

@ -2,7 +2,9 @@ pub mod auth;
pub mod config;
pub mod entity;
pub use progress_pile_core::*;
pub use progress_pile_core::{
error,
};

View file

@ -8,6 +8,8 @@ repository.workspace = true
[features]
default = ["clap",]
clap = ["dep:clap"]
postgres = ["sea-orm/sqlx-postgres", "sea-orm/sqlx-postgres"]
server = ["clap", "postgres"]
[dependencies]
async-graphql.workspace = true
@ -21,6 +23,7 @@ dotenv = {workspace = true}
iana-time-zone = "0.1.63"
log = "0.4.27"
sea-orm.workspace = true
sea-orm-migration.workspace = true
serde.workspace = true
thiserror.workspace = true
tokio.workspace = true

View file

@ -1,46 +0,0 @@
use crate::config::{
DATABASE_CONFIG,
DatabaseConfig
};
use std::time::Duration;
use sea_orm::{entity::*, query::*, ConnectOptions, Database, DatabaseConnection};
use tokio::sync::OnceCell;
pub struct OnceDatabaseConnection {
inner: OnceCell<DatabaseConnection>,
}
impl OnceDatabaseConnection {
const fn new() -> Self {
Self {
inner: OnceCell::const_new(),
}
}
pub fn get(&self) -> Option<&DatabaseConnection> {
self.inner.get()
}
pub async fn get_or_init<T>(&self, c: T) -> &DatabaseConnection where
T: Into<ConnectOptions>
{
self.inner.get_or_init(|| async {
Database::connect(c).await.unwrap()
}).await
}
}
pub static DATABASE_CONNECTION: OnceDatabaseConnection = OnceDatabaseConnection::new();
#[cfg(test)]
mod tests {
use super::*;
//#[tokio::test]
async fn check_database_connection() {
DATABASE_CONNECTION.get_or_init("sqlite::memory:").await;
let db = DATABASE_CONNECTION.get().unwrap();
assert!(db.ping().await.is_ok());
}
}

View file

View file

@ -0,0 +1,63 @@
use crate::error::Error;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use sea_orm_migration::MigratorTrait;
use tokio::sync::OnceCell;
/*
pub struct OnceDatabaseConnection {
inner: OnceCell<DatabaseConnection>,
}
impl OnceDatabaseConnection {
const fn new() -> Self {
Self {
inner: OnceCell::const_new(),
}
}
pub fn get(&self) -> Option<&DatabaseConnection> {
self.inner.get()
}
pub async fn get_or_try_init<T, U>(&self, options: T, _: U) -> Result<&DatabaseConnection, Error> where
T: Into<ConnectOptions>,
U: MigratorTrait
{
self.inner.get_or_try_init(|| async {
let db = Database::connect(options).await?;
U::up(&db, None).await?;
Ok(db)
}).await
}
}
pub static DATABASE_CONNECTION: OnceDatabaseConnection = OnceDatabaseConnection::new();
*/
pub static DATABASE_CONNECTION: OnceCell<DatabaseConnection> = OnceCell::const_new();
pub struct GlobalDatabase;
impl GlobalDatabase {
pub fn get() -> Option<&'static DatabaseConnection> {
DATABASE_CONNECTION.get()
}
pub async fn get_or_try_init<T, U>(options: T, _: U) -> Result<&'static DatabaseConnection, Error> where
T: Into<ConnectOptions>,
U: MigratorTrait
{
DATABASE_CONNECTION.get_or_try_init(|| async {
let db = Database::connect(options).await?;
U::up(&db, None).await?;
Ok(db)
}).await
}
}
#[cfg(test)]
mod tests {
use super::*;
}

View file

@ -0,0 +1,4 @@
pub mod config;
mod database;
pub use database::GlobalDatabase;

View file

@ -1,8 +1,8 @@
pub mod config;
pub mod csv;
pub mod database_connection;
pub mod entity;
pub mod error;
pub mod global;
pub mod graphql;
#[cfg(test)]

View file

@ -12,6 +12,7 @@ axum = "0.8.4"
clap = {workspace = true, features = ["derive"]}
progress-pile-core = {workspace = true}
chrono = {workspace = true}
sea-orm = { workspace = true, features = ["sqlx-postgres"] }
serde.workspace = true
thiserror.workspace = true
tokio.workspace = true

View file

@ -0,0 +1,55 @@
use core::time;
use async_graphql::*;
use chrono::Local;
use sea_orm::entity::{
Set,
prelude::*
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)]
#[sea_orm(table_name = "access_token")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(indexed)]
pub user_id: i32,
#[sea_orm(indexed)]
pub token_value: String,
pub note: String,
#[sea_orm(indexed)]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(indexed)]
pub updated_at: DateTimeWithTimeZone,
#[sea_orm(indexed)]
pub expired_at: Option<DateTimeWithTimeZone>,
}
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id"
)]
User,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModel {
pub fn new() -> Self {
let timestamp = Local::now().fixed_offset();
Self{
note: Set("".to_string()),
created_at: Set(timestamp),
updated_at: Set(timestamp),
..Default::default()
}
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,112 @@
mod access_token;
mod progress_category;
mod progress_entry;
mod user;
pub use access_token::{
ActiveModel as AccessTokenActiveModel,
Column as AccessTokenColumn,
Entity as AccessTokenEntity,
Model as AccessTokenModel,
};
pub use progress_category::{
ActiveModel as ProgressCategoryActiveModel,
Column as ProgressCategoryColumn,
Entity as ProgressCategoryEntity,
Model as ProgressCategoryModel,
};
pub use progress_entry::{
ActiveModel as ProgressEntryActiveModel,
Column as ProgressEntryColumn,
Entity as ProgressEntryEntity,
Model as ProgressEntryModel,
};
pub use user::{
ActiveModel as UserActiveModel,
Column as UserColumn,
Entity as UserEntity,
Model as UserModel,
};
pub use progress_pile_core::entity::*;
#[cfg(test)]
mod tests {
use super::*;
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::*;
#[tokio::test]
async fn check_database_connection() {
DATABASE_CONNECTION.init_test().await;
let db = DATABASE_CONNECTION.get().unwrap();
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 local_date_time = Local::now();
let offset_date_time = local_date_time.with_timezone(local_date_time.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();
let record_tag = RecordTagActiveModel{
user_id: Set(user.id),
name: Set("test".to_owned()),
..Default::default()
}.insert(db)
.await.unwrap();
let record_header = RecordHeaderActiveModel{
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()),
..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),
..Default::default()
}.insert(db)
.await.unwrap();
Migrator::reset(db).await.unwrap();
db.clone().close().await.unwrap();
}
}

View file

@ -0,0 +1,43 @@
use async_graphql::*;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)]
#[sea_orm(table_name = "record_tag")]
#[graphql(concrete(name = "RecordTag", params()))]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
#[sea_orm(indexed)]
#[serde(skip_deserializing)]
pub user_id: i32,
#[sea_orm(indexed)]
pub name: String,
}
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
pub enum Relation {
#[sea_orm(has_many = "super::record_detail::Entity")]
RecordDetail,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id"
)]
User,
}
impl Related<super::record_detail::Entity> for Entity {
fn to() -> RelationDef {
Relation::RecordDetail.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,46 @@
use async_graphql::*;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)]
#[sea_orm(table_name = "record_detail")]
#[graphql(concrete(name = "RecordDetail", params()))]
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,
}
#[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"
)]
RecordHeader,
#[sea_orm(
belongs_to = "super::record_tag::Entity",
from = "Column::RecordTagId",
to = "super::record_tag::Column::Id"
)]
RecordTag,
}
impl Related<super::record_header::Entity> for Entity {
fn to() -> RelationDef {
Relation::RecordHeader.def()
}
}
impl Related<super::record_tag::Entity> for Entity {
fn to() -> RelationDef {
Relation::RecordTag.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,65 @@
use async_graphql::SimpleObject;
use chrono::{DateTime, FixedOffset, Local,};
use sea_orm::{entity::prelude::*, ActiveValue::Set};
use serde::{Deserialize, Serialize};
use crate::error::Error;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, SimpleObject, Deserialize)]
#[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,
pub password_hash: String,
#[sea_orm(indexed)]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(indexed)]
pub updated_at: DateTimeWithTimeZone,
#[sea_orm(indexed)]
pub deleted_at: Option<DateTimeWithTimeZone>,
}
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
pub enum Relation {
#[sea_orm(has_many = "super::access_token::Entity")]
AccessToken,
#[sea_orm(has_many = "super::progress_category::Entity")]
ProgressCategory,
#[sea_orm(has_many = "super::progress_entry::Entity")]
ProgressEntry,
}
impl Related<super::access_token::Entity> for Model {
fn to() -> RelationDef {
Relation::AccessToken.def()
}
}
impl Related<super::progress_category::Entity> for Model {
fn to() -> RelationDef {
Relation::ProgressCategory.def()
}
}
impl Related<super::progress_entry::Entity> for Model {
fn to() -> RelationDef {
Relation::ProgressEntry.def()
}
}
impl ActiveModel {
pub fn new() -> Self {
let timestamp = Local::now().fixed_offset();
Self {
created_at: Set(timestamp),
updated_at: Set(timestamp),
..Default::default()
}
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -1,6 +1,7 @@
mod args;
mod auth;
mod config;
pub mod entity;
pub mod error;
pub mod graphql;