Files
imphnen-backend-service/imphnen-dimentorin/src/materials/infrastructure/http/dto.rs
T
asepharyana ac8177d87e feat(dimentorin): materials module (CRUD konten mentoring, public+protected routes)
- app_materials table: mentor_id, title, slug, category, description, content
- domain/application/infrastructure pola articles: repo postgres, service, DTO ZodValidate
- routes: GET /materials (public, published only), GET /materials/{id|slug|categories}, POST/PUT/DELETE (auth, author-only)
- verified e2e: create 3 materi sebagai mentor, list, slug, categories
2026-08-05 17:01:59 +07:00

138 lines
3.7 KiB
Rust

use paginator_utils::{PaginatorResponse, PaginatorResponseMeta};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
use zod_rs::prelude::*;
use crate::materials::domain::{MaterialEntity, MaterialListItem};
use imphnen_libs::ZodValidate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, ZodSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateMaterialRequest {
#[zod(min_length(3), max_length(200))]
pub title: String,
#[zod(min_length(1), max_length(100))]
pub category: String,
#[zod(min_length(3), max_length(500))]
pub description: String,
#[zod(min_length(10))]
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub cover_url: Option<String>,
}
impl ZodValidate for CreateMaterialRequest {
fn zod_validate(value: &serde_json::Value) -> Result<Self, String> {
Self::validate_and_parse(value).map_err(|e| e.to_string())
}
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, ZodSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateMaterialRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cover_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_published: Option<bool>,
}
impl ZodValidate for UpdateMaterialRequest {
fn zod_validate(value: &serde_json::Value) -> Result<Self, String> {
Self::validate_and_parse(value).map_err(|e| e.to_string())
}
}
#[derive(Serialize, Debug, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MaterialResponse {
pub id: Uuid,
pub mentor_id: Uuid,
pub title: String,
pub slug: String,
pub category: String,
pub description: String,
pub content: String,
pub cover_url: Option<String>,
pub is_published: bool,
pub created_at: String,
pub updated_at: String,
}
impl MaterialResponse {
pub fn from_entity(e: &MaterialEntity) -> Self {
Self {
id: e.id,
mentor_id: e.mentor_id,
title: e.title.clone(),
slug: e.slug.clone(),
category: e.category.clone(),
description: e.description.clone(),
content: e.content.clone(),
cover_url: e.cover_url.clone(),
is_published: e.is_published,
created_at: e.created_at.to_rfc3339(),
updated_at: e.updated_at.to_rfc3339(),
}
}
}
#[derive(Serialize, Debug, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MaterialListItemResponse {
pub id: Uuid,
pub mentor_id: Uuid,
pub title: String,
pub slug: String,
pub category: String,
pub description: String,
pub cover_url: Option<String>,
pub is_published: bool,
pub created_at: String,
pub updated_at: String,
}
impl From<&MaterialEntity> for MaterialListItemResponse {
fn from(e: &MaterialEntity) -> Self {
Self {
id: e.id,
mentor_id: e.mentor_id,
title: e.title.clone(),
slug: e.slug.clone(),
category: e.category.clone(),
description: e.description.clone(),
cover_url: e.cover_url.clone(),
is_published: e.is_published,
created_at: e.created_at.to_rfc3339(),
updated_at: e.updated_at.to_rfc3339(),
}
}
}
impl From<&MaterialListItem> for MaterialListItemResponse {
fn from(e: &MaterialListItem) -> Self {
Self {
id: e.id,
mentor_id: e.mentor_id,
title: e.title.clone(),
slug: e.slug.clone(),
category: e.category.clone(),
description: e.description.clone(),
cover_url: e.cover_url.clone(),
is_published: e.is_published,
created_at: e.created_at.to_rfc3339(),
updated_at: e.updated_at.to_rfc3339(),
}
}
}
pub type MaterialListResponse = PaginatorResponse<MaterialListItemResponse>;