postgress

This commit is contained in:
MythEclipse
2025-12-01 00:20:42 +07:00
parent 6fe495eed1
commit b429b3a9c7
325 changed files with 35728 additions and 50259 deletions
+298
View File
@@ -0,0 +1,298 @@
//! SeaORM entity for Mentors table
//! Corresponding to ResourceEnum::Mentors
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelBehavior, DeriveEntityModel, DeriveRelation};
use sea_orm::ActiveValue::Set;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, imphnen_macros::Builder)]
#[sea_orm(table_name = "app_mentors")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(unique, not_null)]
pub user_id: Uuid,
#[sea_orm(type = "jsonb", nullable)]
pub industries: Option<serde_json::Value>,
#[sea_orm(type = "jsonb", nullable)]
pub expertise: Option<serde_json::Value>,
#[sea_orm(type = "jsonb", nullable)]
pub languages: Option<serde_json::Value>,
#[sea_orm(nullable)]
pub current_company: Option<String>,
#[sea_orm(nullable)]
pub current_role: Option<String>,
#[sea_orm(nullable)]
pub years_of_experience: Option<i32>,
#[sea_orm(type = "jsonb", nullable)]
pub topics_of_interest: Option<serde_json::Value>,
#[sea_orm(nullable)]
pub preferred_mentee_level: Option<String>,
#[sea_orm(type = "jsonb", nullable)]
pub preferred_mentoring_formats: Option<serde_json::Value>,
#[sea_orm(nullable)]
pub availability_commitment: Option<String>,
#[sea_orm(nullable)]
pub mentoring_rate: Option<f64>,
#[sea_orm(nullable)]
pub status: Option<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, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id")]
User,
}
impl Related<super::users::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {
// Default implementation - SeaORM will handle timestamps automatically
}
// Builder pattern for Mentor creation
#[derive(Default, Serialize, Deserialize)]
pub struct MentorBuilder {
user_id: Option<Uuid>,
industries: Option<Vec<String>>,
expertise: Option<Vec<String>>,
languages: Option<Vec<String>>,
current_company: Option<String>,
current_role: Option<String>,
years_of_experience: Option<i32>,
topics_of_interest: Option<Vec<String>>,
preferred_mentee_level: Option<String>,
preferred_mentoring_formats: Option<Vec<String>>,
availability_commitment: Option<String>,
mentoring_rate: Option<f64>,
status: Option<String>,
is_deleted: Option<bool>,
}
impl MentorBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn user_id(mut self, user_id: Uuid) -> Self {
self.user_id = Some(user_id);
self
}
#[must_use]
pub fn industries(mut self, industries: Vec<String>) -> Self {
self.industries = Some(industries);
self
}
#[must_use]
pub fn expertise(mut self, expertise: Vec<String>) -> Self {
self.expertise = Some(expertise);
self
}
#[must_use]
pub fn languages(mut self, languages: Vec<String>) -> Self {
self.languages = Some(languages);
self
}
#[must_use]
pub fn current_company(mut self, current_company: String) -> Self {
self.current_company = Some(current_company);
self
}
#[must_use]
pub fn current_role(mut self, current_role: String) -> Self {
self.current_role = Some(current_role);
self
}
#[must_use]
pub fn years_of_experience(mut self, years_of_experience: i32) -> Self {
self.years_of_experience = Some(years_of_experience);
self
}
#[must_use]
pub fn topics_of_interest(mut self, topics_of_interest: Vec<String>) -> Self {
self.topics_of_interest = Some(topics_of_interest);
self
}
#[must_use]
pub fn preferred_mentee_level(mut self, preferred_mentee_level: String) -> Self {
self.preferred_mentee_level = Some(preferred_mentee_level);
self
}
#[must_use]
pub fn preferred_mentoring_formats(mut self, preferred_mentoring_formats: Vec<String>) -> Self {
self.preferred_mentoring_formats = Some(preferred_mentoring_formats);
self
}
#[must_use]
pub fn availability_commitment(mut self, availability_commitment: String) -> Self {
self.availability_commitment = Some(availability_commitment);
self
}
#[must_use]
pub fn mentoring_rate(mut self, mentoring_rate: f64) -> Self {
self.mentoring_rate = Some(mentoring_rate);
self
}
#[must_use]
pub fn status(mut self, status: String) -> Self {
self.status = Some(status);
self
}
#[must_use]
pub fn is_deleted(mut self, is_deleted: bool) -> Self {
self.is_deleted = Some(is_deleted);
self
}
pub fn build(self) -> Result<ActiveModel, String> {
let mut active_model = <ActiveModel as std::default::Default>::default();
if let Some(user_id) = self.user_id {
active_model.user_id = Set(user_id);
} else {
return Err("User ID is required".to_string());
}
if let Some(industries) = self.industries {
active_model.industries = Set(Some(serde_json::to_value(industries).map_err(|e| format!("Failed to serialize industries: {}", e))?));
}
if let Some(expertise) = self.expertise {
active_model.expertise = Set(Some(serde_json::to_value(expertise).map_err(|e| format!("Failed to serialize expertise: {}", e))?));
}
if let Some(languages) = self.languages {
active_model.languages = Set(Some(serde_json::to_value(languages).map_err(|e| format!("Failed to serialize languages: {}", e))?));
}
if let Some(current_company) = self.current_company {
active_model.current_company = Set(Some(current_company));
}
if let Some(current_role) = self.current_role {
active_model.current_role = Set(Some(current_role));
}
if let Some(years_of_experience) = self.years_of_experience {
active_model.years_of_experience = Set(Some(years_of_experience));
}
if let Some(topics_of_interest) = self.topics_of_interest {
active_model.topics_of_interest = Set(Some(serde_json::to_value(topics_of_interest).map_err(|e| format!("Failed to serialize topics_of_interest: {}", e))?));
}
if let Some(preferred_mentee_level) = self.preferred_mentee_level {
active_model.preferred_mentee_level = Set(Some(preferred_mentee_level));
}
if let Some(preferred_mentoring_formats) = self.preferred_mentoring_formats {
active_model.preferred_mentoring_formats = Set(Some(serde_json::to_value(preferred_mentoring_formats).map_err(|e| format!("Failed to serialize preferred_mentoring_formats: {}", e))?));
}
if let Some(availability_commitment) = self.availability_commitment {
active_model.availability_commitment = Set(Some(availability_commitment));
}
if let Some(mentoring_rate) = self.mentoring_rate {
active_model.mentoring_rate = Set(Some(mentoring_rate));
}
if let Some(status) = self.status {
active_model.status = Set(Some(status));
}
if let Some(is_deleted) = self.is_deleted {
active_model.is_deleted = Set(is_deleted);
}
Ok(active_model)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::seaorm::common::utils::generate_uuid;
use serde_json::json;
#[test]
fn test_mentor_model_creation() {
let uid = generate_uuid();
let mentor = MentorBuilder::new()
.user_id(uid)
.industries(vec!["Technology".to_string(), "Finance".to_string()])
.expertise(vec!["Blockchain".to_string(), "AI".to_string()])
.languages(vec!["English".to_string(), "Spanish".to_string()])
.current_company("Tech Corp".to_string())
.current_role("Senior Engineer".to_string())
.years_of_experience(10)
.topics_of_interest(vec!["Web3".to_string(), "Machine Learning".to_string()])
.preferred_mentee_level("Intermediate".to_string())
.preferred_mentoring_formats(vec!["1:1".to_string(), "Group".to_string()])
.availability_commitment("Weekly".to_string())
.mentoring_rate(150.0)
.status("active".to_string())
.build();
assert!(mentor.is_ok());
let mentor_model = mentor.unwrap();
assert_eq!(mentor_model.user_id, Set(uid));
assert_eq!(mentor_model.industries, Set(Some(json!(["Technology", "Finance"]))));
assert_eq!(mentor_model.expertise, Set(Some(json!(["Blockchain", "AI"]))));
}
#[test]
fn test_mentor_model_missing_required_fields() {
let mentor = MentorBuilder::new()
// Missing user_id
.industries(vec!["Technology".to_string()])
.build();
assert!(mentor.is_err());
assert_eq!(mentor.unwrap_err(), "User ID is required");
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod users;
pub mod roles;
pub mod permissions;
pub mod roles_permissions;
pub mod mentors;
pub mod sessions;
@@ -0,0 +1,60 @@
//! SeaORM entity for Permissions table
//! Corresponding to ResourceEnum::Permissions
//! Represents system permissions
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelBehavior, DeriveEntityModel, DeriveRelation};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "app_permissions")]
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, 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>,
#[sea_orm(nullable)]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Copy, Clone, Debug, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::roles_permissions::Entity")]
RolesPermissions,
}
impl Related<super::roles_permissions::Entity> for Entity {
fn to() -> RelationDef {
Relation::RolesPermissions.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
impl Entity {
pub fn find_by_id(id: Uuid) -> Select<Entity> {
Self::find().filter(Column::Id.eq(id))
}
pub fn find_by_name(name: &str) -> Select<Entity> {
Self::find().filter(Column::Name.eq(name))
}
pub fn find_active() -> Select<Entity> {
Self::find().filter(Column::IsDeleted.eq(false))
}
}
+150
View File
@@ -0,0 +1,150 @@
//! SeaORM entity for Roles table
//! Corresponding to ResourceEnum::Roles
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelBehavior, DeriveEntityModel, DeriveRelation};
use sea_orm::ActiveValue::Set;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "app_roles")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(unique, not_null)]
pub name: String,
#[sea_orm(not_null)]
pub description: String,
#[sea_orm(default = "false")]
pub is_system_role: bool,
#[sea_orm(default = "false")]
pub is_default: bool,
#[sea_orm(type = "jsonb", nullable)]
pub permissions: Option<serde_json::Value>,
#[sea_orm(not_null, default = "now()")]
pub created_at: DateTime<Utc>,
#[sea_orm(not_null, default = "now()")]
pub updated_at: DateTime<Utc>,
#[sea_orm(nullable)]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Copy, Clone, Debug, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {
}
impl ActiveModelBehavior for ActiveModel {
// Default implementation - SeaORM will handle timestamps automatically
}
// Builder pattern for Role creation
#[derive(Default, Serialize, Deserialize)]
pub struct RoleBuilder {
name: Option<String>,
description: Option<String>,
is_system_role: Option<bool>,
is_default: Option<bool>,
permissions: Option<Vec<String>>,
}
impl RoleBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
#[must_use]
pub fn description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
#[must_use]
pub fn is_system_role(mut self, is_system_role: bool) -> Self {
self.is_system_role = Some(is_system_role);
self
}
#[must_use]
pub fn is_default(mut self, is_default: bool) -> Self {
self.is_default = Some(is_default);
self
}
#[must_use]
pub fn permissions(mut self, permissions: Vec<String>) -> Self {
self.permissions = Some(permissions);
self
}
pub fn build(self) -> Result<ActiveModel, String> {
let mut active_model = <ActiveModel as std::default::Default>::default();
if let Some(name) = self.name {
active_model.name = Set(name);
} else {
return Err("Role name is required".to_string());
}
if let Some(description) = self.description {
active_model.description = Set(description);
} else {
return Err("Role description is required".to_string());
}
if let Some(is_system_role) = self.is_system_role {
active_model.is_system_role = Set(is_system_role);
}
if let Some(is_default) = self.is_default {
active_model.is_default = Set(is_default);
}
if let Some(permissions) = self.permissions {
active_model.permissions = Set(Some(serde_json::Value::Array(
permissions.into_iter().map(serde_json::Value::String).collect()
)));
}
Ok(active_model)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_role_model_creation() {
let role = RoleBuilder::new()
.name("admin".to_string())
.description("Administrator role".to_string())
.is_system_role(true)
.is_default(false)
.build();
assert!(role.is_ok());
let role_model = role.unwrap();
assert_eq!(role_model.name, Set("admin".to_string()));
assert_eq!(role_model.description, Set("Administrator role".to_string()));
assert_eq!(role_model.is_system_role, Set(true));
assert_eq!(role_model.is_default, Set(false));
}
}
@@ -0,0 +1,159 @@
//! SeaORM entity for RolesPermissions table
//! Corresponding to ResourceEnum::RolesPermissions
//! Represents the many-to-many relationship between Users and Roles
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelBehavior, DeriveEntityModel, DeriveRelation};
use sea_orm::ActiveValue::Set;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "app_roles_permissions")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(not_null)]
pub user_id: Uuid,
#[sea_orm(not_null)]
pub role_id: Uuid,
#[sea_orm(not_null)]
pub permission_id: Uuid,
#[sea_orm(not_null, default = "now()")]
pub assigned_at: DateTime<Utc>,
#[sea_orm(not_null, default = "false")]
pub is_active: bool,
#[sea_orm(not_null, default = "now()")]
pub created_at: DateTime<Utc>,
#[sea_orm(not_null, default = "now()")]
pub updated_at: DateTime<Utc>,
#[sea_orm(nullable)]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Copy, Clone, Debug, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id")]
User,
#[sea_orm(belongs_to = "super::roles::Entity", from = "Column::RoleId", to = "super::roles::Column::Id")]
Role,
#[sea_orm(belongs_to = "super::permissions::Entity", from = "Column::PermissionId", to = "super::permissions::Column::Id")]
Permission,
}
impl Related<super::users::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::roles::Entity> for Entity {
fn to() -> RelationDef {
Relation::Role.def()
}
}
impl Related<super::permissions::Entity> for Entity {
fn to() -> RelationDef {
Relation::Permission.def()
}
}
impl ActiveModelBehavior for ActiveModel {
// Default implementation - SeaORM will handle timestamps automatically
}
// Builder pattern for RolePermission creation
#[derive(Default, Serialize, Deserialize)]
pub struct RolePermissionBuilder {
user_id: Option<Uuid>,
role_id: Option<Uuid>,
permission_id: Option<Uuid>,
is_active: Option<bool>,
}
impl RolePermissionBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn user_id(mut self, user_id: Uuid) -> Self {
self.user_id = Some(user_id);
self
}
#[must_use]
pub fn role_id(mut self, role_id: Uuid) -> Self {
self.role_id = Some(role_id);
self
}
#[must_use]
pub fn permission_id(mut self, permission_id: Uuid) -> Self {
self.permission_id = Some(permission_id);
self
}
#[must_use]
pub fn is_active(mut self, is_active: bool) -> Self {
self.is_active = Some(is_active);
self
}
pub fn build(self) -> Result<ActiveModel, String> {
let mut active_model = <ActiveModel as std::default::Default>::default();
if let (Some(user_id), Some(role_id), Some(permission_id)) = (self.user_id, self.role_id, self.permission_id) {
active_model.user_id = Set(user_id);
active_model.role_id = Set(role_id);
active_model.permission_id = Set(permission_id);
} else {
return Err("User ID, Role ID, and Permission ID are required".to_string());
}
if let Some(is_active) = self.is_active {
active_model.is_active = Set(is_active);
}
Ok(active_model)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::seaorm::common::utils::generate_uuid;
#[test]
fn test_role_permission_model_creation() {
let user_id = generate_uuid();
let role_id = generate_uuid();
let permission_id = generate_uuid();
let role_permission = RolePermissionBuilder::new()
.user_id(user_id)
.role_id(role_id)
.permission_id(permission_id)
.is_active(true)
.build();
assert!(role_permission.is_ok());
let role_permission_model = role_permission.unwrap();
assert_eq!(role_permission_model.user_id, Set(user_id));
assert_eq!(role_permission_model.role_id, Set(role_id));
assert_eq!(role_permission_model.permission_id, Set(permission_id));
assert_eq!(role_permission_model.is_active, Set(true));
}
}
@@ -0,0 +1,70 @@
use sea_orm::entity::prelude::*;
use chrono::{DateTime, Utc};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "sessions")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(column_type = "Uuid")]
pub mentor_id: Uuid,
#[sea_orm(column_type = "Uuid")]
pub mentee_id: Uuid,
pub topic: String,
#[sea_orm(nullable)]
pub description: Option<String>,
pub scheduled_at: DateTime<Utc>,
pub duration_minutes: i32,
#[sea_orm(nullable)]
pub meeting_link: Option<String>,
pub session_type: String, // "video_call", "phone_call", "chat"
pub status: String, // "pending", "confirmed", "completed", "cancelled", "no_show"
#[sea_orm(nullable)]
pub feedback: Option<String>,
#[sea_orm(nullable)]
pub rating: Option<i32>, // 1-5
#[sea_orm(nullable)]
pub feedback_submitted_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::users::Entity",
from = "Column::MentorId",
to = "super::users::Column::Id"
)]
Mentor,
#[sea_orm(
belongs_to = "super::users::Entity",
from = "Column::MenteeId",
to = "super::users::Column::Id"
)]
Mentee,
}
impl Related<super::users::Entity> for Entity {
fn to() -> RelationDef {
Relation::Mentor.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
+178
View File
@@ -0,0 +1,178 @@
//! SeaORM entity for Users table
//! Corresponding to ResourceEnum::Users
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use sea_orm::ActiveValue::Set;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, imphnen_macros::Builder)]
#[sea_orm(table_name = "app_users")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(unique, not_null)]
pub email: String,
#[sea_orm(not_null)]
pub password_hash: String,
#[sea_orm(not_null)]
pub username: String,
#[sea_orm(column_name = "role_id", nullable)]
pub role_id: Option<Uuid>,
#[sea_orm(nullable)]
pub first_name: Option<String>,
#[sea_orm(nullable)]
pub last_name: Option<String>,
#[sea_orm(nullable)]
pub avatar_url: Option<String>,
#[sea_orm(default = "false")]
pub is_verified: bool,
#[sea_orm(default = "false")]
pub is_active: bool,
#[sea_orm(type = "jsonb", nullable)]
pub metadata: Option<serde_json::Value>,
#[sea_orm(not_null, default = "now()")]
pub created_at: DateTime<Utc>,
#[sea_orm(not_null, default = "now()")]
pub updated_at: DateTime<Utc>,
#[sea_orm(nullable)]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Copy, Clone, Debug, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::roles_permissions::Entity")]
RolesPermissions,
#[sea_orm(belongs_to = "super::roles::Entity", from = "Column::RoleId", to = "super::roles::Column::Id")]
Role,
}
impl Related<super::roles_permissions::Entity> for Entity {
fn to() -> RelationDef {
Relation::RolesPermissions.def()
}
}
impl Related<super::roles::Entity> for Entity {
fn to() -> RelationDef {
Relation::Role.def()
}
}
impl ActiveModelBehavior for ActiveModel {
// Default implementation - SeaORM will handle timestamps automatically
}
// Builder pattern for User creation
// Generated by #[derive(Builder)]
pub type UserBuilder = ModelBuilder;
impl ModelBuilder {
pub fn build(self) -> Result<ActiveModel, String> {
let mut active_model = <ActiveModel as std::default::Default>::default();
if let Some(email) = self.email {
active_model.email = Set(email);
} else {
return Err("Email is required".to_string());
}
if let Some(password_hash) = self.password_hash {
active_model.password_hash = Set(password_hash);
} else {
return Err("Password hash is required".to_string());
}
if let Some(username) = self.username {
active_model.username = Set(username);
} else {
return Err("Username is required".to_string());
}
if let Some(role_id) = self.role_id {
active_model.role_id = Set(Some(role_id));
}
if let Some(first_name) = self.first_name {
active_model.first_name = Set(Some(first_name));
}
if let Some(last_name) = self.last_name {
active_model.last_name = Set(Some(last_name));
}
if let Some(avatar_url) = self.avatar_url {
active_model.avatar_url = Set(Some(avatar_url));
}
if let Some(is_verified) = self.is_verified {
active_model.is_verified = Set(is_verified);
}
if let Some(is_active) = self.is_active {
active_model.is_active = Set(is_active);
}
if let Some(metadata) = self.metadata {
active_model.metadata = Set(Some(metadata));
}
Ok(active_model)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_model_creation() {
let user = UserBuilder::new()
.email("test@example.com".to_string())
.password_hash("hashed_password".to_string())
.username("testuser".to_string())
.first_name("Test".to_string())
.last_name("User".to_string())
.is_verified(true)
.is_active(true)
.build();
assert!(user.is_ok());
let user_model = user.unwrap();
assert_eq!(user_model.email, Set("test@example.com".to_string()));
assert_eq!(user_model.password_hash, Set("hashed_password".to_string()));
assert_eq!(user_model.username, Set("testuser".to_string()));
assert_eq!(user_model.first_name, Set(Some("Test".to_string())));
assert_eq!(user_model.last_name, Set(Some("User".to_string())));
assert_eq!(user_model.is_verified, Set(true));
assert_eq!(user_model.is_active, Set(true));
}
#[test]
fn test_user_model_missing_required_fields() {
let user = UserBuilder::new()
.email("test@example.com".to_string())
// Missing password_hash
.username("testuser".to_string())
.build();
assert!(user.is_err());
assert_eq!(user.unwrap_err(), "Password hash is required");
}
}
@@ -0,0 +1,28 @@
//! 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,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
+206
View File
@@ -0,0 +1,206 @@
//! 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");
}
}
@@ -0,0 +1,51 @@
//! 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 {}
+11
View File
@@ -0,0 +1,11 @@
pub mod enums;
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};
@@ -0,0 +1,21 @@
//! 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,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
@@ -0,0 +1,51 @@
//! 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 {}
@@ -0,0 +1,89 @@
//! 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)
}
}
impl From<PgUuid> for Uuid {
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()
}
}
/// 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)
}
}
impl From<PgTimestamp> for DateTime<Utc> {
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,
{
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>,
}
// Helper macros for common field definitions
#[macro_export]
macro_rules! common_fields {
() => {
pub id: ColumnDef<Uuid> = ColumnDef::new(sea_orm::sea_query::Column::new("id"))
.primary_key()
.not_null()
.default(sea_orm::sea_query::Expr::cust("gen_random_uuid()")),
pub created_at: ColumnDef<DateTime<Utc>> = ColumnDef::new(sea_orm::sea_query::Column::new("created_at"))
.not_null()
.default(sea_orm::sea_query::Expr::cust("now()")),
pub updated_at: ColumnDef<DateTime<Utc>> = ColumnDef::new(sea_orm::sea_query::Column::new("updated_at"))
.not_null()
.default(sea_orm::sea_query::Expr::cust("now()"))
.extra(sea_orm::sea_query::PostgresExtension::new("GENERATED ALWAYS AS (now()) STORED")),
pub deleted_at: ColumnDef<Option<DateTime<Utc>>> = ColumnDef::new(sea_orm::sea_query::Column::new("deleted_at"))
.default(None),
};
}
@@ -0,0 +1,90 @@
//! 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());
}
}
@@ -0,0 +1,178 @@
//! SeaORM entity for GachaClaims table
//! Corresponding to ResourceEnum::GachaClaims
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelBehavior, DeriveEntityModel, DeriveRelation};
use sea_orm::ActiveValue::Set;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "app_gacha_claims")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(not_null)]
pub user_id: Uuid,
#[sea_orm(not_null)]
pub gacha_item_id: Uuid,
#[sea_orm(not_null)]
pub claim_id: Uuid,
#[sea_orm(not_null)]
pub claim_type: String,
#[sea_orm(not_null)]
pub status: String,
#[sea_orm(default = "0")]
pub quantity: i32,
#[sea_orm(type = "jsonb", nullable)]
pub metadata: Option<serde_json::Value>,
#[sea_orm(not_null, default = "now()")]
pub claimed_at: DateTime<Utc>,
#[sea_orm(not_null, default = "now()")]
pub created_at: DateTime<Utc>,
#[sea_orm(not_null, default = "now()")]
pub updated_at: DateTime<Utc>,
#[sea_orm(nullable)]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Copy, Clone, Debug, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {
}
impl ActiveModelBehavior for ActiveModel {
// Default implementation - SeaORM will handle timestamps automatically
}
// Builder pattern for GachaClaim creation
#[derive(Default, Serialize, Deserialize)]
pub struct GachaClaimBuilder {
user_id: Option<Uuid>,
gacha_item_id: Option<Uuid>,
claim_type: Option<String>,
status: Option<String>,
quantity: Option<i32>,
metadata: Option<serde_json::Value>,
}
impl GachaClaimBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn user_id(mut self, user_id: Uuid) -> Self {
self.user_id = Some(user_id);
self
}
#[must_use]
pub fn gacha_item_id(mut self, gacha_item_id: Uuid) -> Self {
self.gacha_item_id = Some(gacha_item_id);
self
}
#[must_use]
pub fn claim_type(mut self, claim_type: String) -> Self {
self.claim_type = Some(claim_type);
self
}
#[must_use]
pub fn status(mut self, status: String) -> Self {
self.status = Some(status);
self
}
#[must_use]
pub fn quantity(mut self, quantity: i32) -> Self {
self.quantity = Some(quantity);
self
}
#[must_use]
pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn build(self) -> Result<ActiveModel, String> {
let mut active_model = <ActiveModel as std::default::Default>::default();
if let Some(user_id) = self.user_id {
active_model.user_id = Set(user_id);
} else {
return Err("User ID is required".to_string());
}
if let Some(gacha_item_id) = self.gacha_item_id {
active_model.gacha_item_id = Set(gacha_item_id);
} else {
return Err("Gacha Item ID is required".to_string());
}
if let Some(claim_type) = self.claim_type {
active_model.claim_type = Set(claim_type);
} else {
return Err("Claim type is required".to_string());
}
if let Some(status) = self.status {
active_model.status = Set(status);
} else {
return Err("Status is required".to_string());
}
if let Some(quantity) = self.quantity {
active_model.quantity = Set(quantity);
}
if let Some(metadata) = self.metadata {
active_model.metadata = Set(Some(metadata));
}
Ok(active_model)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::seaorm::common::utils::generate_uuid;
#[test]
fn test_gacha_claim_model_creation() {
let user_id = generate_uuid();
let gacha_item_id = generate_uuid();
let claim = GachaClaimBuilder::new()
.user_id(user_id)
.gacha_item_id(gacha_item_id)
.claim_type("direct".to_string())
.status("claimed".to_string())
.quantity(1)
.build();
assert!(claim.is_ok());
let claim_model = claim.unwrap();
assert_eq!(claim_model.user_id, Set(user_id));
assert_eq!(claim_model.gacha_item_id, Set(gacha_item_id));
assert_eq!(claim_model.claim_type, Set("direct".to_string()));
assert_eq!(claim_model.status, Set("claimed".to_string()));
assert_eq!(claim_model.quantity, Set(1));
}
}
@@ -0,0 +1,34 @@
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 = "gacha_credits")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(column_type = "Uuid")]
pub user_id: Uuid,
pub available_rolls: i32,
pub is_deleted: bool,
pub created_at: Option<DateTime>,
pub updated_at: Option<DateTime>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::super::auth::users::Entity",
from = "Column::UserId",
to = "super::super::auth::users::Column::Id"
)]
Users,
}
impl Related<super::super::auth::users::Entity> for Entity {
fn to() -> RelationDef {
Relation::Users.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
@@ -0,0 +1,256 @@
//! SeaORM entity for GachaItems table
//! Corresponding to ResourceEnum::GachaItems
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelBehavior, DeriveEntityModel, DeriveRelation};
use sea_orm::ActiveValue::Set;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "app_gacha_items")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(unique, not_null)]
pub item_code: String,
#[sea_orm(not_null)]
pub name: String,
#[sea_orm(not_null)]
pub description: String,
#[sea_orm(not_null)]
pub rarity: String,
#[sea_orm(not_null)]
pub type_: String,
#[sea_orm(not_null)]
pub category: String,
#[sea_orm(not_null)]
pub value: i32,
#[sea_orm(not_null)]
pub weight: f64,
#[sea_orm(default = "0")]
pub stock: i32,
#[sea_orm(default = "false")]
pub is_limited: bool,
#[sea_orm(type = "jsonb", nullable)]
pub metadata: Option<serde_json::Value>,
#[sea_orm(not_null, default = "now()")]
pub created_at: DateTime<Utc>,
#[sea_orm(not_null, default = "now()")]
pub updated_at: DateTime<Utc>,
#[sea_orm(nullable)]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Copy, Clone, Debug, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {
}
impl ActiveModelBehavior for ActiveModel {
// Default implementation - SeaORM will handle timestamps automatically
}
// Builder pattern for GachaItem creation
#[derive(Default, Serialize, Deserialize)]
pub struct GachaItemBuilder {
item_code: Option<String>,
name: Option<String>,
description: Option<String>,
rarity: Option<String>,
type_: Option<String>,
category: Option<String>,
value: Option<i32>,
weight: Option<f64>,
stock: Option<i32>,
is_limited: Option<bool>,
metadata: Option<serde_json::Value>,
}
impl GachaItemBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn item_code(mut self, item_code: String) -> Self {
self.item_code = Some(item_code);
self
}
#[must_use]
pub fn name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
#[must_use]
pub fn description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
#[must_use]
pub fn rarity(mut self, rarity: String) -> Self {
self.rarity = Some(rarity);
self
}
#[must_use]
pub fn type_(mut self, type_: String) -> Self {
self.type_ = Some(type_);
self
}
#[must_use]
pub fn category(mut self, category: String) -> Self {
self.category = Some(category);
self
}
#[must_use]
pub fn value(mut self, value: i32) -> Self {
self.value = Some(value);
self
}
#[must_use]
pub fn weight(mut self, weight: f64) -> Self {
self.weight = Some(weight);
self
}
#[must_use]
pub fn stock(mut self, stock: i32) -> Self {
self.stock = Some(stock);
self
}
#[must_use]
pub fn is_limited(mut self, is_limited: bool) -> Self {
self.is_limited = Some(is_limited);
self
}
#[must_use]
pub fn metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn build(self) -> Result<ActiveModel, String> {
let mut active_model = <ActiveModel as std::default::Default>::default();
if let Some(item_code) = self.item_code {
active_model.item_code = Set(item_code);
} else {
return Err("Item code is required".to_string());
}
if let Some(name) = self.name {
active_model.name = Set(name);
} else {
return Err("Name is required".to_string());
}
if let Some(description) = self.description {
active_model.description = Set(description);
} else {
return Err("Description is required".to_string());
}
if let Some(rarity) = self.rarity {
active_model.rarity = Set(rarity);
} else {
return Err("Rarity is required".to_string());
}
if let Some(type_) = self.type_ {
active_model.type_ = Set(type_);
} else {
return Err("Type is required".to_string());
}
if let Some(category) = self.category {
active_model.category = Set(category);
} else {
return Err("Category is required".to_string());
}
if let Some(value) = self.value {
active_model.value = Set(value);
} else {
return Err("Value is required".to_string());
}
if let Some(weight) = self.weight {
active_model.weight = Set(weight);
} else {
return Err("Weight is required".to_string());
}
if let Some(stock) = self.stock {
active_model.stock = Set(stock);
}
if let Some(is_limited) = self.is_limited {
active_model.is_limited = Set(is_limited);
}
if let Some(metadata) = self.metadata {
active_model.metadata = Set(Some(metadata));
}
Ok(active_model)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gacha_item_model_creation() {
let item = GachaItemBuilder::new()
.item_code("SWORD_001".to_string())
.name("Legendary Sword".to_string())
.description("A powerful legendary sword".to_string())
.rarity("legendary".to_string())
.type_("weapon".to_string())
.category("sword".to_string())
.value(100)
.weight(0.01)
.stock(10)
.is_limited(true)
.build();
assert!(item.is_ok());
let item_model = item.unwrap();
assert_eq!(item_model.item_code, Set("SWORD_001".to_string()));
assert_eq!(item_model.name, Set("Legendary Sword".to_string()));
assert_eq!(item_model.description, Set("A powerful legendary sword".to_string()));
assert_eq!(item_model.rarity, Set("legendary".to_string()));
assert_eq!(item_model.type_, Set("weapon".to_string()));
assert_eq!(item_model.category, Set("sword".to_string()));
assert_eq!(item_model.value, Set(100));
assert_eq!(item_model.weight, Set(0.01));
assert_eq!(item_model.stock, Set(10));
assert_eq!(item_model.is_limited, Set(true));
}
}
@@ -0,0 +1,50 @@
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 = "gacha_rolls")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(column_type = "Uuid")]
pub user_id: Uuid,
pub gacha_id: String,
#[sea_orm(column_type = "Uuid")]
pub item_id: Uuid,
pub weight: f32,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<DateTime>,
pub updated_at: Option<DateTime>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::gacha_items::Entity",
from = "Column::ItemId",
to = "super::gacha_items::Column::Id"
)]
GachaItems,
#[sea_orm(
belongs_to = "super::super::auth::users::Entity",
from = "Column::UserId",
to = "super::super::auth::users::Column::Id"
)]
Users,
}
impl Related<super::gacha_items::Entity> for Entity {
fn to() -> RelationDef {
Relation::GachaItems.def()
}
}
impl Related<super::super::auth::users::Entity> for Entity {
fn to() -> RelationDef {
Relation::Users.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
+4
View File
@@ -0,0 +1,4 @@
pub mod gacha_credits;
pub mod gacha_rolls;
pub mod gacha_items;
pub mod gacha_claims;
+72
View File
@@ -0,0 +1,72 @@
//! SeaORM entity definitions for Imphenia backend
//! Provides PostgreSQL-compatible entity definitions corresponding to SurrealDB ResourceEnum
pub mod auth;
pub mod gacha;
pub mod common;
pub mod relationships;
pub mod schema_validation;
pub mod examples;
// Re-export specific items from modules for better API clarity
pub use auth::{
users, mentors, roles, permissions, roles_permissions, sessions
};
pub use gacha::{
gacha_items, gacha_claims, gacha_credits, gacha_rolls
};
pub use common::{
ResourceEnum, PgUuid, generate_uuid, current_timestamp,
audit_log, rate_limit, events, testimonials
};
pub use relationships;
pub use schema_validation;
pub use examples;
/// Initialize the SeaORM entity system
/// Should be called once at application startup
pub fn initialize() -> Result<(), String> {
// Perform schema validation on initialization
validate_schema_equivalence()?;
// Initialize any global utilities or configurations
common::utils::initialize_utils();
Ok(())
}
/// Get the table name for a given ResourceEnum
/// Provides a consistent way to access table names across the application
pub fn get_table_name(resource: &common::enums::ResourceEnum) -> &str {
resource.as_str()
}
/// Get the schema name for all entities (default: "public")
pub fn get_schema_name() -> &str {
"public"
}
#[cfg(test)]
mod tests {
use super::*;
use common::enums::ResourceEnum;
#[test]
fn test_table_name_resolution() {
assert_eq!(get_table_name(&ResourceEnum::Users), "app_users");
assert_eq!(get_table_name(&ResourceEnum::Roles), "app_roles");
assert_eq!(get_table_name(&ResourceEnum::GachaItems), "app_gacha_items");
}
#[test]
fn test_schema_name() {
assert_eq!(get_schema_name(), "public");
}
#[test]
fn test_initialize() {
// This should not panic and should return Ok(())
let result = initialize();
assert!(result.is_ok());
}
}
@@ -0,0 +1,100 @@
//! Migration status tracking entity for database migration validation
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use chrono::{Utc, DateTime};
use uuid::Uuid;
// PgUuid and PgTimestamp are not used in this file, but kept for potential future use
// use crate::seaorm::common::types::{PgUuid, PgTimestamp};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
#[sea_orm(table_name = "app_migration_status")]
pub struct Model {
#[sea_orm(primary_key, default = "gen_random_uuid()", auto_increment = false)]
pub id: Uuid,
#[sea_orm(column_type = "Text")]
pub resource_type: String,
#[sea_orm(column_type = "Text")]
pub status: String,
#[sea_orm(column_type = "Json", default = "null")]
pub validation_results: Option<serde_json::Value>,
#[sea_orm(column_type = "Text", default = "null")]
pub last_error: Option<String>,
#[sea_orm(column_type = "Integer", default = 0)]
pub total_records: i32,
#[sea_orm(column_type = "Integer", default = 0)]
pub validated_records: i32,
#[sea_orm(column_type = "Integer", default = 0)]
pub failed_records: i32,
#[sea_orm(column_type = "Integer", default = 0)]
pub skipped_records: i32,
#[sea_orm(column_type = "Text", default = "null")]
pub validation_mode: Option<String>,
#[sea_orm(column_type = "Timestamp", default = "now()")]
pub last_validated_at: DateTime<Utc>,
#[sea_orm(column_type = "Timestamp", default = "now()")]
pub created_at: DateTime<Utc>,
#[sea_orm(column_type = "Timestamp", default = "now()")]
pub updated_at: DateTime<Utc>,
#[sea_orm(column_type = "Timestamp", default = "null")]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Copy, Clone, Debug, sea_orm::EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {
// Default implementation - SeaORM will handle timestamps automatically
}
/// Migration status constants
pub mod status {
pub const PENDING: &str = "pending";
pub const IN_PROGRESS: &str = "in_progress";
pub const COMPLETED: &str = "completed";
pub const FAILED: &str = "failed";
pub const PARTIAL: &str = "partial";
pub const SKIPPED: &str = "skipped";
}
/// Validation mode constants
pub mod validation_mode {
pub const FULL: &str = "full";
pub const INCREMENTAL: &str = "incremental";
pub const QUICK_CHECK: &str = "quick_check";
}
/// Resource type constants matching ResourceEnum
pub mod resource_type {
pub const USERS: &str = "users";
pub const ROLES: &str = "roles";
pub const PERMISSIONS: &str = "permissions";
pub const ROLES_PERMISSIONS: &str = "roles_permissions";
pub const GACHA_ITEMS: &str = "gacha_items";
pub const GACHA_CLAIMS: &str = "gacha_claims";
pub const GACHA_ROLLS: &str = "gacha_rolls";
pub const GACHA_CREDITS: &str = "gacha_credits";
pub const NOTIFICATIONS: &str = "notifications";
pub const AUDIT_LOG: &str = "audit_log";
pub const SESSIONS: &str = "sessions";
pub const OTP_CACHE: &str = "otp_cache";
pub const USERS_CACHE: &str = "users_cache";
pub const RATE_LIMIT: &str = "rate_limit";
pub const TESTIMONIALS: &str = "testimonials";
pub const MENTORS: &str = "mentors";
pub const EVENTS: &str = "events";
}
+8
View File
@@ -0,0 +1,8 @@
// SeaORM entity definitions for Imphenia backend
// This module provides PostgreSQL-compatible entity definitions
// corresponding to the SurrealDB ResourceEnum
pub mod auth;
pub mod gacha;
pub mod common;
pub mod migration_status;