feat: search & beginning of cache

This commit is contained in:
2022-03-23 03:53:48 +08:00
parent cfe39f7645
commit dcc7bb1268
11 changed files with 320 additions and 115 deletions

View File

@@ -1,5 +1,5 @@
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::models::*;
use crate::rds_conn::RdsConn;
@@ -22,20 +22,22 @@ pub async fn attention_post(
rconn: RdsConn,
) -> API<Value> {
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")?;
let mut att = Attention::init(&user.namehash, rconn);
let mut att = Attention::init(&user.namehash, &rconn);
let switch_to = ai.switch == 1;
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 {
att.add(ai.pid).await.m()?;
att.add(ai.pid).await?;
delta = 1;
} else {
att.remove(ai.pid).await.m()?;
att.remove(ai.pid).await?;
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!({
@@ -49,12 +51,9 @@ pub async fn attention_post(
#[get("/getattention")]
pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> API<Value> {
let ids = Attention::init(&user.namehash, rconn.clone())
.all()
.await
.m()?;
let ps = Post::get_multi(&db, ids).await.m()?;
let ps_data = ps2outputs(&ps, &user, &db, rconn.clone()).await;
let ids = Attention::init(&user.namehash, &rconn).all().await?;
let ps = Post::get_multi(&db, ids).await?;
let ps_data = ps2outputs(&ps, &user, &db, &rconn, &vec![]).await;
Ok(json!({
"code": 0,

View File

@@ -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::models::*;
use crate::rds_conn::RdsConn;
use crate::rds_models::*;
use chrono::{offset::Utc, DateTime};
use rocket::form::Form;
use rocket::futures::{future::TryFutureExt, try_join};
use rocket::serde::{
json::{json, Value},
Serialize,
@@ -24,6 +25,7 @@ pub struct CommentInput {
pub struct CommentOutput {
cid: i32,
text: String,
author_title: String,
can_del: bool,
name_id: i32,
is_tmp: bool,
@@ -32,7 +34,12 @@ pub struct CommentOutput {
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)]);
cs.iter()
.filter_map(|c| {
@@ -48,10 +55,15 @@ pub fn c2output<'r>(p: &'r Post, cs: &Vec<Comment>, user: &CurrentUser) -> Vec<C
// TODO: block
None
} else {
let mut text = c.content.to_string();
for kw in kws {
text = text.replace(kw, &format!(" **{}**", kw));
}
Some(CommentOutput {
cid: c.id,
text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, c.content),
can_del: user.is_admin || c.author_hash == user.namehash,
text: format!("{}{}", if c.is_tmp { "[tmp]\n" } else { "" }, text),
author_title: c.author_title.to_string(),
can_del: c.check_permission(user, "wd").is_ok(),
name_id: name_id,
is_tmp: c.is_tmp,
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>")]
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 {
return Err(APIError::PcError(IsDeleted));
}
let pid = p.id;
let cs = Comment::gets_by_post_id(&db, pid).await.m()?;
let data = c2output(&p, &cs, &user);
let cs = Comment::gets_by_post_id(&db, pid).await?;
let data = c2output(&p, &cs, &user, &vec![]);
Ok(json!({
"code": 0,
@@ -78,7 +90,7 @@ pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) ->
"n_attentions": p.n_attentions,
// for old version frontend
"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,
rconn: RdsConn,
) -> API<Value> {
let p = Post::get(&db, ci.pid).await.m()?;
let mut p = Post::get(&db, ci.pid).await?;
Comment::create(
&db,
NewComment {
@@ -100,15 +112,28 @@ pub async fn add_comment(
post_id: ci.pid,
},
)
.await
.m()?;
p.change_n_comments(&db, 1).await.m()?;
.await?;
p = p.change_n_comments(&db, 1).await?;
// auto attention after comment
let mut att = Attention::init(&user.namehash, rconn);
if !att.has(p.id).await.m()? {
att.add(p.id).await.m()?;
p.change_n_attentions(&db, 1).await.m()?;
let mut att = Attention::init(&user.namehash, &rconn);
let mut hs_delta = 1;
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!({
"code": 0
}))

View File

@@ -70,16 +70,6 @@ pub enum APIError {
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 {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
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 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]
pub trait UGC {
fn get_author_hash(&self) -> &str;
@@ -178,7 +161,7 @@ impl UGC for Post {
self.n_comments == 0
}
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
}
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 operation;
pub mod post;
pub mod search;
pub mod systemlog;

View File

@@ -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::rds_conn::RdsConn;
use crate::models::*;
use rocket::form::Form;
use rocket::serde::json::{json, Value};
@@ -13,16 +14,18 @@ pub struct DeleteInput {
}
#[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() {
"cid" => {
let c = Comment::get(&db, di.id).await.m()?;
let c = Comment::get(&db, di.id).await?;
c.soft_delete(&user, &db).await?;
let p = Post::get(&db, c.post_id).await.m()?;
p.change_n_comments(&db, -1).await.m()?;
let mut p = Post::get(&db, c.post_id).await?;
p = p.change_n_comments(&db, -1).await?;
p = p.change_hot_score(&db, -2).await?;
p.refresh_cache(&rconn, false).await;
}
"pid" => {
let p = Post::get(&db, di.id).await.m()?;
let p = Post::get(&db, di.id).await?;
p.soft_delete(&user, &db).await?;
}
_ => return Err(APIError::PcError(NotAllowed)),

View File

@@ -1,5 +1,5 @@
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::models::*;
use crate::rds_conn::RdsConn;
@@ -25,7 +25,7 @@ pub struct PostOutput {
pid: i32,
text: String,
cw: Option<String>,
custom_title: Option<String>,
author_title: Option<String>,
is_tmp: bool,
n_attentions: i32,
n_comments: i32,
@@ -49,10 +49,21 @@ pub struct CwInput {
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 {
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 {
Some(p.cw.to_string())
} else {
@@ -63,7 +74,7 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: RdsConn) -> Post
create_time: p.create_time,
last_comment_time: p.last_comment_time,
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())
} else {
None
@@ -80,13 +91,13 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: RdsConn) -> Post
// 单个洞还有查询评论的接口,这里挂了不用报错
let pid = p.id;
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 {
None
}
},
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)
.await
.unwrap_or_default(),
@@ -101,21 +112,23 @@ pub async fn ps2outputs(
ps: &Vec<Post>,
user: &CurrentUser,
db: &Db,
rconn: RdsConn,
rconn: &RdsConn,
kws: &Vec<&str>,
) -> Vec<PostOutput> {
future::join_all(
ps.iter()
.map(|p| async { p2output(p, &user, &db, rconn.clone()).await }),
.map(|p| async { p2output(p, &user, &db, &rconn, &kws.clone()).await }),
)
.await
}
#[get("/getone?<pid>")]
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")?;
Ok(json!({
"data": p2output(&p, &user,&db, rconn).await,
"data": p2output(&p, &user,&db, &rconn, &vec![]).await,
"code": 0,
}))
}
@@ -129,8 +142,10 @@ pub async fn get_list(
rconn: RdsConn,
) -> JsonAPI {
let page = p.unwrap_or(1);
let ps = Post::gets_by_page(&db, order_mode, page, 25).await.m()?;
let ps_data = ps2outputs(&ps, &user, &db, rconn.clone()).await;
let page_size = 25;
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!({
"data": ps_data,
"count": ps_data.len(),
@@ -139,7 +154,12 @@ pub async fn get_list(
}
#[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(
&db,
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(),
},
)
.await
.m()?;
// TODO: attention
.await?;
Attention::init(&user.namehash, &rconn).add(p.id).await?;
Ok(json!({
"code": 0
}))
@@ -162,19 +181,19 @@ pub async fn publish_post(poi: Form<PostInput>, user: CurrentUser, db: Db) -> Js
#[post("/editcw", data = "<cwi>")]
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) {
return Err(APIError::PcError(NotAllowed));
}
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}))
}
#[get("/getmulti?<pids>")]
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_data = ps2outputs(&ps, &user, &db, rconn.clone()).await;
let ps = Post::get_multi(&db, pids).await?;
let ps_data = ps2outputs(&ps, &user, &db, &rconn, &vec![]).await;
Ok(json!({
"code": 0,

40
src/api/search.rs Normal file
View 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
}))
}