feat: v0.3.0 — standardize codebase, centralize infra, merge QR into CMS
- Enforce axum best practices across all 13 workspace crates (max 200 LOC/file, no comments, no unwrap, clean architecture) - Fix domain→infrastructure dependency inversions in imphnen-iam and imphnen-dimentorin - Extract imphnen-storage (MinIO) and imphnen-email (Lettre) as standalone crates - Centralize all config in ENV struct: CDN_URL, CORS_ALLOWED_ORIGINS - Centralize SMTP through imphnen-email; remove dead HackathonConfig - Centralize database: QR crate now shares main DB pool (single DATABASE_URL) - Rename QR users table to qr_users to avoid collision with main users table - Merge imphnen-qr into imphnen-cms/src/qr (13 crates, down from 14) - Restructure imphnen-hackathon flat modules into clean architecture - Remove all stale env vars from .env.example (SurrealDB, QR_JWT, Hackathon infra) - Fix Dockerfile to include all current workspace crates - Bump all crate versions 0.2.0 → 0.3.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
2ae43b3bcc
commit
331a4a4e88
@@ -1,25 +1,23 @@
|
||||
//! SeaORM Entity for AuditLog
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "app_audit_log")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub user_email: String,
|
||||
pub action: String,
|
||||
pub resource: String,
|
||||
pub resource_id: Option<String>,
|
||||
#[sea_orm(column_type = "JsonBinary", nullable)]
|
||||
pub old_data: Option<Json>,
|
||||
#[sea_orm(column_type = "JsonBinary", nullable)]
|
||||
pub new_data: Option<Json>,
|
||||
pub ip_address: String,
|
||||
pub user_agent: Option<String>,
|
||||
pub timestamp: DateTimeWithTimeZone,
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub user_email: String,
|
||||
pub action: String,
|
||||
pub resource: String,
|
||||
pub resource_id: Option<String>,
|
||||
#[sea_orm(column_type = "JsonBinary", nullable)]
|
||||
pub old_data: Option<Json>,
|
||||
#[sea_orm(column_type = "JsonBinary", nullable)]
|
||||
pub new_data: Option<Json>,
|
||||
pub ip_address: String,
|
||||
pub user_agent: Option<String>,
|
||||
pub timestamp: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
use super::enums::ResourceEnum;
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for ResourceEnum {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
@@ -1,206 +1,101 @@
|
||||
//! Enum definitions for SeaORM entities
|
||||
//! Provides resource type enumerations matching SurrealDB ResourceEnum
|
||||
|
||||
use std::fmt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::types::PgUuid;
|
||||
|
||||
/// Database resource enumeration for SeaORM
|
||||
/// Matches the SurrealDB ResourceEnum with PostgreSQL compatibility
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ResourceEnum {
|
||||
/// OTP cache table for temporary authentication codes
|
||||
OtpCache,
|
||||
/// User cache table for user session data
|
||||
UsersCache,
|
||||
/// Gacha items table
|
||||
GachaItems,
|
||||
/// Gacha claims table for user item claims
|
||||
GachaClaims,
|
||||
/// Gacha rolls table for user roll history
|
||||
GachaRolls,
|
||||
/// Gacha credits table for user currency
|
||||
GachaCredits,
|
||||
/// Users table for user accounts
|
||||
Users,
|
||||
/// Roles table for user roles
|
||||
Roles,
|
||||
/// Permissions table for system permissions
|
||||
Permissions,
|
||||
/// Role-permission relationships table
|
||||
RolesPermissions,
|
||||
/// Events table for application events
|
||||
Events,
|
||||
/// Testimonials table for user testimonials
|
||||
Testimonials,
|
||||
/// Mentors table for mentor profiles
|
||||
Mentors,
|
||||
/// Notifications table for user notifications
|
||||
Notifications,
|
||||
/// Rate limiting table for IP-based rate limiting
|
||||
RateLimit,
|
||||
/// Audit log table for admin action tracking
|
||||
AuditLog,
|
||||
/// Sessions table for mentoring sessions
|
||||
Sessions,
|
||||
/// Migration status tracking table
|
||||
MigrationStatus,
|
||||
}
|
||||
|
||||
impl fmt::Display for ResourceEnum {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let table_name = match self {
|
||||
ResourceEnum::Users => "app_users",
|
||||
ResourceEnum::UsersCache => "app_users_cache",
|
||||
ResourceEnum::OtpCache => "app_otp_cache",
|
||||
ResourceEnum::Roles => "app_roles",
|
||||
ResourceEnum::Permissions => "app_permissions",
|
||||
ResourceEnum::RolesPermissions => "app_roles_permissions",
|
||||
ResourceEnum::GachaItems => "app_gacha_items",
|
||||
ResourceEnum::GachaClaims => "app_gacha_claims",
|
||||
ResourceEnum::GachaRolls => "app_gacha_rolls",
|
||||
ResourceEnum::GachaCredits => "app_gacha_credits",
|
||||
ResourceEnum::Events => "app_events",
|
||||
ResourceEnum::Testimonials => "app_testimonials",
|
||||
ResourceEnum::Mentors => "app_mentors",
|
||||
ResourceEnum::Notifications => "app_notifications",
|
||||
ResourceEnum::RateLimit => "app_rate_limit",
|
||||
ResourceEnum::AuditLog => "app_audit_log",
|
||||
ResourceEnum::Sessions => "app_sessions",
|
||||
ResourceEnum::MigrationStatus => "app_migration_status",
|
||||
};
|
||||
write!(f, "{}", table_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceEnum {
|
||||
/// Get the table name as a string slice.
|
||||
///
|
||||
/// # Returns
|
||||
/// The PostgreSQL table name for this resource
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ResourceEnum::Users => "app_users",
|
||||
ResourceEnum::UsersCache => "app_users_cache",
|
||||
ResourceEnum::OtpCache => "app_otp_cache",
|
||||
ResourceEnum::Roles => "app_roles",
|
||||
ResourceEnum::Permissions => "app_permissions",
|
||||
ResourceEnum::RolesPermissions => "app_roles_permissions",
|
||||
ResourceEnum::GachaItems => "app_gacha_items",
|
||||
ResourceEnum::GachaClaims => "app_gacha_claims",
|
||||
ResourceEnum::GachaRolls => "app_gacha_rolls",
|
||||
ResourceEnum::GachaCredits => "app_gacha_credits",
|
||||
ResourceEnum::Events => "app_events",
|
||||
ResourceEnum::Testimonials => "app_testimonials",
|
||||
ResourceEnum::Mentors => "app_mentors",
|
||||
ResourceEnum::Notifications => "app_notifications",
|
||||
ResourceEnum::RateLimit => "app_rate_limit",
|
||||
ResourceEnum::AuditLog => "app_audit_log",
|
||||
ResourceEnum::Sessions => "app_sessions",
|
||||
ResourceEnum::MigrationStatus => "app_migration_status",
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the schema name for the resource
|
||||
///
|
||||
/// # Returns
|
||||
/// The database schema name (usually "public" for PostgreSQL)
|
||||
pub fn schema(&self) -> &'static str {
|
||||
"public"
|
||||
}
|
||||
|
||||
/// Create a SeaORM entity name from the resource enum
|
||||
///
|
||||
/// # Returns
|
||||
/// A string suitable for use as a SeaORM entity name
|
||||
pub fn to_entity_name(&self) -> String {
|
||||
self.as_str().replace("app_", "").to_pascal_case()
|
||||
}
|
||||
|
||||
/// Check if this resource is cache-related.
|
||||
///
|
||||
/// # Returns
|
||||
/// true if the resource is used for caching, false otherwise
|
||||
pub fn is_cache(&self) -> bool {
|
||||
matches!(self, ResourceEnum::OtpCache | ResourceEnum::UsersCache)
|
||||
}
|
||||
|
||||
/// Check if this resource is gacha-related.
|
||||
///
|
||||
/// # Returns
|
||||
/// true if the resource is part of the gacha system, false otherwise
|
||||
pub fn is_gacha(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ResourceEnum::GachaItems
|
||||
| ResourceEnum::GachaClaims
|
||||
| ResourceEnum::GachaRolls
|
||||
| ResourceEnum::GachaCredits
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if this resource is user-related.
|
||||
///
|
||||
/// # Returns
|
||||
/// true if the resource contains user data, false otherwise
|
||||
pub fn is_user_related(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ResourceEnum::Users | ResourceEnum::UsersCache | ResourceEnum::Mentors
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate a reference ID for the resource
|
||||
///
|
||||
/// # Returns
|
||||
/// A formatted string suitable for use as a reference ID
|
||||
pub fn generate_ref_id(&self, uuid: &PgUuid) -> String {
|
||||
format!("{}_{}", self.as_str().replace("app_", ""), uuid.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper trait for string case conversion
|
||||
trait ToPascalCase {
|
||||
fn to_pascal_case(&self) -> String;
|
||||
}
|
||||
|
||||
impl ToPascalCase for str {
|
||||
fn to_pascal_case(&self) -> String {
|
||||
self.split('_')
|
||||
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_resource_enum_table_names() {
|
||||
assert_eq!(ResourceEnum::Users.as_str(), "app_users");
|
||||
assert_eq!(ResourceEnum::Roles.as_str(), "app_roles");
|
||||
assert_eq!(ResourceEnum::GachaItems.as_str(), "app_gacha_items");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_enum_display() {
|
||||
assert_eq!(format!("{}", ResourceEnum::Users), "app_users");
|
||||
assert_eq!(format!("{}", ResourceEnum::RolesPermissions), "app_roles_permissions");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_enum_categories() {
|
||||
assert!(ResourceEnum::Users.is_user_related());
|
||||
assert!(ResourceEnum::GachaItems.is_gacha());
|
||||
assert!(ResourceEnum::OtpCache.is_cache());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_enum_to_entity_name() {
|
||||
assert_eq!(ResourceEnum::Users.to_entity_name(), "Users");
|
||||
assert_eq!(ResourceEnum::RolesPermissions.to_entity_name(), "RolesPermissions");
|
||||
assert_eq!(ResourceEnum::GachaItems.to_entity_name(), "GachaItems");
|
||||
}
|
||||
}
|
||||
use super::types::PgUuid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ResourceEnum {
|
||||
OtpCache,
|
||||
UsersCache,
|
||||
GachaItems,
|
||||
GachaClaims,
|
||||
GachaRolls,
|
||||
GachaCredits,
|
||||
Users,
|
||||
Roles,
|
||||
Permissions,
|
||||
RolesPermissions,
|
||||
Events,
|
||||
Testimonials,
|
||||
Mentors,
|
||||
Notifications,
|
||||
RateLimit,
|
||||
AuditLog,
|
||||
Sessions,
|
||||
MigrationStatus,
|
||||
}
|
||||
|
||||
impl ResourceEnum {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ResourceEnum::Users => "app_users",
|
||||
ResourceEnum::UsersCache => "app_users_cache",
|
||||
ResourceEnum::OtpCache => "app_otp_cache",
|
||||
ResourceEnum::Roles => "app_roles",
|
||||
ResourceEnum::Permissions => "app_permissions",
|
||||
ResourceEnum::RolesPermissions => "app_roles_permissions",
|
||||
ResourceEnum::GachaItems => "app_gacha_items",
|
||||
ResourceEnum::GachaClaims => "app_gacha_claims",
|
||||
ResourceEnum::GachaRolls => "app_gacha_rolls",
|
||||
ResourceEnum::GachaCredits => "app_gacha_credits",
|
||||
ResourceEnum::Events => "app_events",
|
||||
ResourceEnum::Testimonials => "app_testimonials",
|
||||
ResourceEnum::Mentors => "app_mentors",
|
||||
ResourceEnum::Notifications => "app_notifications",
|
||||
ResourceEnum::RateLimit => "app_rate_limit",
|
||||
ResourceEnum::AuditLog => "app_audit_log",
|
||||
ResourceEnum::Sessions => "app_sessions",
|
||||
ResourceEnum::MigrationStatus => "app_migration_status",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schema(&self) -> &'static str {
|
||||
"public"
|
||||
}
|
||||
|
||||
pub fn to_entity_name(&self) -> String {
|
||||
self.as_str().replace("app_", "").to_pascal_case()
|
||||
}
|
||||
|
||||
pub fn is_cache(&self) -> bool {
|
||||
matches!(self, ResourceEnum::OtpCache | ResourceEnum::UsersCache)
|
||||
}
|
||||
|
||||
pub fn is_gacha(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ResourceEnum::GachaItems
|
||||
| ResourceEnum::GachaClaims
|
||||
| ResourceEnum::GachaRolls
|
||||
| ResourceEnum::GachaCredits
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_user_related(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ResourceEnum::Users | ResourceEnum::UsersCache | ResourceEnum::Mentors
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_ref_id(&self, uuid: &PgUuid) -> String {
|
||||
format!("{}_{}", self.as_str().replace("app_", ""), uuid.0)
|
||||
}
|
||||
}
|
||||
|
||||
trait ToPascalCase {
|
||||
fn to_pascal_case(&self) -> String;
|
||||
}
|
||||
|
||||
impl ToPascalCase for str {
|
||||
fn to_pascal_case(&self) -> String {
|
||||
self
|
||||
.split('_')
|
||||
.map(|s| {
|
||||
let mut chars = s.chars();
|
||||
chars
|
||||
.next()
|
||||
.map(|c| c.to_uppercase().collect::<String>() + chars.as_str())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
//! SeaORM entity for Events table
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "events")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub name: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub description: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub detail_link: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub price: f64,
|
||||
|
||||
#[sea_orm(default = "false")]
|
||||
pub is_online: bool,
|
||||
|
||||
#[sea_orm(default = "false")]
|
||||
pub is_deleted: bool,
|
||||
|
||||
#[sea_orm(nullable)]
|
||||
pub location: Option<String>,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub start_date: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub end_date: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
use chrono::{DateTime, Utc};
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "events")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub name: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub description: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub detail_link: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub price: f64,
|
||||
|
||||
#[sea_orm(default = "false")]
|
||||
pub is_online: bool,
|
||||
|
||||
#[sea_orm(default = "false")]
|
||||
pub is_deleted: bool,
|
||||
|
||||
#[sea_orm(nullable)]
|
||||
pub location: Option<String>,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub start_date: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub end_date: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
pub mod audit_log;
|
||||
pub mod enum_impls;
|
||||
pub mod enums;
|
||||
pub mod events;
|
||||
pub mod rate_limit;
|
||||
pub mod testimonials;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
pub mod audit_log;
|
||||
pub mod rate_limit;
|
||||
pub mod events;
|
||||
pub mod testimonials;
|
||||
|
||||
pub use enums::ResourceEnum;
|
||||
pub use types::PgUuid;
|
||||
pub use utils::{generate_uuid, current_timestamp};
|
||||
pub use utils::{current_timestamp, generate_uuid};
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
//! SeaORM Entity for RateLimit
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "app_rate_limit")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: String,
|
||||
pub ip_address: String,
|
||||
pub request_count: u32,
|
||||
pub first_request_time: DateTimeWithTimeZone,
|
||||
pub last_request_time: DateTimeWithTimeZone,
|
||||
pub window_duration_secs: i64,
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: String,
|
||||
pub ip_address: String,
|
||||
pub request_count: u32,
|
||||
pub first_request_time: DateTimeWithTimeZone,
|
||||
pub last_request_time: DateTimeWithTimeZone,
|
||||
pub window_duration_secs: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
//! SeaORM entity for Testimonials table
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid; // Added Uuid import
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "testimonials")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
|
||||
#[sea_orm(not_null, column_type = "Uuid")]
|
||||
pub user_id: Uuid,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub role: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub content: String,
|
||||
|
||||
#[sea_orm(default = "false")]
|
||||
pub is_deleted: bool,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "crate::seaorm::auth::users::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "crate::seaorm::auth::users::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
Users,
|
||||
}
|
||||
|
||||
impl Related<crate::seaorm::auth::users::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Users.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
use chrono::{DateTime, Utc};
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "testimonials")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
|
||||
#[sea_orm(not_null, column_type = "Uuid")]
|
||||
pub user_id: Uuid,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub role: String,
|
||||
|
||||
#[sea_orm(not_null)]
|
||||
pub content: String,
|
||||
|
||||
#[sea_orm(default = "false")]
|
||||
pub is_deleted: bool,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
#[sea_orm(not_null, default = "now()")]
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "crate::seaorm::auth::users::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "crate::seaorm::auth::users::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
Users,
|
||||
}
|
||||
|
||||
impl Related<crate::seaorm::auth::users::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Users.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
@@ -1,73 +1,63 @@
|
||||
//! Shared type definitions for SeaORM entities
|
||||
//! Provides PostgreSQL-compatible type aliases and custom types
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// UUID type alias for PostgreSQL UUID compatibility
|
||||
/// Uses `Uuid` from the `uuid` crate with SeaORM conversion traits
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PgUuid(pub Uuid);
|
||||
|
||||
impl From<Uuid> for PgUuid {
|
||||
fn from(uuid: Uuid) -> Self {
|
||||
Self(uuid)
|
||||
}
|
||||
fn from(uuid: Uuid) -> Self {
|
||||
Self(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PgUuid> for Uuid {
|
||||
fn from(pg_uuid: PgUuid) -> Self {
|
||||
pg_uuid.0
|
||||
}
|
||||
fn from(pg_uuid: PgUuid) -> Self {
|
||||
pg_uuid.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PgUuid> for String {
|
||||
fn from(pg_uuid: PgUuid) -> Self {
|
||||
pg_uuid.0.to_string()
|
||||
}
|
||||
fn from(pg_uuid: PgUuid) -> Self {
|
||||
pg_uuid.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Timestamp type alias for PostgreSQL TIMESTAMP with time zone
|
||||
/// Uses `DateTime<Utc>` from the `chrono` crate
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PgTimestamp(pub DateTime<Utc>);
|
||||
|
||||
impl From<DateTime<Utc>> for PgTimestamp {
|
||||
fn from(timestamp: DateTime<Utc>) -> Self {
|
||||
Self(timestamp)
|
||||
}
|
||||
fn from(timestamp: DateTime<Utc>) -> Self {
|
||||
Self(timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PgTimestamp> for DateTime<Utc> {
|
||||
fn from(pg_timestamp: PgTimestamp) -> Self {
|
||||
pg_timestamp.0
|
||||
}
|
||||
fn from(pg_timestamp: PgTimestamp) -> Self {
|
||||
pg_timestamp.0
|
||||
}
|
||||
}
|
||||
|
||||
/// JSONB type alias for PostgreSQL JSONB compatibility
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PgJsonB<T>(pub T);
|
||||
|
||||
impl<T> From<T> for PgJsonB<T>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
T: serde::Serialize,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Common fields that should be included in all entities
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CommonFields {
|
||||
pub id: PgUuid,
|
||||
pub created_at: PgTimestamp,
|
||||
pub updated_at: PgTimestamp,
|
||||
pub deleted_at: Option<PgTimestamp>,
|
||||
pub id: PgUuid,
|
||||
pub created_at: PgTimestamp,
|
||||
pub updated_at: PgTimestamp,
|
||||
pub deleted_at: Option<PgTimestamp>,
|
||||
}
|
||||
|
||||
// Helper macros for common field definitions
|
||||
#[macro_export]
|
||||
macro_rules! common_fields {
|
||||
() => {
|
||||
@@ -86,4 +76,3 @@ macro_rules! common_fields {
|
||||
.default(None),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,90 +1,74 @@
|
||||
//! Utility functions for SeaORM entities
|
||||
//! Provides helper functions for UUID generation, timestamp handling, and resource management
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::types::{PgTimestamp, PgUuid};
|
||||
|
||||
/// Generate a new UUID for entity IDs
|
||||
/// Uses cryptographically secure random UUID version 4
|
||||
pub fn generate_uuid() -> Uuid {
|
||||
Uuid::new_v4()
|
||||
}
|
||||
|
||||
/// Generate a new timestamp for entity timestamps
|
||||
/// Uses UTC timezone with millisecond precision
|
||||
pub fn generate_timestamp() -> PgTimestamp {
|
||||
PgTimestamp(DateTime::from_timestamp_millis(Utc::now().timestamp_millis()).unwrap())
|
||||
}
|
||||
|
||||
/// Convert a string to PgUuid
|
||||
/// Returns Result<PgUuid, String> with error message on failure
|
||||
pub fn string_to_uuid(uuid_str: &str) -> Result<PgUuid, String> {
|
||||
Uuid::parse_str(uuid_str)
|
||||
.map(PgUuid)
|
||||
.map_err(|e| format!("Invalid UUID format: {e}"))
|
||||
}
|
||||
|
||||
/// Convert PgUuid to string representation
|
||||
pub fn uuid_to_string(uuid: &uuid::Uuid) -> String {
|
||||
uuid.to_string()
|
||||
}
|
||||
|
||||
/// Get current timestamp as DateTime<Utc>
|
||||
pub fn current_timestamp() -> DateTime<Utc> {
|
||||
Utc::now()
|
||||
}
|
||||
|
||||
/// Format timestamp for display
|
||||
pub fn format_timestamp(timestamp: &PgTimestamp) -> String {
|
||||
timestamp.0.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
}
|
||||
|
||||
/// Create a soft delete timestamp
|
||||
pub fn create_deleted_at() -> Option<DateTime<Utc>> {
|
||||
Some(current_timestamp())
|
||||
}
|
||||
|
||||
/// Remove soft delete timestamp
|
||||
pub fn remove_deleted_at() -> Option<PgTimestamp> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_uuid() {
|
||||
let uuid1 = generate_uuid();
|
||||
let uuid2 = generate_uuid();
|
||||
assert_ne!(uuid1, uuid2);
|
||||
assert!(Uuid::parse_str(&uuid_to_string(&uuid1)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_timestamp() {
|
||||
let ts1 = generate_timestamp();
|
||||
let ts2 = generate_timestamp();
|
||||
// Timestamps should be close to each other
|
||||
let diff = ts2.0.signed_duration_since(ts1.0).num_milliseconds();
|
||||
assert!(diff >= 0);
|
||||
assert!(diff < 1000); // Should be within 1 second
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_uuid() {
|
||||
let uuid_str = "123e4567-e89b-12d3-a456-426614174000";
|
||||
let result = string_to_uuid(uuid_str);
|
||||
assert!(result.is_ok());
|
||||
let uuid = result.unwrap();
|
||||
// `uuid` is a `PgUuid`; convert to `Uuid` before comparing string representation
|
||||
let uuid_plain: uuid::Uuid = uuid.into();
|
||||
assert_eq!(uuid_to_string(&uuid_plain), uuid_str);
|
||||
|
||||
let invalid_uuid = "invalid-uuid";
|
||||
let result = string_to_uuid(invalid_uuid);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::types::{PgTimestamp, PgUuid};
|
||||
|
||||
pub fn generate_uuid() -> Uuid {
|
||||
Uuid::new_v4()
|
||||
}
|
||||
|
||||
pub fn generate_timestamp() -> PgTimestamp {
|
||||
PgTimestamp(Utc::now())
|
||||
}
|
||||
|
||||
pub fn string_to_uuid(uuid_str: &str) -> Result<PgUuid, String> {
|
||||
Uuid::parse_str(uuid_str)
|
||||
.map(PgUuid)
|
||||
.map_err(|e| format!("Invalid UUID format: {e}"))
|
||||
}
|
||||
|
||||
pub fn uuid_to_string(uuid: &uuid::Uuid) -> String {
|
||||
uuid.to_string()
|
||||
}
|
||||
|
||||
pub fn current_timestamp() -> DateTime<Utc> {
|
||||
Utc::now()
|
||||
}
|
||||
|
||||
pub fn format_timestamp(timestamp: &PgTimestamp) -> String {
|
||||
timestamp.0.format("%Y-%m-%d %H:%M:%S UTC").to_string()
|
||||
}
|
||||
|
||||
pub fn create_deleted_at() -> Option<DateTime<Utc>> {
|
||||
Some(current_timestamp())
|
||||
}
|
||||
|
||||
pub fn remove_deleted_at() -> Option<PgTimestamp> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_uuid() {
|
||||
let uuid1 = generate_uuid();
|
||||
let uuid2 = generate_uuid();
|
||||
assert_ne!(uuid1, uuid2);
|
||||
assert!(Uuid::parse_str(&uuid_to_string(&uuid1)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_timestamp() {
|
||||
let ts1 = generate_timestamp();
|
||||
let ts2 = generate_timestamp();
|
||||
let diff = ts2.0.signed_duration_since(ts1.0).num_milliseconds();
|
||||
assert!(diff >= 0);
|
||||
assert!(diff < 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_to_uuid() {
|
||||
let uuid_str = "123e4567-e89b-12d3-a456-426614174000";
|
||||
let result = string_to_uuid(uuid_str);
|
||||
assert!(result.is_ok());
|
||||
let uuid = result.unwrap();
|
||||
let uuid_plain: uuid::Uuid = uuid.into();
|
||||
assert_eq!(uuid_to_string(&uuid_plain), uuid_str);
|
||||
|
||||
let invalid_uuid = "invalid-uuid";
|
||||
let result = string_to_uuid(invalid_uuid);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user