ad ded migration

This commit is contained in:
2025-02-07 23:30:27 +01:00
parent 19eff26934
commit 6d08c39a6d
19 changed files with 6521 additions and 2 deletions

34
src/base/controller.rs Normal file
View File

@@ -0,0 +1,34 @@
use crate::{base::service::BaseService, utils::errors::AppError};
use axum::{http::StatusCode, response::IntoResponse, Json};
use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, ModelTrait};
pub struct BaseController<Entity, Model, ActiveModel>
where
Entity: EntityTrait,
Model: ModelTrait + Send + Sync + 'static,
ActiveModel: ActiveModelTrait + Send + Sync + 'static,
{
service: BaseService<Entity, Model, ActiveModel>,
}
impl<Entity, Model, ActiveModel> BaseController<Entity, Model, ActiveModel>
where
Entity: EntityTrait,
Model: ModelTrait + Send + Sync + 'static,
ActiveModel: ActiveModelTrait + Send + Sync + 'static,
{
pub fn new() -> Self {
Self {
service: BaseService::new(),
}
}
pub async fn create(
&self,
state: State<DatabaseConnection>,
Json(data): Json<ActiveModel>,
) -> Result<IntoResponse, AppError> {
let model = self.service.create(&state, data).await?;
Ok((StatusCode::CREATED, Json(model)))
}
}

10
src/base/entity.rs Normal file
View File

@@ -0,0 +1,10 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)]
pub struct BaseEntity {
#[sea_orm(primary_key)]
pub id: i32,
pub created_at: DateTime,
pub updated_at: DateTime,
}

22
src/base/i_service.rs Normal file
View File

@@ -0,0 +1,22 @@
use crate::utils::errors::AppError;
use async_trait::async_trait;
use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, ModelTrait};
#[async_trait]
pub trait IService<Entity, Model, ActiveModel>
where
Entity: EntityTrait,
Model: ModelTrait + Send + Sync,
ActiveModel: ActiveModelTrait + Send + Sync,
{
async fn create(&self, db: &DatabaseConnection, data: ActiveModel) -> Result<Model, AppError>;
async fn get_by_id(&self, db: &DatabaseConnection, id: i32) -> Result<Option<Model>, AppError>;
async fn get_all(&self, db: &DatabaseConnection) -> Result<Vec<Model>, AppError>;
async fn update(
&self,
db: &DatabaseConnection,
id: i32,
data: ActiveModel,
) -> Result<Model, AppError>;
async fn delete(&self, db: &DatabaseConnection, id: i32) -> Result<(), AppError>;
}

4
src/base/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod controller;
pub mod entity;
pub mod i_service;
pub mod service;

84
src/base/service.rs Normal file
View File

@@ -0,0 +1,84 @@
use crate::base::i_service::IService;
use crate::utils::errors::AppError;
use async_trait::async_trait;
use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, Order, QueryFilter,
QueryOrder, Set,
};
pub struct BaseService<Entity, Model, ActiveModel>
where
Entity: EntityTrait,
Model: ModelTrait + Send + Sync + 'static,
ActiveModel: ActiveModelTrait + Send + Sync + 'static,
{
entity: std::marker::PhantomData<Entity>,
}
impl<Entity, Model, ActiveModel> BaseService<Entity, Model, ActiveModel>
where
Entity: EntityTrait,
Model: ModelTrait + Send + Sync + 'static,
ActiveModel: ActiveModelTrait + Send + Sync + 'static,
{
pub fn new() -> Self {
Self {
entity: std::marker::PhantomData,
}
}
}
#[async_trait]
impl<Entity, Model, ActiveModel> IService<Entity, Model, ActiveModel>
for BaseService<Entity, Model, ActiveModel>
where
Entity: EntityTrait,
Model: ModelTrait + Send + Sync + 'static,
ActiveModel: ActiveModelTrait + Send + Sync + 'static,
{
async fn create(&self, db: &DatabaseConnection, data: ActiveModel) -> Result<Model, AppError> {
Ok(data.insert(db).await?)
}
async fn get_by_id(&self, db: &DatabaseConnection, id: i32) -> Result<Option<Model>, AppError> {
Ok(Entity::find_by_id(id).one(db).await?)
}
async fn get_all(&self, db: &DatabaseConnection) -> Result<Vec<Model>, AppError> {
Ok(Entity::find().all(db).await?)
}
async fn update(
&self,
db: &DatabaseConnection,
id: i32,
data: ActiveModel,
) -> Result<Model, AppError> {
let model = Entity::find_by_id(id)
.one(db)
.await?
.ok_or(AppError::NotFound)?;
let mut active_model: ActiveModel = model.into();
// Update fields individually, handling Optionals correctly:
// Example: Assume your Model has a 'name' field.
if let Some(name) = data.get_name() {
// Assuming ActiveModel has get_name()
active_model.set_name(name); // And set_name()
}
if let Some(description) = data.get_description() {
active_model.set_description(description);
}
// ... update other fields similarly ...
Ok(active_model.update(db).await?)
}
async fn delete(&self, db: &DatabaseConnection, id: i32) -> Result<(), AppError> {
let model = Entity::find_by_id(id)
.one(db)
.await?
.ok_or(AppError::NotFound)?;
Ok(model.delete(db).await?)
}
}

