media-kit/backend/src/api.rs
2026-11-05 22:51:51 +08:00

144 lines
3.6 KiB
Rust

use axum::{
Json,
extract::State,
http::{HeaderMap, StatusCode},
};
use serde_json::{Value, json};
use std::collections::HashMap;
use crate::auth::is_authorized;
use crate::db::{self, PlatformInput};
use crate::state::AppState;
use crate::twitch;
const MEDIA_KIT_CACHE_KEY: &str = "media_kit";
const TTL_SECS: u64 = 300;
type ApiError = (StatusCode, Json<Value>);
fn internal_error(message: impl ToString) -> ApiError {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({ "error": message.to_string() })),
)
}
fn unauthorized() -> ApiError {
(
StatusCode::UNAUTHORIZED,
Json(json!({ "error": "unauthorized" })),
)
}
fn require_admin(state: &AppState, headers: &HeaderMap) -> Result<(), ApiError> {
if is_authorized(state, headers) {
Ok(())
} else {
Err(unauthorized())
}
}
pub async fn get_media_kit(State(state): State<AppState>) -> Result<Json<Value>, ApiError> {
if let Some(cached) = state.cache.get(MEDIA_KIT_CACHE_KEY) {
return Ok(Json(cached));
}
let content = {
let conn = state.db.lock().unwrap();
db::get_content(&conn).map_err(internal_error)?
};
let platforms = {
let conn = state.db.lock().unwrap();
db::get_platforms(&conn).map_err(internal_error)?
};
let twitch = match twitch::get_twitch(&state).await {
Ok(value) => value,
Err(message) => json!({
"configured": true,
"found": false,
"login": state.twitch_login,
"message": message,
}),
};
let payload = json!({
"content": content,
"platforms": platforms,
"twitch": twitch,
});
state
.cache
.set(MEDIA_KIT_CACHE_KEY, payload.clone(), TTL_SECS);
Ok(Json(payload))
}
pub async fn admin_verify(State(state): State<AppState>, headers: HeaderMap) -> StatusCode {
if is_authorized(&state, &headers) {
StatusCode::OK
} else {
StatusCode::UNAUTHORIZED
}
}
pub async fn admin_get_state(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<Value>, ApiError> {
require_admin(&state, &headers)?;
let conn = state.db.lock().unwrap();
let content = db::get_content(&conn).map_err(internal_error)?;
let platforms = db::get_platforms(&conn).map_err(internal_error)?;
Ok(Json(json!({
"content": content,
"platforms": platforms,
})))
}
pub async fn admin_update_content(
State(state): State<AppState>,
headers: HeaderMap,
Json(entries): Json<HashMap<String, String>>,
) -> Result<Json<Value>, ApiError> {
require_admin(&state, &headers)?;
let entries: Vec<(String, String)> = entries.into_iter().collect();
{
let conn = state.db.lock().unwrap();
db::upsert_content(&conn, &entries).map_err(internal_error)?;
}
state.cache.invalidate(MEDIA_KIT_CACHE_KEY);
let conn = state.db.lock().unwrap();
let content = db::get_content(&conn).map_err(internal_error)?;
Ok(Json(json!({ "content": content })))
}
pub async fn admin_update_platforms(
State(state): State<AppState>,
headers: HeaderMap,
Json(platforms): Json<Vec<PlatformInput>>,
) -> Result<Json<Value>, ApiError> {
require_admin(&state, &headers)?;
{
let conn = state.db.lock().unwrap();
db::upsert_platforms(&conn, &platforms).map_err(internal_error)?;
}
state.cache.invalidate(MEDIA_KIT_CACHE_KEY);
let conn = state.db.lock().unwrap();
let saved = db::get_platforms(&conn).map_err(internal_error)?;
Ok(Json(json!({ "platforms": saved })))
}