@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
use notes_domain::{Note, NoteFilter, Tag};
|
||||
use notes_domain::{Note, Tag};
|
||||
|
||||
/// Request to create a new note
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
@@ -47,17 +47,8 @@ pub struct UpdateNoteRequest {
|
||||
pub struct ListNotesQuery {
|
||||
pub pinned: Option<bool>,
|
||||
pub archived: Option<bool>,
|
||||
pub tag: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl From<ListNotesQuery> for NoteFilter {
|
||||
fn from(query: ListNotesQuery) -> Self {
|
||||
let mut filter = NoteFilter::new();
|
||||
filter.is_pinned = query.pinned;
|
||||
filter.is_archived = query.archived;
|
||||
filter.tag_id = query.tag;
|
||||
filter
|
||||
}
|
||||
/// Tag name to filter by (will be looked up by route handler)
|
||||
pub tag: Option<String>,
|
||||
}
|
||||
|
||||
/// Query parameters for search
|
||||
@@ -119,6 +110,13 @@ pub struct CreateTagRequest {
|
||||
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, Validate)]
|
||||
pub struct LoginRequest {
|
||||
|
||||
@@ -36,5 +36,8 @@ pub fn api_v1_router() -> Router<AppState> {
|
||||
.route("/import", post(import_export::import_data))
|
||||
// Tag routes
|
||||
.route("/tags", get(tags::list_tags).post(tags::create_tag))
|
||||
.route("/tags/{id}", delete(tags::delete_tag))
|
||||
.route(
|
||||
"/tags/{id}",
|
||||
delete(tags::delete_tag).patch(tags::rename_tag),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,9 +33,23 @@ pub async fn list_notes(
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
// Build the filter, looking up tag_id by name if needed
|
||||
let mut filter = notes_domain::NoteFilter::new();
|
||||
filter.is_pinned = query.pinned;
|
||||
filter.is_archived = query.archived;
|
||||
|
||||
let notes = service.list_notes(user_id, query.into()).await?;
|
||||
// Look up tag by name if provided
|
||||
if let Some(ref tag_name) = query.tag {
|
||||
if let Ok(Some(tag)) = state.tag_repo.find_by_name(user_id, tag_name).await {
|
||||
filter.tag_id = Some(tag.id);
|
||||
} else {
|
||||
// Tag not found, return empty results
|
||||
return Ok(Json(vec![]));
|
||||
}
|
||||
}
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
let notes = service.list_notes(user_id, filter).await?;
|
||||
let response: Vec<NoteResponse> = notes.into_iter().map(NoteResponse::from).collect();
|
||||
|
||||
Ok(Json(response))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Tag route handlers
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use axum_login::{AuthSession, AuthUser};
|
||||
use uuid::Uuid;
|
||||
@@ -12,7 +12,7 @@ use validator::Validate;
|
||||
use notes_domain::TagService;
|
||||
|
||||
use crate::auth::AuthBackend;
|
||||
use crate::dto::{CreateTagRequest, TagResponse};
|
||||
use crate::dto::{CreateTagRequest, RenameTagRequest, TagResponse};
|
||||
use crate::error::{ApiError, ApiResult};
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -22,14 +22,18 @@ pub async fn list_tags(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<AuthBackend>,
|
||||
) -> ApiResult<Json<Vec<TagResponse>>> {
|
||||
let user = auth.user.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized("Login required".to_string())))?;
|
||||
let user = auth
|
||||
.user
|
||||
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
|
||||
"Login required".to_string(),
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
|
||||
let tags = service.list_tags(user_id).await?;
|
||||
let response: Vec<TagResponse> = tags.into_iter().map(TagResponse::from).collect();
|
||||
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
@@ -40,18 +44,50 @@ pub async fn create_tag(
|
||||
auth: AuthSession<AuthBackend>,
|
||||
Json(payload): Json<CreateTagRequest>,
|
||||
) -> ApiResult<(StatusCode, Json<TagResponse>)> {
|
||||
let user = auth.user.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized("Login required".to_string())))?;
|
||||
let user = auth
|
||||
.user
|
||||
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
|
||||
"Login required".to_string(),
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
payload.validate().map_err(|e| ApiError::validation(e.to_string()))?;
|
||||
|
||||
payload
|
||||
.validate()
|
||||
.map_err(|e| ApiError::validation(e.to_string()))?;
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
|
||||
let tag = service.create_tag(user_id, &payload.name).await?;
|
||||
|
||||
|
||||
Ok((StatusCode::CREATED, Json(TagResponse::from(tag))))
|
||||
}
|
||||
|
||||
/// Rename a tag
|
||||
/// PATCH /api/v1/tags/:id
|
||||
pub async fn rename_tag(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<AuthBackend>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(payload): Json<RenameTagRequest>,
|
||||
) -> ApiResult<Json<TagResponse>> {
|
||||
let user = auth
|
||||
.user
|
||||
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
|
||||
"Login required".to_string(),
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
payload
|
||||
.validate()
|
||||
.map_err(|e| ApiError::validation(e.to_string()))?;
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
let tag = service.rename_tag(id, user_id, &payload.name).await?;
|
||||
|
||||
Ok(Json(TagResponse::from(tag)))
|
||||
}
|
||||
|
||||
/// Delete a tag
|
||||
/// DELETE /api/v1/tags/:id
|
||||
pub async fn delete_tag(
|
||||
@@ -59,12 +95,16 @@ pub async fn delete_tag(
|
||||
auth: AuthSession<AuthBackend>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> ApiResult<StatusCode> {
|
||||
let user = auth.user.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized("Login required".to_string())))?;
|
||||
let user = auth
|
||||
.user
|
||||
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
|
||||
"Login required".to_string(),
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
|
||||
service.delete_tag(id, user_id).await?;
|
||||
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user