use serde::{Deserialize, Serialize};
#[cfg(feature = "export-types")]
use ts_rs::TS;
use crate::errors::app_error::{AppError, AppResult};
use crate::types::snowflake_id::SnowflakeId;
use crate::utils::tz::Timestamp;
#[cfg_attr(feature = "export-types", derive(TS))]
#[derive(Debug, Serialize, Deserialize, Clone, sqlx::FromRow)]
pub struct Tag {
pub id: SnowflakeId,
pub tenant_id: Option<String>,
pub name: String,
pub slug: String,
pub description: Option<String>,
pub cover_image: Option<String>,
pub meta_title: Option<String>,
pub meta_description: Option<String>,
pub og_title: Option<String>,
pub og_description: Option<String>,
pub og_image: Option<String>,
pub created_by: Option<SnowflakeId>,
pub updated_by: Option<SnowflakeId>,
pub created_at: Timestamp,
pub updated_at: Timestamp,
}
pub async fn find_all(pool: &crate::db::Pool, tenant_id: Option<&str>) -> AppResult<Vec<Tag>> {
raisfast_derive::crud_list!(pool, "tags", Tag, order_by: "name", tenant: tenant_id)
.map_err(Into::into)
}
pub async fn find_paginated(
pool: &crate::db::Pool,
tenant_id: Option<&str>,
page: i64,
page_size: i64,
) -> AppResult<(Vec<Tag>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Tag,
table: "tags",
order_by: "name",
tenant: tenant_id,
page: page,
page_size: page_size
);
Ok(result)
}
pub async fn find_by_id(
pool: &crate::db::Pool,
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Tag> {
raisfast_derive::crud_find_one!(pool, "tags", Tag, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
pub async fn create(
pool: &crate::db::Pool,
name: &str,
slug: &str,
tenant_id: Option<&str>,
created_by: Option<i64>,
) -> AppResult<Tag> {
let (id, now) = (
crate::utils::id::new_snowflake_id(),
crate::utils::tz::now_utc(),
);
raisfast_derive::crud_insert!(
pool,
"tags",
[
"id" => id,
"name" => name,
"slug" => slug,
"created_by" => created_by,
"updated_by" => created_by,
"created_at" => &now,
"updated_at" => &now
],
tenant: tenant_id
)?;
find_by_id(pool, id, tenant_id).await
}
pub async fn update(
pool: &crate::db::Pool,
id: SnowflakeId,
name: &str,
slug: &str,
tenant_id: Option<&str>,
) -> AppResult<Tag> {
let now = crate::utils::tz::now_utc();
let result = raisfast_derive::crud_update!(pool, "tags",
bind: ["name" => name, "slug" => slug, "updated_at" => &now],
where: ("id", id),
tenant: tenant_id
)?;
AppError::expect_affected(&result, "tag")?;
find_by_id(pool, id, tenant_id).await
}
pub async fn delete(
pool: &crate::db::Pool,
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "tags", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "tag")
}
#[cfg(test)]
mod tests {
use super::*;
async fn setup_pool() -> crate::db::Pool {
crate::test_pool!()
}
#[tokio::test]
async fn create_and_find_by_id() {
let pool = setup_pool().await;
let tag = create(&pool, "rust", "rust", None, None).await.unwrap();
assert_eq!(tag.name, "rust");
assert_eq!(tag.slug, "rust");
let found = find_by_id(&pool, tag.id, None).await.unwrap();
assert_eq!(found.id, tag.id);
assert_eq!(found.name, "rust");
}
#[tokio::test]
async fn find_all_returns_all() {
let pool = setup_pool().await;
create(&pool, "rust", "rust", None, None).await.unwrap();
create(&pool, "axum", "axum", None, None).await.unwrap();
create(&pool, "tokio", "tokio", None, None).await.unwrap();
let tags = find_all(&pool, None).await.unwrap();
assert_eq!(tags.len(), 3);
}
#[tokio::test]
async fn find_paginated() {
let pool = setup_pool().await;
create(&pool, "rust", "rust", None, None).await.unwrap();
create(&pool, "axum", "axum", None, None).await.unwrap();
create(&pool, "tokio", "tokio", None, None).await.unwrap();
create(&pool, "serde", "serde", None, None).await.unwrap();
create(&pool, "clap", "clap", None, None).await.unwrap();
let (items, total) = super::find_paginated(&pool, None, 1, 3).await.unwrap();
assert_eq!(total, 5);
assert_eq!(items.len(), 3);
}
#[tokio::test]
async fn update_changes_name() {
let pool = setup_pool().await;
let tag = create(&pool, "rust", "rust", None, None).await.unwrap();
let updated = update(&pool, tag.id, "Rust Lang", "rust-lang", None)
.await
.unwrap();
assert_eq!(updated.name, "Rust Lang");
assert_eq!(updated.slug, "rust-lang");
assert_eq!(updated.id, tag.id);
}
#[tokio::test]
async fn delete_removes_tag() {
let pool = setup_pool().await;
let tag = create(&pool, "rust", "rust", None, None).await.unwrap();
delete(&pool, tag.id, None).await.unwrap();
let result = find_by_id(&pool, tag.id, None).await;
assert!(result.is_err());
}
}