Skip to content

Commit

Permalink
E05 - RpcRouter, TimeStamp, ReqStamp
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremychone committed Dec 10, 2023
1 parent 546f4f2 commit 0d5049c
Show file tree
Hide file tree
Showing 34 changed files with 1,197 additions and 236 deletions.
4 changes: 4 additions & 0 deletions crates/libs/lib-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ lib-utils = { path = "../../libs/lib-utils"}
lib-auth = { path = "../../libs/lib-auth"}
# -- Async
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
# -- Json
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand All @@ -29,6 +30,9 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# -- Others
uuid = {version = "1", features = ["v4","fast-rng",]}
time = {version = "0.3", features = ["formatting", "parsing", "serde"]}
strum_macros = "0.25"
enum_dispatch = "0.3"
derive_more = {version = "1.0.0-beta", features = ["from"] }


Expand Down
18 changes: 18 additions & 0 deletions crates/libs/lib-core/src/_dev_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod dev_db;

use crate::ctx::Ctx;
use crate::model::project::{ProjectBmc, ProjectForCreate};
use crate::model::task::{Task, TaskBmc, TaskForCreate};
use crate::model::{self, ModelManager};
use tokio::sync::OnceCell;
Expand Down Expand Up @@ -37,9 +38,25 @@ pub async fn init_test() -> ModelManager {
mm.clone()
}

pub async fn seed_project(
ctx: &Ctx,
mm: &ModelManager,
name: &str,
) -> model::Result<i64> {
ProjectBmc::create(
ctx,
mm,
ProjectForCreate {
name: name.to_string(),
},
)
.await
}

