//! Request and Response DTOs for notes API use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use validator::Validate; use notes_domain::{Email, Note, Password, Tag}; use crate::config::AuthMode; /// Request to create a new note #[derive(Debug, Deserialize, Validate)] pub struct CreateNoteRequest { #[validate(length(max = 200, message = "Title must be at most 200 characters"))] pub title: String, #[serde(default)] pub content: String, #[serde(default)] #[validate(length(max = 10, message = "Maximum 10 tags allowed"))] pub tags: Vec, pub color: Option, #[serde(default)] pub is_pinned: bool, } /// Request to update an existing note (all fields optional) #[derive(Debug, Deserialize, Validate)] pub struct UpdateNoteRequest { #[validate(length(max = 200, message = "Title must be at most 200 characters"))] pub title: Option, pub content: Option, #[validate(length(max = 10, message = "Maximum 10 tags allowed"))] pub tags: Option>, pub color: Option, pub is_pinned: Option, pub is_archived: Option, } /// Query parameters for listing notes #[derive(Debug, Deserialize, Default)] pub struct ListNotesQuery { pub pinned: Option, pub archived: Option, /// Tag name to filter by (will be looked up by route handler) pub tag: Option, } /// Query parameters for search #[derive(Debug, Deserialize)] pub struct SearchQuery { pub q: String, } /// Tag response DTO #[derive(Debug, Serialize)] pub struct TagResponse { pub id: Uuid, pub name: String, } impl From for TagResponse { fn from(tag: Tag) -> Self { Self { id: tag.id, name: tag.name.into_inner(), // Convert TagName to String } } } /// Note response DTO #[derive(Debug, Serialize)] pub struct NoteResponse { pub id: Uuid, pub title: String, pub content: String, pub color: String, pub is_pinned: bool, pub is_archived: bool, pub created_at: DateTime, pub updated_at: DateTime, pub tags: Vec, } impl From for NoteResponse { fn from(note: Note) -> Self { Self { id: note.id, title: note.title_str().to_string(), // Convert Option to String content: note.content, color: note.color, is_pinned: note.is_pinned, is_archived: note.is_archived, created_at: note.created_at, updated_at: note.updated_at, tags: note.tags.into_iter().map(TagResponse::from).collect(), } } } /// Request to create a new tag #[derive(Debug, Deserialize, Validate)] pub struct CreateTagRequest { #[validate(length(min = 1, max = 50, message = "Tag name must be 1-50 characters"))] pub name: String, } /// Request to rename a tag #[derive(Debug, Deserialize, Validate)] pub struct RenameTagRequest { #[validate(length(min = 1, max = 50, message = "Tag name must be 1-50 characters"))] pub name: String, } /// Login request #[derive(Debug, Deserialize)] pub struct LoginRequest { pub email: Email, pub password: Password, } /// Register request #[derive(Debug, Deserialize)] pub struct RegisterRequest { pub email: Email, pub password: Password, } /// User response DTO #[derive(Debug, Serialize)] pub struct UserResponse { pub id: Uuid, pub email: Email, pub created_at: DateTime, } /// Note Version response DTO #[derive(Debug, Serialize)] pub struct NoteVersionResponse { pub id: Uuid, pub note_id: Uuid, pub title: String, pub content: String, pub created_at: DateTime, } impl From for NoteVersionResponse { fn from(version: notes_domain::NoteVersion) -> Self { Self { id: version.id, note_id: version.note_id, title: version.title.unwrap_or_default(), content: version.content, created_at: version.created_at, } } } /// System configuration response #[derive(Debug, Serialize)] pub struct ConfigResponse { pub allow_registration: bool, pub auth_mode: AuthMode, pub oidc_enabled: bool, pub password_login_enabled: bool, } /// Note Link response DTO #[derive(Debug, Serialize)] pub struct NoteLinkResponse { pub source_note_id: Uuid, pub target_note_id: Uuid, pub score: f32, pub created_at: DateTime, } impl From for NoteLinkResponse { fn from(link: notes_domain::entities::NoteLink) -> Self { Self { source_note_id: link.source_note_id, target_note_id: link.target_note_id, score: link.score, created_at: link.created_at, } } }