#![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use axum::http::HeaderMap; use loco_rs::prelude::*; use sea_orm::{ColumnTrait, QueryFilter, QueryOrder}; use serde::{Deserialize, Serialize}; use crate::controllers::admin::check_auth; use crate::models::_entities::friend_links::{ActiveModel, Column, Entity, Model}; use crate::services::{admin_audit, notifications}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Params { pub site_name: Option, pub site_url: String, pub avatar_url: Option, pub description: Option, pub category: Option, pub status: Option, } impl Params { fn update(&self, item: &mut ActiveModel) { item.site_url = Set(self.site_url.clone()); if let Some(site_name) = &self.site_name { item.site_name = Set(Some(site_name.clone())); } if let Some(avatar_url) = &self.avatar_url { item.avatar_url = Set(Some(avatar_url.clone())); } if let Some(description) = &self.description { item.description = Set(Some(description.clone())); } if let Some(category) = &self.category { item.category = Set(Some(category.clone())); } if let Some(status) = &self.status { item.status = Set(Some(status.clone())); } } } #[derive(Clone, Debug, Default, Deserialize)] pub struct ListQuery { pub status: Option, pub category: Option, } #[derive(Clone, Debug, Deserialize)] pub struct CreateFriendLinkRequest { #[serde(default, alias = "siteName")] pub site_name: Option, #[serde(alias = "siteUrl")] pub site_url: String, #[serde(default, alias = "avatarUrl")] pub avatar_url: Option, #[serde(default)] pub description: Option, #[serde(default)] pub category: Option, #[serde(default)] pub status: Option, } async fn load_item(ctx: &AppContext, id: i32) -> Result { let item = Entity::find_by_id(id).one(&ctx.db).await?; item.ok_or_else(|| Error::NotFound) } #[debug_handler] pub async fn list( Query(query): Query, State(ctx): State, headers: HeaderMap, ) -> Result { let authenticated = check_auth(&headers).ok(); let mut db_query = Entity::find().order_by_desc(Column::CreatedAt); if let Some(status) = query.status { db_query = db_query.filter(Column::Status.eq(status)); } else if authenticated.is_none() { db_query = db_query.filter(Column::Status.eq("approved")); } if let Some(category) = query.category { db_query = db_query.filter(Column::Category.eq(category)); } format::json(db_query.all(&ctx.db).await?) } #[debug_handler] pub async fn add( State(ctx): State, Json(params): Json, ) -> Result { let mut item = ActiveModel { ..Default::default() }; item.site_name = Set(params.site_name); item.site_url = Set(params.site_url); item.avatar_url = Set(params.avatar_url); item.description = Set(params.description); item.category = Set(params.category); item.status = Set(Some(params.status.unwrap_or_else(|| "pending".to_string()))); let item = item.insert(&ctx.db).await?; notifications::notify_new_friend_link(&ctx, &item).await; format::json(item) } #[debug_handler] pub async fn update( headers: HeaderMap, Path(id): Path, State(ctx): State, Json(params): Json, ) -> Result { let actor = check_auth(&headers)?; let item = load_item(&ctx, id).await?; let mut item = item.into_active_model(); params.update(&mut item); let item = item.update(&ctx.db).await?; admin_audit::log_event( &ctx, Some(&actor), "friend_link.update", "friend_link", Some(item.id.to_string()), item.site_name.clone().or_else(|| Some(item.site_url.clone())), Some(serde_json::json!({ "status": item.status })), ) .await?; format::json(item) } #[debug_handler] pub async fn remove( headers: HeaderMap, Path(id): Path, State(ctx): State, ) -> Result { let actor = check_auth(&headers)?; let item = load_item(&ctx, id).await?; let label = item.site_name.clone().or_else(|| Some(item.site_url.clone())); item.delete(&ctx.db).await?; admin_audit::log_event( &ctx, Some(&actor), "friend_link.delete", "friend_link", Some(id.to_string()), label, None, ) .await?; format::empty() } #[debug_handler] pub async fn get_one( headers: HeaderMap, Path(id): Path, State(ctx): State, ) -> Result { check_auth(&headers)?; format::json(load_item(&ctx, id).await?) } pub fn routes() -> Routes { Routes::new() .prefix("api/friend_links/") .add("/", get(list)) .add("/", post(add)) .add("{id}", get(get_one)) .add("{id}", delete(remove)) .add("{id}", put(update)) .add("{id}", patch(update)) }