use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema, JsonSchema)]
pub struct ListNotesQuery {
pub tag: Option<String>,
pub search: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
#[schema(example = json!({"title": "My Note", "content": "Hello world", "tags": ["work", "ideas"]}))]
pub struct CreateNoteRequest {
pub title: String,
pub content: String,
#[serde(default)]
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema, JsonSchema)]
pub struct UpdateNoteRequest {
pub title: Option<String>,
pub content: Option<String>,
pub tags: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
pub struct NoteRecord {
pub id: Uuid,
pub workspace_id: Uuid,
pub title: String,
pub content: String,
#[serde(default)]
pub tags: Vec<String>,
pub created_by: Option<Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
pub struct ListNotesResponse {
pub notes: Vec<NoteRecord>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn note_record_round_trips_snake_case() {
let note = NoteRecord {
id: Uuid::nil(),
workspace_id: Uuid::nil(),
title: "t".into(),
content: "c".into(),
tags: vec!["a".into()],
created_by: None,
created_at: DateTime::<Utc>::from_timestamp(0, 0).unwrap(),
updated_at: DateTime::<Utc>::from_timestamp(0, 0).unwrap(),
};
let v = serde_json::to_value(¬e).unwrap();
let obj = v.as_object().unwrap();
for key in [
"id",
"workspace_id",
"created_by",
"created_at",
"updated_at",
] {
assert!(obj.contains_key(key), "missing key `{key}`");
}
let back: NoteRecord = serde_json::from_value(v).unwrap();
assert_eq!(back.title, "t");
assert_eq!(back.tags, vec!["a".to_string()]);
}
#[test]
fn create_note_request_round_trips() {
let req = CreateNoteRequest {
title: "x".into(),
content: "y".into(),
tags: vec!["z".into()],
};
let back: CreateNoteRequest =
serde_json::from_value(serde_json::to_value(&req).unwrap()).unwrap();
assert_eq!(back.title, "x");
assert_eq!(back.tags, vec!["z".to_string()]);
}
#[test]
fn update_note_request_omits_none_friendly() {
let req: UpdateNoteRequest = serde_json::from_value(serde_json::json!({})).unwrap();
assert!(req.title.is_none() && req.content.is_none() && req.tags.is_none());
}
}