feat: get comment & send post
This commit is contained in:
3
migrations/2022-03-15-104943_create_comments/down.sql
Normal file
3
migrations/2022-03-15-104943_create_comments/down.sql
Normal file
@@ -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
Normal file
14
migrations/2022-03-15-104943_create_comments/up.sql
Normal file
@@ -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
Normal file
65
src/api/comment.rs
Normal file
@@ -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,
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
@@ -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": [],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user