Files
termi-blog/backend/src/controllers/site_settings.rs

180 lines
6.7 KiB
Rust

#![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<String>,
#[serde(default, alias = "siteShortName")]
pub site_short_name: Option<String>,
#[serde(default, alias = "siteUrl")]
pub site_url: Option<String>,
#[serde(default, alias = "siteTitle")]
pub site_title: Option<String>,
#[serde(default, alias = "siteDescription")]
pub site_description: Option<String>,
#[serde(default, alias = "heroTitle")]
pub hero_title: Option<String>,
#[serde(default, alias = "heroSubtitle")]
pub hero_subtitle: Option<String>,
#[serde(default, alias = "ownerName")]
pub owner_name: Option<String>,
#[serde(default, alias = "ownerTitle")]
pub owner_title: Option<String>,
#[serde(default, alias = "ownerBio")]
pub owner_bio: Option<String>,
#[serde(default, alias = "ownerAvatarUrl")]
pub owner_avatar_url: Option<String>,
#[serde(default, alias = "socialGithub")]
pub social_github: Option<String>,
#[serde(default, alias = "socialTwitter")]
pub social_twitter: Option<String>,
#[serde(default, alias = "socialEmail")]
pub social_email: Option<String>,
#[serde(default)]
pub location: Option<String>,
#[serde(default, alias = "techStack")]
pub tech_stack: Option<Vec<String>>,
}
fn normalize_optional_string(value: Option<String>) -> Option<String> {
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<Model> {
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<AppContext>) -> Result<Response> {
format::json(load_current(&ctx).await?)
}
#[debug_handler]
pub async fn update(
State(ctx): State<AppContext>,
Json(params): Json<SiteSettingsPayload>,
) -> Result<Response> {
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))
}