From cbf933e74fdc5cea54ce5f56c95f49d4e264e9bf Mon Sep 17 00:00:00 2001 From: hole-thu Date: Sat, 26 Mar 2022 19:08:51 +0800 Subject: [PATCH] feat: report --- Cargo.toml | 1 + src/api/attention.rs | 21 +++++--- src/api/comment.rs | 13 ++--- src/api/mod.rs | 11 ++-- src/api/operation.rs | 38 ++++++++++--- src/api/post.rs | 7 +-- src/api/systemlog.rs | 4 +- src/main.rs | 3 ++ src/models.rs | 124 +++++++++++++++++-------------------------- src/rds_models.rs | 9 ++++ 10 files changed, 122 insertions(+), 109 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6b02b58..de1ad7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ dotenv = "0.15.0" sha2 = "0.10.2" log = "0.4.16" env_logger = "0.9.0" +paste = "1.0.6" diff --git a/src/api/attention.rs b/src/api/attention.rs index 75a99b9..6a62131 100644 --- a/src/api/attention.rs +++ b/src/api/attention.rs @@ -1,11 +1,11 @@ use crate::api::post::ps2outputs; -use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC}; +use crate::api::{CurrentUser, JsonAPI, PolicyError::*, UGC}; use crate::db_conn::Db; use crate::models::*; use crate::rds_conn::RdsConn; use crate::rds_models::*; use rocket::form::Form; -use rocket::serde::json::{json, Value}; +use rocket::serde::json::json; #[derive(FromForm)] pub struct AttentionInput { @@ -20,13 +20,15 @@ pub async fn attention_post( user: CurrentUser, db: Db, rconn: RdsConn, -) -> API { - user.id.ok_or_else(|| APIError::PcError(NotAllowed))?; +) -> JsonAPI { + // 临时用户不允许手动关注 + user.id.ok_or_else(|| NotAllowed)?; + let mut p = Post::get(&db, &rconn, ai.pid).await?; p.check_permission(&user, "r")?; let mut att = Attention::init(&user.namehash, &rconn); let switch_to = ai.switch == 1; - let mut delta: i32 = 0; + let delta: i32; if att.has(ai.pid).await? != switch_to { if switch_to { att.add(ai.pid).await?; @@ -37,20 +39,23 @@ pub async fn attention_post( } p.change_n_attentions(&db, delta).await?; p.change_hot_score(&db, delta * 2).await?; + if switch_to && user.is_admin { + p.set_is_reported(&db, false).await?; + } p.refresh_cache(&rconn, false).await; } Ok(json!({ "code": 0, "attention": ai.switch == 1, - "n_attentions": p.n_attentions + delta, + "n_attentions": p.n_attentions, // for old version frontend - "likenum": p.n_attentions + delta, + "likenum": p.n_attentions, })) } #[get("/getattention")] -pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> API { +pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { let ids = Attention::init(&user.namehash, &rconn).all().await?; let ps = Post::get_multi(&db, &rconn, &ids).await?; let ps_data = ps2outputs(&ps, &user, &db, &rconn).await; diff --git a/src/api/comment.rs b/src/api/comment.rs index bfca314..4e8da4e 100644 --- a/src/api/comment.rs +++ b/src/api/comment.rs @@ -1,4 +1,4 @@ -use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC}; +use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, UGC}; use crate::db_conn::Db; use crate::models::*; use crate::rds_conn::RdsConn; @@ -6,10 +6,7 @@ use crate::rds_models::*; use chrono::{offset::Utc, DateTime}; use rocket::form::Form; use rocket::futures::{future::TryFutureExt, join, try_join}; -use rocket::serde::{ - json::{json, Value}, - Serialize, -}; +use rocket::serde::{json::json, Serialize}; use std::collections::HashMap; #[derive(FromForm)] @@ -66,7 +63,7 @@ pub fn c2output<'r>(p: &'r Post, cs: &Vec, user: &CurrentUser) -> Vec")] -pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> API { +pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { let p = Post::get(&db, &rconn, pid).await?; if p.is_deleted { return Err(APIError::PcError(IsDeleted)); @@ -90,7 +87,7 @@ pub async fn add_comment( user: CurrentUser, db: Db, rconn: RdsConn, -) -> API { +) -> JsonAPI { let mut p = Post::get(&db, &rconn, ci.pid).await?; let c = Comment::create( &db, @@ -104,7 +101,7 @@ pub async fn add_comment( ) .await?; p.change_n_comments(&db, 1).await?; - p.update_comment_time(&db, c.create_time).await?; + p.set_last_comment_time(&db, c.create_time).await?; // auto attention after comment let mut att = Attention::init(&user.namehash, &rconn); diff --git a/src/api/mod.rs b/src/api/mod.rs index 8c61395..7f00467 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -19,7 +19,6 @@ pub fn catch_403_error() -> &'static str { "可能被封禁了,等下次重置吧" } - pub struct CurrentUser { id: Option, // tmp user has no id, only for block namehash: String, @@ -123,6 +122,12 @@ impl From for APIError { } } +impl From for APIError { + fn from(err: PolicyError) -> APIError { + APIError::PcError(err) + } +} + pub type API = Result; pub type JsonAPI = API; @@ -175,7 +180,7 @@ impl UGC for Post { self.n_comments == 0 } async fn do_set_deleted(&mut self, db: &Db) -> API<()> { - self.set_deleted(db).await.map_err(From::from) + self.set_is_deleted(db, true).await.map_err(From::from) } } @@ -194,7 +199,7 @@ impl UGC for Comment { true } async fn do_set_deleted(&mut self, db: &Db) -> API<()> { - self.set_deleted(db).await.map_err(From::from) + self.set_is_deleted(db, true).await.map_err(From::from) } } diff --git a/src/api/operation.rs b/src/api/operation.rs index d62a83c..9bd5239 100644 --- a/src/api/operation.rs +++ b/src/api/operation.rs @@ -1,11 +1,11 @@ -use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC}; +use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, 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}; +use rocket::serde::json::json; #[derive(FromForm)] pub struct DeleteInput { @@ -16,12 +16,7 @@ pub struct DeleteInput { } #[post("/delete", data = "")] -pub async fn delete( - di: Form, - user: CurrentUser, - db: Db, - rconn: RdsConn, -) -> API { +pub async fn delete(di: Form, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { let mut p: Post; let mut c: Comment; let author_hash: &str; @@ -78,3 +73,30 @@ pub async fn delete( "code": 0 })) } + +#[derive(FromForm)] +pub struct ReportInput { + pid: i32, + reason: String, +} + +#[post("/report", data = "")] +pub async fn report(ri: Form, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { + // 临时用户不允许举报 + user.id.ok_or_else(|| NotAllowed)?; + + let mut p = Post::get(&db, &rconn, ri.pid).await?; + p.set_is_reported(&db, true).await?; + p.refresh_cache(&rconn, false).await; + Systemlog { + user_hash: user.namehash, + action_type: LogType::Report, + target: format!("#{} {}", ri.pid, if ri.reason.starts_with("评论区") { "评论区" } else {""}), + detail: ri.reason.clone(), + time: Local::now(), + }.create(&rconn) + .await?; + Ok(json!({ + "code": 0 + })) +} diff --git a/src/api/post.rs b/src/api/post.rs index cf59976..36afa08 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -1,5 +1,5 @@ use crate::api::comment::{c2output, CommentOutput}; -use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, UGC}; +use crate::api::{CurrentUser, JsonAPI, UGC}; use crate::db_conn::Db; use crate::models::*; use crate::rds_conn::RdsConn; @@ -158,11 +158,8 @@ pub async fn publish_post( #[post("/editcw", data = "")] pub async fn edit_cw(cwi: Form, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { let mut p = Post::get(&db, &rconn, cwi.pid).await?; - if !(user.is_admin || p.author_hash == user.namehash) { - return Err(APIError::PcError(NotAllowed)); - } p.check_permission(&user, "w")?; - p.update_cw(&db, cwi.cw.to_string()).await?; + p.set_cw(&db, cwi.cw.to_string()).await?; p.refresh_cache(&rconn, false).await; Ok(json!({"code": 0})) } diff --git a/src/api/systemlog.rs b/src/api/systemlog.rs index 70e586e..78f0785 100644 --- a/src/api/systemlog.rs +++ b/src/api/systemlog.rs @@ -1,7 +1,7 @@ use crate::api::{CurrentUser, JsonAPI}; use crate::random_hasher::RandomHasher; use crate::rds_conn::RdsConn; -use crate::rds_models::Systemlog; +use crate::rds_models::{Systemlog}; use rocket::serde::json::{json, Value}; use rocket::State; @@ -19,7 +19,7 @@ pub async fn get_systemlog(user: CurrentUser, rh: &State, rconn: R "type": log.action_type, "user": look!(log.user_hash), "timestamp": log.time.timestamp(), - "detail": format!("{}\n{}", &log.target, &log.detail) + "detail": format!("{}\n{}", &log.target, if user.is_admin || !log.action_type.contains_ugc() { &log.detail } else { "" }) }) ).collect::>(), })) diff --git a/src/main.rs b/src/main.rs index 6ec7d9f..f1cb828 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![feature(concat_idents)] + #[macro_use] extern crate rocket; @@ -63,6 +65,7 @@ async fn main() -> Result<(), rocket::Error> { api::attention::get_attention, api::systemlog::get_systemlog, api::operation::delete, + api::operation::report, ], ) .register( diff --git a/src/models.rs b/src/models.rs index 1d6d7a6..155ff19 100644 --- a/src/models.rs +++ b/src/models.rs @@ -48,18 +48,40 @@ macro_rules! _get_multi { }; } -macro_rules! set_deleted { - ($table:ident) => { - pub async fn set_deleted(&mut self, db: &Db) -> QueryResult<()> { - let id = self.id; - *self = db - .run(move |c| { - diesel::update($table::table.find(id)) - .set($table::is_deleted.eq(true)) - .get_result(with_log!(c)) - }) - .await?; - Ok(()) +macro_rules! impl_update_method { + ($self:expr, $db:expr, $table:ident, $col:ident, $to:expr) => {{ + let id = $self.id; + *$self = $db + .run(move |c| { + diesel::update($table::table.find(id)) + .set($table::$col.eq($to)) + .get_result(with_log!(c)) + }) + .await?; + Ok(()) + }}; +} + +macro_rules! make_set_column { + ($table:ident { $({ $col:ident, $col_type:ty }), * }) => { + paste::paste! { + $( + pub async fn [< set_ $col>](&mut self, db: &Db, v: $col_type) -> QueryResult<()> { + impl_update_method!(self, db, $table, $col, v) + } + )* + } + }; +} + +macro_rules! make_change_column { + ($table:ident { $({ $col:ident, $col_type:ty }), * }) => { + paste::paste! { + $( + pub async fn [< change_ $col>](&mut self, db: &Db, delta: $col_type) -> QueryResult<()> { + impl_update_method!(self, db, $table, $col, $table::$col + delta) + } + )* } }; } @@ -138,7 +160,18 @@ impl Post { _get_multi!(posts); - set_deleted!(posts); + make_set_column!(posts { + {is_reported, bool}, + {is_deleted, bool}, + {cw, String}, + {last_comment_time, DateTime} + }); + + make_change_column!(posts { + {n_comments, i32}, + {n_attentions, i32}, + {hot_score, i32} + }); pub async fn get_multi(db: &Db, rconn: &RdsConn, ids: &Vec) -> QueryResult> { let mut cacher = PostCache::init(&rconn); @@ -305,7 +338,6 @@ impl Post { } pub async fn create(db: &Db, new_post: NewPost) -> QueryResult { - // TODO: tags db.run(move |c| { insert_into(posts::table) .values(&new_post) @@ -314,66 +346,6 @@ impl Post { .await } - pub async fn update_cw(&mut self, db: &Db, new_cw: String) -> QueryResult<()> { - let pid = self.id; - *self = db - .run(move |c| { - diesel::update(posts::table.find(pid)) - .set(posts::cw.eq(new_cw)) - .get_result(with_log!(c)) - }) - .await?; - Ok(()) - } - - pub async fn update_comment_time(&mut self, db: &Db, t: DateTime) -> QueryResult<()> { - let pid = self.id; - *self = db - .run(move |c| { - diesel::update(posts::table.find(pid)) - .set(posts::last_comment_time.eq(t)) - .get_result(with_log!(c)) - }) - .await?; - Ok(()) - } - - pub async fn change_n_comments(&mut self, db: &Db, delta: i32) -> QueryResult<()> { - let pid = self.id; - *self = db - .run(move |c| { - diesel::update(posts::table.find(pid)) - .set(posts::n_comments.eq(posts::n_comments + delta)) - .get_result(with_log!(c)) - }) - .await?; - Ok(()) - } - - pub async fn change_n_attentions(&mut self, db: &Db, delta: i32) -> QueryResult<()> { - let pid = self.id; - *self = db - .run(move |c| { - diesel::update(posts::table.find(pid)) - .set(posts::n_attentions.eq(posts::n_attentions + delta)) - .get_result(with_log!(c)) - }) - .await?; - Ok(()) - } - - pub async fn change_hot_score(&mut self, db: &Db, delta: i32) -> QueryResult<()> { - let pid = self.id; - *self = db - .run(move |c| { - diesel::update(posts::table.find(pid)) - .set(posts::hot_score.eq(posts::hot_score + delta)) - .get_result(with_log!(c)) - }) - .await?; - Ok(()) - } - pub async fn set_instance_cache(&self, rconn: &RdsConn) { PostCache::init(rconn).sets(&vec![self]).await; } @@ -438,7 +410,9 @@ pub struct NewComment { impl Comment { _get!(comments); - set_deleted!(comments); + make_set_column!(comments { + {is_deleted, bool} + }); pub async fn get(db: &Db, id: i32) -> QueryResult { // no cache for single comment diff --git a/src/rds_models.rs b/src/rds_models.rs index 4a978ef..ea85c02 100644 --- a/src/rds_models.rs +++ b/src/rds_models.rs @@ -48,6 +48,15 @@ pub enum LogType { Ban, } +impl LogType { + pub fn contains_ugc(&self) -> bool { + match self { + Self::Report => true, + _ => false, + } + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(crate = "rocket::serde")] pub struct Systemlog {