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

@@ -1,11 +1,14 @@
#![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 {
@@ -69,11 +72,15 @@ async fn load_item(ctx: &AppContext, id: i32) -> Result<Model> {
pub async fn list(
Query(query): Query<ListQuery>,
State(ctx): State<AppContext>,
headers: HeaderMap,
) -> Result<Response> {
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 {
@@ -98,30 +105,65 @@ pub async fn add(
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<i32>,
State(ctx): State<AppContext>,
Json(params): Json<Params>,
) -> Result<Response> {
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(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Result<Response> {
load_item(&ctx, id).await?.delete(&ctx.db).await?;
pub async fn remove(
headers: HeaderMap,
Path(id): Path<i32>,
State(ctx): State<AppContext>,
) -> Result<Response> {
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(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Result<Response> {
pub async fn get_one(
headers: HeaderMap,
Path(id): Path<i32>,
State(ctx): State<AppContext>,
) -> Result<Response> {
check_auth(&headers)?;
format::json(load_item(&ctx, id).await?)
}