pub async fn seed_tasks(
ctx: &Ctx,
mm: &ModelManager,
project_id: i64,
titles: &[&str],
) -> model::Result<Vec<Task>> {
let mut tasks = Vec::new();
Expand All @@ -49,6 +66,7 @@ pub async fn seed_tasks(
ctx,
mm,
TaskForCreate {
project_id,
title: title.to_string(),
},
)
Expand Down
2 changes: 1 addition & 1 deletion crates/libs/lib-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod config;
pub mod config;
pub mod ctx;
pub mod model;

Expand Down
71 changes: 51 additions & 20 deletions crates/libs/lib-core/src/model/base.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::ctx::Ctx;
use crate::model::ModelManager;
use crate::model::{Error, Result};
use modql::field::HasFields;
use lib_utils::time::now_utc;
use modql::field::{Field, Fields, HasFields};
use modql::filter::{FilterGroups, ListOptions};
use modql::SIden;
use sea_query::{
Expand All @@ -11,14 +12,22 @@ use sea_query_binder::SqlxBinder;
use sqlx::postgres::PgRow;
use sqlx::FromRow;

const LIST_LIMIT_DEFAULT: i64 = 300;
const LIST_LIMIT_MAX: i64 = 1000;
const LIST_LIMIT_DEFAULT: i64 = 1000;
const LIST_LIMIT_MAX: i64 = 5000;

#[derive(Iden)]
pub enum CommonIden {
Id,
}

#[derive(Iden)]
pub enum TimestampIden {
Cid,
Ctime,
Mid,
Mtime,
}

pub trait DbBmc {
const TABLE: &'static str;

Expand All @@ -27,10 +36,9 @@ pub trait DbBmc {
}
}

pub fn finalize_list_options(
pub fn compute_list_options(
list_options: Option<ListOptions>,
) -> Result<ListOptions> {
// When Some, validate limit
if let Some(mut list_options) = list_options {
// Validate the limit.
if let Some(limit) = list_options.limit {
Expand All @@ -57,15 +65,16 @@ pub fn finalize_list_options(
}
}

pub async fn create<MC, E>(_ctx: &Ctx, mm: &ModelManager, data: E) -> Result<i64>
pub async fn create<MC, E>(ctx: &Ctx, mm: &ModelManager, data: E) -> Result<i64>
where
MC: DbBmc,
E: HasFields,
{
let db = mm.db();

// -- Prep data
let fields = data.not_none_fields();
// -- Extract fields (name / sea-query value expression)
let mut fields = data.not_none_fields();
add_timestamps_for_create(&mut fields, ctx.user_id());
let (columns, sea_values) = fields.for_sea_insert();

// -- Build query
Expand Down Expand Up @@ -116,7 +125,7 @@ where
pub async fn list<MC, E, F>(
_ctx: &Ctx,
mm: &ModelManager,
filters: Option<F>,
filter: Option<F>,
list_options: Option<ListOptions>,
) -> Result<Vec<E>>
where
Expand All @@ -127,22 +136,21 @@ where
{
let db = mm.db();

// -- Build query
// -- Build the query
let mut query = Query::select();
query.from(MC::table_ref()).columns(E::field_column_refs());

// condition from filter
if let Some(filters) = filters {
let filters: FilterGroups = filters.into();
if let Some(filter) = filter {
let filters: FilterGroups = filter.into();
let cond: Condition = filters.try_into()?;
query.cond_where(cond);
}

// list options
let list_options = finalize_list_options(list_options)?;
let list_options = compute_list_options(list_options)?;
list_options.apply_to_sea_query(&mut query);

// -- Exec query
// -- Execute the query
let (sql, values) = query.build_sqlx(PostgresQueryBuilder);
let entities = sqlx::query_as_with::<_, E, _>(&sql, values)
.fetch_all(db)
Expand All @@ -152,7 +160,7 @@ where
}

pub async fn update<MC, E>(
_ctx: &Ctx,
ctx: &Ctx,
mm: &ModelManager,
id: i64,
data: E,
Expand All @@ -163,8 +171,8 @@ where
{
let db = mm.db();

// -- Prep data
let fields = data.not_none_fields();
let mut fields = data.not_none_fields();
add_timestamps_for_update(&mut fields, ctx.user_id());
let fields = fields.for_sea_update();

// -- Build query
Expand All @@ -174,7 +182,7 @@ where
.values(fields)
.and_where(Expr::col(CommonIden::Id).eq(id));

// -- Exec query
// -- Execute query
let (sql, values) = query.build_sqlx(PostgresQueryBuilder);
let count = sqlx::query_with(&sql, values)
.execute(db)
Expand Down Expand Up @@ -204,7 +212,7 @@ where
.from_table(MC::table_ref())
.and_where(Expr::col(CommonIden::Id).eq(id));

// -- Exec query
// -- Execute query
let (sql, values) = query.build_sqlx(PostgresQueryBuilder);
let count = sqlx::query_with(&sql, values)
.execute(db)
Expand All @@ -221,3 +229,26 @@ where
Ok(())
}
}

// region: --- Utils

/// Update the timestamps info for create
/// (e.g., cid, ctime, and mid, mtime will be updated with the same values)
pub fn add_timestamps_for_create(fields: &mut Fields, user_id: i64) {
let now = now_utc();
fields.push(Field::new(TimestampIden::Cid.into_iden(), user_id.into()));
fields.push(Field::new(TimestampIden::Ctime.into_iden(), now.into()));

fields.push(Field::new(TimestampIden::Mid.into_iden(), user_id.into()));
fields.push(Field::new(TimestampIden::Mtime.into_iden(), now.into()));
}

/// Update the timestamps info only for update.
/// (.e.g., only mid, mtime will be udpated)
pub fn add_timestamps_for_update(fields: &mut Fields, user_id: i64) {
let now = now_utc();
fields.push(Field::new(TimestampIden::Mid.into_iden(), user_id.into()));
fields.push(Field::new(TimestampIden::Mtime.into_iden(), now.into()));
}

// endregion: --- Utils
4 changes: 2 additions & 2 deletions crates/libs/lib-core/src/model/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ pub enum Error {

// -- Externals
#[from]
Sqlx(#[serde_as(as = "DisplayFromStr")] sqlx::Error),
#[from]
SeaQuery(#[serde_as(as = "DisplayFromStr")] sea_query::error::Error),
#[from]
Sqlx(#[serde_as(as = "DisplayFromStr")] sqlx::Error),
#[from]
ModqlIntoSea(#[serde_as(as = "DisplayFromStr")] modql::filter::IntoSeaError),
}

Expand Down
2 changes: 2 additions & 0 deletions crates/libs/lib-core/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

mod base;
mod error;
pub mod modql_utils;
pub mod project;
mod store;
pub mod task;
pub mod user;
Expand Down
7 changes: 7 additions & 0 deletions crates/libs/lib-core/src/model/modql_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use time::serde::rfc3339;

pub fn time_to_sea_value(
json_value: serde_json::Value,
) -> modql::filter::SeaResult<sea_query::Value> {
Ok(rfc3339::deserialize(json_value)?.into())
}
119 changes: 119 additions & 0 deletions crates/libs/lib-core/src/model/project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use crate::ctx::Ctx;
use crate::model::base::{self, DbBmc};
use crate::model::modql_utils::time_to_sea_value;
use crate::model::ModelManager;
use crate::model::Result;
use lib_utils::time::Rfc3339;
use modql::field::Fields;
use modql::filter::{FilterNodes, OpValsString, OpValsValue};
use modql::filter::{ListOptions, OpValsInt64};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use sqlx::types::time::OffsetDateTime;
use sqlx::FromRow;

// region: --- Project Types
#[serde_as]
#[derive(Debug, Clone, Fields, FromRow, Serialize)]
pub struct Project {
pub id: i64,

pub owner_id: i64,
pub name: String,

// -- Timestamps
// (creator and last modified user_id/time)
pub cid: i64,
#[serde_as(as = "Rfc3339")]
pub ctime: OffsetDateTime,
pub mid: i64,
#[serde_as(as = "Rfc3339")]
pub mtime: OffsetDateTime,
}

#[derive(Fields, Deserialize)]
pub struct ProjectForCreate {
pub name: String,
}

#[derive(Fields, Deserialize)]
pub struct ProjectForUpdate {
pub name: Option<String>,
pub owner_id: Option<i64>,
}

/// The `ProjectForCreateInner` contains all necessary properties
/// for a database insert.
/// NOTE: In this design, `project.owner_id` is intrinsic to the
/// `ProjectCreate` action, and should not be exposed to the API.
/// It must be respected in rights by referencing the user initiating the action.
/// Hence, in this scenario, we differentiate between `ProjectForCreate` (the public data structure)
/// and `ProjectForCreateInner` (the representation of the data to be executed, i.e., inserted).
/// (e.g., `owner_id` which is a db required field)
#[derive(Fields)]
struct ProjectForCreateInner {
pub name: String,
pub owner_id: i64,
}

#[derive(FilterNodes, Default, Deserialize)]
pub struct ProjectFilter {
id: Option<OpValsInt64>,
name: Option<OpValsString>,

cid: Option<OpValsInt64>,
#[modql(to_sea_value_fn = "time_to_sea_value")]
ctime: Option<OpValsValue>,
mid: Option<OpValsInt64>,
#[modql(to_sea_value_fn = "time_to_sea_value")]
mtime: Option<OpValsValue>,
}
// endregion: --- Project Types

// region: --- ProjectBmc
pub struct ProjectBmc;

impl DbBmc for ProjectBmc {
const TABLE: &'static str = "project";
}

impl ProjectBmc {
pub async fn create(
ctx: &Ctx,
mm: &ModelManager,
project_c: ProjectForCreate,
) -> Result<i64> {
let project_c = ProjectForCreateInner {
name: project_c.name,
owner_id: ctx.user_id(),
};
base::create::<Self, _>(ctx, mm, project_c).await
}

pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i64) -> Result<Project> {
base::get::<Self, _>(ctx, mm, id).await
}

pub async fn list(
ctx: &Ctx,
mm: &ModelManager,
filter: Option<Vec<ProjectFilter>>,
list_options: Option<ListOptions>,
) -> Result<Vec<Project>> {
base::list::<Self, _, _>(ctx, mm, filter, list_options).await
}

pub async fn update(
ctx: &Ctx,
mm: &ModelManager,
id: i64,
project_u: ProjectForUpdate,
) -> Result<()> {
base::update::<Self, _>(ctx, mm, id, project_u).await
}

pub async fn delete(ctx: &Ctx, mm: &ModelManager, id: i64) -> Result<()> {
base::delete::<Self>(ctx, mm, id).await
}
}
// endregion: --- ProjectBmc
Loading

0 comments on commit 0d5049c

Please sign in to comment.