M2a: server workspace, kvitt-domain, skeleton routes, OpenAPI match, Dockerfile
Cargo workspace (kvitt-domain, kvitt-server, kvitt-import), Rust 1.99.1 pinned. kvitt-domain passes every Rust vector and adds next_occurrence_date (D-015). kvitt-server: config, SQLite pool (WAL, §8.9 pragmas), migration 0001 identical to the contract schema, error type for all 25 codes, auth extractor and protocol layer, all 33 routes (E-1/E-2 implemented, the rest 501), utoipa document equal to contracts/openapi.yaml, seams for M3/M4/M9, serve/migrate/healthcheck. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
//! Category objects (SPEC §14.3.3, §14.3.4 E-23, E-24).
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{ServerVersionSchema, TimestampSchema, UuidSchema};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DefaultCategoryKey {
|
||||
Groceries,
|
||||
Restaurants,
|
||||
Home,
|
||||
Utilities,
|
||||
Transport,
|
||||
Travel,
|
||||
Entertainment,
|
||||
Health,
|
||||
Shopping,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
pub struct Category {
|
||||
#[schema(value_type = UuidSchema)]
|
||||
pub id: Uuid,
|
||||
#[schema(required = true)]
|
||||
pub default_key: Option<DefaultCategoryKey>,
|
||||
#[schema(required = true)]
|
||||
pub name: Option<String>,
|
||||
/// An icon token of SPEC §9.7.
|
||||
pub icon: String,
|
||||
/// A colour token of SPEC §9.7.
|
||||
pub color: String,
|
||||
#[schema(schema_with = super::int)]
|
||||
pub sort_order: i64,
|
||||
#[schema(value_type = TimestampSchema)]
|
||||
pub created_at: String,
|
||||
#[schema(value_type = TimestampSchema)]
|
||||
pub updated_at: String,
|
||||
#[schema(value_type = Option<TimestampSchema>, required = true)]
|
||||
pub archived_at: Option<String>,
|
||||
#[schema(value_type = Option<TimestampSchema>, required = true)]
|
||||
pub deleted_at: Option<String>,
|
||||
#[schema(value_type = ServerVersionSchema)]
|
||||
pub server_version: i64,
|
||||
}
|
||||
|
||||
/// E-23 `POST /categories`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateCategoryRequest {
|
||||
pub name: String,
|
||||
pub icon: String,
|
||||
pub color: String,
|
||||
}
|
||||
|
||||
/// E-24 `PATCH /categories/{category_id}`. `name`: absent = unchanged (`None`), `null` = reset a default category's
|
||||
/// name (`Some(None)`).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[schema(min_properties = 1)]
|
||||
pub struct PatchCategoryRequest {
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "super::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
#[schema(value_type = Option<String>, required = false)]
|
||||
pub name: Option<Option<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schema(value_type = String)]
|
||||
pub icon: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schema(value_type = String)]
|
||||
pub color: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[schema(schema_with = super::int)]
|
||||
pub sort_order: Option<i64>,
|
||||
}
|
||||
Reference in New Issue
Block a user