This commit is contained in:
2025-04-22 07:50:03 +04:00
parent 6443c6b647
commit d75de1e73c
20 changed files with 1007 additions and 640 deletions

11
heromodels/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "heromodels"
version = "0.1.0"
edition = "2021"
description = "A library for hero models with base model trait implementation"
authors = ["Your Name <your.email@example.com>"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3"
chrono = { version = "0.4", features = ["serde"] }

63
heromodels/src/comment.rs Normal file
View File

@@ -0,0 +1,63 @@
use serde::{Deserialize, Serialize};
use crate::model::{BaseModel, BaseModelData, IndexKey};
/// Represents a comment on a model
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
/// Base model data
pub base_data: BaseModelData,
/// The ID of the user who created the comment
pub user_id: u32,
/// The ID of the model this comment is attached to
pub model_id: u32,
/// The type of model this comment is attached to
pub model_type: String,
/// The content of the comment
pub content: String,
}
impl Comment {
/// Create a new comment
pub fn new(id: u32, user_id: u32, model_id: u32, model_type: String, content: String) -> Self {
Self {
base_data: BaseModelData::new(id),
user_id,
model_id,
model_type,
content,
}
}
/// Update the comment content
pub fn update_content(&mut self, content: String) {
self.content = content;
self.base_data.update_modified();
}
}
impl BaseModel for Comment {
fn db_prefix() -> &'static str {
"comment"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
name: "user_id",
value: self.user_id.to_string(),
},
IndexKey {
name: "model_id",
value: format!("{}:{}", self.model_type, self.model_id),
},
]
}
}

47
heromodels/src/lib.rs Normal file
View File

@@ -0,0 +1,47 @@
//! # Hero Models
//!
//! A library for hero models with base model trait implementation.
//!
//! This crate provides a base model trait and implementation that other models can inherit from.
//! It also provides a Comment model that can be used to add comments to any model.
pub mod model;
pub mod comment;
pub mod user;
// Re-export key types for convenience
pub use model::{BaseModel, BaseModelData, IndexKey, impl_base_model};
pub use comment::Comment;
pub use user::User;
/// Example of how to use the heromodels crate
///
/// ```rust
/// use heromodels::{BaseModel, User, Comment};
///
/// // Create a new user
/// let mut user = User::new(
/// 1,
/// "johndoe".to_string(),
/// "john.doe@example.com".to_string(),
/// "John Doe".to_string()
/// );
///
/// // Create a comment for the user
/// let comment = Comment::new(
/// 1,
/// 2, // commenter's user ID
/// user.get_id(),
/// User::db_prefix().to_string(),
/// "This is a comment on the user".to_string()
/// );
///
/// // Add the comment to the user
/// user.base_data.add_comment(comment.get_id());
///
/// // Get the database prefix for the User model
/// assert_eq!(User::db_prefix(), "user");
///
/// // Get the database keys for the user
/// let keys = user.db_keys();
/// assert!(keys.iter().any(|k| k.name == "username" && k.value == "johndoe"));

181
heromodels/src/model.rs Normal file
View File

