Browse Source

feat: report

master
hole-thu 3 years ago
parent
commit
cbf933e74f
  1. 1
      Cargo.toml
  2. 21
      src/api/attention.rs
  3. 13
      src/api/comment.rs
  4. 11
      src/api/mod.rs
  5. 38
      src/api/operation.rs
  6. 7
      src/api/post.rs
  7. 4
      src/api/systemlog.rs
  8. 3
      src/main.rs
  9. 112
      src/models.rs
  10. 9
      src/rds_models.rs

1
Cargo.toml

@ -19,3 +19,4 @@ dotenv = "0.15.0"
sha2 = "0.10.2" sha2 = "0.10.2"
log = "0.4.16" log = "0.4.16"
env_logger = "0.9.0" env_logger = "0.9.0"
paste = "1.0.6"

21
src/api/attention.rs

@ -1,11 +1,11 @@
use crate::api::post::ps2outputs; 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::db_conn::Db;
use crate::models::*; use crate::models::*;
use crate::rds_conn::RdsConn; use crate::rds_conn::RdsConn;
use crate::rds_models::*; use crate::rds_models::*;
use rocket::form::Form; use rocket::form::Form;
use rocket::serde::json::{json, Value}; use rocket::serde::json::json;
#[derive(FromForm)] #[derive(FromForm)]
pub struct AttentionInput { pub struct AttentionInput {
@ -20,13 +20,15 @@ pub async fn attention_post(
user: CurrentUser, user: CurrentUser,
db: Db, db: Db,
rconn: RdsConn, rconn: RdsConn,
) -> API<Value> { ) -> JsonAPI {
user.id.ok_or_else(|| APIError::PcError(NotAllowed))?; // 临时用户不允许手动关注
user.id.ok_or_else(|| NotAllowed)?;
let mut p = Post::get(&db, &rconn, ai.pid).await?; let mut p = Post::get(&db, &rconn, ai.pid).await?;
p.check_permission(&user, "r")?; p.check_permission(&user, "r")?;
let mut att = Attention::init(&user.namehash, &rconn); let mut att = Attention::init(&user.namehash, &rconn);
let switch_to = ai.switch == 1; let switch_to = ai.switch == 1;
let mut delta: i32 = 0; let delta: i32;
if att.has(ai.pid).await? != switch_to { if att.has(ai.pid).await? != switch_to {
if switch_to { if switch_to {
att.add(ai.pid).await?; att.add(ai.pid).await?;
@ -37,20 +39,23 @@ pub async fn attention_post(
} }
p.change_n_attentions(&db, delta).await?; p.change_n_attentions(&db, delta).await?;
p.change_hot_score(&db, delta * 2).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; p.refresh_cache(&rconn, false).await;
} }
Ok(json!({ Ok(json!({
"code": 0, "code": 0,
"attention": ai.switch == 1, "attention": ai.switch == 1,
"n_attentions": p.n_attentions + delta, "n_attentions": p.n_attentions,
// for old version frontend // for old version frontend
"likenum": p.n_attentions + delta, "likenum": p.n_attentions,
})) }))
} }
#[get("/getattention")] #[get("/getattention")]
pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> { pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
let ids = Attention::init(&user.namehash, &rconn).all().await?; let ids = Attention::init(&user.namehash, &rconn).all().await?;
let ps = Post::get_multi(&db, &rconn, &ids).await?; let ps = Post::get_multi(&db, &rconn, &ids).await?;
let ps_data = ps2outputs(&ps, &user, &db, &rconn).await; let ps_data = ps2outputs(&ps, &user, &db, &rconn).await;

13
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::db_conn::Db;
use crate::models::*; use crate::models::*;
use crate::rds_conn::RdsConn; use crate::rds_conn::RdsConn;
@ -6,10 +6,7 @@ use crate::rds_models::*;
use chrono::{offset::Utc, DateTime}; use chrono::{offset::Utc, DateTime};
use rocket::form::Form; use rocket::form::Form;
use rocket::futures::{future::TryFutureExt, join, try_join}; use rocket::futures::{future::TryFutureExt, join, try_join};
use rocket::serde::{ use rocket::serde::{json::json, Serialize};
json::{json, Value},
Serialize,
};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(FromForm)] #[derive(FromForm)]
@ -66,7 +63,7 @@ pub fn c2output<'r>(p: &'r Post, cs: &Vec<Comment>, user: &CurrentUser) -> Vec<C
} }
#[get("/getcomment?<pid>")] #[get("/getcomment?<pid>")]
pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> { pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
let p = Post::get(&db, &rconn, pid).await?; let p = Post::get(&db, &rconn, pid).await?;
if p.is_deleted { if p.is_deleted {
return Err(APIError::PcError(IsDeleted)); return Err(APIError::PcError(IsDeleted));
@ -90,7 +87,7 @@ pub async fn add_comment(
user: CurrentUser, user: CurrentUser,
db: Db, db: Db,
rconn: RdsConn, rconn: RdsConn,
) -> API<Value> { ) -> JsonAPI {
let mut p = Post::get(&db, &rconn, ci.pid).await?; let mut p = Post::get(&db, &rconn, ci.pid).await?;
let c = Comment::create( let c = Comment::create(
&db, &db,
@ -104,7 +101,7 @@ pub async fn add_comment(
) )
.await?; .await?;
p.change_n_comments(&db, 1).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 // auto attention after comment
let mut att = Attention::init(&user.namehash, &rconn); let mut att = Attention::init(&user.namehash, &rconn);

11
src/api/mod.rs

@ -19,7 +19,6 @@ pub fn catch_403_error() -> &'static str {
"可能被封禁了,等下次重置吧" "可能被封禁了,等下次重置吧"
} }
pub struct CurrentUser { pub struct CurrentUser {
id: Option<i32>, // tmp user has no id, only for block id: Option<i32>, // tmp user has no id, only for block
namehash: String, namehash: String,
@ -123,6 +122,12 @@ impl From<redis::RedisError> for APIError {
} }
} }
impl From<PolicyError> for APIError {
fn from(err: PolicyError) -> APIError {
APIError::PcError(err)
}
}
pub type API<T> = Result<T, APIError>; pub type API<T> = Result<T, APIError>;
pub type JsonAPI = API<Value>; pub type JsonAPI = API<Value>;
@ -175,7 +180,7 @@ impl UGC for Post {
self.n_comments == 0 self.n_comments == 0
} }
async fn do_set_deleted(&mut self, db: &Db) -> API<()> { 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 true
} }
async fn do_set_deleted(&mut self, db: &Db) -> API<()> { 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)
} }
} }

