diff --git a/.github/resource/Image.jpg b/.github/resource/Image.jpg index 120b5d4..0da2dc4 100644 Binary files a/.github/resource/Image.jpg and b/.github/resource/Image.jpg differ diff --git a/Cargo.lock b/Cargo.lock index 361d578..7a51997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3617,7 +3617,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stream-gears" -version = "1.1.2" +version = "1.1.3" dependencies = [ "anyhow", "async-channel", diff --git a/Cargo.toml b/Cargo.toml index a98c51d..4818af1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ resolver = "3" [workspace.package] -version = "1.1.2" +version = "1.1.3" [workspace.dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index ba35e24..71b6422 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ ## 💴捐赠 -![支付宝](.github/resource/Image.jpg) + [爱发电 »](https://afdian.com/a/biliup) ## ⭐Stars diff --git a/crates/stream-gears/src/server/api/endpoints.rs b/crates/stream-gears/src/server/api/endpoints.rs index 82c9907..c599bde 100644 --- a/crates/stream-gears/src/server/api/endpoints.rs +++ b/crates/stream-gears/src/server/api/endpoints.rs @@ -1,17 +1,20 @@ use crate::server::config::Config; -use crate::server::errors::{AppError, report_to_response, AppResult}; +use crate::server::errors::{AppError, AppResult, report_to_response}; use crate::server::infrastructure::connection_pool::ConnectionPool; use crate::server::infrastructure::context::Worker; use crate::server::infrastructure::dto::LiveStreamerResponse; -use crate::server::infrastructure::models::{Configuration, FileItem, InsertConfiguration, InsertLiveStreamer, InsertUploadStreamer, LiveStreamer, StreamerInfo, UploadStreamer}; +use crate::server::infrastructure::models::{ + Configuration, FileItem, InsertConfiguration, InsertLiveStreamer, InsertUploadStreamer, + LiveStreamer, StreamerInfo, UploadStreamer, +}; use crate::server::infrastructure::repositories::{del_streamer, get_all_streamer}; use crate::server::infrastructure::service_register::ServiceRegister; -use axum::Json; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; +use axum::{Json, debug_handler}; use biliup::credential::{Credential, LoginInfo}; -use error_stack::ResultExt; +use error_stack::{Report, ResultExt}; use ormlite::{Insert, Model}; use serde_json::json; use std::sync::{Arc, RwLock}; @@ -128,6 +131,91 @@ pub async fn get_configuration( Ok(Json(config.read().unwrap().clone())) } +// #[axum_macros::debug_handler(state = ServiceRegister)] +pub async fn put_configuration( + State(config): State>>, + State(pool): State, + Json(json_data): Json, +) -> Result, Response> { + // 将 JSON 序列化为 TEXT 存库 + let value_txt = serde_json::to_string(&json_data) + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + + let mut tx = pool + .begin() + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + + // 最多取 2 条判断是否多行 + let ids: Vec = + sqlx::query_scalar::<_, i64>("SELECT id FROM configuration WHERE key = ?1 LIMIT 2") + .bind("config") + .fetch_all(&mut *tx) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + + let saved: Configuration = if ids.is_empty() { + // 插入 + sqlx::query("INSERT INTO configuration (key, value) VALUES (?1, ?2)") + .bind("config") + .bind(&value_txt) + .execute(&mut *tx) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + + // 取 last_insert_rowid 并读回整行 + let id: i64 = sqlx::query_scalar::<_, i64>("SELECT last_insert_rowid()") + .fetch_one(&mut *tx) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + + sqlx::query_as::<_, Configuration>("SELECT id, key, value FROM configuration WHERE id = ?1") + .bind(id) + .fetch_one(&mut *tx) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)? + } else if ids.len() == 1 { + // 更新 + let id = ids[0]; + sqlx::query("UPDATE configuration SET value = ?1 WHERE id = ?2") + .bind(&value_txt) + .bind(id) + .execute(&mut *tx) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + + sqlx::query_as::<_, Configuration>("SELECT id, key, value FROM configuration WHERE id = ?1") + .bind(id) + .fetch_one(&mut *tx) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)? + } else { + // 多行报错 + return Err(report_to_response(Report::new(AppError::Custom( + format!("有多个空间配置同时存在 (key='config'): {} 行", ids.len()).to_string(), + )))); + }; + + tx.commit() + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + // 提交后从 DB 重新加载配置 + let saved_config: Config = serde_json::from_str(&saved.value) + .change_context(AppError::Unknown) + .map_err(report_to_response)?; + *config.write().unwrap() = saved_config; + Ok(Json(config.read().unwrap().clone())) +} + pub async fn get_streamer_info( // Extension(streamers_service): Extension, State(pool): State, @@ -248,14 +336,24 @@ pub async fn add_user_endpoint( State(pool): State, Json(user): Json, ) -> Result, Response> { - let res = user.insert(&pool).await.change_context(AppError::Unknown).map_err(report_to_response)?; + let res = user + .insert(&pool) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; Ok(Json(res)) } -pub async fn delete_user_endpoint(Path(id): Path, State(pool): State) -> Result, Response> { +pub async fn delete_user_endpoint( + Path(id): Path, + State(pool): State, +) -> Result, Response> { let x = sqlx::query("DELETE FROM configuration WHERE id = ?") .bind(id) - .execute(&pool).await.change_context(AppError::Unknown).map_err(report_to_response)?; + .execute(&pool) + .await + .change_context(AppError::Unknown) + .map_err(report_to_response)?; info!("{:?}", x); Ok(Json(())) } @@ -317,9 +415,13 @@ pub async fn get_videos() -> Result>, Response> { } if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - if media_extensions.iter().any(|allowed| &ext == &allowed.trim_start_matches('.')) { + if media_extensions + .iter() + .any(|allowed| &ext == &allowed.trim_start_matches('.')) + { if let Ok(metadata) = entry.metadata().await { - let mtime = metadata.modified() + let mtime = metadata + .modified() .ok() .and_then(|time| time.duration_since(UNIX_EPOCH).ok()) .map(|d| d.as_secs()) diff --git a/crates/stream-gears/src/server/router.rs b/crates/stream-gears/src/server/router.rs index 7b220fa..4c05a9c 100644 --- a/crates/stream-gears/src/server/router.rs +++ b/crates/stream-gears/src/server/router.rs @@ -1,7 +1,13 @@ use crate::server::api::bilibili_endpoints::{ archive_pre_endpoint, get_myinfo_endpoint, get_proxy_endpoint, }; -use crate::server::api::endpoints::{add_upload_streamer_endpoint, add_user_endpoint, delete_streamers_endpoint, delete_template_endpoint, delete_user_endpoint, get_configuration, get_qrcode, get_streamer_info, get_streamers_endpoint, get_upload_streamer_endpoint, get_upload_streamers_endpoint, get_users_endpoint, get_videos, login_by_qrcode, post_streamers_endpoint, put_streamers_endpoint}; +use crate::server::api::endpoints::{ + add_upload_streamer_endpoint, add_user_endpoint, delete_streamers_endpoint, + delete_template_endpoint, delete_user_endpoint, get_configuration, get_qrcode, + get_streamer_info, get_streamers_endpoint, get_upload_streamer_endpoint, + get_upload_streamers_endpoint, get_users_endpoint, get_videos, login_by_qrcode, + post_streamers_endpoint, put_configuration, put_streamers_endpoint, +}; use crate::server::infrastructure::service_register::ServiceRegister; use axum::Router; use axum::routing::{delete, get, post}; @@ -16,7 +22,10 @@ pub fn router(service_register: ServiceRegister) -> Router<()> { .put(put_streamers_endpoint), ) .route("/v1/streamers/{id}", delete(delete_streamers_endpoint)) - .route("/v1/configuration", get(get_configuration)) + .route( + "/v1/configuration", + get(get_configuration).put(put_configuration), + ) .route("/v1/streamer-info", get(get_streamer_info)) .route("/v1/upload/streamers", get(get_upload_streamers_endpoint)) .route( @@ -35,11 +44,7 @@ pub fn router(service_register: ServiceRegister) -> Router<()> { // ) // .route("/v1/upload/streamers/:id", ) .route("/v1/upload/streamers", post(add_upload_streamer_endpoint)) - .route( - "/v1/users", - get(get_users_endpoint) - .post(add_user_endpoint), - ) + .route("/v1/users", get(get_users_endpoint).post(add_user_endpoint)) .route("/v1/users/{id}", delete(delete_user_endpoint)) .route("/bili/archive/pre", get(archive_pre_endpoint)) .route("/bili/space/myinfo", get(get_myinfo_endpoint))