proper productionize project

This commit is contained in:
2026-07-15 23:54:53 -07:00
parent b8850c87a4
commit edb6d2b5b4
46 changed files with 11346 additions and 1560 deletions
+3 -61
View File
@@ -1,15 +1,12 @@
use std::{collections::HashMap, sync::Arc};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use serde_json::Value;
use tokio::sync::RwLock;
use tokio_postgres::NoTls;
use tracing::warn;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OverlaySettings {
pub title: String,
#[serde(default = "default_font_scale")]
pub font_scale: u16,
pub show_danmaku: bool,
@@ -37,7 +34,6 @@ pub struct OverlaySettings {
impl Default for OverlaySettings {
fn default() -> Self {
Self {
title: "洛星瓷专用弹幕猪!".into(),
font_scale: default_font_scale(),
show_danmaku: true,
show_enter: true,
@@ -61,10 +57,6 @@ impl Default for OverlaySettings {
impl OverlaySettings {
pub fn sanitize(mut self) -> Self {
self.title = self.title.trim().chars().take(48).collect();
if self.title.is_empty() {
self.title = Self::default().title;
}
self.max_visible = self.max_visible.clamp(1, 12);
self.font_scale = self.font_scale.clamp(50, 300);
self.collapse_after_seconds = self.collapse_after_seconds.clamp(2, 120);
@@ -95,45 +87,6 @@ fn default_particle_speed() -> u16 {
100
}
pub async fn load_settings(
database_url: &str,
room_id: &str,
defaults: OverlaySettings,
) -> Result<OverlaySettings, String> {
let (client, connection) = tokio_postgres::connect(database_url, NoTls)
.await
.map_err(|e| e.to_string())?;
tokio::spawn(async move {
let _ = connection.await;
});
let row = client
.query_opt(
"SELECT settings FROM overlay_settings WHERE room_id=$1",
&[&room_id],
)
.await
.map_err(|e| e.to_string())?;
Ok(row
.and_then(|row| serde_json::from_value::<OverlaySettings>(row.get::<_, Value>(0)).ok())
.unwrap_or(defaults)
.sanitize())
}
pub async fn save_settings(
database_url: &str,
room_id: &str,
settings: &OverlaySettings,
) -> Result<(), String> {
let (client, connection) = tokio_postgres::connect(database_url, NoTls)
.await
.map_err(|e| e.to_string())?;
tokio::spawn(async move {
let _ = connection.await;
});
client.execute("INSERT INTO overlay_settings(room_id,settings,updated_at) VALUES($1,$2,now()) ON CONFLICT(room_id) DO UPDATE SET settings=EXCLUDED.settings,updated_at=now()", &[&room_id, &json!(settings)]).await.map_err(|e| e.to_string())?;
Ok(())
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GiftMeta {
@@ -276,17 +229,6 @@ impl GiftCatalog {
*self.by_name.write().await = names;
Ok(count)
}
pub fn spawn_refresh(self, room_id: String, interval_seconds: u64, timeout_seconds: u64) {
tokio::spawn(async move {
loop {
tokio::time::sleep(std::time::Duration::from_secs(interval_seconds)).await;
if let Err(error) = self.refresh(&room_id, timeout_seconds).await {
warn!(%error, "gift catalog refresh failed; retaining the last successful cache");
}
}
});
}
}
fn parse_catalog(
@@ -441,6 +383,7 @@ fn normalize_name(name: &str) -> String {
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parses_current_gift_panel_fields_and_indexes_by_id_and_name() {
@@ -497,7 +440,6 @@ mod tests {
#[test]
fn settings_are_bounded_before_persistence() {
let settings = OverlaySettings {
title: " ".into(),
font_scale: 999,
max_visible: 99,
collapse_after_seconds: 1,
@@ -510,7 +452,6 @@ mod tests {
..OverlaySettings::default()
}
.sanitize();
assert_eq!(settings.title, "洛星瓷专用弹幕猪!");
assert_eq!(settings.font_scale, 300);
assert_eq!(settings.max_visible, 12);
assert_eq!(settings.collapse_after_seconds, 2);
@@ -532,6 +473,7 @@ mod tests {
fn old_saved_settings_receive_new_field_defaults() {
let mut value = json!(OverlaySettings::default());
let object = value.as_object_mut().expect("settings object");
object.insert("title".into(), json!("旧版弹幕栏标题"));
object.remove("fontScale");
object.remove("unfoldDurationMs");
object.remove("particleCount");