- 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
138 lines
3.7 KiB
Rust
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>;
|