From 04a0d1084fc0b1c5309a0b2fd7182c914068e5d7 Mon Sep 17 00:00:00 2001 From: hole-thu Date: Sun, 16 Oct 2022 02:21:44 +0800 Subject: [PATCH] feat: upvote & downvote --- .../2022-10-15-161115_post_reaction/down.sql | 4 ++ .../2022-10-15-161115_post_reaction/up.sql | 4 ++ src/api/attention.rs | 3 - src/api/comment.rs | 3 - src/api/mod.rs | 4 +- src/api/operation.rs | 3 - src/api/post.rs | 9 ++- src/api/reaction.rs | 65 +++++++++++++++++++ src/cache.rs | 2 +- src/main.rs | 1 + src/models.rs | 10 ++- src/rds_models.rs | 36 ++++++++++ src/schema.rs | 2 + 13 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 migrations/postgres/2022-10-15-161115_post_reaction/down.sql create mode 100644 migrations/postgres/2022-10-15-161115_post_reaction/up.sql create mode 100644 src/api/reaction.rs diff --git a/migrations/postgres/2022-10-15-161115_post_reaction/down.sql b/migrations/postgres/2022-10-15-161115_post_reaction/down.sql new file mode 100644 index 0000000..f19b322 --- /dev/null +++ b/migrations/postgres/2022-10-15-161115_post_reaction/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE posts +DROP COLUMN up_votes, +DROP COLUMN down_votes diff --git a/migrations/postgres/2022-10-15-161115_post_reaction/up.sql b/migrations/postgres/2022-10-15-161115_post_reaction/up.sql new file mode 100644 index 0000000..6ea7ad6 --- /dev/null +++ b/migrations/postgres/2022-10-15-161115_post_reaction/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE posts +ADD COLUMN up_votes INTEGER NOT NULL DEFAULT 0, +ADD COLUMN down_votes INTEGER NOT NULL DEFAULT 0 diff --git a/src/api/attention.rs b/src/api/attention.rs index 590da25..4ad8b1f 100644 --- a/src/api/attention.rs +++ b/src/api/attention.rs @@ -1,12 +1,9 @@ use crate::api::post::ps2outputs; use crate::api::{CurrentUser, JsonApi, PolicyError::*, Ugc}; use crate::db_conn::Db; -use crate::libs::diesel_logger::LoggingConnection; use crate::models::*; use crate::rds_conn::RdsConn; use crate::rds_models::*; -use crate::schema; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use rocket::form::Form; use rocket::serde::json::json; use rocket::serde::json::serde_json; diff --git a/src/api/comment.rs b/src/api/comment.rs index e01d756..7e908e4 100644 --- a/src/api/comment.rs +++ b/src/api/comment.rs @@ -1,12 +1,9 @@ use crate::api::{ApiError, CurrentUser, JsonApi, PolicyError::*, Ugc}; use crate::cache::BlockDictCache; use crate::db_conn::Db; -use crate::libs::diesel_logger::LoggingConnection; use crate::models::*; use crate::rds_conn::RdsConn; use crate::rds_models::*; -use crate::schema; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use futures::{future, join}; use rocket::form::Form; use rocket::serde::{json::json, Serialize}; diff --git a/src/api/mod.rs b/src/api/mod.rs index c62e799..9cde9e5 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,13 +1,10 @@ #![allow(clippy::unnecessary_lazy_evaluations)] use crate::db_conn::Db; -use crate::libs::diesel_logger::LoggingConnection; use crate::models::*; use crate::random_hasher::RandomHasher; use crate::rds_conn::RdsConn; use crate::rds_models::*; -use crate::schema; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use rocket::http::Status; use rocket::outcome::try_outcome; use rocket::request::{FromRequest, Outcome, Request}; @@ -300,6 +297,7 @@ pub mod attention; pub mod comment; pub mod operation; pub mod post; +pub mod reaction; pub mod search; pub mod systemlog; pub mod upload; diff --git a/src/api/operation.rs b/src/api/operation.rs index 1852ed0..853a299 100644 --- a/src/api/operation.rs +++ b/src/api/operation.rs @@ -1,13 +1,10 @@ use crate::api::{ApiError, CurrentUser, JsonApi, PolicyError::*, Ugc}; use crate::cache::*; use crate::db_conn::Db; -use crate::libs::diesel_logger::LoggingConnection; use crate::models::*; use crate::rds_conn::RdsConn; use crate::rds_models::*; -use crate::schema; use chrono::offset::Local; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use rocket::form::Form; use rocket::serde::json::json; diff --git a/src/api/post.rs b/src/api/post.rs index d3851be..96f8224 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -3,12 +3,9 @@ use crate::api::vote::get_poll_dict; use crate::api::{Api, CurrentUser, JsonApi, PolicyError::*, Ugc}; use crate::cache::*; use crate::db_conn::Db; -use crate::libs::diesel_logger::LoggingConnection; use crate::models::*; use crate::rds_conn::RdsConn; use crate::rds_models::*; -use crate::schema; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use rocket::form::Form; use rocket::futures::future::{self, OptionFuture}; use rocket::serde::{ @@ -51,6 +48,9 @@ pub struct PostOutput { is_blocked: bool, //blocked_count: Option, poll: Option, + up_votes: i32, + down_votes: i32, + reaction_status: i32, // -1, 0, 1 // for old version frontend timestamp: i64, likenum: i32, @@ -117,6 +117,9 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> Api } else { None }, + up_votes: p.up_votes, + down_votes: p.down_votes, + reaction_status: get_user_post_reaction_status(rconn, p.id, &user.namehash).await?, // for old version frontend timestamp: p.create_time.timestamp(), likenum: p.n_attentions, diff --git a/src/api/reaction.rs b/src/api/reaction.rs new file mode 100644 index 0000000..325ef2f --- /dev/null +++ b/src/api/reaction.rs @@ -0,0 +1,65 @@ +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; + +#[derive(FromForm)] +pub struct ReactionInput { + #[field(validate = range(-1..2))] + status: i32, +} + +#[post("/post//reaction", data = "")] +pub async fn reaction( + pid: i32, + ri: Form, + user: CurrentUser, + db: Db, + rconn: RdsConn, +) -> JsonApi { + user.id.ok_or(YouAreTmp)?; + + let mut p = Post::get(&db, &rconn, pid).await?; + p.check_permission(&user, "r")?; + let mut r_up = Reaction::init(pid, 1, &rconn); + let mut r_down = Reaction::init(pid, -1, &rconn); + + let (delta_up, delta_down): (i32, i32) = match ri.status { + 1 => ( + r_up.add(&user.namehash).await? as i32, + -(r_down.rem(&user.namehash).await? as i32), + ), + -1 => ( + -(r_up.rem(&user.namehash).await? as i32), + r_down.add(&user.namehash).await? as i32, + ), + _ => ( + -(r_up.rem(&user.namehash).await? as i32), + -(r_down.rem(&user.namehash).await? as i32), + ), + }; + + if delta_up != 0 || delta_down != 0 { + update!( + p, + posts, + &db, + { up_votes, add delta_up }, + { down_votes, add delta_down } + ); + + p.refresh_cache(&rconn, false).await; + } + + Ok(json!({ + "code": 0, + "data": { + "up_votes": p.up_votes, + "down_votes": p.down_votes, + "reaction_status": ri.status, + }, + })) +} diff --git a/src/cache.rs b/src/cache.rs index 38099ef..4270dc6 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -17,7 +17,7 @@ const CUT_LENGTH: isize = 100; macro_rules! post_cache_key { ($id: expr) => { - format!("hole_v2:cache:post:{}", $id) + format!("hole_v2:cache:post:{}:v2", $id) }; } diff --git a/src/main.rs b/src/main.rs index 99e1e6d..f57d785 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,6 +88,7 @@ async fn main() { "/_api/v2", routes![ api::attention::set_notification, + api::reaction::reaction, api::comment::add_comment, api::operation::set_title, api::upload::local_upload, diff --git a/src/models.rs b/src/models.rs index 1e4d8b2..8cdd18c 100644 --- a/src/models.rs +++ b/src/models.rs @@ -2,7 +2,6 @@ use crate::cache::*; use crate::db_conn::{Conn, Db}; -use crate::libs::diesel_logger::LoggingConnection; use crate::random_hasher::random_string; use crate::rds_conn::RdsConn; use crate::schema::*; @@ -60,6 +59,8 @@ macro_rules! op_to_col_expr { macro_rules! update { ($obj:expr, $table:ident, $db:expr, $({ $col:ident, $op:ident $v:expr }), + ) => {{ + use crate::schema; + use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; let id = $obj.id; $obj = $db .run(move |c| { @@ -83,9 +84,10 @@ macro_rules! base_query { } macro_rules! with_log { - ($c: expr) => { + ($c: expr) => {{ + use crate::libs::diesel_logger::LoggingConnection; &LoggingConnection::new($c) - }; + }}; } #[derive(Queryable, Insertable, Serialize, Deserialize, Debug)] @@ -120,6 +122,8 @@ pub struct Post { pub hot_score: i32, pub allow_search: bool, pub room_id: i32, + pub up_votes: i32, + pub down_votes: i32, } #[derive(Queryable, Insertable, Serialize, Deserialize, Debug)] diff --git a/src/rds_models.rs b/src/rds_models.rs index c89f04f..da6be76 100644 --- a/src/rds_models.rs +++ b/src/rds_models.rs @@ -48,6 +48,14 @@ macro_rules! add { }; } +macro_rules! rem { + ($vtype:ty) => { + pub async fn rem(&mut self, v: $vtype) -> RedisResult { + self.rconn.srem(&self.key, v).await + } + }; +} + const KEY_SYSTEMLOG: &str = "hole_v2:systemlog_list"; const KEY_BANNED_USERS: &str = "hole_v2:banned_user_hash_list"; const KEY_BLOCKED_COUNTER: &str = "hole_v2:blocked_counter"; @@ -106,6 +114,34 @@ impl Attention { } } +pub struct Reaction { + key: String, + rconn: RdsConn, +} + +impl Reaction { + init!(i32, i32, "hole_v2:reaction:{}:{}"); + + add!(&str); + + rem!(&str); + + has!(&str); +} + +pub async fn get_user_post_reaction_status( + rconn: &RdsConn, + pid: i32, + namehash: &str, +) -> RedisResult { + for rt in [-1, 1] { + if Reaction::init(pid, rt, rconn).has(namehash).await? { + return Ok(rt); + } + } + Ok(0) +} + #[derive(Serialize, Deserialize, Debug)] #[serde(crate = "rocket::serde")] pub enum LogType { diff --git a/src/schema.rs b/src/schema.rs index 72e7a76..c7a1a57 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -29,6 +29,8 @@ table! { hot_score -> Int4, allow_search -> Bool, room_id -> Int4, + up_votes -> Int4, + down_votes -> Int4, } }