From e373ac9ab66550d5ecfbe0cb205bccff6aa007a5 Mon Sep 17 00:00:00 2001 From: hole-thu Date: Wed, 16 Mar 2022 00:46:55 +0800 Subject: [PATCH] feat: get comment & send post --- .../down.sql | 3 + .../2022-03-15-104943_create_comments/up.sql | 14 ++++ src/api/comment.rs | 65 ++++++++++++++++++ src/api/mod.rs | 1 + src/api/post.rs | 68 +++++++++++++------ src/api/systemlog.rs | 4 +- src/main.rs | 1 + src/models.rs | 20 ++++++ src/schema.rs | 13 ++++ tools/migdb.py | 20 +++++- 10 files changed, 186 insertions(+), 23 deletions(-) create mode 100644 migrations/2022-03-15-104943_create_comments/down.sql create mode 100644 migrations/2022-03-15-104943_create_comments/up.sql create mode 100644 src/api/comment.rs diff --git a/migrations/2022-03-15-104943_create_comments/down.sql b/migrations/2022-03-15-104943_create_comments/down.sql new file mode 100644 index 0000000..8b4364f --- /dev/null +++ b/migrations/2022-03-15-104943_create_comments/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE comments diff --git a/migrations/2022-03-15-104943_create_comments/up.sql b/migrations/2022-03-15-104943_create_comments/up.sql new file mode 100644 index 0000000..2f87559 --- /dev/null +++ b/migrations/2022-03-15-104943_create_comments/up.sql @@ -0,0 +1,14 @@ +-- Your SQL goes here + +CREATE TABLE comments ( + id INTEGER NOT NULL PRIMARY KEY, + author_hash VARCHAR NOT NULL, + author_title VARCHAR(10) NOT NULL DEFAULT '', + content TEXT NOT NULL, + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + is_deleted BOOLEAN NOT NULL DEFAULT FALSE, + post_id INTEGER NOT NULL, + FOREIGN KEY(post_id) REFERENCES post(id) +); +CREATE INDEX comments_postId_idx ON comments (`post_id`); + diff --git a/src/api/comment.rs b/src/api/comment.rs new file mode 100644 index 0000000..c17b524 --- /dev/null +++ b/src/api/comment.rs @@ -0,0 +1,65 @@ +use crate::api::{APIError, CurrentUser, PolicyError::*, API}; +use crate::models::*; +use chrono::NaiveDateTime; +use rocket::serde::{ + json::{json, Value}, + Serialize, +}; +use std::collections::HashMap; + +#[derive(Serialize)] +#[serde(crate = "rocket::serde")] +pub struct CommentOutput { + cid: i32, + text: String, + can_del: bool, + name_id: i32, + create_time: NaiveDateTime, + // for old version frontend + timestamp: i64, +} + +pub fn c2output(p: &Post, cs: &Vec, user: &CurrentUser) -> Vec { + let mut hash2id = HashMap::<&String, i32>::from([(&p.author_hash, 0)]); + cs.iter() + .filter_map(|c| { + let name_id: i32 = match hash2id.get(&c.author_hash) { + Some(id) => *id, + None => { + let x = hash2id.len().try_into().unwrap(); + hash2id.insert(&c.author_hash, x); + x + } + }; + if c.is_deleted { + None + } else { + Some(CommentOutput { + cid: c.id, + text: c.content.to_string(), + can_del: user.is_admin || c.author_hash == user.namehash, + name_id: name_id, + create_time: c.create_time, + timestamp: c.create_time.timestamp(), + }) + } + }) + .collect() +} + +#[get("/getcomment?")] +pub fn get_comment(pid: i32, user: CurrentUser) -> API { + let conn = establish_connection(); + let p = Post::get(&conn, pid).map_err(APIError::from_db)?; + if p.is_deleted { + return Err(APIError::PcError(IsDeleted)); + } + let cs = p.get_comments(&conn).map_err(APIError::from_db)?; + Ok(json!({ + "code": 0, + "data": c2output(&p, &cs, &user), + "n_likes": p.n_likes, + // for old version frontend + "likenum": p.n_likes, + })) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 73654d9..7098fee 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -103,5 +103,6 @@ macro_rules! look { pub type API = Result; +pub mod comment; pub mod post; pub mod systemlog; diff --git a/src/api/post.rs b/src/api/post.rs index 4238e37..f48fe53 100644 --- a/src/api/post.rs +++ b/src/api/post.rs @@ -1,24 +1,30 @@ +use crate::api::comment::{c2output, CommentOutput}; use crate::api::{APIError, CurrentUser, PolicyError::*, API}; use crate::models::*; use chrono::NaiveDateTime; +use diesel::SqliteConnection; +use rocket::form::Form; use rocket::serde::{ - json::{json, Json, Value}, - Deserialize, Serialize, + json::{json, Value}, + Serialize, }; -#[derive(Deserialize)] -#[serde(crate = "rocket::serde")] +#[derive(FromForm)] pub struct PostInput<'r> { - content: &'r str, + #[field(validate = len(1..4097))] + text: &'r str, + #[field(validate = len(0..33))] cw: &'r str, + allow_search: Option, + use_title: Option, } #[derive(Serialize)] #[serde(crate = "rocket::serde")] pub struct PostOutput { - id: i32, - content: String, - cw: String, + pid: i32, + text: String, + cw: Option, author_title: String, n_likes: i32, n_comments: i32, @@ -26,15 +32,23 @@ pub struct PostOutput { last_comment_time: NaiveDateTime, allow_search: bool, is_reported: Option, + comments: Vec, + can_del: bool, // for old version frontend - timestamp: NaiveDateTime, + timestamp: i64, + custom_title: Option, } -fn p2output(p: &Post, user: &CurrentUser) -> PostOutput { +fn p2output(p: &Post, user: &CurrentUser, conn: &SqliteConnection) -> PostOutput { PostOutput { - id: p.id, - content: p.content.to_string(), - cw: p.cw.to_string(), + pid: p.id, + text: p.content.to_string(), + + cw: if p.cw.len() > 0 { + Some(p.cw.to_string()) + } else { + None + }, author_title: p.author_title.to_string(), n_likes: p.n_likes, n_comments: p.n_comments, @@ -46,12 +60,24 @@ fn p2output(p: &Post, user: &CurrentUser) -> PostOutput { } else { None }, + comments: if p.n_comments > 50 { + vec![] + } else { + // 单个洞还有查询评论的接口,这里挂了不用报错 + c2output(p, &p.get_comments(conn).unwrap_or(vec![]), user) + }, + can_del: user.is_admin || p.author_hash == user.namehash, // for old version frontend - timestamp: p.create_time, + timestamp: p.create_time.timestamp(), + custom_title: if p.author_title.len() > 0 { + Some(p.author_title.to_string()) + } else { + None + }, } } -#[get("/post/")] +#[get("/getone?")] pub fn get_one(pid: i32, user: CurrentUser) -> API { let conn = establish_connection(); let p = Post::get(&conn, pid).map_err(APIError::from_db)?; @@ -64,7 +90,7 @@ pub fn get_one(pid: i32, user: CurrentUser) -> API { } } Ok(json!({ - "data": p2output(&p, &user), + "data": p2output(&p, &user, &conn), "code": 0, })) } @@ -77,7 +103,7 @@ pub fn get_list(p: Option, order_mode: u8, user: CurrentUser) -> API .map_err(APIError::from_db)?; let ps_data = ps .iter() - .map(|p| p2output(p, &user)) + .map(|p| p2output(p, &user, &conn)) .collect::>(); Ok(json!({ "data": ps_data, @@ -86,16 +112,18 @@ pub fn get_list(p: Option, order_mode: u8, user: CurrentUser) -> API })) } -#[post("/dopost", format = "json", data = "")] -pub fn publish_post(poi: Json, user: CurrentUser) -> API { +#[post("/dopost", data = "")] +pub fn publish_post(poi: Form, user: CurrentUser) -> API { let conn = establish_connection(); + dbg!(poi.use_title, poi.allow_search); let r = Post::create( &conn, NewPost { - content: &poi.content, + content: &poi.text, cw: &poi.cw, author_hash: &user.namehash, author_title: "", + allow_search: poi.allow_search.is_some(), }, ) .map_err(APIError::from_db)?; diff --git a/src/api/systemlog.rs b/src/api/systemlog.rs index 584dff7..0119636 100644 --- a/src/api/systemlog.rs +++ b/src/api/systemlog.rs @@ -1,6 +1,5 @@ use crate::api::{CurrentUser, API}; use crate::random_hasher::RandomHasher; -use chrono::SubsecRound; use rocket::serde::json::{json, Value}; use rocket::State; @@ -9,7 +8,8 @@ pub fn get_systemlog(user: CurrentUser, rh: &State) -> API Ok(json!({ "tmp_token": rh.get_tmp_token(), "salt": look!(rh.salt), - "start_time": rh.start_time.round_subsecs(0), + "start_time": rh.start_time.timestamp(), "custom_title": user.custom_title, + "data": [], })) } diff --git a/src/main.rs b/src/main.rs index 10b2f64..947da03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ fn rocket() -> _ { .mount( "/_api/v1", routes![ + api::comment::get_comment, api::post::get_list, api::post::get_one, api::post::publish_post, diff --git a/src/models.rs b/src/models.rs index 120205b..c2d8be1 100644 --- a/src/models.rs +++ b/src/models.rs @@ -40,6 +40,7 @@ pub struct NewPost<'a> { pub cw: &'a str, pub author_hash: &'a str, pub author_title: &'a str, + pub allow_search: bool, // TODO: tags } @@ -75,6 +76,12 @@ impl Post { .load(conn) } + pub fn get_comments(&self, conn: &SqliteConnection) -> MR> { + comments::table + .filter(comments::post_id.eq(self.id)) + .load(conn) + } + pub fn create(conn: &SqliteConnection, new_post: NewPost) -> MR { // TODO: tags insert_into(posts::table).values(&new_post).execute(conn) @@ -94,3 +101,16 @@ impl User { users::table.filter(users::token.eq(token)).first(conn).ok() } } + +#[derive(Queryable, Debug)] +pub struct Comment { + pub id: i32, + pub author_hash: String, + pub author_title: String, + pub content: String, + pub create_time: NaiveDateTime, + pub is_deleted: bool, + pub post_id: i32, +} + +impl Comment {} diff --git a/src/schema.rs b/src/schema.rs index 6277029..abe665a 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,3 +1,15 @@ +table! { + comments (id) { + id -> Integer, + author_hash -> Text, + author_title -> Text, + content -> Text, + create_time -> Timestamp, + is_deleted -> Bool, + post_id -> Integer, + } +} + table! { posts (id) { id -> Integer, @@ -26,6 +38,7 @@ table! { } allow_tables_to_appear_in_same_query!( + comments, posts, users, ); diff --git a/tools/migdb.py b/tools/migdb.py index 4d6c98e..22dc389 100644 --- a/tools/migdb.py +++ b/tools/migdb.py @@ -41,9 +41,27 @@ def mig_user(): db_new.commit() +def mig_comment(): + rs = c_old.execute( + 'SELECT id, name_hash, author_title, content, timestamp, deleted, post_id ' + 'FROM comment' + ) + for r in rs: + r = list(r) + r[2] = r[2] or '' + r[4] = datetime.fromtimestamp(r[4]) + r[5] = r[5] or False + c_new.execute( + 'INSERT OR REPLACE INTO comments VALUES({})'.format(','.join(['?'] * 7)), + r + ) + db_new.commit() + + if __name__ == '__main__': # mig_post() - mig_user() + # mig_user() + mig_comment() c_old.close() c_new.close()