Browse Source

feat: upvote & downvote

master
hole-thu 3 years ago
parent
commit
04a0d1084f
  1. 4
      migrations/postgres/2022-10-15-161115_post_reaction/down.sql
  2. 4
      migrations/postgres/2022-10-15-161115_post_reaction/up.sql
  3. 3
      src/api/attention.rs
  4. 3
      src/api/comment.rs
  5. 4
      src/api/mod.rs
  6. 3
      src/api/operation.rs
  7. 9
      src/api/post.rs
  8. 65
      src/api/reaction.rs
  9. 2
      src/cache.rs
  10. 1
      src/main.rs
  11. 10
      src/models.rs
  12. 36
      src/rds_models.rs
  13. 2
      src/schema.rs

4
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

4
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

3
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;

3
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};

4
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;

3
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;

9
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<i32>,
poll: Option<Value>,
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,

65
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/<pid>/reaction", data = "<ri>")]
pub async fn reaction(
pid: i32,
ri: Form<ReactionInput>,
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,
},
}))
}

2
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)
};
}

1
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,

10
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)]

36
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<usize> {
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<i32> {
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 {

2
src/schema.rs

@ -29,6 +29,8 @@ table! {
hot_score -> Int4,
allow_search -> Bool,
room_id -> Int4,
up_votes -> Int4,
down_votes -> Int4,
}
}

Loading…
Cancel
Save