Browse Source

feat: get comment & send post

master
hole-thu 3 years ago
parent
commit
e373ac9ab6
  1. 3
      migrations/2022-03-15-104943_create_comments/down.sql
  2. 14
      migrations/2022-03-15-104943_create_comments/up.sql
  3. 65
      src/api/comment.rs
  4. 1
      src/api/mod.rs
  5. 68
      src/api/post.rs
  6. 4
      src/api/systemlog.rs
  7. 1
      src/main.rs
  8. 20
      src/models.rs
  9. 13
      src/schema.rs
  10. 20
      tools/migdb.py

3
migrations/2022-03-15-104943_create_comments/down.sql

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP TABLE comments

14
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`);

65
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<Comment>, user: &CurrentUser) -> Vec<CommentOutput> {
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?<pid>")]
pub fn get_comment(pid: i32, user: CurrentUser) -> API<Value> {
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,
}))
}

1
src/api/mod.rs

@ -103,5 +103,6 @@ macro_rules! look {
pub type API<T> = Result<T, APIError>; pub type API<T> = Result<T, APIError>;
pub mod comment;
pub mod post; pub mod post;
pub mod systemlog; pub mod systemlog;

68
src/api/post.rs

@ -1,24 +1,30 @@
use crate::api::comment::{c2output, CommentOutput};
use crate::api::{APIError, CurrentUser, PolicyError::*, API}; use crate::api::{APIError, CurrentUser, PolicyError::*, API};
use crate::models::*; use crate::models::*;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::SqliteConnection;
use rocket::form::Form;
use rocket::serde::{ use rocket::serde::{
json::{json, Json, Value}, json::{json, Value},
Deserialize, Serialize, Serialize,
}; };
#[derive(Deserialize)] #[derive(FromForm)]
#[serde(crate = "rocket::serde")]
pub struct PostInput<'r> { pub struct PostInput<'r> {
content: &'r str, #[field(validate = len(1..4097))]
text: &'r str,
#[field(validate = len(0..33))]
cw: &'r str, cw: &'r str,
allow_search: Option<i8>,
use_title: Option<i8>,
} }
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub struct PostOutput { pub struct PostOutput {
id: i32, pid: i32,
content: String, text: String,
cw: String, cw: Option<String>,
author_title: String, author_title: String,
n_likes: i32, n_likes: i32,
n_comments: i32, n_comments: i32,
@ -26,15 +32,23 @@ pub struct PostOutput {
last_comment_time: NaiveDateTime, last_comment_time: NaiveDateTime,
allow_search: bool, allow_search: bool,
is_reported: Option<bool>, is_reported: Option<bool>,
comments: Vec<CommentOutput>,
can_del: bool,
// for old version frontend // for old version frontend
timestamp: NaiveDateTime, timestamp: i64,
custom_title: Option<String>,
} }
fn p2output(p: &Post, user: &CurrentUser) -> PostOutput { fn p2output(p: &Post, user: &CurrentUser, conn: &SqliteConnection) -> PostOutput {
PostOutput { PostOutput {
id: p.id, pid: p.id,
content: p.content.to_string(), text: p.content.to_string(),
cw: p.cw.to_string(),
cw: if p.cw.len() > 0 {
Some(p.cw.to_string())
} else {
None
},
author_title: p.author_title.to_string(), author_title: p.author_title.to_string(),
n_likes: p.n_likes, n_likes: p.n_likes,
n_comments: p.n_comments, n_comments: p.n_comments,
@ -46,12 +60,24 @@ fn p2output(p: &Post, user: &CurrentUser) -> PostOutput {
} else { } else {
None 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 // 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/<pid>")] #[get("/getone?<pid>")]
pub fn get_one(pid: i32, user: CurrentUser) -> API<Value> { pub fn get_one(pid: i32, user: CurrentUser) -> API<Value> {
let conn = establish_connection(); let conn = establish_connection();
let p = Post::get(&conn, pid).map_err(APIError::from_db)?; let p = Post::get(&conn, pid).map_err(APIError::from_db)?;
@ -64,7 +90,7 @@ pub fn get_one(pid: i32, user: CurrentUser) -> API<Value> {
} }
} }
Ok(json!({ Ok(json!({
"data": p2output(&p, &user), "data": p2output(&p, &user, &conn),
"code": 0, "code": 0,
})) }))
} }
@ -77,7 +103,7 @@ pub fn get_list(p: Option<u32>, order_mode: u8, user: CurrentUser) -> API<Value>
.map_err(APIError::from_db)?; .map_err(APIError::from_db)?;
let ps_data = ps let ps_data = ps
.iter() .iter()
.map(|p| p2output(p, &user)) .map(|p| p2output(p, &user, &conn))
.collect::<Vec<PostOutput>>(); .collect::<Vec<PostOutput>>();
Ok(json!({ Ok(json!({
"data": ps_data, "data": ps_data,
@ -86,16 +112,18 @@ pub fn get_list(p: Option<u32>, order_mode: u8, user: CurrentUser) -> API<Value>
})) }))
} }
#[post("/dopost", format = "json", data = "<poi>")] #[post("/dopost", data = "<poi>")]
pub fn publish_post(poi: Json<PostInput>, user: CurrentUser) -> API<Value> { pub fn publish_post(poi: Form<PostInput>, user: CurrentUser) -> API<Value> {
let conn = establish_connection(); let conn = establish_connection();
dbg!(poi.use_title, poi.allow_search);
let r = Post::create( let r = Post::create(
&conn, &conn,
NewPost { NewPost {
content: &poi.content, content: &poi.text,
cw: &poi.cw, cw: &poi.cw,
author_hash: &user.namehash, author_hash: &user.namehash,
author_title: "", author_title: "",
allow_search: poi.allow_search.is_some(),
}, },
) )
.map_err(APIError::from_db)?; .map_err(APIError::from_db)?;

4
src/api/systemlog.rs

@ -1,6 +1,5 @@
use crate::api::{CurrentUser, API}; use crate::api::{CurrentUser, API};
use crate::random_hasher::RandomHasher; use crate::random_hasher::RandomHasher;
use chrono::SubsecRound;
use rocket::serde::json::{json, Value}; use rocket::serde::json::{json, Value};
use rocket::State; use rocket::State;
@ -9,7 +8,8 @@ pub fn get_systemlog(user: CurrentUser, rh: &State<RandomHasher>) -> API<Value>
Ok(json!({ Ok(json!({
"tmp_token": rh.get_tmp_token(), "tmp_token": rh.get_tmp_token(),
"salt": look!(rh.salt), "salt": look!(rh.salt),
"start_time": rh.start_time.round_subsecs(0), "start_time": rh.start_time.timestamp(),
"custom_title": user.custom_title, "custom_title": user.custom_title,
"data": [],
})) }))
} }

1
src/main.rs

@ -18,6 +18,7 @@ fn rocket() -> _ {
.mount( .mount(
"/_api/v1", "/_api/v1",
routes![ routes![
api::comment::get_comment,
api::post::get_list, api::post::get_list,
api::post::get_one, api::post::get_one,
api::post::publish_post, api::post::publish_post,

20
src/models.rs

@ -40,6 +40,7 @@ pub struct NewPost<'a> {
pub cw: &'a str, pub cw: &'a str,
pub author_hash: &'a str, pub author_hash: &'a str,
pub author_title: &'a str, pub author_title: &'a str,
pub allow_search: bool,
// TODO: tags // TODO: tags
} }
@ -75,6 +76,12 @@ impl Post {
.load(conn) .load(conn)
} }
pub fn get_comments(&self, conn: &SqliteConnection) -> MR<Vec<Comment>> {
comments::table
.filter(comments::post_id.eq(self.id))
.load(conn)
}
pub fn create(conn: &SqliteConnection, new_post: NewPost) -> MR<usize> { pub fn create(conn: &SqliteConnection, new_post: NewPost) -> MR<usize> {
// TODO: tags // TODO: tags
insert_into(posts::table).values(&new_post).execute(conn) 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() 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 {}

13
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! { table! {
posts (id) { posts (id) {
id -> Integer, id -> Integer,
@ -26,6 +38,7 @@ table! {
} }
allow_tables_to_appear_in_same_query!( allow_tables_to_appear_in_same_query!(
comments,
posts, posts,
users, users,
); );

20
tools/migdb.py

@ -41,9 +41,27 @@ def mig_user():
db_new.commit() 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__': if __name__ == '__main__':
# mig_post() # mig_post()
mig_user() # mig_user()
mig_comment()
c_old.close() c_old.close()
c_new.close() c_new.close()

Loading…
Cancel
Save