From ce3379c5ae53a4ca96483822660ea110efaca2e1 Mon Sep 17 00:00:00 2001 From: hole-thu Date: Sat, 26 Mar 2022 02:49:18 +0800 Subject: [PATCH] feat: record and show systemlog, admin delete log --- src/api/operation.rs | 22 +++++++++++++++++++- src/api/post.rs | 1 - src/api/systemlog.rs | 18 +++++++++++++---- src/models.rs | 4 +--- src/rds_models.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/api/operation.rs b/src/api/operation.rs index 419b958..f58c922 100644 --- a/src/api/operation.rs +++ b/src/api/operation.rs @@ -2,6 +2,8 @@ use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC}; use crate::db_conn::Db; use crate::models::*; use crate::rds_conn::RdsConn; +use crate::rds_models::*; +use chrono::offset::Local; use rocket::form::Form; use rocket::serde::json::{json, Value}; @@ -21,9 +23,11 @@ pub async fn delete( rconn: RdsConn, ) -> API { let mut p: Post; + let mut c: Comment; + let author_hash: &str; match di.id_type.as_str() { "cid" => { - let mut c = Comment::get(&db, di.id).await?; + c = Comment::get(&db, di.id).await?; c.soft_delete(&user, &db).await?; p = Post::get(&db, &rconn, c.post_id).await?; p.change_n_comments(&db, -1).await?; @@ -31,16 +35,32 @@ pub async fn delete( p.refresh_cache(&rconn, false).await; p.clear_comments_cache(&rconn).await; + + author_hash = &c.author_hash; } "pid" => { p = Post::get(&db, &rconn, di.id).await?; p.soft_delete(&user, &db).await?; // 如果是删除,需要也从0号缓存队列中去掉 p.refresh_cache(&rconn, true).await; + + author_hash = &p.author_hash; } _ => return Err(APIError::PcError(NotAllowed)), } + if user.is_admin && author_hash != user.namehash { + Systemlog { + user_hash: user.namehash, + action_type: LogType::AdminDelete, + target: format!("#{}, {}={}", p.id, di.id_type, di.id), + detail: di.note.clone(), + time: Local::now(), + } + .create(&rconn) + .await?; + } + Ok(json!({ "code": 0 })) diff --git a/src/api/post.rs b/src/api/post.rs index 3142b8f..cf59976 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -100,7 +100,6 @@ pub async fn ps2outputs( #[get("/getone?")] pub async fn get_one(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { - // let p = Post::get(&db, pid).await?; let p = Post::get(&db, &rconn, pid).await?; p.check_permission(&user, "ro")?; Ok(json!({ diff --git a/src/api/systemlog.rs b/src/api/systemlog.rs index fd9df47..70e586e 100644 --- a/src/api/systemlog.rs +++ b/src/api/systemlog.rs @@ -1,16 +1,26 @@ -use crate::api::{CurrentUser, API}; -use crate::db_conn::Db; +use crate::api::{CurrentUser, JsonAPI}; use crate::random_hasher::RandomHasher; +use crate::rds_conn::RdsConn; +use crate::rds_models::Systemlog; use rocket::serde::json::{json, Value}; use rocket::State; #[get("/systemlog")] -pub async fn get_systemlog(user: CurrentUser, rh: &State, db: Db) -> API { +pub async fn get_systemlog(user: CurrentUser, rh: &State, rconn: RdsConn) -> JsonAPI { + let logs = Systemlog::get_list(&rconn, 50).await?; + Ok(json!({ "tmp_token": rh.get_tmp_token(), "salt": look!(rh.salt), "start_time": rh.start_time.timestamp(), "custom_title": user.custom_title, - "data": [], + "data": logs.into_iter().map(|log| + json!({ + "type": log.action_type, + "user": look!(log.user_hash), + "timestamp": log.time.timestamp(), + "detail": format!("{}\n{}", &log.target, &log.detail) + }) + ).collect::>(), })) } diff --git a/src/models.rs b/src/models.rs index 95ce1f9..1d6d7a6 100644 --- a/src/models.rs +++ b/src/models.rs @@ -179,11 +179,9 @@ impl Post { } pub async fn get(db: &Db, rconn: &RdsConn, id: i32) -> QueryResult { + // 注意即使is_deleted也应该缓存和返回 let mut cacher = PostCache::init(&rconn); if let Some(p) = cacher.get(&id).await { - if p.is_deleted { - return Err(diesel::result::Error::NotFound); - } Ok(p) } else { let p = Self::_get(db, id).await?; diff --git a/src/rds_models.rs b/src/rds_models.rs index 93161bb..2b762e1 100644 --- a/src/rds_models.rs +++ b/src/rds_models.rs @@ -1,5 +1,11 @@ use crate::rds_conn::RdsConn; +use chrono::{offset::Local, DateTime}; use redis::{AsyncCommands, RedisResult}; +use rocket::serde::json::serde_json; +use rocket::serde::{Deserialize, Serialize}; + +const KEY_SYSTEMLOG: &str = "hole_v2:systemlog_list"; +const SYSTEMLOG_MAX_LEN: isize = 1000; pub struct Attention { key: String, @@ -30,3 +36,45 @@ impl Attention { self.rconn.smembers(&self.key).await } } + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "rocket::serde")] +pub enum LogType { + AdminDelete, + Report, + Ban +} + + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "rocket::serde")] +pub struct Systemlog { + pub user_hash: String, + pub action_type: LogType, + pub target: String, + pub detail: String, + pub time: DateTime, +} + +impl Systemlog { + pub async fn create(&self, rconn: &RdsConn) -> RedisResult<()> { + let mut rconn = rconn.clone(); + if rconn.llen::<&str, isize>(KEY_SYSTEMLOG).await? > SYSTEMLOG_MAX_LEN { + rconn.ltrim(KEY_SYSTEMLOG, 0, SYSTEMLOG_MAX_LEN - 1).await?; + } + rconn + .lpush(KEY_SYSTEMLOG, serde_json::to_string(&self).unwrap()) + .await + } + + pub async fn get_list(rconn: &RdsConn, limit: isize) -> RedisResult> { + let rds_result = rconn + .clone() + .lrange::<&str, Vec>(KEY_SYSTEMLOG, 0, limit) + .await?; + Ok(rds_result + .iter() + .map(|s| serde_json::from_str(s).unwrap()) + .collect()) + } +}