#![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use loco_rs::prelude::*; use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel, QueryOrder, Set}; use serde::{Deserialize, Serialize}; use crate::models::_entities::site_settings::{self, ActiveModel, Entity, Model}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct SiteSettingsPayload { #[serde(default, alias = "siteName")] pub site_name: Option, #[serde(default, alias = "siteShortName")] pub site_short_name: Option, #[serde(default, alias = "siteUrl")] pub site_url: Option, #[serde(default, alias = "siteTitle")] pub site_title: Option, #[serde(default, alias = "siteDescription")] pub site_description: Option, #[serde(default, alias = "heroTitle")] pub hero_title: Option, #[serde(default, alias = "heroSubtitle")] pub hero_subtitle: Option, #[serde(default, alias = "ownerName")] pub owner_name: Option, #[serde(default, alias = "ownerTitle")] pub owner_title: Option, #[serde(default, alias = "ownerBio")] pub owner_bio: Option, #[serde(default, alias = "ownerAvatarUrl")] pub owner_avatar_url: Option, #[serde(default, alias = "socialGithub")] pub social_github: Option, #[serde(default, alias = "socialTwitter")] pub social_twitter: Option, #[serde(default, alias = "socialEmail")] pub social_email: Option, #[serde(default)] pub location: Option, #[serde(default, alias = "techStack")] pub tech_stack: Option>, } fn normalize_optional_string(value: Option) -> Option { value.and_then(|item| { let trimmed = item.trim().to_string(); if trimmed.is_empty() { None } else { Some(trimmed) } }) } impl SiteSettingsPayload { fn apply(self, item: &mut ActiveModel) { if let Some(site_name) = self.site_name { item.site_name = Set(normalize_optional_string(Some(site_name))); } if let Some(site_short_name) = self.site_short_name { item.site_short_name = Set(normalize_optional_string(Some(site_short_name))); } if let Some(site_url) = self.site_url { item.site_url = Set(normalize_optional_string(Some(site_url))); } if let Some(site_title) = self.site_title { item.site_title = Set(normalize_optional_string(Some(site_title))); } if let Some(site_description) = self.site_description { item.site_description = Set(normalize_optional_string(Some(site_description))); } if let Some(hero_title) = self.hero_title { item.hero_title = Set(normalize_optional_string(Some(hero_title))); } if let Some(hero_subtitle) = self.hero_subtitle { item.hero_subtitle = Set(normalize_optional_string(Some(hero_subtitle))); } if let Some(owner_name) = self.owner_name { item.owner_name = Set(normalize_optional_string(Some(owner_name))); } if let Some(owner_title) = self.owner_title { item.owner_title = Set(normalize_optional_string(Some(owner_title))); } if let Some(owner_bio) = self.owner_bio { item.owner_bio = Set(normalize_optional_string(Some(owner_bio))); } if let Some(owner_avatar_url) = self.owner_avatar_url { item.owner_avatar_url = Set(normalize_optional_string(Some(owner_avatar_url))); } if let Some(social_github) = self.social_github { item.social_github = Set(normalize_optional_string(Some(social_github))); } if let Some(social_twitter) = self.social_twitter { item.social_twitter = Set(normalize_optional_string(Some(social_twitter))); } if let Some(social_email) = self.social_email { item.social_email = Set(normalize_optional_string(Some(social_email))); } if let Some(location) = self.location { item.location = Set(normalize_optional_string(Some(location))); } if let Some(tech_stack) = self.tech_stack { item.tech_stack = Set(Some(serde_json::json!(tech_stack))); } } } fn default_payload() -> SiteSettingsPayload { SiteSettingsPayload { site_name: Some("InitCool".to_string()), site_short_name: Some("Termi".to_string()), site_url: Some("https://termi.dev".to_string()), site_title: Some("InitCool - 终端风格的内容平台".to_string()), site_description: Some("一个基于终端美学的个人内容站,记录代码、设计和生活。".to_string()), hero_title: Some("欢迎来到我的极客终端博客".to_string()), hero_subtitle: Some("这里记录技术、代码和生活点滴".to_string()), owner_name: Some("InitCool".to_string()), owner_title: Some("前端开发者 / 技术博主".to_string()), owner_bio: Some( "一名热爱技术的前端开发者,专注于构建高性能、优雅的用户界面。相信代码不仅是工具,更是一种艺术表达。" .to_string(), ), owner_avatar_url: None, social_github: Some("https://github.com".to_string()), social_twitter: Some("https://twitter.com".to_string()), social_email: Some("mailto:hello@termi.dev".to_string()), location: Some("Hong Kong".to_string()), tech_stack: Some(vec![ "Astro".to_string(), "Svelte".to_string(), "Tailwind CSS".to_string(), "TypeScript".to_string(), ]), } } async fn load_current(ctx: &AppContext) -> Result { if let Some(settings) = Entity::find() .order_by_asc(site_settings::Column::Id) .one(&ctx.db) .await? { return Ok(settings); } let mut item = ActiveModel { id: Set(1), ..Default::default() }; default_payload().apply(&mut item); Ok(item.insert(&ctx.db).await?) } #[debug_handler] pub async fn show(State(ctx): State) -> Result { format::json(load_current(&ctx).await?) } #[debug_handler] pub async fn update( State(ctx): State, Json(params): Json, ) -> Result { let current = load_current(&ctx).await?; let mut item = current.into_active_model(); params.apply(&mut item); format::json(item.update(&ctx.db).await?) } pub fn routes() -> Routes { Routes::new() .prefix("api/site_settings/") .add("/", get(show)) .add("/", put(update)) .add("/", patch(update)) }