@@ -0,0 +1,181 @@
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
/// Represents an index key for a model
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexKey {
/// The name of the index key
pub name: &'static str,
/// The value of the index key for a specific model instance
pub value: String,
}
/// Builder for IndexKey
pub struct IndexKeyBuilder {
name: &'static str,
value: String,
}
impl IndexKeyBuilder {
/// Create a new IndexKeyBuilder
pub fn new(name: &'static str) -> Self {
Self {
name,
value: String::new(),
}
}
/// Set the value for this index key
pub fn value(mut self, value: impl ToString) -> Self {
self.value = value.to_string();
self
}
/// Build the IndexKey
pub fn build(self) -> IndexKey {
IndexKey {
name: self.name,
value: self.value,
}
}
}
/// Base trait for all models
pub trait BaseModel: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static {
/// Get the database prefix for this model type
fn db_prefix() -> &'static str where Self: Sized;
/// Returns a list of index keys for this model instance
/// These keys will be used to create additional indexes in the TST
/// The default implementation returns an empty vector
/// Override this method to provide custom indexes
fn db_keys(&self) -> Vec<IndexKey> {
Vec::new()
}
/// Get the unique ID for this model
fn get_id(&self) -> u32;
}
/// Base struct that all models should include
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaseModelData {
/// Unique incremental ID per circle
pub id: u32,
/// Unix epoch timestamp for creation time
pub created_at: i64,
/// Unix epoch timestamp for last modification time
pub modified_at: i64,
/// List of comment IDs referencing Comment objects
pub comments: Vec<u32>,
}
impl BaseModelData {
/// Create a new BaseModelData instance
pub fn new(id: u32) -> Self {
let now = chrono::Utc::now().timestamp();
Self {
id,
created_at: now,
modified_at: now,
comments: Vec::new(),
}
}
/// Create a new BaseModelDataBuilder
pub fn builder(id: u32) -> BaseModelDataBuilder {
BaseModelDataBuilder::new(id)
}
/// Add a comment to this model
pub fn add_comment(&mut self, comment_id: u32) {
self.comments.push(comment_id);
self.modified_at = chrono::Utc::now().timestamp();
}
/// Remove a comment from this model
pub fn remove_comment(&mut self, comment_id: u32) {
self.comments.retain(|&id| id != comment_id);
self.modified_at = chrono::Utc::now().timestamp();
}
/// Update the modified timestamp
pub fn update_modified(&mut self) {
self.modified_at = chrono::Utc::now().timestamp();
}
}
/// Builder for BaseModelData
pub struct BaseModelDataBuilder {
id: u32,
created_at: Option<i64>,
modified_at: Option<i64>,
comments: Vec<u32>,
}
impl BaseModelDataBuilder {
/// Create a new BaseModelDataBuilder
pub fn new(id: u32) -> Self {
Self {
id,
created_at: None,
modified_at: None,
comments: Vec::new(),
}
}
/// Set the created_at timestamp
pub fn created_at(mut self, timestamp: i64) -> Self {
self.created_at = Some(timestamp);
self
}
/// Set the modified_at timestamp
pub fn modified_at(mut self, timestamp: i64) -> Self {
self.modified_at = Some(timestamp);
self
}
/// Add a comment ID
pub fn add_comment(mut self, comment_id: u32) -> Self {
self.comments.push(comment_id);
self
}
/// Add multiple comment IDs
pub fn add_comments(mut self, comment_ids: Vec<u32>) -> Self {
self.comments.extend(comment_ids);
self
}
/// Build the BaseModelData
pub fn build(self) -> BaseModelData {
let now = chrono::Utc::now().timestamp();
BaseModelData {
id: self.id,
created_at: self.created_at.unwrap_or(now),
modified_at: self.modified_at.unwrap_or(now),
comments: self.comments,
}
}
}
/// Macro to implement BaseModel for a struct that contains a base_data field of type BaseModelData
#[macro_export]
macro_rules! impl_base_model {
($type:ty, $prefix:expr) => {
impl BaseModel for $type {
fn db_prefix() -> &'static str {
$prefix
}
fn get_id(&self) -> u32 {
self.base_data.id
}
}
};
}

86
heromodels/src/user.rs Normal file
View File

@@ -0,0 +1,86 @@
use serde::{Deserialize, Serialize};
use crate::model::{BaseModel, BaseModelData, IndexKey};
/// Represents a user in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
/// Base model data
pub base_data: BaseModelData,
/// User's username
pub username: String,
/// User's email address
pub email: String,
/// User's full name
pub full_name: String,
/// Whether the user is active
pub is_active: bool,
}
impl User {
/// Create a new user
pub fn new(id: u32, username: String, email: String, full_name: String) -> Self {
Self {
base_data: BaseModelData::new(id),
username,
email,
full_name,
is_active: true,
}
}
/// Deactivate the user
pub fn deactivate(&mut self) {
self.is_active = false;
self.base_data.update_modified();
}
/// Activate the user
pub fn activate(&mut self) {
self.is_active = true;
self.base_data.update_modified();
}
/// Update user's email
pub fn update_email(&mut self, email: String) {
self.email = email;
self.base_data.update_modified();
}
/// Update user's full name
pub fn update_full_name(&mut self, full_name: String) {
self.full_name = full_name;
self.base_data.update_modified();
}
}
// Implement BaseModel for User
impl BaseModel for User {
fn db_prefix() -> &'static str {
"user"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
name: "username",
value: self.username.clone(),
},
IndexKey {
name: "email",
value: self.email.clone(),
},
IndexKey {
name: "is_active",
value: self.is_active.to_string(),
},
]
}
}