38
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::db_conn::Db;
use crate::models::*; use crate::models::*;
use crate::rds_conn::RdsConn; use crate::rds_conn::RdsConn;
use crate::rds_models::*; use crate::rds_models::*;
use chrono::offset::Local; use chrono::offset::Local;
use rocket::form::Form; use rocket::form::Form;
use rocket::serde::json::{json, Value}; use rocket::serde::json::json;
#[derive(FromForm)] #[derive(FromForm)]
pub struct DeleteInput { pub struct DeleteInput {
@ -16,12 +16,7 @@ pub struct DeleteInput {
} }
#[post("/delete", data = "<di>")] #[post("/delete", data = "<di>")]
pub async fn delete( pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
di: Form<DeleteInput>,
user: CurrentUser,
db: Db,
rconn: RdsConn,
) -> API<Value> {
let mut p: Post; let mut p: Post;
let mut c: Comment; let mut c: Comment;
let author_hash: &str; let author_hash: &str;
@ -78,3 +73,30 @@ pub async fn delete(
"code": 0 "code": 0
})) }))
} }
#[derive(FromForm)]
pub struct ReportInput {
pid: i32,
reason: String,
}
#[post("/report", data = "<ri>")]
pub async fn report(ri: Form<ReportInput>, 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
}))
}

7
src/api/post.rs

@ -1,5 +1,5 @@
use crate::api::comment::{c2output, CommentOutput}; 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::db_conn::Db;
use crate::models::*; use crate::models::*;
use crate::rds_conn::RdsConn; use crate::rds_conn::RdsConn;
@ -158,11 +158,8 @@ pub async fn publish_post(
#[post("/editcw", data = "<cwi>")] #[post("/editcw", data = "<cwi>")]
pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
let mut p = Post::get(&db, &rconn, cwi.pid).await?; 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.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; p.refresh_cache(&rconn, false).await;
Ok(json!({"code": 0})) Ok(json!({"code": 0}))
} }

