Add syncable traits

This commit is contained in:
fluo10 2025-06-25 07:31:49 +09:00
parent 8fbf8a16ec
commit ebbe3d82d6
6 changed files with 138 additions and 16 deletions

View file

@ -1,11 +1,11 @@
mod node; mod trusted_peer;
mod record_deletion; mod record_deletion;
pub use node::{ pub use trusted_peer::{
ActiveModel as NodeActiveModel, ActiveModel as TrustedPeerActiveModel,
Column as NodeColumn, Column as TrustedPeerColumn,
Entity as NodeEntity, Entity as TrustedPeerEntity,
Model as NodeModel, Model as TrustedPeerModel,
}; };
pub use record_deletion::{ pub use record_deletion::{

View file

@ -5,9 +5,11 @@ use sea_orm::entity::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::data::value::PeerIdValue;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "node")] #[sea_orm(table_name = "trusted_peer")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key, auto_increment = false)] #[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid, pub id: Uuid,
@ -18,9 +20,10 @@ pub struct Model {
#[sea_orm(indexed)] #[sea_orm(indexed)]
pub synced_at: Option<DateTimeUtc>, pub synced_at: Option<DateTimeUtc>,
#[sea_orm(indexed)] #[sea_orm(indexed)]
pub peer_id: String, pub peer_id: PeerIdValue,
#[sea_orm(column_type = "Text")] #[sea_orm(column_type = "Text")]
pub note: String, pub note: String,
pub is_prefered: bool,
} }
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)] #[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
@ -46,14 +49,14 @@ mod tests {
use super::*; use super::*;
use libp2p::identity; use libp2p::{identity, PeerId};
#[tokio::test] #[tokio::test]
async fn check_insert_node() { async fn check_insert_node() {
let db = get_or_init_test_data_database().await; let db = get_or_init_test_data_database().await;
ActiveModel{ ActiveModel{
peer_id: Set(identity::Keypair::generate_ed25519().public().to_peer_id().to_string()), peer_id: Set(PeerIdValue::from(PeerId::random())),
note: Set("test note".to_owned()), note: Set("test note".to_owned()),
..ActiveModel::new() ..ActiveModel::new()
}.insert(db).await.unwrap(); }.insert(db).await.unwrap();

View file

@ -8,20 +8,20 @@ pub struct Migration;
#[async_trait::async_trait] #[async_trait::async_trait]
impl MigrationTrait for Migration { impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
Node::up(manager).await?; TrustedPeer::up(manager).await?;
RecordDeletion::up(manager).await?; RecordDeletion::up(manager).await?;
Ok(()) Ok(())
} }
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
Node::down(manager).await?; TrustedPeer::down(manager).await?;
RecordDeletion::down(manager).await?; RecordDeletion::down(manager).await?;
Ok(()) Ok(())
} }
} }
#[derive(DeriveIden)] #[derive(DeriveIden)]
enum Node { enum TrustedPeer {
Table, Table,
Id, Id,
CreatedAt, CreatedAt,
@ -29,10 +29,11 @@ enum Node {
SyncedAt, SyncedAt,
PeerId, PeerId,
Note, Note,
IsPrefered,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl TableMigration for Node { impl TableMigration for TrustedPeer {
async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> { async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
manager.create_table( manager.create_table(
Table::create() Table::create()
@ -44,6 +45,7 @@ impl TableMigration for Node {
.col(timestamp_null(Self::SyncedAt)) .col(timestamp_null(Self::SyncedAt))
.col(string_len(Self::PeerId, 255)) .col(string_len(Self::PeerId, 255))
.col(text(Self::Note)) .col(text(Self::Note))
.col(boolean(Self::IsPrefered))
.to_owned() .to_owned()
).await?; ).await?;
Ok(()) Ok(())

View file

@ -1,3 +1,4 @@
pub mod entity; pub mod entity;
pub mod migration; pub mod migration;
pub mod syncable;
pub mod value; pub mod value;

View file

@ -0,0 +1,64 @@
use sea_orm::{*, prelude::*, query::*};
pub trait SyncableModel: ModelTrait<Entity = Self::SyncableEntity> {
type SyncableEntity: SyncableEntity<SyncableModel = Self>;
fn get_updated_at(&self) -> DateTimeUtc;
fn get_uuid(&self) -> Uuid;
}
pub trait SyncableEntity: EntityTrait<
Model = Self::SyncableModel,
ActiveModel = Self::SyncableActiveModel,
Column = Self::SyncableColumn,
>{
type SyncableModel: SyncableModel<SyncableEntity = Self> + FromQueryResult;
type SyncableActiveModel: SyncableActiveModel<SyncableEntity= Self>;
type SyncableColumn: SyncableColumn;
async fn get_updated_after(date: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
.filter(Self::SyncableColumn::updated_at().gte(date))
.all(db)
.await.unwrap();
Ok(result)
}
fn apply_updated(models: Vec<<Self as EntityTrait>::Model>, db: &DatabaseConnection) {
todo!()
}
}
pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
type SyncableEntity: SyncableEntity<SyncableActiveModel = Self>;
fn get_uuid(&self) -> Option<Uuid>;
fn get_updated_at(&self) -> Option<DateTimeUtc>;
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() {
return Err(SyncableError::MismatchUuid)
}
if self.get_updated_at().ok_or(SyncableError::MissingField("updated_at"))? < other.get_updated_at() {
for column in <<<Self as ActiveModelTrait>::Entity as EntityTrait>::Column as Iterable>::iter() {
self.take(column).set_if_not_equals(other.get(column));
}
}
Ok(())
}
}
pub trait SyncableColumn: ColumnTrait {
fn is_uuid(&self) -> bool;
fn is_updated_at(&self) -> bool;
fn updated_at() -> Self;
fn should_not_sync(&self);
}
#[derive(Debug, thiserror::Error)]
pub enum SyncableError {
#[error("Invalid UUID")]
MismatchUuid,
#[error("mandatory field {0} is missing")]
MissingField(&'static str),
}

View file

@ -4,9 +4,42 @@ use libp2p::PeerId;
use sea_orm::{sea_query::ValueTypeErr, DbErr}; use sea_orm::{sea_query::ValueTypeErr, DbErr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::error::Error;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PeerIdValue(PeerId); pub struct PeerIdValue(PeerId);
impl<'de> Deserialize<'de> for PeerIdValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
Self::from_str(&String::deserialize(deserializer)?).or(Err(<D::Error as serde::de::Error>::custom("fail to parse PeerId")))
}
}
impl Serialize for PeerIdValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
serializer.serialize_str(&self.0.to_string())
}
}
impl FromStr for PeerIdValue{
type Err = libp2p::identity::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(PeerId::from_str(s)?))
}
}
impl ToString for PeerIdValue {
fn to_string(&self) -> String {
self.0.to_string()
}
}
impl From<PeerId> for PeerIdValue { impl From<PeerId> for PeerIdValue {
fn from(source: PeerId) -> Self { fn from(source: PeerId) -> Self {
Self(source) Self(source)
@ -66,3 +99,22 @@ impl sea_orm::sea_query::Nullable for PeerIdValue {
<String as sea_orm::sea_query::Nullable>::null() <String as sea_orm::sea_query::Nullable>::null()
} }
} }
#[cfg(test)]
mod tests {
use crate::tests::{test_cbor_serialize_deserialize, test_toml_serialize_deserialize};
use super::*;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
struct PeerIdValueWrapper {
content: PeerIdValue
}
#[test]
fn test_serialize_deserialize() {
let peer_id= PeerIdValueWrapper{content: PeerIdValue::from(PeerId::random())};
let x = toml::to_string(&peer_id).unwrap();
assert_eq!(peer_id.content, toml::from_str::<PeerIdValueWrapper>(&x).unwrap().content)
}
}