Browse Source

feat: block and dangerous users

master
hole-thu 3 years ago
parent
commit
5bef37cd62
  1. 79
      src/api/comment.rs
  2. 6
      src/api/mod.rs
  3. 88
      src/api/operation.rs
  4. 32
      src/api/post.rs
  5. 54
      src/cache.rs
  6. 1
      src/main.rs
  7. 7
      src/models.rs
  8. 88
      src/rds_models.rs

79
src/api/comment.rs

@ -8,6 +8,7 @@ use crate::schema;
use chrono::{offset::Utc, DateTime}; use chrono::{offset::Utc, DateTime};
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
use rocket::form::Form; use rocket::form::Form;
use rocket::futures::future;
use rocket::futures::join; use rocket::futures::join;
use rocket::serde::{json::json, Serialize}; use rocket::serde::{json::json, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
@ -30,39 +31,59 @@ pub struct CommentOutput {
name_id: i32, name_id: i32,
is_tmp: bool, is_tmp: bool,
create_time: DateTime<Utc>, create_time: DateTime<Utc>,
is_blocked: bool,
blocked_count: Option<i32>,
// for old version frontend // for old version frontend
timestamp: i64, timestamp: i64,
blocked: bool,
} }
pub fn c2output<'r>(p: &'r Post, cs: &Vec<Comment>, user: &CurrentUser) -> Vec<CommentOutput> { pub async fn c2output<'r>(
p: &'r Post,
cs: &Vec<Comment>,
user: &CurrentUser,
rconn: &RdsConn,
) -> Vec<CommentOutput> {
let mut hash2id = HashMap::<&String, i32>::from([(&p.author_hash, 0)]); let mut hash2id = HashMap::<&String, i32>::from([(&p.author_hash, 0)]);
cs.iter() let name_ids_iter = cs.iter().map(|c| match hash2id.get(&c.author_hash) {
.filter_map(|c| { Some(id) => *id,
let name_id: i32 = match hash2id.get(&c.author_hash) { None => {
Some(id) => *id, let x = hash2id.len().try_into().unwrap();
None => { hash2id.insert(&c.author_hash, x);
let x = hash2id.len().try_into().unwrap(); x
hash2id.insert(&c.author_hash, x); }
x });
} future::join_all(cs.iter().zip(name_ids_iter).map(|(c, name_id)| async move {
}; if c.is_deleted {
if c.is_deleted { None
// TODO: block } else {
None let is_blocked =
} else { BlockedUsers::check_blocked(rconn, user.id, &user.namehash, &c.author_hash)
Some(CommentOutput { .await
cid: c.id, .unwrap_or_default();
text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, c.content), Some(CommentOutput {
author_title: c.author_title.to_string(), cid: c.id,
can_del: c.check_permission(user, "wd").is_ok(), text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, c.content),
name_id: name_id, author_title: c.author_title.to_string(),
is_tmp: c.is_tmp, can_del: c.check_permission(user, "wd").is_ok(),
create_time: c.create_time, name_id: name_id,
timestamp: c.create_time.timestamp(), is_tmp: c.is_tmp,
}) create_time: c.create_time,
} is_blocked: is_blocked,
}) blocked_count: if user.is_admin {
.collect() BlockCounter::get_count(rconn, &c.author_hash).await.ok()
} else {
None
},
timestamp: c.create_time.timestamp(),
blocked: is_blocked,
})
}
}))
.await
.into_iter()
.filter_map(|x| x)
.collect()
} }
#[get("/getcomment?<pid>")] #[get("/getcomment?<pid>")]
@ -72,7 +93,7 @@ pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) ->
return Err(APIError::PcError(IsDeleted)); return Err(APIError::PcError(IsDeleted));
} }
let cs = p.get_comments(&db, &rconn).await?; let cs = p.get_comments(&db, &rconn).await?;
let data = c2output(&p, &cs, &user); let data = c2output(&p, &cs, &user, &rconn).await;
Ok(json!({ Ok(json!({
"code": 0, "code": 0,

6
src/api/mod.rs

@ -12,6 +12,12 @@ use rocket::request::{FromRequest, Outcome, Request};
use rocket::response::{self, Responder}; use rocket::response::{self, Responder};
use rocket::serde::json::{json, Value}; use rocket::serde::json::{json, Value};
macro_rules! code0 {
() => (
Ok(json!({"code": 0}))
);
}
#[catch(401)] #[catch(401)]
pub fn catch_401_error() -> &'static str { pub fn catch_401_error() -> &'static str {
"未登录或token过期" "未登录或token过期"

88
src/api/operation.rs

@ -1,14 +1,14 @@
use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, UGC}; use crate::api::{CurrentUser, JsonAPI, PolicyError::*, UGC};
use crate::db_conn::Db; use crate::db_conn::Db;
use crate::libs::diesel_logger::LoggingConnection;
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 crate::schema;
use chrono::offset::Local; use chrono::offset::Local;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
use rocket::form::Form; use rocket::form::Form;
use rocket::serde::json::json; use rocket::serde::json::json;
use crate::libs::diesel_logger::LoggingConnection;
use crate::schema;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
#[derive(FromForm)] #[derive(FromForm)]
pub struct DeleteInput { pub struct DeleteInput {
@ -20,14 +20,11 @@ pub struct DeleteInput {
#[post("/delete", data = "<di>")] #[post("/delete", data = "<di>")]
pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI { pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
let mut p: Post; let (author_hash, p) = match di.id_type.as_str() {
let mut c: Comment;
let author_hash: &str;
match di.id_type.as_str() {
"cid" => { "cid" => {
c = Comment::get(&db, di.id).await?; let mut c = Comment::get(&db, di.id).await?;
c.soft_delete(&user, &db).await?; c.soft_delete(&user, &db).await?;
p = Post::get(&db, &rconn, c.post_id).await?; let mut p = Post::get(&db, &rconn, c.post_id).await?;
update!( update!(
p, p,
posts, posts,
@ -39,20 +36,21 @@ pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: Rds
p.refresh_cache(&rconn, false).await; p.refresh_cache(&rconn, false).await;
p.clear_comments_cache(&rconn).await; p.clear_comments_cache(&rconn).await;
author_hash = &c.author_hash; (c.author_hash.clone(), p)
} }
"pid" => { "pid" => {
p = Post::get(&db, &rconn, di.id).await?; let mut p = Post::get(&db, &rconn, di.id).await?;
p.soft_delete(&user, &db).await?; p.soft_delete(&user, &db).await?;
// 如果是删除,需要也从0号缓存队列中去掉 // 如果是删除,需要也从0号缓存队列中去掉
p.refresh_cache(&rconn, true).await; p.refresh_cache(&rconn, true).await;
author_hash = &p.author_hash; (p.author_hash.clone(), p)
} }
_ => return Err(APIError::PcError(NotAllowed)), _ => { Err(NotAllowed) }?,
} };
if user.is_admin && !user.namehash.eq(author_hash) { if user.is_admin && !user.namehash.eq(&author_hash) {
Systemlog { Systemlog {
user_hash: user.namehash.clone(), user_hash: user.namehash.clone(),
action_type: LogType::AdminDelete, action_type: LogType::AdminDelete,
@ -73,18 +71,17 @@ pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: Rds
} }
.create(&rconn) .create(&rconn)
.await?; .await?;
BannedUsers::add(&rconn, author_hash).await?; BannedUsers::add(&rconn, &author_hash).await?;
} }
} }
Ok(json!({ code0!()
"code": 0
}))
} }
#[derive(FromForm)] #[derive(FromForm)]
pub struct ReportInput { pub struct ReportInput {
pid: i32, pid: i32,
#[field(validate = len(0..1000))]
reason: String, reason: String,
} }
@ -99,12 +96,57 @@ pub async fn report(ri: Form<ReportInput>, user: CurrentUser, db: Db, rconn: Rds
Systemlog { Systemlog {
user_hash: user.namehash, user_hash: user.namehash,
action_type: LogType::Report, action_type: LogType::Report,
target: format!("#{} {}", ri.pid, if ri.reason.starts_with("评论区") { "评论区" } else {""}), target: format!(
"#{} {}",
ri.pid,
if ri.reason.starts_with("评论区") {
"评论区"
} else {
""
}
),
detail: ri.reason.clone(), detail: ri.reason.clone(),
time: Local::now(), time: Local::now(),
}.create(&rconn) }
.create(&rconn)
.await?; .await?;
code0!()
}
#[derive(FromForm)]
pub struct BlockInput {
#[field(name = "type")]
content_type: String,
id: i32,
}
#[post("/block", data = "<bi>")]
pub async fn block(bi: Form<BlockInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
let mut blk = BlockedUsers::init(user.id.ok_or_else(|| NotAllowed)?, &rconn);
let nh_to_block = match bi.content_type.as_str() {
"post" => Post::get(&db, &rconn, bi.id).await?.author_hash,
"comment" => Comment::get(&db, bi.id).await?.author_hash,
_ => { Err(NotAllowed) }?,
};
if nh_to_block.eq(&user.namehash) {
{ Err(NotAllowed) }?;
}
blk.add(&nh_to_block).await?;
let curr = BlockCounter::count_incr(&rconn, &nh_to_block).await?;
if curr >= BLOCK_THRESHOLD || user.is_admin {
DangerousUser::add(&rconn, &nh_to_block).await?;
}
Ok(json!({ Ok(json!({
"code": 0 "code": 0,
"data": {
"curr": curr,
"threshold": BLOCK_THRESHOLD,
},
})) }))
} }

32
src/api/post.rs

@ -40,10 +40,13 @@ pub struct PostOutput {
can_del: bool, can_del: bool,
attention: bool, attention: bool,
hot_score: Option<i32>, hot_score: Option<i32>,
is_blocked: bool,
blocked_count: Option<i32>,
// for old version frontend // for old version frontend
timestamp: i64, timestamp: i64,
likenum: i32, likenum: i32,
reply: i32, reply: i32,
blocked: bool,
} }
#[derive(FromForm)] #[derive(FromForm)]
@ -54,6 +57,9 @@ pub struct CwInput {
} }
async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> PostOutput { async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> PostOutput {
let is_blocked = BlockedUsers::check_blocked(rconn, user.id, &user.namehash, &p.author_hash)
.await
.unwrap_or_default();
PostOutput { PostOutput {
pid: p.id, pid: p.id,
text: format!("{}{}", if p.is_tmp { "[tmp]\n" } else { "" }, p.content), text: format!("{}{}", if p.is_tmp { "[tmp]\n" } else { "" }, p.content),
@ -70,10 +76,15 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> Pos
None None
} else { } else {
// 单个洞还有查询评论的接口,这里挂了不用报错 // 单个洞还有查询评论的接口,这里挂了不用报错
p.get_comments(db, rconn) Some(
.await c2output(
.ok() p,
.map(|cs| c2output(p, &cs, user)) &p.get_comments(db, rconn).await.unwrap_or(vec![]),
user,
rconn,
)
.await,
)
}, },
can_del: p.check_permission(user, "wd").is_ok(), can_del: p.check_permission(user, "wd").is_ok(),
attention: Attention::init(&user.namehash, &rconn) attention: Attention::init(&user.namehash, &rconn)
@ -81,10 +92,17 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> Pos
.await .await
.unwrap_or_default(), .unwrap_or_default(),
hot_score: user.is_admin.then(|| p.hot_score), hot_score: user.is_admin.then(|| p.hot_score),
is_blocked: is_blocked,
blocked_count: if user.is_admin {
BlockCounter::get_count(rconn, &p.author_hash).await.ok()
} else {
None
},
// for old version frontend // for old version frontend
timestamp: p.create_time.timestamp(), timestamp: p.create_time.timestamp(),
likenum: p.n_attentions, likenum: p.n_attentions,
reply: p.n_comments, reply: p.n_comments,
blocked: is_blocked,
} }
} }
@ -153,9 +171,7 @@ pub async fn publish_post(
.await?; .await?;
Attention::init(&user.namehash, &rconn).add(p.id).await?; Attention::init(&user.namehash, &rconn).add(p.id).await?;
p.refresh_cache(&rconn, true).await; p.refresh_cache(&rconn, true).await;
Ok(json!({ code0!()
"code": 0
}))
} }
#[post("/editcw", data = "<cwi>")] #[post("/editcw", data = "<cwi>")]
@ -164,7 +180,7 @@ pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db, rconn: RdsCo
p.check_permission(&user, "w")?; p.check_permission(&user, "w")?;
update!(p, posts, &db, { cw, to cwi.cw.to_string() }); update!(p, posts, &db, { cw, to cwi.cw.to_string() });
p.refresh_cache(&rconn, false).await; p.refresh_cache(&rconn, false).await;
Ok(json!({"code": 0})) code0!()
} }
#[get("/getmulti?<pids>")] #[get("/getmulti?<pids>")]