4
src/api/systemlog.rs

@ -1,7 +1,7 @@
use crate::api::{CurrentUser, JsonAPI}; use crate::api::{CurrentUser, JsonAPI};
use crate::random_hasher::RandomHasher; use crate::random_hasher::RandomHasher;
use crate::rds_conn::RdsConn; use crate::rds_conn::RdsConn;
use crate::rds_models::Systemlog; use crate::rds_models::{Systemlog};
use rocket::serde::json::{json, Value}; use rocket::serde::json::{json, Value};
use rocket::State; use rocket::State;
@ -19,7 +19,7 @@ pub async fn get_systemlog(user: CurrentUser, rh: &State<RandomHasher>, rconn: R
"type": log.action_type, "type": log.action_type,
"user": look!(log.user_hash), "user": look!(log.user_hash),
"timestamp": log.time.timestamp(), "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::<Vec<Value>>(), ).collect::<Vec<Value>>(),
})) }))

3
src/main.rs

@ -1,3 +1,5 @@
#![feature(concat_idents)]
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
@ -63,6 +65,7 @@ async fn main() -> Result<(), rocket::Error> {
api::attention::get_attention, api::attention::get_attention,
api::systemlog::get_systemlog, api::systemlog::get_systemlog,
api::operation::delete, api::operation::delete,
api::operation::report,
], ],
) )
.register( .register(

112
src/models.rs

@ -48,18 +48,40 @@ macro_rules! _get_multi {
}; };
} }
macro_rules! set_deleted { macro_rules! impl_update_method {
($table:ident) => { ($self:expr, $db:expr, $table:ident, $col:ident, $to:expr) => {{
pub async fn set_deleted(&mut self, db: &Db) -> QueryResult<()> { let id = $self.id;
let id = self.id; *$self = $db
*self = db
.run(move |c| { .run(move |c| {
diesel::update($table::table.find(id)) diesel::update($table::table.find(id))
.set($table::is_deleted.eq(true)) .set($table::$col.eq($to))
.get_result(with_log!(c)) .get_result(with_log!(c))
}) })
.await?; .await?;
Ok(()) 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); _get_multi!(posts);
set_deleted!(posts); make_set_column!(posts {
{is_reported, bool},
{is_deleted, bool},
{cw, String},
{last_comment_time, DateTime<Utc>}
});
make_change_column!(posts {
{n_comments, i32},
{n_attentions, i32},
{hot_score, i32}
});
pub async fn get_multi(db: &Db, rconn: &RdsConn, ids: &Vec<i32>) -> QueryResult<Vec<Self>> { pub async fn get_multi(db: &Db, rconn: &RdsConn, ids: &Vec<i32>) -> QueryResult<Vec<Self>> {
let mut cacher = PostCache::init(&rconn); let mut cacher = PostCache::init(&rconn);
@ -305,7 +338,6 @@ impl Post {
} }
pub async fn create(db: &Db, new_post: NewPost) -> QueryResult<Self> { pub async fn create(db: &Db, new_post: NewPost) -> QueryResult<Self> {
// TODO: tags
db.run(move |c| { db.run(move |c| {
insert_into(posts::table) insert_into(posts::table)
.values(&new_post) .values(&new_post)
@ -314,66 +346,6 @@ impl Post {
.await .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<Utc>) -> 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) { pub async fn set_instance_cache(&self, rconn: &RdsConn) {
PostCache::init(rconn).sets(&vec![self]).await; PostCache::init(rconn).sets(&vec![self]).await;
} }
@ -438,7 +410,9 @@ pub struct NewComment {
impl Comment { impl Comment {
_get!(comments); _get!(comments);
set_deleted!(comments); make_set_column!(comments {
{is_deleted, bool}
});
pub async fn get(db: &Db, id: i32) -> QueryResult<Self> { pub async fn get(db: &Db, id: i32) -> QueryResult<Self> {
// no cache for single comment // no cache for single comment

9
src/rds_models.rs

@ -48,6 +48,15 @@ pub enum LogType {
Ban, Ban,
} }
impl LogType {
pub fn contains_ugc(&self) -> bool {
match self {
Self::Report => true,
_ => false,
}
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub struct Systemlog { pub struct Systemlog {

Loading…
Cancel
Save