feat: ship blog platform admin and deploy stack

This commit is contained in:
2026-03-31 21:48:39 +08:00
parent a9a05aa105
commit 313f174fbc
210 changed files with 25476 additions and 5803 deletions

View File

@@ -0,0 +1,164 @@
use loco_rs::prelude::*;
use crate::{
controllers::site_settings,
models::_entities::{comments, friend_links},
services::subscriptions,
};
fn trim_to_option(value: Option<String>) -> Option<String> {
value.and_then(|item| {
let trimmed = item.trim().to_string();
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
})
}
fn excerpt(value: Option<&str>, limit: usize) -> Option<String> {
let flattened = value?
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
.trim()
.to_string();
if flattened.is_empty() {
return None;
}
let mut shortened = flattened.chars().take(limit).collect::<String>();
if flattened.chars().count() > limit {
shortened.push_str("...");
}
Some(shortened)
}
pub async fn notify_new_comment(ctx: &AppContext, item: &comments::Model) {
let settings = match site_settings::load_current(ctx).await {
Ok(settings) => settings,
Err(error) => {
tracing::warn!("failed to load site settings before comment notification: {error}");
return;
}
};
let payload = serde_json::json!({
"event_type": subscriptions::EVENT_COMMENT_CREATED,
"id": item.id,
"post_slug": item.post_slug,
"author": item.author,
"email": item.email,
"scope": item.scope,
"paragraph_key": item.paragraph_key,
"approved": item.approved.unwrap_or(false),
"excerpt": excerpt(item.content.as_deref(), 200),
"created_at": item.created_at.to_rfc3339(),
});
let text = format!(
"收到一条新的评论。\n\n文章:{}\n作者:{}\n范围:{}\n状态:{}\n摘要:{}",
item.post_slug.clone().unwrap_or_else(|| "未知文章".to_string()),
item.author.clone().unwrap_or_else(|| "匿名".to_string()),
item.scope,
if item.approved.unwrap_or(false) { "已通过" } else { "待审核" },
excerpt(item.content.as_deref(), 200).unwrap_or_else(|| "".to_string()),
);
if let Err(error) = subscriptions::queue_event_for_active_subscriptions(
ctx,
subscriptions::EVENT_COMMENT_CREATED,
"新评论通知",
&text,
payload.clone(),
trim_to_option(settings.site_name.clone()),
trim_to_option(settings.site_url.clone()),
)
.await
{
tracing::warn!("failed to queue comment subscription notification: {error}");
}
if settings.notification_comment_enabled.unwrap_or(false) {
if let Some(target) = trim_to_option(settings.notification_webhook_url.clone()) {
if let Err(error) = subscriptions::queue_direct_notification(
ctx,
subscriptions::CHANNEL_WEBHOOK,
&target,
subscriptions::EVENT_COMMENT_CREATED,
"新评论通知",
&text,
payload,
trim_to_option(settings.site_name),
trim_to_option(settings.site_url),
)
.await
{
tracing::warn!("failed to queue legacy comment webhook notification: {error}");
}
}
}
}
pub async fn notify_new_friend_link(ctx: &AppContext, item: &friend_links::Model) {
let settings = match site_settings::load_current(ctx).await {
Ok(settings) => settings,
Err(error) => {
tracing::warn!("failed to load site settings before friend-link notification: {error}");
return;
}
};
let payload = serde_json::json!({
"event_type": subscriptions::EVENT_FRIEND_LINK_CREATED,
"id": item.id,
"site_name": item.site_name,
"site_url": item.site_url,
"category": item.category,
"status": item.status,
"description": item.description,
"created_at": item.created_at.to_rfc3339(),
});
let text = format!(
"收到新的友链申请。\n\n站点:{}\n链接:{}\n分类:{}\n状态:{}\n描述:{}",
item.site_name.clone().unwrap_or_else(|| "未命名站点".to_string()),
item.site_url,
item.category.clone().unwrap_or_else(|| "未分类".to_string()),
item.status.clone().unwrap_or_else(|| "pending".to_string()),
item.description.clone().unwrap_or_else(|| "".to_string()),
);
if let Err(error) = subscriptions::queue_event_for_active_subscriptions(
ctx,
subscriptions::EVENT_FRIEND_LINK_CREATED,
"新友链申请通知",
&text,
payload.clone(),
trim_to_option(settings.site_name.clone()),
trim_to_option(settings.site_url.clone()),
)
.await
{
tracing::warn!("failed to queue friend-link subscription notification: {error}");
}
if settings.notification_friend_link_enabled.unwrap_or(false) {
if let Some(target) = trim_to_option(settings.notification_webhook_url.clone()) {
if let Err(error) = subscriptions::queue_direct_notification(
ctx,
subscriptions::CHANNEL_WEBHOOK,
&target,
subscriptions::EVENT_FRIEND_LINK_CREATED,
"新友链申请通知",
&text,
payload,
trim_to_option(settings.site_name),
trim_to_option(settings.site_url),
)
.await
{
tracing::warn!("failed to queue legacy friend-link webhook notification: {error}");
}
}
}
}