//! Category and tag models. use chrono::{DateTime, Utc}; use sqlx::FromRow; use super::super::id_types::{CategoryId, ItemId, TagId}; use super::super::validated_types::Slug; // ── Category models ── /// A project category for discover-page filtering. #[derive(Debug, Clone, FromRow)] #[allow(dead_code)] // Fields populated by sqlx query, read during type conversion pub struct DbCategory { pub id: CategoryId, pub name: String, pub slug: Slug, pub created_at: DateTime, } /// A category with a count of public projects, for discover sidebar facets. #[derive(Debug, Clone, FromRow)] #[allow(dead_code)] // Fields populated by sqlx query, read during type conversion pub struct DbCategoryCount { pub name: String, pub slug: Slug, pub count: i64, } // ── Tag models ── /// A tag in the hierarchical taxonomy. /// /// Tag slugs use dot-notation (`audio.genre.electronic`) and are validated /// by tagtree, not the general `Slug` type. #[derive(Debug, Clone, FromRow)] #[allow(dead_code)] // Fields populated by sqlx query, read during type conversion pub struct DbTag { pub id: TagId, pub name: String, pub slug: String, pub parent_id: Option, pub sort_order: i32, pub created_at: DateTime, pub path: String, } /// A tag attached to an item, with joined tag name/slug for display. #[derive(Debug, Clone, FromRow)] #[allow(dead_code)] // Fields populated by sqlx query, read during type conversion pub struct DbItemTag { pub item_id: ItemId, pub tag_id: TagId, pub is_primary: bool, pub tag_name: String, pub tag_slug: String, } /// Tag with item count, used for discover sidebar facets. #[derive(Debug, Clone, FromRow)] #[allow(dead_code)] // Fields populated by sqlx query, read during type conversion pub struct DbTagCount { pub tag_id: TagId, pub tag_name: String, pub tag_slug: String, pub count: i64, } /// One rung of the discover sidebar's tag drill-down: an immediate child of the /// browse cursor, with the number of matching items anywhere in its subtree. /// /// `count` is a subtree total, not a direct-assignment total, because the /// drill-down's promise is "pick this and you get that many". Items are only /// ever tagged with depth-3+ leaves, so a category's direct count is always /// zero and would render a sidebar full of noise. #[derive(Debug, Clone, FromRow)] pub struct DbTagChild { pub tag_id: TagId, pub tag_name: String, pub tag_slug: String, pub count: i64, /// True once this tag is deep enough to be assigned to an item, and /// therefore to act as a filter rather than only as a navigation rung. pub assignable: bool, /// True when this tag has children of its own, so the UI knows whether /// selecting it should also offer to drill further. pub has_children: bool, }