Implement SyncableModel derive

This commit is contained in:
fluo10 2025-06-28 08:04:32 +09:00
parent 72c9710283
commit 2b70b130f2
4 changed files with 47 additions and 34 deletions

View file

@ -12,11 +12,13 @@ use crate::data::syncable::*;
#[sea_orm(table_name = "record_deletion")] #[sea_orm(table_name = "record_deletion")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
#[cfg_attr(feature="macros", syncable(uuid))] #[cfg_attr(feature="macros", syncable(id))]
pub id: Uuid, pub id: Uuid,
#[sea_orm(indexed)] #[sea_orm(indexed)]
#[cfg_attr(feature="macros", syncable(timestamp))] #[cfg_attr(feature="macros", syncable(timestamp))]
pub created_at: DateTimeUtc, pub created_at: DateTimeUtc,
#[cfg_attr(feature="macros", syncable(author_id))]
pub created_by: Uuid,
pub table_name: String, pub table_name: String,
pub record_id: Uuid, pub record_id: Uuid,
} }

View file

@ -17,17 +17,17 @@ pub trait SyncableEntity: EntityTrait<
type SyncableActiveModel: SyncableActiveModel<SyncableEntity= Self>; type SyncableActiveModel: SyncableActiveModel<SyncableEntity= Self>;
type SyncableColumn: SyncableColumn; type SyncableColumn: SyncableColumn;
async fn get_updated(from: DateTimeUtc,until: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> { async fn get_updated(from: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find() let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
.filter(Self::SyncableColumn::timestamp_between(from, until)) .filter(Self::SyncableColumn::timestamp_after(from))
.all(db) .all(db)
.await.unwrap(); .await.unwrap();
Ok(result) Ok(result)
} }
async fn get_updated_by_author(from: DateTimeUtc, author: Uuid, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> { async fn get_updated_by(author: Uuid, from: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find() let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
.filter(Self::SyncableColumn::timestamp_between(from, until)) .filter(Self::SyncableColumn::timestamp_after(from))
.filter(Self::SyncableColumn::author_eq(author)) .filter(Self::SyncableColumn::author_id_eq(author))
.all(db) .all(db)
.await.unwrap(); .await.unwrap();
Ok(result) Ok(result)
@ -42,14 +42,14 @@ pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
type SyncableEntity: SyncableEntity<SyncableActiveModel = Self>; type SyncableEntity: SyncableEntity<SyncableActiveModel = Self>;
fn get_id(&self) -> Option<Uuid>; fn get_id(&self) -> Option<Uuid>;
fn get_timestamp(&self) -> Option<DateTimeUtc>; fn get_timestamp(&self) -> Option<DateTimeUtc>;
fn get_author_id(&self) -> Option<DateTimeUtc>; fn get_author_id(&self) -> Option<Uuid>;
fn try_merge(&mut self, other: <Self::SyncableEntity as SyncableEntity>::SyncableModel) -> Result<(), SyncableError> { fn try_merge(&mut self, other: <Self::SyncableEntity as SyncableEntity>::SyncableModel) -> Result<(), SyncableError> {
if self.get_uuid().ok_or(SyncableError::MissingField("uuid"))? != other.get_uuid() { if self.get_id().ok_or(SyncableError::MissingField("uuid"))? != other.get_id() {
return Err(SyncableError::MismatchUuid) return Err(SyncableError::MismatchUuid)
} }
if self.get_timestamp().ok_or(SyncableError::MissingField("updated_at"))? < other.get_timestamp() { if self.get_timestamp().ok_or(SyncableError::MissingField("updated_at"))? < other.get_timestamp() {
for column in <<<Self as ActiveModelTrait>::Entity as EntityTrait>::Column as Iterable>::iter() { for column in <<<Self as ActiveModelTrait>::Entity as EntityTrait>::Column as Iterable>::iter() {
if column.should_sync(){ if column.should_synced(){
self.take(column).set_if_not_equals(other.get(column)); self.take(column).set_if_not_equals(other.get(column));
} }
} }
@ -62,9 +62,9 @@ pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
pub trait SyncableColumn: ColumnTrait { pub trait SyncableColumn: ColumnTrait {
fn is_id(&self) -> bool; fn is_id(&self) -> bool;
fn is_timestamp(&self) -> bool; fn is_timestamp(&self) -> bool;
fn should_sync(&self) -> bool; fn should_synced(&self) -> bool;
fn timestamp_between(from: DateTimeUtc, to: DateTimeUtc) -> SimpleExpr; fn timestamp_after(from: DateTimeUtc) -> SimpleExpr;
fn author_eq(author_id: Uuid) -> SimpleExpr; fn author_id_eq(author_id: Uuid) -> SimpleExpr;
fn is_author_id(&self) -> bool; fn is_author_id(&self) -> bool;
} }

View file

@ -23,11 +23,11 @@ pub fn syncable_model(input: TokenStream) -> TokenStream {
fn get_id(&self) -> Uuid { fn get_id(&self) -> Uuid {
self.#id_snake self.#id_snake
} }
fn get_timestamp() -> DateTimeUtc { fn get_timestamp(&self) -> DateTimeUtc {
self.#timestamp_snake self.#timestamp_snake
} }
fn get_author_id() -> Uuid { fn get_author_id(&self) -> Uuid {
self.#timestamp_snake self.#author_id_snake
} }
} }
impl SyncableEntity for Entity { impl SyncableEntity for Entity {
@ -39,14 +39,15 @@ pub fn syncable_model(input: TokenStream) -> TokenStream {
impl SyncableActiveModel for ActiveModel { impl SyncableActiveModel for ActiveModel {
type SyncableEntity = Entity; type SyncableEntity = Entity;
fn get_id(&self) -> Option<Uuid> { fn get_id(&self) -> Option<Uuid> {
self.#id_snake.into_value() self.#id_snake.try_as_ref().cloned()
} }
fn get_timestamp(&self) -> Option<DateTimeUtc> { fn get_timestamp(&self) -> Option<DateTimeUtc> {
self.#timestamp_snake.into_value() self.#timestamp_snake.try_as_ref().cloned()
} }
fn get_author_id(&self) -> Option<DateTimeUtc> { fn get_author_id(&self) -> Option<Uuid> {
self.#author_id_snake.into_value() self.#author_id_snake.try_as_ref().cloned()
} } }
}
impl SyncableColumn for Column { impl SyncableColumn for Column {
fn is_id(&self) -> bool { fn is_id(&self) -> bool {
matches!(self, Column::#id_camel) matches!(self, Column::#id_camel)
@ -57,12 +58,16 @@ pub fn syncable_model(input: TokenStream) -> TokenStream {
fn is_author_id(&self) -> bool { fn is_author_id(&self) -> bool {
matches!(self, Column::#author_id_camel) matches!(self, Column::#author_id_camel)
} }
fn should_sync(&self) -> bool { fn should_synced(&self) -> bool {
todo!() todo!()
} }
fn timestamp_between(from: DateTimeUtc, until: DateTimeUtc) -> SimpleExpr { fn timestamp_after(timestamp: DateTimeUtc) -> sea_orm::sea_query::expr::SimpleExpr {
todo!() Column::#timestamp_camel.gte(timestamp)
} }
fn author_id_eq(author_id: Uuid) -> sea_orm::sea_query::expr::SimpleExpr {
Column::#author_id_camel.eq(author_id)
}
} }
}; };
output.into() output.into()
@ -70,9 +75,9 @@ pub fn syncable_model(input: TokenStream) -> TokenStream {
fn extract_unique_field_ident<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> &'a Ident { fn extract_unique_field_ident<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> &'a Ident {
let mut fields = extract_field_idents(fields, attribute_arg); let mut fields = extract_field_idents(fields, attribute_arg);
if fields.len() == 1 { if fields.len() == 1 {
fields.pop().unwrap() return fields.pop().unwrap()
} else { } else {
panic!("Model must need one {} field attribute", attribute_arg) panic!("Model must need one {} field attribute", attribute_arg);
} }
} }

View file

@ -1,13 +1,15 @@
use chrono::Local; use chrono::Local;
use sea_orm::entity::{ use sea_orm::{
*, prelude::*,
prelude::* entity::{
*,
prelude::*
}
}; };
use lazy_supplements_core::data::syncable::*; use lazy_supplements_core::data::syncable::*;
use lazy_supplements_macros::SyncableModel; use lazy_supplements_macros::SyncableModel;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, SyncableModel)]
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SyncableModel)]
#[sea_orm(table_name = "syncable")] #[sea_orm(table_name = "syncable")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
@ -16,14 +18,18 @@ pub struct Model {
#[sea_orm(indexed)] #[sea_orm(indexed)]
#[syncable(timestamp)] #[syncable(timestamp)]
pub created_at: DateTimeUtc, pub created_at: DateTimeUtc,
pub table_name: String,
#[syncable(author_id)] #[syncable(author_id)]
pub updated_by: Uuid, pub created_by: Uuid,
pub record_id: Uuid,
} }
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] #[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
pub enum Relation{} pub enum Relation{}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[test]
fn test_columns() {
assert!(Column::Id.is_id());
assert!(Column::CreatedAt.is_timestamp());
assert!(Column::CreatedBy.is_author_id());
}