use axum::{ extract::{Path, Query, State}, http::StatusCode, response::Json, routing::{get, post, put, delete}, Router, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; use utoipa::{ToSchema, IntoParams}; use uuid::Uuid; use chrono::{DateTime, Utc}; use sqlx::{FromRow, Row}; use crate::{auth::AuthUser, AppState}; #[derive(Debug, Clone, Serialize, Deserialize, FromRow, ToSchema)] pub struct Label { pub id: Uuid, pub user_id: Option, // nullable for system labels pub name: String, pub description: Option, pub color: String, pub background_color: Option, pub icon: Option, pub is_system: bool, pub created_at: DateTime, pub updated_at: DateTime, #[serde(default)] pub document_count: i64, #[serde(default)] pub source_count: i64, } #[derive(Debug, Deserialize, ToSchema)] pub struct CreateLabel { pub name: String, pub description: Option, #[serde(default = "default_color")] pub color: String, pub background_color: Option, pub icon: Option, } fn default_color() -> String { "#0969da".to_string() } #[derive(Debug, Deserialize, ToSchema)] pub struct UpdateLabel { pub name: Option, pub description: Option, pub color: Option, pub background_color: Option, pub icon: Option, } #[derive(Debug, Deserialize, ToSchema)] pub struct LabelAssignment { pub label_ids: Vec, } #[derive(Debug, Deserialize, ToSchema, IntoParams)] pub struct LabelQuery { #[serde(default)] pub include_counts: bool, } #[derive(Debug, Deserialize, ToSchema)] pub struct BulkUpdateRequest { pub document_ids: Vec, pub label_ids: Vec, #[serde(default = "default_bulk_mode")] pub mode: String, // "replace", "add", or "remove" } fn default_bulk_mode() -> String { "replace".to_string() } pub fn router() -> Router> { Router::new() .route("/", get(get_labels)) .route("/", post(create_label)) .route("/{id}", get(get_label)) .route("/{id}", put(update_label)) .route("/{id}", delete(delete_label)) .route("/documents/{document_id}", get(get_document_labels)) .route("/documents/{document_id}", put(update_document_labels)) .route("/documents/{document_id}/labels/{label_id}", post(add_document_label)) .route("/documents/{document_id}/labels/{label_id}", delete(remove_document_label)) .route("/bulk/documents", post(bulk_update_document_labels)) } #[utoipa::path( get, path = "/api/labels", tag = "labels", security(("bearer_auth" = [])), params(LabelQuery), responses( (status = 200, description = "List of labels", body = Vec