feat: search & beginning of cache
This commit is contained in:
@@ -9,7 +9,7 @@ license = "AGPL-3.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { version = "0.5.0-rc.1", features = ["json"] }
|
rocket = { version = "0.5.0-rc.1", features = ["json"] }
|
||||||
diesel = { version = "1.4.8", features = ["postgres", "chrono"] }
|
diesel = { version = "1.4.8", features = ["postgres", "chrono"] }
|
||||||
redis = { version="0.21.5", features = ["aio", "async-std-comp"] }
|
redis = { version="0.21.5", features = ["aio", "tokio-comp"] }
|
||||||
chrono = { version="0.*", features =["serde"] }
|
chrono = { version="0.*", features =["serde"] }
|
||||||
rand = "0.*"
|
rand = "0.*"
|
||||||
dotenv = "0.*"
|
dotenv = "0.*"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::api::post::ps2outputs;
|
use crate::api::post::ps2outputs;
|
||||||
use crate::api::{APIError, CurrentUser, MapToAPIError, PolicyError::*, API, UGC};
|
use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC};
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
@@ -22,20 +22,22 @@ pub async fn attention_post(
|
|||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> API<Value> {
|
) -> API<Value> {
|
||||||
user.id.ok_or_else(|| APIError::PcError(NotAllowed))?;
|
user.id.ok_or_else(|| APIError::PcError(NotAllowed))?;
|
||||||
let p = Post::get(&db, ai.pid).await.m()?;
|
let mut p = Post::get(&db, ai.pid).await?;
|
||||||
p.check_permission(&user, "r")?;
|
p.check_permission(&user, "r")?;
|
||||||
let mut att = Attention::init(&user.namehash, rconn);
|
let mut att = Attention::init(&user.namehash, &rconn);
|
||||||
let switch_to = ai.switch == 1;
|
let switch_to = ai.switch == 1;
|
||||||
let mut delta: i32 = 0;
|
let mut delta: i32 = 0;
|
||||||
if att.has(ai.pid).await.m()? != switch_to {
|
if att.has(ai.pid).await? != switch_to {
|
||||||
if switch_to {
|
if switch_to {
|
||||||
att.add(ai.pid).await.m()?;
|
att.add(ai.pid).await?;
|
||||||
delta = 1;
|
delta = 1;
|
||||||
} else {
|
} else {
|
||||||
att.remove(ai.pid).await.m()?;
|
att.remove(ai.pid).await?;
|
||||||
delta = -1;
|
delta = -1;
|
||||||
}
|
}
|
||||||
p.change_n_attentions(&db, delta).await.m()?;
|
p = p.change_n_attentions(&db, delta).await?;
|
||||||
|
p = p.change_hot_score(&db, delta * 2).await?;
|
||||||
|
p.refresh_cache(&rconn, false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
@@ -49,12 +51,9 @@ pub async fn attention_post(
|
|||||||
|
|
||||||
#[get("/getattention")]
|
#[get("/getattention")]
|
||||||
pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> {
|
pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> {
|
||||||
let ids = Attention::init(&user.namehash, rconn.clone())
|
let ids = Attention::init(&user.namehash, &rconn).all().await?;
|
||||||
.all()
|
let ps = Post::get_multi(&db, ids).await?;
|
||||||
.await
|
let ps_data = ps2outputs(&ps, &user, &db, &rconn, &vec![]).await;
|
||||||
.m()?;
|
|
||||||
let ps = Post::get_multi(&db, ids).await.m()?;
|
|
||||||
let ps_data = ps2outputs(&ps, &user, &db, rconn.clone()).await;
|
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"code": 0,
|
"code": 0,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use crate::api::{APIError, CurrentUser, MapToAPIError, PolicyError::*, API};
|
use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC};
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
use crate::rds_models::*;
|
use crate::rds_models::*;
|
||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
|
use rocket::futures::{future::TryFutureExt, try_join};
|
||||||
use rocket::serde::{
|
use rocket::serde::{
|
||||||
json::{json, Value},
|
json::{json, Value},
|
||||||
Serialize,
|
Serialize,
|
||||||
@@ -24,6 +25,7 @@ pub struct CommentInput {
|
|||||||
pub struct CommentOutput {
|
pub struct CommentOutput {
|
||||||
cid: i32,
|
cid: i32,
|
||||||
text: String,
|
text: String,
|
||||||
|
author_title: String,
|
||||||
can_del: bool,
|
can_del: bool,
|
||||||
name_id: i32,
|
name_id: i32,
|
||||||
is_tmp: bool,
|
is_tmp: bool,
|
||||||
@@ -32,7 +34,12 @@ pub struct CommentOutput {
|
|||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn c2output<'r>(p: &'r Post, cs: &Vec<Comment>, user: &CurrentUser) -> Vec<CommentOutput> {
|
pub fn c2output<'r>(
|
||||||
|
p: &'r Post,
|
||||||
|
cs: &Vec<Comment>,
|
||||||
|
user: &CurrentUser,
|
||||||
|
kws: &Vec<&str>,
|
||||||
|
) -> Vec<CommentOutput> {
|
||||||
let mut hash2id = HashMap::<&String, i32>::from([(&p.author_hash, 0)]);
|
let mut hash2id = HashMap::<&String, i32>::from([(&p.author_hash, 0)]);
|
||||||
cs.iter()
|
cs.iter()
|
||||||
.filter_map(|c| {
|
.filter_map(|c| {
|
||||||
@@ -48,10 +55,15 @@ pub fn c2output<'r>(p: &'r Post, cs: &Vec<Comment>, user: &CurrentUser) -> Vec<C
|
|||||||
// TODO: block
|
// TODO: block
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
let mut text = c.content.to_string();
|
||||||
|
for kw in kws {
|
||||||
|
text = text.replace(kw, &format!(" **{}**", kw));
|
||||||
|
}
|
||||||
Some(CommentOutput {
|
Some(CommentOutput {
|
||||||
cid: c.id,
|
cid: c.id,
|
||||||
text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, c.content),
|
text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, text),
|
||||||
can_del: user.is_admin || c.author_hash == user.namehash,
|
author_title: c.author_title.to_string(),
|
||||||
|
can_del: c.check_permission(user, "wd").is_ok(),
|
||||||
name_id: name_id,
|
name_id: name_id,
|
||||||
is_tmp: c.is_tmp,
|
is_tmp: c.is_tmp,
|
||||||
create_time: c.create_time,
|
create_time: c.create_time,
|
||||||
@@ -64,13 +76,13 @@ pub fn c2output<'r>(p: &'r Post, cs: &Vec<Comment>, user: &CurrentUser) -> Vec<C
|
|||||||
|
|
||||||
#[get("/getcomment?<pid>")]
|
#[get("/getcomment?<pid>")]
|
||||||
pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> {
|
pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> {
|
||||||
let p = Post::get(&db, pid).await.m()?;
|
let p = Post::get(&db, pid).await?;
|
||||||
if p.is_deleted {
|
if p.is_deleted {
|
||||||
return Err(APIError::PcError(IsDeleted));
|
return Err(APIError::PcError(IsDeleted));
|
||||||
}
|
}
|
||||||
let pid = p.id;
|
let pid = p.id;
|
||||||
let cs = Comment::gets_by_post_id(&db, pid).await.m()?;
|
let cs = Comment::gets_by_post_id(&db, pid).await?;
|
||||||
let data = c2output(&p, &cs, &user);
|
let data = c2output(&p, &cs, &user, &vec![]);
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"code": 0,
|
"code": 0,
|
||||||
@@ -78,7 +90,7 @@ pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) ->
|
|||||||
"n_attentions": p.n_attentions,
|
"n_attentions": p.n_attentions,
|
||||||
// for old version frontend
|
// for old version frontend
|
||||||
"likenum": p.n_attentions,
|
"likenum": p.n_attentions,
|
||||||
"attention": Attention::init(&user.namehash, rconn.clone()).has(p.id).await.m()? ,
|
"attention": Attention::init(&user.namehash, &rconn).has(p.id).await? ,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +101,7 @@ pub async fn add_comment(
|
|||||||
db: Db,
|
db: Db,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> API<Value> {
|
) -> API<Value> {
|
||||||
let p = Post::get(&db, ci.pid).await.m()?;
|
let mut p = Post::get(&db, ci.pid).await?;
|
||||||
Comment::create(
|
Comment::create(
|
||||||
&db,
|
&db,
|
||||||
NewComment {
|
NewComment {
|
||||||
@@ -100,15 +112,28 @@ pub async fn add_comment(
|
|||||||
post_id: ci.pid,
|
post_id: ci.pid,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.m()?;
|
p = p.change_n_comments(&db, 1).await?;
|
||||||
p.change_n_comments(&db, 1).await.m()?;
|
|
||||||
// auto attention after comment
|
// auto attention after comment
|
||||||
let mut att = Attention::init(&user.namehash, rconn);
|
let mut att = Attention::init(&user.namehash, &rconn);
|
||||||
if !att.has(p.id).await.m()? {
|
|
||||||
att.add(p.id).await.m()?;
|
let mut hs_delta = 1;
|
||||||
p.change_n_attentions(&db, 1).await.m()?;
|
|
||||||
|
if !att.has(p.id).await? {
|
||||||
|
hs_delta += 2;
|
||||||
|
try_join!(
|
||||||
|
att.add(p.id).err_into::<APIError>(),
|
||||||
|
async {
|
||||||
|
p = p.change_n_attentions(&db, 1).await?;
|
||||||
|
Ok::<(), APIError>(())
|
||||||
}
|
}
|
||||||
|
.err_into::<APIError>(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p.change_hot_score(&db, hs_delta).await?;
|
||||||
|
p.refresh_cache(&rconn, false).await;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"code": 0
|
"code": 0
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -70,16 +70,6 @@ pub enum APIError {
|
|||||||
PcError(PolicyError),
|
PcError(PolicyError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl APIError {
|
|
||||||
fn from_db(err: diesel::result::Error) -> APIError {
|
|
||||||
APIError::DbError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_rds(err: redis::RedisError) -> APIError {
|
|
||||||
APIError::RdsError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> Responder<'r, 'static> for APIError {
|
impl<'r> Responder<'r, 'static> for APIError {
|
||||||
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
|
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
|
||||||
dbg!(&self);
|
dbg!(&self);
|
||||||
@@ -107,28 +97,21 @@ impl<'r> Responder<'r, 'static> for APIError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<diesel::result::Error> for APIError {
|
||||||
|
fn from(err: diesel::result::Error) -> APIError {
|
||||||
|
APIError::DbError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<redis::RedisError> for APIError {
|
||||||
|
fn from(err: redis::RedisError) -> APIError {
|
||||||
|
APIError::RdsError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type API<T> = Result<T, APIError>;
|
pub type API<T> = Result<T, APIError>;
|
||||||
pub type JsonAPI = API<Value>;
|
pub type JsonAPI = API<Value>;
|
||||||
|
|
||||||
pub trait MapToAPIError {
|
|
||||||
type Data;
|
|
||||||
fn m(self) -> API<Self::Data>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> MapToAPIError for redis::RedisResult<T> {
|
|
||||||
type Data = T;
|
|
||||||
fn m(self) -> API<Self::Data> {
|
|
||||||
Ok(self.map_err(APIError::from_rds)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> MapToAPIError for diesel::QueryResult<T> {
|
|
||||||
type Data = T;
|
|
||||||
fn m(self) -> API<Self::Data> {
|
|
||||||
Ok(self.map_err(APIError::from_db)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
pub trait UGC {
|
pub trait UGC {
|
||||||
fn get_author_hash(&self) -> &str;
|
fn get_author_hash(&self) -> &str;
|
||||||
@@ -178,7 +161,7 @@ impl UGC for Post {
|
|||||||
self.n_comments == 0
|
self.n_comments == 0
|
||||||
}
|
}
|
||||||
async fn do_set_deleted(&self, db: &Db) -> API<usize> {
|
async fn do_set_deleted(&self, db: &Db) -> API<usize> {
|
||||||
self.set_deleted(db).await.m()
|
self.set_deleted(db).await.map_err(From::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +180,7 @@ impl UGC for Comment {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
async fn do_set_deleted(&self, db: &Db) -> API<usize> {
|
async fn do_set_deleted(&self, db: &Db) -> API<usize> {
|
||||||
self.set_deleted(db).await.m()
|
self.set_deleted(db).await.map_err(From::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,4 +194,5 @@ pub mod attention;
|
|||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod operation;
|
pub mod operation;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
|
pub mod search;
|
||||||
pub mod systemlog;
|
pub mod systemlog;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC, MapToAPIError};
|
use crate::api::{APIError, CurrentUser, PolicyError::*, API, UGC};
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
|
use crate::rds_conn::RdsConn;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use rocket::serde::json::{json, Value};
|
use rocket::serde::json::{json, Value};
|
||||||
@@ -13,16 +14,18 @@ pub struct DeleteInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/delete", data = "<di>")]
|
#[post("/delete", data = "<di>")]
|
||||||
pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db) -> API<Value> {
|
pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> {
|
||||||
match di.id_type.as_str() {
|
match di.id_type.as_str() {
|
||||||
"cid" => {
|
"cid" => {
|
||||||
let c = Comment::get(&db, di.id).await.m()?;
|
let c = Comment::get(&db, di.id).await?;
|
||||||
c.soft_delete(&user, &db).await?;
|
c.soft_delete(&user, &db).await?;
|
||||||
let p = Post::get(&db, c.post_id).await.m()?;
|
let mut p = Post::get(&db, c.post_id).await?;
|
||||||
p.change_n_comments(&db, -1).await.m()?;
|
p = p.change_n_comments(&db, -1).await?;
|
||||||
|
p = p.change_hot_score(&db, -2).await?;
|
||||||
|
p.refresh_cache(&rconn, false).await;
|
||||||
}
|
}
|
||||||
"pid" => {
|
"pid" => {
|
||||||
let p = Post::get(&db, di.id).await.m()?;
|
let p = Post::get(&db, di.id).await?;
|
||||||
p.soft_delete(&user, &db).await?;
|
p.soft_delete(&user, &db).await?;
|
||||||
}
|
}
|
||||||
_ => return Err(APIError::PcError(NotAllowed)),
|
_ => return Err(APIError::PcError(NotAllowed)),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::api::comment::{c2output, CommentOutput};
|
use crate::api::comment::{c2output, CommentOutput};
|
||||||
use crate::api::{APIError, CurrentUser, JsonAPI, MapToAPIError, PolicyError::*, UGC};
|
use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, UGC};
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
@@ -25,7 +25,7 @@ pub struct PostOutput {
|
|||||||
pid: i32,
|
pid: i32,
|
||||||
text: String,
|
text: String,
|
||||||
cw: Option<String>,
|
cw: Option<String>,
|
||||||
custom_title: Option<String>,
|
author_title: Option<String>,
|
||||||
is_tmp: bool,
|
is_tmp: bool,
|
||||||
n_attentions: i32,
|
n_attentions: i32,
|
||||||
n_comments: i32,
|
n_comments: i32,
|
||||||
@@ -49,10 +49,21 @@ pub struct CwInput {
|
|||||||
cw: String,
|
cw: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: RdsConn) -> PostOutput {
|
async fn p2output(
|
||||||
|
p: &Post,
|
||||||
|
user: &CurrentUser,
|
||||||
|
db: &Db,
|
||||||
|
rconn: &RdsConn,
|
||||||
|
kws: &Vec<&str>,
|
||||||
|
) -> PostOutput {
|
||||||
|
let mut text = p.content.to_string();
|
||||||
|
for kw in kws {
|
||||||
|
text = text.replace(kw, &format!(" **{}**", kw));
|
||||||
|
}
|
||||||
|
|
||||||
PostOutput {
|
PostOutput {
|
||||||
pid: p.id,
|
pid: p.id,
|
||||||
text: format!("{}{}", if p.is_tmp { "[tmp]\n" } else { "" }, p.content),
|
text: format!("{}{}", if p.is_tmp { "[tmp]\n" } else { "" }, text),
|
||||||
cw: if p.cw.len() > 0 {
|
cw: if p.cw.len() > 0 {
|
||||||
Some(p.cw.to_string())
|
Some(p.cw.to_string())
|
||||||
} else {
|
} else {
|
||||||
@@ -63,7 +74,7 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: RdsConn) -> Post
|
|||||||
create_time: p.create_time,
|
create_time: p.create_time,
|
||||||
last_comment_time: p.last_comment_time,
|
last_comment_time: p.last_comment_time,
|
||||||
allow_search: p.allow_search,
|
allow_search: p.allow_search,
|
||||||
custom_title: if p.author_title.len() > 0 {
|
author_title: if p.author_title.len() > 0 {
|
||||||
Some(p.author_title.to_string())
|
Some(p.author_title.to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -80,13 +91,13 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: RdsConn) -> Post
|
|||||||
// 单个洞还有查询评论的接口,这里挂了不用报错
|
// 单个洞还有查询评论的接口,这里挂了不用报错
|
||||||
let pid = p.id;
|
let pid = p.id;
|
||||||
if let Some(cs) = Comment::gets_by_post_id(db, pid).await.ok() {
|
if let Some(cs) = Comment::gets_by_post_id(db, pid).await.ok() {
|
||||||
Some(c2output(p, &cs, user))
|
Some(c2output(p, &cs, user, kws))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
can_del: p.check_permission(user, "wd").is_ok(),
|
can_del: p.check_permission(user, "wd").is_ok(),
|
||||||
attention: Attention::init(&user.namehash, rconn.clone())
|
attention: Attention::init(&user.namehash, &rconn)
|
||||||
.has(p.id)
|
.has(p.id)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@@ -101,21 +112,23 @@ pub async fn ps2outputs(
|
|||||||
ps: &Vec<Post>,
|
ps: &Vec<Post>,
|
||||||
user: &CurrentUser,
|
user: &CurrentUser,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
rconn: RdsConn,
|
rconn: &RdsConn,
|
||||||
|
kws: &Vec<&str>,
|
||||||
) -> Vec<PostOutput> {
|
) -> Vec<PostOutput> {
|
||||||
future::join_all(
|
future::join_all(
|
||||||
ps.iter()
|
ps.iter()
|
||||||
.map(|p| async { p2output(p, &user, &db, rconn.clone()).await }),
|
.map(|p| async { p2output(p, &user, &db, &rconn, &kws.clone()).await }),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/getone?<pid>")]
|
#[get("/getone?<pid>")]
|
||||||
pub async fn get_one(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn get_one(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
||||||
let p = Post::get(&db, pid).await.m()?;
|
// let p = Post::get(&db, pid).await?;
|
||||||
|
let p = Post::get_with_cache(&db, &rconn, pid).await?;
|
||||||
p.check_permission(&user, "ro")?;
|
p.check_permission(&user, "ro")?;
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"data": p2output(&p, &user,&db, rconn).await,
|
"data": p2output(&p, &user,&db, &rconn, &vec![]).await,
|
||||||
"code": 0,
|
"code": 0,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -129,8 +142,10 @@ pub async fn get_list(
|
|||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> JsonAPI {
|
) -> JsonAPI {
|
||||||
let page = p.unwrap_or(1);
|
let page = p.unwrap_or(1);
|
||||||
let ps = Post::gets_by_page(&db, order_mode, page, 25).await.m()?;
|
let page_size = 25;
|
||||||
let ps_data = ps2outputs(&ps, &user, &db, rconn.clone()).await;
|
let start = (page - 1) * page_size;
|
||||||
|
let ps = Post::gets_by_page(&db, order_mode, start.into(), page_size.into()).await?;
|
||||||
|
let ps_data = ps2outputs(&ps, &user, &db, &rconn, &vec![]).await;
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"data": ps_data,
|
"data": ps_data,
|
||||||
"count": ps_data.len(),
|
"count": ps_data.len(),
|
||||||
@@ -139,7 +154,12 @@ pub async fn get_list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/dopost", data = "<poi>")]
|
#[post("/dopost", data = "<poi>")]
|
||||||
pub async fn publish_post(poi: Form<PostInput>, user: CurrentUser, db: Db) -> JsonAPI {
|
pub async fn publish_post(
|
||||||
|
poi: Form<PostInput>,
|
||||||
|
user: CurrentUser,
|
||||||
|
db: Db,
|
||||||
|
rconn: RdsConn,
|
||||||
|
) -> JsonAPI {
|
||||||
let p = Post::create(
|
let p = Post::create(
|
||||||
&db,
|
&db,
|
||||||
NewPost {
|
NewPost {
|
||||||
@@ -152,9 +172,8 @@ pub async fn publish_post(poi: Form<PostInput>, user: CurrentUser, db: Db) -> Js
|
|||||||
allow_search: poi.allow_search.is_some(),
|
allow_search: poi.allow_search.is_some(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.m()?;
|
Attention::init(&user.namehash, &rconn).add(p.id).await?;
|
||||||
// TODO: attention
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"code": 0
|
"code": 0
|
||||||
}))
|
}))
|
||||||
@@ -162,19 +181,19 @@ pub async fn publish_post(poi: Form<PostInput>, user: CurrentUser, db: Db) -> Js
|
|||||||
|
|
||||||
#[post("/editcw", data = "<cwi>")]
|
#[post("/editcw", data = "<cwi>")]
|
||||||
pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db) -> JsonAPI {
|
pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db) -> JsonAPI {
|
||||||
let p = Post::get(&db, cwi.pid).await.m()?;
|
let p = Post::get(&db, cwi.pid).await?;
|
||||||
if !(user.is_admin || p.author_hash == user.namehash) {
|
if !(user.is_admin || p.author_hash == user.namehash) {
|
||||||
return Err(APIError::PcError(NotAllowed));
|
return Err(APIError::PcError(NotAllowed));
|
||||||
}
|
}
|
||||||
p.check_permission(&user, "w")?;
|
p.check_permission(&user, "w")?;
|
||||||
_ = p.update_cw(&db, cwi.cw.to_string()).await.m()?;
|
_ = p.update_cw(&db, cwi.cw.to_string()).await?;
|
||||||
Ok(json!({"code": 0}))
|
Ok(json!({"code": 0}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/getmulti?<pids>")]
|
#[get("/getmulti?<pids>")]
|
||||||
pub async fn get_multi(pids: Vec<i32>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn get_multi(pids: Vec<i32>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
||||||
let ps = Post::get_multi(&db, pids).await.m()?;
|
let ps = Post::get_multi(&db, pids).await?;
|
||||||
let ps_data = ps2outputs(&ps, &user, &db, rconn.clone()).await;
|
let ps_data = ps2outputs(&ps, &user, &db, &rconn, &vec![]).await;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"code": 0,
|
"code": 0,
|
||||||
|
|||||||
40
src/api/search.rs
Normal file
40
src/api/search.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use crate::api::post::ps2outputs;
|
||||||
|
use crate::api::{CurrentUser, JsonAPI};
|
||||||
|
use crate::db_conn::Db;
|
||||||
|
use crate::models::*;
|
||||||
|
use crate::rds_conn::RdsConn;
|
||||||
|
use rocket::serde::json::json;
|
||||||
|
|
||||||
|
#[get("/search?<search_mode>&<page>&<keywords>")]
|
||||||
|
pub async fn search(
|
||||||
|
keywords: String,
|
||||||
|
search_mode: u8,
|
||||||
|
page: i32,
|
||||||
|
user: CurrentUser,
|
||||||
|
db: Db,
|
||||||
|
rconn: RdsConn,
|
||||||
|
) -> JsonAPI {
|
||||||
|
let page_size = 25;
|
||||||
|
let start = (page - 1) * page_size;
|
||||||
|
|
||||||
|
let kws = keywords.split(" ").filter(|x| !x.is_empty()).collect::<Vec<&str>>();
|
||||||
|
let ps = if kws.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
Post::search(
|
||||||
|
&db,
|
||||||
|
search_mode,
|
||||||
|
keywords.to_string(),
|
||||||
|
start.into(),
|
||||||
|
page_size.into(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
let mark_kws = if search_mode == 1 {kws} else {vec![]};
|
||||||
|
let ps_data = ps2outputs(&ps, &user, &db, &rconn, &mark_kws).await;
|
||||||
|
Ok(json!({
|
||||||
|
"data": ps_data,
|
||||||
|
"count": ps_data.len(),
|
||||||
|
"code": 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
api::post::publish_post,
|
api::post::publish_post,
|
||||||
api::post::edit_cw,
|
api::post::edit_cw,
|
||||||
api::post::get_multi,
|
api::post::get_multi,
|
||||||
|
api::search::search,
|
||||||
api::attention::attention_post,
|
api::attention::attention_post,
|
||||||
api::attention::get_attention,
|
api::attention::get_attention,
|
||||||
api::systemlog::get_systemlog,
|
api::systemlog::get_systemlog,
|
||||||
|
|||||||
124
src/models.rs
124
src/models.rs
@@ -1,9 +1,15 @@
|
|||||||
#![allow(clippy::all)]
|
#![allow(clippy::all)]
|
||||||
|
|
||||||
use diesel::{insert_into, ExpressionMethods, QueryDsl, QueryResult, RunQueryDsl};
|
|
||||||
|
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
|
use crate::rds_conn::RdsConn;
|
||||||
|
use crate::rds_models::PostCache;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
|
use chrono::{offset::Utc, DateTime};
|
||||||
|
use diesel::{
|
||||||
|
insert_into, BoolExpressionMethods, ExpressionMethods, QueryDsl, QueryResult, RunQueryDsl,
|
||||||
|
TextExpressionMethods,
|
||||||
|
};
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
no_arg_sql_function!(RANDOM, (), "Represents the sql RANDOM() function");
|
no_arg_sql_function!(RANDOM, (), "Represents the sql RANDOM() function");
|
||||||
|
|
||||||
@@ -23,6 +29,7 @@ macro_rules! get_multi {
|
|||||||
db.run(move |c| {
|
db.run(move |c| {
|
||||||
$table::table
|
$table::table
|
||||||
.filter($table::id.eq_any(ids))
|
.filter($table::id.eq_any(ids))
|
||||||
|
.filter($table::is_deleted.eq(false))
|
||||||
.order($table::id.desc())
|
.order($table::id.desc())
|
||||||
.load(c)
|
.load(c)
|
||||||
})
|
})
|
||||||
@@ -45,7 +52,14 @@ macro_rules! set_deleted {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use chrono::{offset::Utc, DateTime};
|
macro_rules! base_query {
|
||||||
|
($table:ident) => {
|
||||||
|
$table::table
|
||||||
|
.into_boxed()
|
||||||
|
.filter($table::is_deleted.eq(false))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Insertable)]
|
#[derive(Queryable, Insertable)]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
@@ -59,7 +73,8 @@ pub struct Comment {
|
|||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Insertable)]
|
#[derive(Queryable, Insertable, Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
pub struct Post {
|
pub struct Post {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub author_hash: String,
|
pub author_hash: String,
|
||||||
@@ -105,29 +120,83 @@ impl Post {
|
|||||||
|
|
||||||
set_deleted!(posts);
|
set_deleted!(posts);
|
||||||
|
|
||||||
|
pub async fn get_with_cache(db: &Db, rconn: &RdsConn, id: i32) -> QueryResult<Self> {
|
||||||
|
let mut cacher = PostCache::init(&id, &rconn);
|
||||||
|
if let Some(p) = cacher.get().await {
|
||||||
|
dbg!("hint and use post cache");
|
||||||
|
Ok(p)
|
||||||
|
} else {
|
||||||
|
let p = Self::get(db, id).await?;
|
||||||
|
cacher.set(&p).await;
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn gets_by_page(
|
pub async fn gets_by_page(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
order_mode: u8,
|
order_mode: u8,
|
||||||
page: u32,
|
start: i64,
|
||||||
page_size: u32,
|
limit: i64,
|
||||||
) -> QueryResult<Vec<Self>> {
|
) -> QueryResult<Vec<Self>> {
|
||||||
db.run(move |c| {
|
db.run(move |c| {
|
||||||
let mut query = posts::table.into_boxed();
|
let mut query = base_query!(posts);
|
||||||
query = query.filter(posts::is_deleted.eq(false));
|
|
||||||
if order_mode > 0 {
|
if order_mode > 0 {
|
||||||
query = query.filter(posts::is_reported.eq(false))
|
query = query.filter(posts::is_reported.eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
match order_mode {
|
query = match order_mode {
|
||||||
1 => query = query.order(posts::last_comment_time.desc()),
|
0 => query.order(posts::id.desc()),
|
||||||
2 => query = query.order(posts::hot_score.desc()),
|
1 => query.order(posts::last_comment_time.desc()),
|
||||||
3 => query = query.order(RANDOM),
|
2 => query.order(posts::hot_score.desc()),
|
||||||
_ => query = query.order(posts::id.desc()),
|
3 => query.order(RANDOM),
|
||||||
|
_ => panic!("Wrong order mode!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
query.offset(start).limit(limit).load(c)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn search(
|
||||||
|
db: &Db,
|
||||||
|
search_mode: u8,
|
||||||
|
search_text: String,
|
||||||
|
start: i64,
|
||||||
|
limit: i64,
|
||||||
|
) -> QueryResult<Vec<Self>> {
|
||||||
|
let search_text2 = search_text.replace("%", "\\%");
|
||||||
|
db.run(move |c| {
|
||||||
|
let pat;
|
||||||
|
let mut query = base_query!(posts)
|
||||||
|
.distinct()
|
||||||
|
.left_join(comments::table)
|
||||||
|
.filter(comments::is_deleted.eq(false));
|
||||||
|
// 先用搜索+缓存,性能有问题了再真的做tag表
|
||||||
|
query = match search_mode {
|
||||||
|
0 => {
|
||||||
|
pat = format!("%#{}%", &search_text2);
|
||||||
query
|
query
|
||||||
.offset(((page - 1) * page_size).into())
|
.filter(posts::cw.eq(&search_text))
|
||||||
.limit(page_size.into())
|
.or_filter(posts::cw.eq(format!("#{}", &search_text)))
|
||||||
|
.or_filter(posts::content.like(&pat))
|
||||||
|
.or_filter(comments::content.like(&pat))
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
pat = format!("%{}%", search_text2.replace(" ", "%"));
|
||||||
|
query
|
||||||
|
.filter(posts::content.like(&pat).or(comments::content.like(&pat)))
|
||||||
|
.filter(posts::allow_search.eq(true))
|
||||||
|
}
|
||||||
|
2 => query
|
||||||
|
.filter(posts::author_title.eq(&search_text))
|
||||||
|
.or_filter(comments::author_title.eq(&search_text)),
|
||||||
|
_ => panic!("Wrong search mode!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
query
|
||||||
|
.order(posts::id.desc())
|
||||||
|
.offset(start)
|
||||||
|
.limit(limit)
|
||||||
.load(c)
|
.load(c)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -149,25 +218,42 @@ impl Post {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_n_comments(&self, db: &Db, delta: i32) -> QueryResult<usize> {
|
pub async fn change_n_comments(&self, db: &Db, delta: i32) -> QueryResult<Self> {
|
||||||
let pid = self.id;
|
let pid = self.id;
|
||||||
db.run(move |c| {
|
db.run(move |c| {
|
||||||
diesel::update(posts::table.find(pid))
|
diesel::update(posts::table.find(pid))
|
||||||
.set(posts::n_comments.eq(posts::n_comments + delta))
|
.set(posts::n_comments.eq(posts::n_comments + delta))
|
||||||
.execute(c)
|
.get_result(c)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_n_attentions(&self, db: &Db, delta: i32) -> QueryResult<usize> {
|
pub async fn change_n_attentions(&self, db: &Db, delta: i32) -> QueryResult<Self> {
|
||||||
let pid = self.id;
|
let pid = self.id;
|
||||||
db.run(move |c| {
|
db.run(move |c| {
|
||||||
diesel::update(posts::table.find(pid))
|
diesel::update(posts::table.find(pid))
|
||||||
.set(posts::n_attentions.eq(posts::n_attentions + delta))
|
.set(posts::n_attentions.eq(posts::n_attentions + delta))
|
||||||
.execute(c)
|
.get_result(c)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn change_hot_score(&self, db: &Db, delta: i32) -> QueryResult<Self> {
|
||||||
|
let pid = self.id;
|
||||||
|
db.run(move |c| {
|
||||||
|
diesel::update(posts::table.find(pid))
|
||||||
|
.set(posts::hot_score.eq(posts::hot_score + delta))
|
||||||
|
.get_result(c)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_instance_cache(&self, rconn: &RdsConn) {
|
||||||
|
PostCache::init(&self.id, rconn).set(self).await;
|
||||||
|
}
|
||||||
|
pub async fn refresh_cache(&self, rconn: &RdsConn, is_new: bool) {
|
||||||
|
self.set_instance_cache(rconn).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
use crate::models::{Comment, Post};
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
use redis::{AsyncCommands, RedisResult};
|
use redis::{AsyncCommands, RedisResult};
|
||||||
|
use rocket::serde::json::serde_json;
|
||||||
|
// can use rocket::serde::json::to_string in master version
|
||||||
|
|
||||||
|
const INSTANCE_EXPIRE_TIME: usize = 60 * 60;
|
||||||
|
|
||||||
pub struct Attention {
|
pub struct Attention {
|
||||||
key: String,
|
key: String,
|
||||||
@@ -7,10 +12,10 @@ pub struct Attention {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Attention {
|
impl Attention {
|
||||||
pub fn init(namehash: &str, rconn: RdsConn) -> Self {
|
pub fn init(namehash: &str, rconn: &RdsConn) -> Self {
|
||||||
Attention {
|
Attention {
|
||||||
key: format!("hole_v2:attention:{}", namehash),
|
key: format!("hole_v2:attention:{}", namehash),
|
||||||
rconn: rconn,
|
rconn: rconn.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,3 +35,50 @@ impl Attention {
|
|||||||
self.rconn.smembers(&self.key).await
|
self.rconn.smembers(&self.key).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PostCache {
|
||||||
|
key: String,
|
||||||
|
rconn: RdsConn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostCache {
|
||||||
|
pub fn init(pid: &i32, rconn: &RdsConn) -> Self {
|
||||||
|
PostCache {
|
||||||
|
key: format!("hole_v2:cache:post:{}", pid),
|
||||||
|
rconn: rconn.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(&mut self, p: &Post) {
|
||||||
|
self.rconn
|
||||||
|
.set_ex(
|
||||||
|
&self.key,
|
||||||
|
serde_json::to_string(p).unwrap(),
|
||||||
|
INSTANCE_EXPIRE_TIME,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
dbg!("set post cache failed", e, p.id);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&mut self) -> Option<Post> {
|
||||||
|
let rds_result = self.rconn.get::<&String, String>(&self.key).await;
|
||||||
|
if let Ok(s) = rds_result {
|
||||||
|
dbg!("hint post cache", &s);
|
||||||
|
self.rconn
|
||||||
|
.expire::<&String, bool>(&self.key, INSTANCE_EXPIRE_TIME)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
dbg!("get post cache, set new expire failed", e, &self.key, &s);
|
||||||
|
false
|
||||||
|
});
|
||||||
|
serde_json::from_str(&s).unwrap_or_else(|e| {
|
||||||
|
dbg!("get post cache failed", e, s);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,8 +42,4 @@ table! {
|
|||||||
|
|
||||||
joinable!(comments -> posts (post_id));
|
joinable!(comments -> posts (post_id));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(comments, posts, users,);
|
||||||
comments,
|
|
||||||
posts,
|
|
||||||
users,
|
|
||||||
);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user