54
src/cache.rs

@ -1,5 +1,6 @@
use crate::models::{Comment, Post, User}; use crate::models::{Comment, Post, User};
use crate::rds_conn::RdsConn; use crate::rds_conn::RdsConn;
use crate::rds_models::init;
use rand::Rng; use rand::Rng;
use redis::AsyncCommands; use redis::AsyncCommands;
use rocket::serde::json::serde_json; use rocket::serde::json::serde_json;
@ -9,6 +10,7 @@ const INSTANCE_EXPIRE_TIME: usize = 60 * 60;
const MIN_LENGTH: isize = 200; const MIN_LENGTH: isize = 200;
const MAX_LENGTH: isize = 900; const MAX_LENGTH: isize = 900;
const CUT_LENGTH: isize = 100;
macro_rules! post_cache_key { macro_rules! post_cache_key {
($id: expr) => { ($id: expr) => {
@ -21,11 +23,7 @@ pub struct PostCache {
} }
impl PostCache { impl PostCache {
pub fn init(rconn: &RdsConn) -> Self { init!();
PostCache {
rconn: rconn.clone(),
}
}
pub async fn sets(&mut self, ps: &Vec<&Post>) { pub async fn sets(&mut self, ps: &Vec<&Post>) {
if ps.is_empty() { if ps.is_empty() {
@ -98,9 +96,13 @@ impl PostCache {
} }
pub async fn clear_all(&mut self) { pub async fn clear_all(&mut self) {
let mut keys = self.rconn.scan_match::<String, String>(post_cache_key!("*")).await.unwrap(); //.collect::<Vec<String>>().await; let mut keys = self
// colllect() does not work .rconn
// also see: https://github.com/mitsuhiko/redis-rs/issues/583 .scan_match::<String, String>(post_cache_key!("*"))
.await
.unwrap(); //.collect::<Vec<String>>().await;
// colllect() does not work
// also see: https://github.com/mitsuhiko/redis-rs/issues/583
let mut ks_for_del = Vec::new(); let mut ks_for_del = Vec::new();
while let Some(key) = keys.next_item().await { while let Some(key) = keys.next_item().await {
ks_for_del.push(key); ks_for_del.push(key);
@ -108,9 +110,10 @@ impl PostCache {
if ks_for_del.is_empty() { if ks_for_del.is_empty() {
return; return;
} }
self.rconn.del(ks_for_del).await.unwrap_or_else(|e| { self.rconn
warn!("clear all post cache fail, {}", e) .del(ks_for_del)
}); .await
.unwrap_or_else(|e| warn!("clear all post cache fail, {}", e));
} }
} }
@ -120,12 +123,7 @@ pub struct PostCommentCache {
} }
impl PostCommentCache { impl PostCommentCache {
pub fn init(pid: i32, rconn: &RdsConn) -> Self { init!(i32, "hole_v2:cache:post_comments:{}");
PostCommentCache {
key: format!("hole_v2:cache:post_comments:{}", pid),
rconn: rconn.clone(),
}
}
pub async fn set(&mut self, cs: &Vec<Comment>) { pub async fn set(&mut self, cs: &Vec<Comment>) {
self.rconn self.rconn
@ -179,22 +177,20 @@ pub struct PostListCommentCache {
} }
impl PostListCommentCache { impl PostListCommentCache {
pub async fn init(mode: u8, rconn: &RdsConn) -> Self { pub fn init(mode: u8, rconn: &RdsConn) -> Self {
let mut cacher = PostListCommentCache { Self {
key: format!("hole_v2:cache:post_list:{}", &mode), key: format!("hole_v2:cache:post_list:{}", &mode),
mode: mode, mode: mode,
rconn: rconn.clone(), rconn: rconn.clone(),
length: 0, length: 0,
}; }
cacher.set_and_check_length().await;
cacher
} }
async fn set_and_check_length(&mut self) { async fn set_and_check_length(&mut self) {
let mut l = self.rconn.zcard(&self.key).await.unwrap(); let mut l = self.rconn.zcard(&self.key).await.unwrap();
if l > MAX_LENGTH { if l > MAX_LENGTH {
self.rconn self.rconn
.zremrangebyrank::<&String, ()>(&self.key, MIN_LENGTH, -1) .zremrangebyrank::<&String, ()>(&self.key, MAX_LENGTH - CUT_LENGTH, -1)
.await .await
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
warn!("cut list cache failed, {}, {}", e, &self.key); warn!("cut list cache failed, {}, {}", e, &self.key);
@ -204,7 +200,8 @@ impl PostListCommentCache {
self.length = l; self.length = l;
} }
pub fn need_fill(&self) -> bool { pub async fn need_fill(&mut self) -> bool {
self.set_and_check_length().await;
self.length < MIN_LENGTH self.length < MIN_LENGTH
} }
@ -244,7 +241,7 @@ impl PostListCommentCache {
pub async fn put(&mut self, p: &Post) { pub async fn put(&mut self, p: &Post) {
// 其他都是加到最前面的,但热榜不是。可能导致MIN_LENGTH到MAX_LENGTH之间的数据不可靠 // 其他都是加到最前面的,但热榜不是。可能导致MIN_LENGTH到MAX_LENGTH之间的数据不可靠
// 影响不大,先不管了 // 影响不大,先不管了
if p.is_deleted || (self.mode > 0 && p.is_reported) { if p.is_deleted || (self.mode > 0 && p.is_reported) {
self.rconn.zrem(&self.key, p.id).await.unwrap_or_else(|e| { self.rconn.zrem(&self.key, p.id).await.unwrap_or_else(|e| {
warn!( warn!(
"remove from list cache failed, {} {} {}", "remove from list cache failed, {} {} {}",
@ -286,12 +283,7 @@ pub struct UserCache {
} }
impl UserCache { impl UserCache {
pub fn init(token: &str, rconn: &RdsConn) -> Self { init!(&str, "hole_v2:cache:user:{}");
UserCache {
key: format!("hole_v2:cache:user:{}", token),
rconn: rconn.clone(),
}
}
pub async fn set(&mut self, u: &User) { pub async fn set(&mut self, u: &User) {
self.rconn self.rconn

1
src/main.rs

@ -66,6 +66,7 @@ async fn main() -> Result<(), rocket::Error> {
api::systemlog::get_systemlog, api::systemlog::get_systemlog,
api::operation::delete, api::operation::delete,
api::operation::report, api::operation::report,
api::operation::block,
], ],
) )
.register( .register(

7
src/models.rs

@ -218,8 +218,8 @@ impl Post {
start: i64, start: i64,
limit: i64, limit: i64,
) -> QueryResult<Vec<Self>> { ) -> QueryResult<Vec<Self>> {
let mut cacher = PostListCommentCache::init(order_mode, &rconn).await; let mut cacher = PostListCommentCache::init(order_mode, &rconn);
if cacher.need_fill() { if cacher.need_fill().await {
let pids = let pids =
Self::_get_ids_by_page(db, order_mode.clone(), 0, cacher.i64_minlen()).await?; Self::_get_ids_by_page(db, order_mode.clone(), 0, cacher.i64_minlen()).await?;
let ps = Self::get_multi(db, rconn, &pids).await?; let ps = Self::get_multi(db, rconn, &pids).await?;
@ -327,7 +327,6 @@ impl Post {
self.set_instance_cache(rconn), self.set_instance_cache(rconn),
future::join_all((if is_new { 0..4 } else { 1..4 }).map(|mode| async move { future::join_all((if is_new { 0..4 } else { 1..4 }).map(|mode| async move {
PostListCommentCache::init(mode, &rconn.clone()) PostListCommentCache::init(mode, &rconn.clone())
.await
.put(self) .put(self)
.await .await
})), })),
@ -342,7 +341,7 @@ impl Post {
.unwrap(); .unwrap();
PostCache::init(&rconn).clear_all().await; PostCache::init(&rconn).clear_all().await;
PostListCommentCache::init(2, rconn).await.clear().await PostListCommentCache::init(2, rconn).clear().await
} }
} }

88
src/rds_models.rs

@ -4,9 +4,31 @@ use redis::{AsyncCommands, RedisResult};
use rocket::serde::json::serde_json; use rocket::serde::json::serde_json;
use rocket::serde::{Deserialize, Serialize}; use rocket::serde::{Deserialize, Serialize};
macro_rules! init {
($ktype:ty, $formatter:expr) => {
pub fn init(k: $ktype, rconn: &RdsConn) -> Self {
Self {
key: format!($formatter, k),
rconn: rconn.clone(),
}
}
};
() => {
pub fn init(rconn: &RdsConn) -> Self {
Self {
rconn: rconn.clone(),
}
}
};
}
const KEY_SYSTEMLOG: &str = "hole_v2:systemlog_list"; const KEY_SYSTEMLOG: &str = "hole_v2:systemlog_list";
const KEY_BANNED_USERS: &str = "hole_v2:banned_user_hash_list"; const KEY_BANNED_USERS: &str = "hole_v2:banned_user_hash_list";
const KEY_BLOCKED_COUNTER: &str = "hole_v2:blocked_counter";
const KEY_DANGEROUS_USERS: &str = "hole_thu:dangerous_users"; //兼容一下旧版
const SYSTEMLOG_MAX_LEN: isize = 1000; const SYSTEMLOG_MAX_LEN: isize = 1000;
pub const BLOCK_THRESHOLD: i32 = 10;
pub struct Attention { pub struct Attention {
key: String, key: String,
@ -14,12 +36,7 @@ pub struct Attention {
} }
impl Attention { impl Attention {
pub fn init(namehash: &str, rconn: &RdsConn) -> Self { init!(&str, "hole_v2:attention:{}");
Attention {
key: format!("hole_v2:attention:{}", namehash),
rconn: rconn.clone(),
}
}
pub async fn add(&mut self, pid: i32) -> RedisResult<()> { pub async fn add(&mut self, pid: i32) -> RedisResult<()> {
self.rconn.sadd(&self.key, pid).await self.rconn.sadd(&self.key, pid).await
@ -108,3 +125,62 @@ impl BannedUsers {
rconn.clone().del(KEY_BANNED_USERS).await rconn.clone().del(KEY_BANNED_USERS).await
} }
} }
pub struct BlockedUsers {
pub key: String,
rconn: RdsConn,
}
impl BlockedUsers {
init!(i32, "hole_v2:blocked_users:{}");
pub async fn add(&mut self, namehash: &str) -> RedisResult<()> {
self.rconn.sadd(&self.key, namehash).await
}
pub async fn has(&mut self, namehash: &str) -> RedisResult<bool> {
self.rconn.sismember(&self.key, namehash).await
}
pub async fn check_blocked(
rconn: &RdsConn,
viewer_id: Option<i32>,
viewer_hash: &str,
author_hash: &str,
) -> RedisResult<bool> {
Ok(match viewer_id {
Some(id) => Self::init(id, rconn).has(author_hash).await?,
None => false,
} || (DangerousUser::has(rconn, author_hash).await?
&& !DangerousUser::has(rconn,viewer_hash).await?))
}
}
pub struct BlockCounter;
impl BlockCounter {
pub async fn count_incr(rconn: &RdsConn, namehash: &str) -> RedisResult<i32> {
rconn.clone().hincr(KEY_BLOCKED_COUNTER, namehash, 1).await
}
pub async fn get_count(rconn: &RdsConn, namehash: &str) -> RedisResult<i32> {
rconn.clone().hget(KEY_BLOCKED_COUNTER, namehash).await
}
}
pub struct DangerousUser;
impl DangerousUser {
pub async fn add(rconn: &RdsConn, namehash: &str) -> RedisResult<()> {
rconn
.clone()
.sadd::<&str, &str, ()>(KEY_DANGEROUS_USERS, namehash)
.await
}
pub async fn has(rconn: &RdsConn, namehash: &str) -> RedisResult<bool> {
rconn.clone().sismember(KEY_DANGEROUS_USERS, namehash).await
}
}
pub(crate) use init;

Loading…
Cancel
Save