View File

@@ -0,0 +1,61 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use crate::base::entity::BaseEntity;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "product")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub description: Option<String>,
// ... other product-specific fields ...
pub created_at: DateTime,
pub updated_at: DateTime,
}
#[derive(Copy, Clone, Debug, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
// ActiveModel - CORRECT STRUCTURE (No DeriveModel!)
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActiveModel {
pub id: sea_orm::ActiveValue<i32>,
pub name: sea_orm::ActiveValue<String>,
pub description: sea_orm::ActiveValue<Option<String>>,
// ... other fields ...
pub created_at: sea_orm::ActiveValue<DateTime>,
pub updated_at: sea_orm::ActiveValue<DateTime>,
}
impl Entity {
pub fn find_by_id(id: i32) -> Select<'static> {
Self::find().filter(Column::Id.eq(id))
}
}
// Implement get and set methods for the ActiveModel
impl ActiveModel {
pub fn get_name(&self) -> Option<String> {
match self.name {
sea_orm::ActiveValue::Set(val) => Some(val),
_ => None
}
}
pub fn set_name(&mut self, val: String) {
self.name = sea_orm::Set(val);
}
pub fn get_description(&self) -> Option<String> {
match self.description {
sea_orm::ActiveValue::Set(val) => Some(val),
_ => None
}
}
pub fn set_description(&mut self, val: String) {
self.description = sea_orm::Set(val);
}
}

73
src/utils/errors.rs Normal file
View File

@@ -0,0 +1,73 @@
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use sea_orm::error::DbErr;
use serde_json::json;
use std::error::Error as StdError;
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("Not Found")]
NotFound,
#[error("Bad Request: {0}")]
BadRequest(String),
#[error("Unauthorized")]
Unauthorized,
#[error("Internal Server Error: {0}")]
InternalServerError(String),
#[error("Database Error: {0}")]
DatabaseError(#[from] DbErr),
#[error("Other Error: {0}")]
Other(#[from] anyhow::Error), // For other errors using anyhow
}
// Implement the IntoResponse trait for AppError
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::NotFound => (StatusCode::NOT_FOUND, "Not Found".to_string()),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".to_string()),
AppError::InternalServerError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
AppError::DatabaseError(err) => {
// Log the database error for debugging (important!)
eprintln!("Database Error: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Database error occurred".to_string(),
)
}
AppError::Other(err) => {
eprintln!("Other Error: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
"An unexpected error occurred".to_string(),
)
}
};
let body = Json(json!({
"error": message,
}));
(status, body).into_response()
}
}
// You can create a From trait implementation if you have a custom error type
// and want to convert it into AppError. For example, if you had a
// `MyCustomError`:
// impl From<MyCustomError> for AppError {
// fn from(err: MyCustomError) -> Self {
// AppError::InternalServerError(err.to_string()) // Or map to a more specific AppError
// }
// }
// Example usage in your service:
// async fn my_service_function(...) -> Result<..., AppError> {
// let result = some_fallible_function().map_err(|e| AppError::BadRequest(e.to_string()))?;
// // ...
// }

1
src/utils/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod errors;