Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18262a62e0 | |||
| 0e1a088575 | |||
| dbb5732938 | |||
| f2fa7bb6f8 | |||
| 241034ca9b | |||
| 7016718673 | |||
| 999ca30448 | |||
| 273073ba11 | |||
| 58794a479d | |||
| 4c4ddca82b | |||
| 25649e6280 | |||
| 1aa86b0963 | |||
| dbea8f435f | |||
| 9a33ef5a88 | |||
| 9937bc0dc7 | |||
| fcc8ee3b15 | |||
| 9ec106872b | |||
| 462071da54 | |||
| a42ef851be | |||
| 0fcb56c8a3 | |||
| ff4b983ac3 | |||
| 340d6da45c | |||
| c8dcaab936 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
rust-toolchain
|
||||||
|
/user_files
|
||||||
|
*.db
|
||||||
|
.env
|
||||||
@@ -3,6 +3,11 @@ MAST_CLIENT="<your client id>"
|
|||||||
MAST_SECRET="<your client key>"
|
MAST_SECRET="<your client key>"
|
||||||
MAST_SCOPE="read:accounts"
|
MAST_SCOPE="read:accounts"
|
||||||
|
|
||||||
|
AUTH_BACKEND_URL="http://hole.localhost"
|
||||||
|
FRONTEND_WHITELIST="https://thuhollow.github.io"
|
||||||
|
|
||||||
|
UPLOAD_DIR="user_files"
|
||||||
|
|
||||||
DATABASE_URL="postgres://hole:hole_pass@localhost/hole_v2"
|
DATABASE_URL="postgres://hole:hole_pass@localhost/hole_v2"
|
||||||
MIGRATION_DIRECTORY=migrations/postgres
|
MIGRATION_DIRECTORY=migrations/postgres
|
||||||
REDIS_URL="redis://127.0.0.1:6379"
|
REDIS_URL="redis://127.0.0.1:6379"
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
# private key
|
||||||
|
keys
|
||||||
|
|
||||||
# --> sqlite3
|
# --> sqlite3
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
|||||||
13
Cargo.toml
13
Cargo.toml
@@ -7,11 +7,15 @@ license = "WTFPL-2.0"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
mastlogin = ["url", "reqwest"]
|
default = ["mastlogin"]
|
||||||
|
mastlogin = ["reqwest"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { version = "0.5.0-rc.1", features = ["json"] }
|
rocket = { version = "=0.5.0-rc.1", features = ["json"] }
|
||||||
rocket_sync_db_pools = { version = "0.1.0-cr.1", features = ["diesel_postgres_pool"] }
|
rocket_codegen = "=0.5.0-rc.1"
|
||||||
|
rocket_http = "=0.5.0-rc.1"
|
||||||
|
rocket_sync_db_pools_codegen = "=0.1.0-rc.1"
|
||||||
|
rocket_sync_db_pools = { version = "=0.1.0-rc.1", features = ["diesel_postgres_pool"] }
|
||||||
diesel = { version = "1.4.8", features = ["postgres", "chrono"] }
|
diesel = { version = "1.4.8", features = ["postgres", "chrono"] }
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
tokio = "1.17.0"
|
tokio = "1.17.0"
|
||||||
@@ -22,6 +26,7 @@ dotenv = "0.15.0"
|
|||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
log = "0.4.16"
|
log = "0.4.16"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
|
web-push = "0.9.2"
|
||||||
|
url = "2.2.2"
|
||||||
|
|
||||||
url = { version="2.2.2",optional = true }
|
|
||||||
reqwest = { version = "0.11.10", features = ["json"], optional = true }
|
reqwest = { version = "0.11.10", features = ["json"], optional = true }
|
||||||
|
|||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM rust:1-bullseye as builder
|
||||||
|
WORKDIR /usr/src/
|
||||||
|
RUN cargo new myapp --vcs none
|
||||||
|
WORKDIR /usr/src/myapp
|
||||||
|
COPY Cargo.toml ./
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# 为了充分利用docker的缓存
|
||||||
|
COPY src ./src
|
||||||
|
COPY migrations ./migrations
|
||||||
|
RUN touch src/main.rs && cargo build --release
|
||||||
|
|
||||||
|
|
||||||
|
FROM debian:bullseye-slim
|
||||||
|
RUN apt-get update && apt-get install libpq5 -y
|
||||||
|
COPY --from=builder /usr/src/myapp/target/release/hole-thu /usr/local/bin/hole-thu
|
||||||
|
COPY Rocket.toml /usr/local/bin/
|
||||||
|
|
||||||
|
CMD ["hole-thu"]
|
||||||
116
README.md
116
README.md
@@ -1,25 +1,94 @@
|
|||||||
# hole-backend-rust v1.2.0
|
# hole-backend-rust v1.3.0
|
||||||
|
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
|
### 使用docker
|
||||||
|
|
||||||
|
+ 安装最新版docker与docker-compose-plugin
|
||||||
|
|
||||||
|
以Ubuntu为例: https://docs.docker.com/engine/install/ubuntu/
|
||||||
|
|
||||||
|
检查docker版本:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ docker --version
|
||||||
|
Docker version 20.10.17, build 100c701
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
+ 执行
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mkdir hole
|
||||||
|
cd hole
|
||||||
|
|
||||||
|
# 下载docker-compose.yml
|
||||||
|
wget https://git.thu.monster/newthuhole/hole-backend-rust/raw/branch/master/docker-compose.yml
|
||||||
|
|
||||||
|
# 下载add_pg_trgm.sh
|
||||||
|
mkdir psql-docker-init
|
||||||
|
wget https://git.thu.monster/newthuhole/hole-backend-rust/raw/branch/master/psql-docker-init/add_pg_trgm.sh -O psql-docker-init/add_pg_trgm.sh
|
||||||
|
|
||||||
|
|
||||||
|
# 下载镜像
|
||||||
|
docker compose pull
|
||||||
|
# 初始化postgres
|
||||||
|
docker compose up -d postgres
|
||||||
|
|
||||||
|
# 建表
|
||||||
|
docker compose run --rm hole-thu hole-thu --init-database
|
||||||
|
|
||||||
|
# 全部跑起来
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
现在树洞后端应该已经运行在8000端口了
|
||||||
|
|
||||||
|
停止运行:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker compose stop
|
||||||
|
```
|
||||||
|
|
||||||
|
如需创建管理员账户,执行:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker compose exec postgres psql --username hole --dbname hole_v2
|
||||||
|
```
|
||||||
|
|
||||||
|
进入数据库,执行需要的SQL命令。
|
||||||
|
|
||||||
|
|
||||||
|
+ 修改`docker-compose.yml`的情况:
|
||||||
|
|
||||||
|
+ 编辑services.hole-thu.environmen填入你的后端地址(用于登陆时的回调跳转)和前端地址(用于允许跨域)
|
||||||
|
|
||||||
|
+ 如果希望使用其他端口而非8000,编辑services.hole-thu.ports
|
||||||
|
|
||||||
|
+ 如果需要使用闭社登陆,请在services.hole-thu.environment中添加需要用到的更多环境变量(参考`.env.sample`)
|
||||||
|
|
||||||
|
### 使用源码编译
|
||||||
|
|
||||||
*以下内容假设你使用 Ubuntu 20.04*
|
*以下内容假设你使用 Ubuntu 20.04*
|
||||||
|
|
||||||
目前只支持postgresql,对支持sqlite的追踪见 issue #1
|
安装rust与cargo环境 (略)
|
||||||
|
|
||||||
|
clone 代码 (略)
|
||||||
|
|
||||||
安装postgresql (略)
|
安装postgresql (略)
|
||||||
|
|
||||||
安装redis (略)
|
安装redis (略)
|
||||||
|
|
||||||
### 准备数据库
|
#### 准备数据库
|
||||||
|
|
||||||
进入:
|
进入:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
sudo -u postgres psql
|
sudo -u postgres psql
|
||||||
```
|
```
|
||||||
|
|
||||||
执行 (替换`'hole_pass'`为实际希望使用的密码):
|
执行 (替换`hole_pass`为实际希望使用的密码):
|
||||||
|
|
||||||
```postgresql
|
```postgresql
|
||||||
postgres=# CREATE USER hole WITH PASSWORD 'hole_pass';
|
postgres=# CREATE USER hole WITH PASSWORD 'hole_pass';
|
||||||
@@ -32,39 +101,36 @@ hole_v2=# CREATE EXTENSION pg_trgm;
|
|||||||
CREATE EXTENSION
|
CREATE EXTENSION
|
||||||
hole_v2=# \q
|
hole_v2=# \q
|
||||||
```
|
```
|
||||||
### 运行
|
#### 编译&运行
|
||||||
|
|
||||||
创建 .env 文件,写入必要的环境变量。可参考 .env.sample。
|
创建 .env 文件,写入必要的环境变量。可参考 .env.sample。
|
||||||
|
|
||||||
#### 基于二进制文件
|
```shell
|
||||||
|
|
||||||
从[release](https://git.thu.monster/newthuhole/hole-backend-rust/releases)直接下载二进制文件
|
|
||||||
|
|
||||||
```
|
|
||||||
./hole-thu --init-database
|
|
||||||
./hole-thu
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 基于源码
|
|
||||||
|
|
||||||
安装rust与cargo环境 (略)
|
|
||||||
|
|
||||||
clone 代码 (略)
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo run --release -- --init-database
|
cargo run --release -- --init-database
|
||||||
cargo run --release
|
cargo run --release
|
||||||
```
|
```
|
||||||
|
|
||||||
或安装`diesel_cli`后
|
或安装`diesel_cli`后
|
||||||
|
|
||||||
```
|
```shell
|
||||||
diesel migration run
|
diesel migration run
|
||||||
cargo run --release
|
cargo run --release
|
||||||
```
|
```
|
||||||
|
|
||||||
### 关于账号系统
|
### 基于二进制文件
|
||||||
|
|
||||||
+ 如果你希望使用自己的登录系统,将 `/_login/` 路径交由另外的后端处理,只需最终将用户名和token写入users表,并跳转到 `/?token=<token>`。
|
安装与准备数据库同
|
||||||
|
|
||||||
|
从[release](https://git.thu.monster/newthuhole/hole-backend-rust/releases)直接下载二进制文件
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./hole-thu --init-database
|
||||||
|
./hole-thu
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 关于账号系统
|
||||||
|
|
||||||
|
+ 如果你希望使用自己的登录系统,在Nginx或Apache中将 `/_login/` 路径交由另外的后端处理,只需最终将用户名和token写入users表,并跳转到 `/###token=<token>`。
|
||||||
|
|
||||||
+ 如果你希望也使用闭社提供的授权来维护账号系统,使用 `https://thu.closed.social/api/v1/apps` 接口创建应用,并在.env或环境变量中填入client与secret。此操作不需要闭社账号。详情见[文档](https://docs.joinmastodon.org/client/token/#app)。编译运行时,增加`--features mastlogin`: `cargo run --release --features mastlogin`
|
+ 如果你希望也使用闭社提供的授权来维护账号系统,使用 `https://thu.closed.social/api/v1/apps` 接口创建应用,并在.env或环境变量中填入client与secret。此操作不需要闭社账号。详情见[文档](https://docs.joinmastodon.org/client/token/#app)。编译运行时,增加`--features mastlogin`: `cargo run --release --features mastlogin`
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
[default]
|
[default]
|
||||||
limits = { file = "200MB" }
|
limits = { file = "200MB" }
|
||||||
temp_dir = "user_files"
|
|
||||||
|
|||||||
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
version: '1'
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:14.3
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- "./data/postgres:/var/lib/postgresql/data"
|
||||||
|
- "./psql-docker-init:/docker-entrypoint-initdb.d"
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: hole_pass
|
||||||
|
POSTGRES_USER: hole
|
||||||
|
POSTGRES_DB: hole_v2
|
||||||
|
redis:
|
||||||
|
image: redis:7.0.2
|
||||||
|
restart: unless-stopped
|
||||||
|
hole-thu:
|
||||||
|
image: holethu/hole-backend-rust:1.2.0
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8000:8863"
|
||||||
|
volumes:
|
||||||
|
- "./data/user_files:/user_files"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: "postgres://hole:hole_pass@postgres/hole_v2"
|
||||||
|
REDIS_URL: "redis://redis:6379"
|
||||||
|
ROCKET_DATABASES: '{pg_v2={url="postgres://hole:hole_pass@postgres/hole_v2"}}'
|
||||||
|
ROCKET_ADDRESS: "0.0.0.0"
|
||||||
|
ROCKET_PORT: 8863
|
||||||
|
AUTH_BACKEND_URL: "<你的后端地址>"
|
||||||
|
FRONTEND_WHITELIST: "<你的前端地址1>,<你的前端地址2>"
|
||||||
|
UPLOAD_DIR: "/user_files"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
ALTER TABLE posts
|
||||||
|
DROP COLUMN room_id
|
||||||
3
migrations/postgres/2022-08-18-234900_add_room_id/up.sql
Normal file
3
migrations/postgres/2022-08-18-234900_add_room_id/up.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE posts
|
||||||
|
ADD COLUMN room_id INTEGER NOT NULL DEFAULT 0
|
||||||
5
psql-docker-init/add_pg_trgm.sh
Normal file
5
psql-docker-init/add_pg_trgm.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
|
create extension pg_trgm;
|
||||||
|
EOSQL
|
||||||
|
|
||||||
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
@@ -0,0 +1 @@
|
|||||||
|
stable
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::api::post::ps2outputs;
|
use crate::api::post::ps2outputs;
|
||||||
use crate::api::{CurrentUser, JsonAPI, PolicyError::*, UGC};
|
use crate::api::{CurrentUser, JsonApi, PolicyError::*, Ugc};
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
use crate::libs::diesel_logger::LoggingConnection;
|
use crate::libs::diesel_logger::LoggingConnection;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
@@ -9,6 +9,13 @@ use crate::schema;
|
|||||||
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use rocket::serde::json::json;
|
use rocket::serde::json::json;
|
||||||
|
use rocket::serde::json::serde_json;
|
||||||
|
use rocket::serde::Serialize;
|
||||||
|
use std::fs::File;
|
||||||
|
use url::Url;
|
||||||
|
use web_push::{
|
||||||
|
ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct AttentionInput {
|
pub struct AttentionInput {
|
||||||
@@ -23,9 +30,9 @@ pub async fn attention_post(
|
|||||||
user: CurrentUser,
|
user: CurrentUser,
|
||||||
db: Db,
|
db: Db,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> JsonAPI {
|
) -> JsonApi {
|
||||||
// 临时用户不允许手动关注
|
// 临时用户不允许手动关注
|
||||||
user.id.ok_or_else(|| YouAreTmp)?;
|
user.id.ok_or(YouAreTmp)?;
|
||||||
|
|
||||||
let mut p = Post::get(&db, &rconn, ai.pid).await?;
|
let mut p = Post::get(&db, &rconn, ai.pid).await?;
|
||||||
p.check_permission(&user, "r")?;
|
p.check_permission(&user, "r")?;
|
||||||
@@ -40,12 +47,17 @@ pub async fn attention_post(
|
|||||||
att.remove(ai.pid).await?;
|
att.remove(ai.pid).await?;
|
||||||
delta = -1;
|
delta = -1;
|
||||||
}
|
}
|
||||||
|
let hot_delta = if p.n_attentions <= 3 * p.n_comments {
|
||||||
|
delta * 2
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
update!(
|
update!(
|
||||||
p,
|
p,
|
||||||
posts,
|
posts,
|
||||||
&db,
|
&db,
|
||||||
{ n_attentions, add delta },
|
{ n_attentions, add delta },
|
||||||
{ hot_score, add delta * 2 }
|
{ hot_score, add hot_delta }
|
||||||
);
|
);
|
||||||
if switch_to && user.is_admin {
|
if switch_to && user.is_admin {
|
||||||
update!(p, posts, &db, { is_reported, to false });
|
update!(p, posts, &db, { is_reported, to false });
|
||||||
@@ -63,7 +75,7 @@ pub async fn attention_post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/getattention")]
|
#[get("/getattention")]
|
||||||
pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> JsonApi {
|
||||||
let mut ids = Attention::init(&user.namehash, &rconn).all().await?;
|
let mut ids = Attention::init(&user.namehash, &rconn).all().await?;
|
||||||
ids.sort_by_key(|x| -x);
|
ids.sort_by_key(|x| -x);
|
||||||
let ps = Post::get_multi(&db, &rconn, &ids).await?;
|
let ps = Post::get_multi(&db, &rconn, &ids).await?;
|
||||||
@@ -71,3 +83,57 @@ pub async fn get_attention(user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI
|
|||||||
|
|
||||||
code0!(ps_data)
|
code0!(ps_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct NotificatinInput {
|
||||||
|
enable: bool,
|
||||||
|
endpoint: String,
|
||||||
|
auth: String,
|
||||||
|
p256dh: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct PushData {
|
||||||
|
title: String,
|
||||||
|
pid: i32,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/post/<pid>/notification", data = "<ni>")]
|
||||||
|
pub async fn set_notification(pid: i32, ni: Form<NotificatinInput>, _user: CurrentUser) -> JsonApi {
|
||||||
|
let url_host = Url::parse(&ni.endpoint)
|
||||||
|
.map_err(|_| UnknownPushEndpoint)?
|
||||||
|
.host()
|
||||||
|
.ok_or(UnknownPushEndpoint)?
|
||||||
|
.to_string();
|
||||||
|
(url_host.ends_with("googleapis.com") || url_host.ends_with("mozilla.com"))
|
||||||
|
.then(|| ())
|
||||||
|
.ok_or(UnknownPushEndpoint)?;
|
||||||
|
|
||||||
|
if ni.enable {
|
||||||
|
let subscription_info = SubscriptionInfo::new(&ni.endpoint, &ni.p256dh, &ni.auth);
|
||||||
|
|
||||||
|
let file = File::open("keys/private.pem").unwrap();
|
||||||
|
let sig_builder = VapidSignatureBuilder::from_pem(file, &subscription_info)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut builder = WebPushMessageBuilder::new(&subscription_info).unwrap();
|
||||||
|
let data = PushData {
|
||||||
|
title: "测试".to_owned(),
|
||||||
|
pid,
|
||||||
|
text: format!("#{} 开启提醒测试成功,消息提醒功能即将正式上线", &pid),
|
||||||
|
};
|
||||||
|
let content = serde_json::to_string(&data).unwrap();
|
||||||
|
builder.set_payload(ContentEncoding::Aes128Gcm, content.as_bytes());
|
||||||
|
builder.set_vapid_signature(sig_builder);
|
||||||
|
|
||||||
|
let client = WebPushClient::new()?;
|
||||||
|
|
||||||
|
client.send(builder.build()?).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
code0!()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, UGC};
|
use crate::api::{ApiError, CurrentUser, JsonApi, PolicyError::*, Ugc};
|
||||||
use crate::cache::BlockDictCache;
|
use crate::cache::BlockDictCache;
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
use crate::libs::diesel_logger::LoggingConnection;
|
use crate::libs::diesel_logger::LoggingConnection;
|
||||||
@@ -15,7 +15,6 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct CommentInput {
|
pub struct CommentInput {
|
||||||
pid: i32,
|
|
||||||
#[field(validate = len(1..12289))]
|
#[field(validate = len(1..12289))]
|
||||||
text: String,
|
text: String,
|
||||||
use_title: Option<i8>,
|
use_title: Option<i8>,
|
||||||
@@ -40,7 +39,7 @@ pub struct CommentOutput {
|
|||||||
|
|
||||||
pub async fn c2output<'r>(
|
pub async fn c2output<'r>(
|
||||||
p: &'r Post,
|
p: &'r Post,
|
||||||
cs: &Vec<Comment>,
|
cs: &[Comment],
|
||||||
user: &CurrentUser,
|
user: &CurrentUser,
|
||||||
cached_block_dict: &HashMap<String, bool>,
|
cached_block_dict: &HashMap<String, bool>,
|
||||||
rconn: &RdsConn,
|
rconn: &RdsConn,
|
||||||
@@ -66,10 +65,10 @@ pub async fn c2output<'r>(
|
|||||||
text: (if can_view { &c.content } else { "" }).to_string(),
|
text: (if can_view { &c.content } else { "" }).to_string(),
|
||||||
author_title: c.author_title.to_string(),
|
author_title: c.author_title.to_string(),
|
||||||
can_del: c.check_permission(user, "wd").is_ok(),
|
can_del: c.check_permission(user, "wd").is_ok(),
|
||||||
name_id: name_id,
|
name_id,
|
||||||
is_tmp: c.is_tmp,
|
is_tmp: c.is_tmp,
|
||||||
create_time: c.create_time.timestamp(),
|
create_time: c.create_time.timestamp(),
|
||||||
is_blocked: is_blocked,
|
is_blocked,
|
||||||
blocked_count: if user.is_admin {
|
blocked_count: if user.is_admin {
|
||||||
BlockCounter::get_count(rconn, &c.author_hash)
|
BlockCounter::get_count(rconn, &c.author_hash)
|
||||||
.await
|
.await
|
||||||
@@ -85,18 +84,18 @@ pub async fn c2output<'r>(
|
|||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|x| x)
|
.flatten()
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/getcomment?<pid>")]
|
#[get("/getcomment?<pid>")]
|
||||||
pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonApi {
|
||||||
let p = Post::get(&db, &rconn, pid).await?;
|
let p = Post::get(&db, &rconn, pid).await?;
|
||||||
if p.is_deleted {
|
if p.is_deleted {
|
||||||
return Err(APIError::PcError(IsDeleted));
|
return Err(ApiError::Pc(IsDeleted));
|
||||||
}
|
}
|
||||||
let cs = p.get_comments(&db, &rconn).await?;
|
let cs = p.get_comments(&db, &rconn).await?;
|
||||||
let hash_list = cs.iter().map(|c| &c.author_hash).collect();
|
let hash_list = cs.iter().map(|c| &c.author_hash).collect::<Vec<_>>();
|
||||||
let cached_block_dict = BlockDictCache::init(&user.namehash, p.id, &rconn)
|
let cached_block_dict = BlockDictCache::init(&user.namehash, p.id, &rconn)
|
||||||
.get_or_create(&user, &hash_list)
|
.get_or_create(&user, &hash_list)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -112,14 +111,20 @@ pub async fn get_comment(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) ->
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/docomment", data = "<ci>")]
|
#[post("/docomment")]
|
||||||
|
pub async fn old_add_comment() -> ApiError {
|
||||||
|
OldApi.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/post/<pid>/comment", data = "<ci>")]
|
||||||
pub async fn add_comment(
|
pub async fn add_comment(
|
||||||
|
pid: i32,
|
||||||
ci: Form<CommentInput>,
|
ci: Form<CommentInput>,
|
||||||
user: CurrentUser,
|
user: CurrentUser,
|
||||||
db: Db,
|
db: Db,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> JsonAPI {
|
) -> JsonApi {
|
||||||
let mut p = Post::get(&db, &rconn, ci.pid).await?;
|
let mut p = Post::get(&db, &rconn, pid).await?;
|
||||||
let c = Comment::create(
|
let c = Comment::create(
|
||||||
&db,
|
&db,
|
||||||
NewComment {
|
NewComment {
|
||||||
@@ -132,7 +137,7 @@ pub async fn add_comment(
|
|||||||
})
|
})
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
is_tmp: user.id.is_none(),
|
is_tmp: user.id.is_none(),
|
||||||
post_id: ci.pid,
|
post_id: pid,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -146,7 +151,7 @@ pub async fn add_comment(
|
|||||||
at_delta = 1;
|
at_delta = 1;
|
||||||
att.add(p.id).await?;
|
att.add(p.id).await?;
|
||||||
} else {
|
} else {
|
||||||
hs_delta = 1;
|
hs_delta = (p.n_comments < 3 * p.n_attentions) as i32;
|
||||||
at_delta = 0;
|
at_delta = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ macro_rules! code0 {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
macro_rules! code1 {
|
macro_rules! code1 {
|
||||||
($msg:expr) => (
|
($msg:expr) => (
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
@@ -33,6 +34,7 @@ macro_rules! code1 {
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
macro_rules! e2s {
|
macro_rules! e2s {
|
||||||
($e:expr) => (json!({
|
($e:expr) => (json!({
|
||||||
@@ -91,7 +93,7 @@ impl<'r> FromRequest<'r> for CurrentUser {
|
|||||||
Outcome::Failure((Status::Forbidden, ()))
|
Outcome::Failure((Status::Forbidden, ()))
|
||||||
} else {
|
} else {
|
||||||
Outcome::Success(CurrentUser {
|
Outcome::Success(CurrentUser {
|
||||||
id: id,
|
id,
|
||||||
custom_title: CustomTitle::get(&rconn, &nh)
|
custom_title: CustomTitle::get(&rconn, &nh)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
@@ -99,7 +101,7 @@ impl<'r> FromRequest<'r> for CurrentUser {
|
|||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
auto_block_rank: AutoBlockRank::get(&rconn, &nh).await.unwrap_or(2),
|
auto_block_rank: AutoBlockRank::get(&rconn, &nh).await.unwrap_or(2),
|
||||||
namehash: nh,
|
namehash: nh,
|
||||||
is_admin: is_admin,
|
is_admin,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,30 +117,38 @@ pub enum PolicyError {
|
|||||||
NotAllowed,
|
NotAllowed,
|
||||||
TitleUsed,
|
TitleUsed,
|
||||||
YouAreTmp,
|
YouAreTmp,
|
||||||
|
NoReason,
|
||||||
|
OldApi,
|
||||||
|
UnknownPushEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum APIError {
|
pub enum ApiError {
|
||||||
DbError(diesel::result::Error),
|
Db(diesel::result::Error),
|
||||||
RdsError(redis::RedisError),
|
Rds(redis::RedisError),
|
||||||
PcError(PolicyError),
|
WebPush(web_push::WebPushError),
|
||||||
IoError(std::io::Error),
|
Pc(PolicyError),
|
||||||
|
IO(std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
match self {
|
match self {
|
||||||
APIError::DbError(e) => e2s!(e).respond_to(req),
|
ApiError::Db(e) => e2s!(e).respond_to(req),
|
||||||
APIError::RdsError(e) => e2s!(e).respond_to(req),
|
ApiError::Rds(e) => e2s!(e).respond_to(req),
|
||||||
APIError::IoError(e) => e2s!(e).respond_to(req),
|
ApiError::WebPush(e) => e2s!(e).respond_to(req),
|
||||||
APIError::PcError(e) => json!({
|
ApiError::IO(e) => e2s!(e).respond_to(req),
|
||||||
|
ApiError::Pc(e) => json!({
|
||||||
"code": -1,
|
"code": -1,
|
||||||
"msg": match e {
|
"msg": match e {
|
||||||
PolicyError::IsReported => "内容被举报,处理中",
|
PolicyError::IsReported => "内容被举报,处理中",
|
||||||
PolicyError::IsDeleted => "内容被删除",
|
PolicyError::IsDeleted => "内容被删除",
|
||||||
PolicyError::NotAllowed => "不允许的操作",
|
PolicyError::NotAllowed => "不允许的操作",
|
||||||
PolicyError::TitleUsed => "头衔已被使用",
|
PolicyError::TitleUsed => "头衔已被使用",
|
||||||
PolicyError::YouAreTmp => "临时用户只可发布内容和进入单个洞"
|
PolicyError::YouAreTmp => "临时用户只可发布内容和进入单个洞",
|
||||||
|
PolicyError::NoReason => "未填写理由",
|
||||||
|
PolicyError::OldApi => "请使用最新版前端地址并检查更新",
|
||||||
|
PolicyError::UnknownPushEndpoint => "未知的浏览器推送地址",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.respond_to(req),
|
.respond_to(req),
|
||||||
@@ -146,60 +156,66 @@ impl<'r> Responder<'r, 'static> for APIError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<diesel::result::Error> for APIError {
|
impl From<web_push::WebPushError> for ApiError {
|
||||||
fn from(err: diesel::result::Error) -> APIError {
|
fn from(err: web_push::WebPushError) -> ApiError {
|
||||||
APIError::DbError(err)
|
ApiError::WebPush(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<redis::RedisError> for APIError {
|
impl From<diesel::result::Error> for ApiError {
|
||||||
fn from(err: redis::RedisError) -> APIError {
|
fn from(err: diesel::result::Error) -> ApiError {
|
||||||
APIError::RdsError(err)
|
ApiError::Db(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for APIError {
|
impl From<redis::RedisError> for ApiError {
|
||||||
fn from(err: std::io::Error) -> APIError {
|
fn from(err: redis::RedisError) -> ApiError {
|
||||||
APIError::IoError(err)
|
ApiError::Rds(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PolicyError> for APIError {
|
impl From<std::io::Error> for ApiError {
|
||||||
fn from(err: PolicyError) -> APIError {
|
fn from(err: std::io::Error) -> ApiError {
|
||||||
APIError::PcError(err)
|
ApiError::IO(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type API<T> = Result<T, APIError>;
|
impl From<PolicyError> for ApiError {
|
||||||
pub type JsonAPI = API<Value>;
|
fn from(err: PolicyError) -> ApiError {
|
||||||
|
ApiError::Pc(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Api<T> = Result<T, ApiError>;
|
||||||
|
pub type JsonApi = Api<Value>;
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
pub trait UGC {
|
pub trait Ugc {
|
||||||
fn get_author_hash(&self) -> &str;
|
fn get_author_hash(&self) -> &str;
|
||||||
fn get_is_deleted(&self) -> bool;
|
fn get_is_deleted(&self) -> bool;
|
||||||
fn get_is_reported(&self) -> bool;
|
fn get_is_reported(&self) -> bool;
|
||||||
fn extra_delete_condition(&self) -> bool;
|
fn extra_delete_condition(&self) -> bool;
|
||||||
async fn do_set_deleted(&mut self, db: &Db) -> API<()>;
|
async fn do_set_deleted(&mut self, db: &Db) -> Api<()>;
|
||||||
fn check_permission(&self, user: &CurrentUser, mode: &str) -> API<()> {
|
fn check_permission(&self, user: &CurrentUser, mode: &str) -> Api<()> {
|
||||||
if user.is_admin {
|
if user.is_admin {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if mode.contains('r') && self.get_is_deleted() {
|
if mode.contains('r') && self.get_is_deleted() {
|
||||||
return Err(APIError::PcError(PolicyError::IsDeleted));
|
return Err(ApiError::Pc(PolicyError::IsDeleted));
|
||||||
}
|
}
|
||||||
if mode.contains('o') && self.get_is_reported() {
|
if mode.contains('o') && self.get_is_reported() {
|
||||||
return Err(APIError::PcError(PolicyError::IsReported));
|
return Err(ApiError::Pc(PolicyError::IsReported));
|
||||||
}
|
}
|
||||||
if mode.contains('w') && self.get_author_hash() != user.namehash {
|
if mode.contains('w') && self.get_author_hash() != user.namehash {
|
||||||
return Err(APIError::PcError(PolicyError::NotAllowed));
|
return Err(ApiError::Pc(PolicyError::NotAllowed));
|
||||||
}
|
}
|
||||||
if mode.contains('d') && !self.extra_delete_condition() {
|
if mode.contains('d') && !self.extra_delete_condition() {
|
||||||
return Err(APIError::PcError(PolicyError::NotAllowed));
|
return Err(ApiError::Pc(PolicyError::NotAllowed));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn soft_delete(&mut self, user: &CurrentUser, db: &Db) -> API<()> {
|
async fn soft_delete(&mut self, user: &CurrentUser, db: &Db) -> Api<()> {
|
||||||
self.check_permission(user, "rwd")?;
|
self.check_permission(user, "rwd")?;
|
||||||
|
|
||||||
self.do_set_deleted(db).await?;
|
self.do_set_deleted(db).await?;
|
||||||
@@ -208,7 +224,7 @@ pub trait UGC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl UGC for Post {
|
impl Ugc for Post {
|
||||||
fn get_author_hash(&self) -> &str {
|
fn get_author_hash(&self) -> &str {
|
||||||
&self.author_hash
|
&self.author_hash
|
||||||
}
|
}
|
||||||
@@ -219,16 +235,16 @@ impl UGC for Post {
|
|||||||
self.is_deleted
|
self.is_deleted
|
||||||
}
|
}
|
||||||
fn extra_delete_condition(&self) -> bool {
|
fn extra_delete_condition(&self) -> bool {
|
||||||
self.n_comments == 0
|
self.room_id != 42
|
||||||
}
|
}
|
||||||
async fn do_set_deleted(&mut self, db: &Db) -> API<()> {
|
async fn do_set_deleted(&mut self, db: &Db) -> Api<()> {
|
||||||
update!(*self, posts, db, { is_deleted, to true });
|
update!(*self, posts, db, { is_deleted, to true });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl UGC for Comment {
|
impl Ugc for Comment {
|
||||||
fn get_author_hash(&self) -> &str {
|
fn get_author_hash(&self) -> &str {
|
||||||
&self.author_hash
|
&self.author_hash
|
||||||
}
|
}
|
||||||
@@ -241,7 +257,7 @@ impl UGC for Comment {
|
|||||||
fn extra_delete_condition(&self) -> bool {
|
fn extra_delete_condition(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
async fn do_set_deleted(&mut self, db: &Db) -> API<()> {
|
async fn do_set_deleted(&mut self, db: &Db) -> Api<()> {
|
||||||
update!(*self, comments, db, { is_deleted, to true });
|
update!(*self, comments, db, { is_deleted, to true });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::api::{APIError, CurrentUser, JsonAPI, PolicyError::*, UGC};
|
use crate::api::{ApiError, CurrentUser, JsonApi, PolicyError::*, Ugc};
|
||||||
use crate::cache::*;
|
use crate::cache::*;
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
use crate::libs::diesel_logger::LoggingConnection;
|
use crate::libs::diesel_logger::LoggingConnection;
|
||||||
@@ -20,7 +20,7 @@ pub struct DeleteInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/delete", data = "<di>")]
|
#[post("/delete", data = "<di>")]
|
||||||
pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonApi {
|
||||||
let (author_hash, p) = match di.id_type.as_str() {
|
let (author_hash, p) = match di.id_type.as_str() {
|
||||||
"cid" => {
|
"cid" => {
|
||||||
let mut c = Comment::get(&db, di.id).await?;
|
let mut c = Comment::get(&db, di.id).await?;
|
||||||
@@ -41,7 +41,18 @@ pub async fn delete(di: Form<DeleteInput>, user: CurrentUser, db: Db, rconn: Rds
|
|||||||
}
|
}
|
||||||
"pid" => {
|
"pid" => {
|
||||||
let mut p = Post::get(&db, &rconn, di.id).await?;
|
let mut p = Post::get(&db, &rconn, di.id).await?;
|
||||||
|
|
||||||
|
// 有评论:清空主楼而非删除
|
||||||
|
if p.author_hash == user.namehash && p.n_comments > 0 {
|
||||||
|
update! {
|
||||||
|
p,
|
||||||
|
posts,
|
||||||
|
&db,
|
||||||
|
{ content, to "[洞主已删除]" }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
p.soft_delete(&user, &db).await?;
|
p.soft_delete(&user, &db).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果是删除,需要也从0号缓存队列中去掉
|
// 如果是删除,需要也从0号缓存队列中去掉
|
||||||
p.refresh_cache(&rconn, true).await;
|
p.refresh_cache(&rconn, true).await;
|
||||||
@@ -84,34 +95,58 @@ pub struct ReportInput {
|
|||||||
pid: i32,
|
pid: i32,
|
||||||
#[field(validate = len(0..1000))]
|
#[field(validate = len(0..1000))]
|
||||||
reason: String,
|
reason: String,
|
||||||
|
should_hide: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/report", data = "<ri>")]
|
#[post("/report", data = "<ri>")]
|
||||||
pub async fn report(ri: Form<ReportInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn report(ri: Form<ReportInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonApi {
|
||||||
// 临时用户不允许举报
|
// 临时用户不允许举报
|
||||||
user.id.ok_or_else(|| NotAllowed)?;
|
user.id.ok_or(NotAllowed)?;
|
||||||
|
|
||||||
|
// 被拉黑10次不允许举报
|
||||||
|
(BlockCounter::get_count(&rconn, &user.namehash)
|
||||||
|
.await?
|
||||||
|
.unwrap_or(0)
|
||||||
|
< 10)
|
||||||
|
.then(|| ())
|
||||||
|
.ok_or(NotAllowed)?;
|
||||||
|
|
||||||
|
(!ri.reason.is_empty()).then(|| ()).ok_or(NoReason)?;
|
||||||
|
|
||||||
let mut p = Post::get(&db, &rconn, ri.pid).await?;
|
let mut p = Post::get(&db, &rconn, ri.pid).await?;
|
||||||
|
if ri.should_hide.is_some() {
|
||||||
update!(p, posts, &db, { is_reported, to true });
|
update!(p, posts, &db, { is_reported, to true });
|
||||||
p.refresh_cache(&rconn, false).await;
|
p.refresh_cache(&rconn, false).await;
|
||||||
Systemlog {
|
|
||||||
user_hash: user.namehash,
|
|
||||||
action_type: LogType::Report,
|
|
||||||
target: format!(
|
|
||||||
"#{} {}",
|
|
||||||
ri.pid,
|
|
||||||
if ri.reason.starts_with("评论区") {
|
|
||||||
"评论区"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
}
|
||||||
),
|
|
||||||
|
Systemlog {
|
||||||
|
user_hash: user.namehash.to_string(),
|
||||||
|
action_type: LogType::Report,
|
||||||
|
target: format!("#{}", ri.pid),
|
||||||
detail: ri.reason.clone(),
|
detail: ri.reason.clone(),
|
||||||
time: Local::now(),
|
time: Local::now(),
|
||||||
}
|
}
|
||||||
.create(&rconn)
|
.create(&rconn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// 自动发布一条洞
|
||||||
|
let p = Post::create(
|
||||||
|
&db,
|
||||||
|
NewPost {
|
||||||
|
content: format!("[系统自动代发]\n我举报了 #{}\n理由: {}", &p.id, &ri.reason),
|
||||||
|
cw: "举报".to_string(),
|
||||||
|
author_hash: user.namehash.to_string(),
|
||||||
|
author_title: String::default(),
|
||||||
|
is_tmp: false,
|
||||||
|
n_attentions: 1,
|
||||||
|
allow_search: true,
|
||||||
|
room_id: 42,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Attention::init(&user.namehash, &rconn).add(p.id).await?;
|
||||||
|
p.refresh_cache(&rconn, true).await;
|
||||||
|
|
||||||
code0!()
|
code0!()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +158,10 @@ pub struct BlockInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/block", data = "<bi>")]
|
#[post("/block", data = "<bi>")]
|
||||||
pub async fn block(bi: Form<BlockInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn block(bi: Form<BlockInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonApi {
|
||||||
user.id.ok_or_else(|| NotAllowed)?;
|
user.id.ok_or(NotAllowed)?;
|
||||||
|
|
||||||
let mut blk = BlockedUsers::init(user.id.ok_or_else(|| NotAllowed)?, &rconn);
|
let mut blk = BlockedUsers::init(user.id.ok_or(NotAllowed)?, &rconn);
|
||||||
|
|
||||||
let pid;
|
let pid;
|
||||||
let nh_to_block = match bi.content_type.as_str() {
|
let nh_to_block = match bi.content_type.as_str() {
|
||||||
@@ -140,7 +175,7 @@ pub async fn block(bi: Form<BlockInput>, user: CurrentUser, db: Db, rconn: RdsCo
|
|||||||
pid = c.post_id;
|
pid = c.post_id;
|
||||||
c.author_hash
|
c.author_hash
|
||||||
}
|
}
|
||||||
_ => return Err(APIError::PcError(NotAllowed)),
|
_ => return Err(ApiError::Pc(NotAllowed)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if nh_to_block.eq(&user.namehash) {
|
if nh_to_block.eq(&user.namehash) {
|
||||||
@@ -172,7 +207,7 @@ pub struct TitleInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/title", data = "<ti>")]
|
#[post("/title", data = "<ti>")]
|
||||||
pub async fn set_title(ti: Form<TitleInput>, user: CurrentUser, rconn: RdsConn) -> JsonAPI {
|
pub async fn set_title(ti: Form<TitleInput>, user: CurrentUser, rconn: RdsConn) -> JsonApi {
|
||||||
if CustomTitle::set(&rconn, &user.namehash, &ti.title).await? {
|
if CustomTitle::set(&rconn, &user.namehash, &ti.title).await? {
|
||||||
code0!()
|
code0!()
|
||||||
} else {
|
} else {
|
||||||
@@ -190,7 +225,7 @@ pub async fn set_auto_block(
|
|||||||
ai: Form<AutoBlockInput>,
|
ai: Form<AutoBlockInput>,
|
||||||
user: CurrentUser,
|
user: CurrentUser,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> JsonAPI {
|
) -> JsonApi {
|
||||||
AutoBlockRank::set(&rconn, &user.namehash, ai.rank).await?;
|
AutoBlockRank::set(&rconn, &user.namehash, ai.rank).await?;
|
||||||
code0!()
|
code0!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::api::comment::{c2output, CommentOutput};
|
use crate::api::comment::{c2output, CommentOutput};
|
||||||
use crate::api::vote::get_poll_dict;
|
use crate::api::vote::get_poll_dict;
|
||||||
use crate::api::{CurrentUser, JsonAPI, PolicyError::*, API, UGC};
|
use crate::api::{Api, CurrentUser, JsonApi, PolicyError::*, Ugc};
|
||||||
use crate::cache::*;
|
use crate::cache::*;
|
||||||
use crate::db_conn::Db;
|
use crate::db_conn::Db;
|
||||||
use crate::libs::diesel_logger::LoggingConnection;
|
use crate::libs::diesel_logger::LoggingConnection;
|
||||||
@@ -26,12 +26,14 @@ pub struct PostInput {
|
|||||||
use_title: Option<i8>,
|
use_title: Option<i8>,
|
||||||
#[field(validate = len(0..97))]
|
#[field(validate = len(0..97))]
|
||||||
poll_options: Vec<String>,
|
poll_options: Vec<String>,
|
||||||
|
room_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
pub struct PostOutput {
|
pub struct PostOutput {
|
||||||
pid: i32,
|
pid: i32,
|
||||||
|
room_id: i32,
|
||||||
text: String,
|
text: String,
|
||||||
cw: Option<String>,
|
cw: Option<String>,
|
||||||
author_title: Option<String>,
|
author_title: Option<String>,
|
||||||
@@ -63,7 +65,7 @@ pub struct CwInput {
|
|||||||
cw: String,
|
cw: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> API<PostOutput> {
|
async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> Api<PostOutput> {
|
||||||
let comments: Option<Vec<Comment>> = if p.n_comments < 5 {
|
let comments: Option<Vec<Comment>> = if p.n_comments < 5 {
|
||||||
Some(p.get_comments(db, rconn).await?)
|
Some(p.get_comments(db, rconn).await?)
|
||||||
} else {
|
} else {
|
||||||
@@ -74,16 +76,17 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> API
|
|||||||
.flatten()
|
.flatten()
|
||||||
.map(|c| &c.author_hash)
|
.map(|c| &c.author_hash)
|
||||||
.chain(std::iter::once(&p.author_hash))
|
.chain(std::iter::once(&p.author_hash))
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
//dbg!(&hash_list);
|
//dbg!(&hash_list);
|
||||||
let cached_block_dict = BlockDictCache::init(&user.namehash, p.id, rconn)
|
let cached_block_dict = BlockDictCache::init(&user.namehash, p.id, rconn)
|
||||||
.get_or_create(&user, &hash_list)
|
.get_or_create(user, &hash_list)
|
||||||
.await?;
|
.await?;
|
||||||
let is_blocked = cached_block_dict[&p.author_hash];
|
let is_blocked = cached_block_dict[&p.author_hash];
|
||||||
let can_view =
|
let can_view =
|
||||||
user.is_admin || (!is_blocked && user.id.is_some() || user.namehash.eq(&p.author_hash));
|
user.is_admin || (!is_blocked && user.id.is_some() || user.namehash.eq(&p.author_hash));
|
||||||
Ok(PostOutput {
|
Ok(PostOutput {
|
||||||
pid: p.id,
|
pid: p.id,
|
||||||
|
room_id: p.room_id,
|
||||||
text: can_view.then(|| p.content.clone()).unwrap_or_default(),
|
text: can_view.then(|| p.content.clone()).unwrap_or_default(),
|
||||||
cw: (!p.cw.is_empty()).then(|| p.cw.clone()),
|
cw: (!p.cw.is_empty()).then(|| p.cw.clone()),
|
||||||
n_attentions: p.n_attentions,
|
n_attentions: p.n_attentions,
|
||||||
@@ -100,9 +103,9 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> API
|
|||||||
)
|
)
|
||||||
.await,
|
.await,
|
||||||
can_del: p.check_permission(user, "wd").is_ok(),
|
can_del: p.check_permission(user, "wd").is_ok(),
|
||||||
attention: Attention::init(&user.namehash, &rconn).has(p.id).await?,
|
attention: Attention::init(&user.namehash, rconn).has(p.id).await?,
|
||||||
hot_score: user.is_admin.then(|| p.hot_score),
|
hot_score: user.is_admin.then(|| p.hot_score),
|
||||||
is_blocked: is_blocked,
|
is_blocked,
|
||||||
blocked_count: if user.is_admin {
|
blocked_count: if user.is_admin {
|
||||||
BlockCounter::get_count(rconn, &p.author_hash).await?
|
BlockCounter::get_count(rconn, &p.author_hash).await?
|
||||||
} else {
|
} else {
|
||||||
@@ -122,20 +125,20 @@ async fn p2output(p: &Post, user: &CurrentUser, db: &Db, rconn: &RdsConn) -> API
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ps2outputs(
|
pub async fn ps2outputs(
|
||||||
ps: &Vec<Post>,
|
ps: &[Post],
|
||||||
user: &CurrentUser,
|
user: &CurrentUser,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
rconn: &RdsConn,
|
rconn: &RdsConn,
|
||||||
) -> API<Vec<PostOutput>> {
|
) -> Api<Vec<PostOutput>> {
|
||||||
future::try_join_all(
|
future::try_join_all(
|
||||||
ps.iter()
|
ps.iter()
|
||||||
.map(|p| async { Ok(p2output(p, &user, &db, &rconn).await?) }),
|
.map(|p| async { p2output(p, user, db, rconn).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, &rconn, pid).await?;
|
let p = Post::get(&db, &rconn, pid).await?;
|
||||||
p.check_permission(&user, "ro")?;
|
p.check_permission(&user, "ro")?;
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
@@ -144,19 +147,28 @@ pub async fn get_one(pid: i32, user: CurrentUser, db: Db, rconn: RdsConn) -> Jso
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/getlist?<p>&<order_mode>")]
|
#[get("/getlist?<p>&<order_mode>&<room_id>")]
|
||||||
pub async fn get_list(
|
pub async fn get_list(
|
||||||
p: Option<u32>,
|
p: Option<u32>,
|
||||||
order_mode: u8,
|
order_mode: u8,
|
||||||
|
room_id: Option<i32>,
|
||||||
user: CurrentUser,
|
user: CurrentUser,
|
||||||
db: Db,
|
db: Db,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> JsonAPI {
|
) -> JsonApi {
|
||||||
user.id.ok_or_else(|| YouAreTmp)?;
|
user.id.ok_or(YouAreTmp)?;
|
||||||
let page = p.unwrap_or(1);
|
let page = p.unwrap_or(1);
|
||||||
let page_size = 25;
|
let page_size = 25;
|
||||||
let start = (page - 1) * page_size;
|
let start = (page - 1) * page_size;
|
||||||
let ps = Post::gets_by_page(&db, &rconn, order_mode, start.into(), page_size.into()).await?;
|
let ps = Post::gets_by_page(
|
||||||
|
&db,
|
||||||
|
&rconn,
|
||||||
|
room_id,
|
||||||
|
order_mode,
|
||||||
|
start.into(),
|
||||||
|
page_size.into(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let ps_data = ps2outputs(&ps, &user, &db, &rconn).await?;
|
let ps_data = ps2outputs(&ps, &user, &db, &rconn).await?;
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"data": ps_data,
|
"data": ps_data,
|
||||||
@@ -173,17 +185,27 @@ pub async fn publish_post(
|
|||||||
user: CurrentUser,
|
user: CurrentUser,
|
||||||
db: Db,
|
db: Db,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> JsonAPI {
|
) -> JsonApi {
|
||||||
|
let text = if poi.room_id.is_none() {
|
||||||
|
format!(
|
||||||
|
"{}\n\n---\n\n\\* 无效分区或来自旧版前端,已默认归档到0区。分区管理说明见 #100426,建议尽快更新前端(点击ⓘ ,点击“立即更新”)。",
|
||||||
|
&poi.text
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
poi.text.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
let p = Post::create(
|
let p = Post::create(
|
||||||
&db,
|
&db,
|
||||||
NewPost {
|
NewPost {
|
||||||
content: poi.text.to_string(),
|
content: text,
|
||||||
cw: poi.cw.to_string(),
|
cw: poi.cw.to_string(),
|
||||||
author_hash: user.namehash.to_string(),
|
author_hash: user.namehash.to_string(),
|
||||||
author_title: poi.use_title.map(|_| user.custom_title).unwrap_or_default(),
|
author_title: poi.use_title.map(|_| user.custom_title).unwrap_or_default(),
|
||||||
is_tmp: user.id.is_none(),
|
is_tmp: user.id.is_none(),
|
||||||
n_attentions: 1,
|
n_attentions: 1,
|
||||||
allow_search: poi.allow_search.is_some(),
|
allow_search: poi.allow_search.is_some(),
|
||||||
|
room_id: poi.room_id.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -199,7 +221,7 @@ pub async fn publish_post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/editcw", data = "<cwi>")]
|
#[post("/editcw", data = "<cwi>")]
|
||||||
pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonAPI {
|
pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db, rconn: RdsConn) -> JsonApi {
|
||||||
let mut p = Post::get(&db, &rconn, cwi.pid).await?;
|
let mut p = Post::get(&db, &rconn, cwi.pid).await?;
|
||||||
p.check_permission(&user, "w")?;
|
p.check_permission(&user, "w")?;
|
||||||
update!(p, posts, &db, { cw, to cwi.cw.to_string() });
|
update!(p, posts, &db, { cw, to cwi.cw.to_string() });
|
||||||
@@ -208,8 +230,8 @@ pub async fn edit_cw(cwi: Form<CwInput>, user: CurrentUser, db: Db, rconn: RdsCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[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 {
|
||||||
user.id.ok_or_else(|| YouAreTmp)?;
|
user.id.ok_or(YouAreTmp)?;
|
||||||
let ps = Post::get_multi(&db, &rconn, &pids).await?;
|
let ps = Post::get_multi(&db, &rconn, &pids).await?;
|
||||||
let ps_data = ps2outputs(&ps, &user, &db, &rconn).await?;
|
let ps_data = ps2outputs(&ps, &user, &db, &rconn).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,32 @@
|
|||||||
use crate::api::post::ps2outputs;
|
use crate::api::post::ps2outputs;
|
||||||
use crate::api::{CurrentUser, JsonAPI, PolicyError::*};
|
use crate::api::{CurrentUser, JsonApi, PolicyError::*};
|
||||||
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 rocket::serde::json::json;
|
use rocket::serde::json::json;
|
||||||
|
|
||||||
#[get("/search?<search_mode>&<page>&<keywords>")]
|
#[get("/search?<search_mode>&<page>&<keywords>&<room_id>")]
|
||||||
pub async fn search(
|
pub async fn search(
|
||||||
|
room_id: Option<i32>,
|
||||||
keywords: String,
|
keywords: String,
|
||||||
search_mode: u8,
|
search_mode: u8,
|
||||||
page: i32,
|
page: i32,
|
||||||
user: CurrentUser,
|
user: CurrentUser,
|
||||||
db: Db,
|
db: Db,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
) -> JsonAPI {
|
) -> JsonApi {
|
||||||
user.id.ok_or_else(|| YouAreTmp)?;
|
user.id.ok_or(YouAreTmp)?;
|
||||||
|
|
||||||
let page_size = 25;
|
let page_size = 25;
|
||||||
let start = (page - 1) * page_size;
|
let start = (page - 1) * page_size;
|
||||||
|
|
||||||
let kws = keywords
|
let ps = if !keywords.chars().any(|c| !c.eq(&' ')) {
|
||||||
.split(" ")
|
|
||||||
.filter(|x| !x.is_empty())
|
|
||||||
.collect::<Vec<&str>>();
|
|
||||||
let ps = if kws.is_empty() {
|
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
Post::search(
|
Post::search(
|
||||||
&db,
|
&db,
|
||||||
&rconn,
|
&rconn,
|
||||||
|
room_id,
|
||||||
search_mode,
|
search_mode,
|
||||||
keywords.to_string(),
|
keywords.to_string(),
|
||||||
start.into(),
|
start.into(),
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::api::{CurrentUser, JsonAPI};
|
use crate::api::{CurrentUser, JsonApi};
|
||||||
use crate::random_hasher::RandomHasher;
|
use crate::random_hasher::RandomHasher;
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
use crate::rds_models::{Systemlog};
|
use crate::rds_models::Systemlog;
|
||||||
use rocket::serde::json::{json, Value};
|
use rocket::serde::json::{json, Value};
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
|
|
||||||
#[get("/systemlog")]
|
#[get("/systemlog")]
|
||||||
pub async fn get_systemlog(user: CurrentUser, rh: &State<RandomHasher>, rconn: RdsConn) -> JsonAPI {
|
pub async fn get_systemlog(user: CurrentUser, rh: &State<RandomHasher>, rconn: RdsConn) -> JsonApi {
|
||||||
let logs = Systemlog::get_list(&rconn, 50).await?;
|
let logs = Systemlog::get_list(&rconn, 50).await?;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
@@ -19,7 +19,7 @@ pub async fn get_systemlog(user: CurrentUser, rh: &State<RandomHasher>, rconn: R
|
|||||||
"type": log.action_type,
|
"type": log.action_type,
|
||||||
"user": look!(log.user_hash),
|
"user": look!(log.user_hash),
|
||||||
"timestamp": log.time.timestamp(),
|
"timestamp": log.time.timestamp(),
|
||||||
"detail": format!("{}\n{}", &log.target, if user.is_admin || !log.action_type.contains_ugc() { &log.detail } else { "" })
|
"detail": format!("{}\n{}", &log.target, &log.detail),
|
||||||
})
|
})
|
||||||
).collect::<Vec<Value>>(),
|
).collect::<Vec<Value>>(),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,37 +1,26 @@
|
|||||||
use crate::api::{CurrentUser, JsonAPI};
|
use super::PolicyError::OldApi;
|
||||||
|
use super::{ApiError, CurrentUser, JsonApi};
|
||||||
use rocket::fs::TempFile;
|
use rocket::fs::TempFile;
|
||||||
use rocket::serde::json::json;
|
use rocket::serde::json::json;
|
||||||
use std::process::Command;
|
use std::env::var;
|
||||||
|
|
||||||
|
#[post("/upload")]
|
||||||
|
pub async fn ipfs_upload() -> ApiError {
|
||||||
|
OldApi.into()
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/upload", data = "<file>")]
|
#[post("/upload", data = "<file>")]
|
||||||
pub async fn ipfs_upload(_user: CurrentUser, file: TempFile<'_>) -> JsonAPI {
|
pub async fn local_upload(_user: CurrentUser, mut file: TempFile<'_>) -> JsonApi {
|
||||||
// dbg!(&file);
|
let filename: String = format!(
|
||||||
|
"file{}.{}",
|
||||||
|
file.path().unwrap().file_name().unwrap().to_str().unwrap(),
|
||||||
|
file.content_type()
|
||||||
|
.map(|ct| ct.extension().unwrap_or_else(|| ct.sub()).as_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
);
|
||||||
|
|
||||||
// dbg!(&file.path());
|
file.copy_to(format!("{}/{}", var("UPLOAD_DIR").unwrap(), filename))
|
||||||
if let Some(filepath) = file.path() {
|
.await?;
|
||||||
let output = Command::new("ipfs")
|
|
||||||
.args([
|
code0!(json!({ "path": filename }))
|
||||||
"add",
|
|
||||||
"-q",
|
|
||||||
"-r",
|
|
||||||
"-cid-version=1",
|
|
||||||
filepath.to_str().unwrap(),
|
|
||||||
])
|
|
||||||
.output()?;
|
|
||||||
// dbg!(&output);
|
|
||||||
let hash = std::str::from_utf8(&output.stdout)
|
|
||||||
.unwrap()
|
|
||||||
.split_terminator("\n")
|
|
||||||
.last()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
dbg!(&output);
|
|
||||||
dbg!(&file.path());
|
|
||||||
panic!("get ipfs output error");
|
|
||||||
});
|
|
||||||
code0!(json!({
|
|
||||||
"hash": hash,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
code1!("文件丢失")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::api::{CurrentUser, JsonAPI, PolicyError::*};
|
use crate::api::{CurrentUser, JsonApi, PolicyError::*};
|
||||||
use crate::rds_conn::RdsConn;
|
use crate::rds_conn::RdsConn;
|
||||||
use crate::rds_models::*;
|
use crate::rds_models::*;
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
@@ -22,7 +22,7 @@ pub async fn get_poll_dict(pid: i32, rconn: &RdsConn, namehash: &str) -> Option<
|
|||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|x| x)
|
.flatten()
|
||||||
.collect::<Vec<&String>>()
|
.collect::<Vec<&String>>()
|
||||||
.pop();
|
.pop();
|
||||||
Some(json!({
|
Some(json!({
|
||||||
@@ -46,8 +46,8 @@ pub struct VoteInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/vote", data = "<vi>")]
|
#[post("/vote", data = "<vi>")]
|
||||||
pub async fn vote(vi: Form<VoteInput>, user: CurrentUser, rconn: RdsConn) -> JsonAPI {
|
pub async fn vote(vi: Form<VoteInput>, user: CurrentUser, rconn: RdsConn) -> JsonApi {
|
||||||
user.id.ok_or_else(|| NotAllowed)?;
|
user.id.ok_or(NotAllowed)?;
|
||||||
|
|
||||||
let pid = vi.pid;
|
let pid = vi.pid;
|
||||||
let opts = PollOption::init(pid, &rconn).get_list().await?;
|
let opts = PollOption::init(pid, &rconn).get_list().await?;
|
||||||
@@ -61,10 +61,7 @@ pub async fn vote(vi: Form<VoteInput>, user: CurrentUser, rconn: RdsConn) -> Jso
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx: usize = opts
|
let idx: usize = opts.iter().position(|x| x.eq(&vi.vote)).ok_or(NotAllowed)?;
|
||||||
.iter()
|
|
||||||
.position(|x| x.eq(&vi.vote))
|
|
||||||
.ok_or_else(|| NotAllowed)?;
|
|
||||||
|
|
||||||
PollVote::init(pid, idx, &rconn).add(&user.namehash).await?;
|
PollVote::init(pid, idx, &rconn).add(&user.namehash).await?;
|
||||||
|
|
||||||
|
|||||||
27
src/cache.rs
27
src/cache.rs
@@ -28,7 +28,7 @@ pub struct PostCache {
|
|||||||
impl PostCache {
|
impl PostCache {
|
||||||
init!();
|
init!();
|
||||||
|
|
||||||
pub async fn sets(&mut self, ps: &Vec<&Post>) {
|
pub async fn sets(&mut self, ps: &[&Post]) {
|
||||||
if ps.is_empty() {
|
if ps.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ impl PostCache {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn gets(&mut self, pids: &Vec<i32>) -> Vec<Option<Post>> {
|
pub async fn gets(&mut self, pids: &[i32]) -> Vec<Option<Post>> {
|
||||||
// 长度为1时会走GET而非MGET,返回值格式不兼容。愚蠢的设计。
|
// 长度为1时会走GET而非MGET,返回值格式不兼容。愚蠢的设计。
|
||||||
match pids.len() {
|
match pids.len() {
|
||||||
0 => vec![],
|
0 => vec![],
|
||||||
@@ -128,7 +128,7 @@ pub struct PostCommentCache {
|
|||||||
impl PostCommentCache {
|
impl PostCommentCache {
|
||||||
init!(i32, "hole_v2:cache:post_comments:{}");
|
init!(i32, "hole_v2:cache:post_comments:{}");
|
||||||
|
|
||||||
pub async fn set(&mut self, cs: &Vec<Comment>) {
|
pub async fn set(&mut self, cs: &[Comment]) {
|
||||||
self.rconn
|
self.rconn
|
||||||
.set_ex(
|
.set_ex(
|
||||||
&self.key,
|
&self.key,
|
||||||
@@ -172,18 +172,25 @@ impl PostCommentCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PostListCommentCache {
|
pub struct PostListCache {
|
||||||
key: String,
|
key: String,
|
||||||
mode: u8,
|
mode: u8,
|
||||||
rconn: RdsConn,
|
rconn: RdsConn,
|
||||||
length: isize,
|
length: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PostListCommentCache {
|
impl PostListCache {
|
||||||
pub fn init(mode: u8, rconn: &RdsConn) -> Self {
|
pub fn init(room_id: Option<i32>, mode: u8, rconn: &RdsConn) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key: format!("hole_v2:cache:post_list:{}", &mode),
|
key: format!(
|
||||||
mode: mode,
|
"hole_v2:cache:post_list:{}:{}",
|
||||||
|
match room_id {
|
||||||
|
Some(i) => i.to_string(),
|
||||||
|
None => "".to_owned(),
|
||||||
|
},
|
||||||
|
&mode
|
||||||
|
),
|
||||||
|
mode,
|
||||||
rconn: rconn.clone(),
|
rconn: rconn.clone(),
|
||||||
length: 0,
|
length: 0,
|
||||||
}
|
}
|
||||||
@@ -229,7 +236,7 @@ impl PostListCommentCache {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fill(&mut self, ps: &Vec<Post>) {
|
pub async fn fill(&mut self, ps: &[Post]) {
|
||||||
let items: Vec<(i64, i32)> = ps.iter().map(|p| self.p2pair(p)).collect();
|
let items: Vec<(i64, i32)> = ps.iter().map(|p| self.p2pair(p)).collect();
|
||||||
self.rconn
|
self.rconn
|
||||||
.zadd_multiple(&self.key, &items)
|
.zadd_multiple(&self.key, &items)
|
||||||
@@ -337,7 +344,7 @@ impl BlockDictCache {
|
|||||||
pub async fn get_or_create(
|
pub async fn get_or_create(
|
||||||
&mut self,
|
&mut self,
|
||||||
user: &CurrentUser,
|
user: &CurrentUser,
|
||||||
hash_list: &Vec<&String>,
|
hash_list: &[&String],
|
||||||
) -> RedisResult<HashMap<String, bool>> {
|
) -> RedisResult<HashMap<String, bool>> {
|
||||||
let mut block_dict = self
|
let mut block_dict = self
|
||||||
.rconn
|
.rconn
|
||||||
|
|||||||
42
src/cors.rs
Normal file
42
src/cors.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#![allow(clippy::let_unit_value)]
|
||||||
|
|
||||||
|
use rocket::fairing::{Fairing, Info, Kind};
|
||||||
|
use rocket::http::Header;
|
||||||
|
use rocket::{Request, Response};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct Cors {
|
||||||
|
pub whitelist: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl Fairing for Cors {
|
||||||
|
fn info(&self) -> Info {
|
||||||
|
Info {
|
||||||
|
name: "Add CORS headers to responses",
|
||||||
|
kind: Kind::Response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
|
||||||
|
if let Some(origin) = request
|
||||||
|
.headers()
|
||||||
|
.get_one("Origin")
|
||||||
|
.and_then(|origin| self.whitelist.contains(&origin.to_string()).then(|| origin))
|
||||||
|
{
|
||||||
|
response.set_header(Header::new("Access-Control-Allow-Origin", origin));
|
||||||
|
response.set_header(Header::new(
|
||||||
|
"Access-Control-Allow-Methods",
|
||||||
|
"POST, GET, OPTIONS",
|
||||||
|
));
|
||||||
|
response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
|
||||||
|
response.set_header(Header::new(
|
||||||
|
"Access-Control-Allow-Headers",
|
||||||
|
"User-Token, Content-Type",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[options("/<_path..>")]
|
||||||
|
pub async fn options_handler(_path: PathBuf) {}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use rocket_sync_db_pools::{database, diesel};
|
|
||||||
use diesel::Connection;
|
use diesel::Connection;
|
||||||
|
use rocket_sync_db_pools::{database, diesel};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub type Conn = diesel::pg::PgConnection;
|
pub type Conn = diesel::pg::PgConnection;
|
||||||
@@ -7,11 +7,9 @@ pub type Conn = diesel::pg::PgConnection;
|
|||||||
#[database("pg_v2")]
|
#[database("pg_v2")]
|
||||||
pub struct Db(Conn);
|
pub struct Db(Conn);
|
||||||
|
|
||||||
|
|
||||||
// get sync connection, only for annealing
|
// get sync connection, only for annealing
|
||||||
pub fn establish_connection() -> Conn {
|
pub fn establish_connection() -> Conn {
|
||||||
let database_url = env::var("DATABASE_URL")
|
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
.expect("DATABASE_URL must be set");
|
|
||||||
Conn::establish(&database_url)
|
Conn::establish(&database_url)
|
||||||
.expect(&format!("Error connecting to {}", database_url))
|
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/login.rs
45
src/login.rs
@@ -25,15 +25,22 @@ pub fn cs_login(r: RefHeader) -> Redirect {
|
|||||||
let mast_cli = env::var("MAST_CLIENT").unwrap();
|
let mast_cli = env::var("MAST_CLIENT").unwrap();
|
||||||
let mast_scope = env::var("MAST_SCOPE").unwrap();
|
let mast_scope = env::var("MAST_SCOPE").unwrap();
|
||||||
|
|
||||||
let mut redirect_url = Url::parse(&r.0).unwrap();
|
let jump_to_url = Url::parse(&r.0).unwrap();
|
||||||
|
|
||||||
|
let mut redirect_url = env::var("AUTH_BACKEND_URL")
|
||||||
|
.map(|url| Url::parse(&url).unwrap())
|
||||||
|
.unwrap_or_else(|_| jump_to_url.clone());
|
||||||
redirect_url.set_path("/_login/cs/auth");
|
redirect_url.set_path("/_login/cs/auth");
|
||||||
redirect_url.set_query(None);
|
|
||||||
|
|
||||||
redirect_url = Url::parse_with_params(
|
redirect_url = Url::parse_with_params(
|
||||||
redirect_url.as_str(),
|
redirect_url.as_str(),
|
||||||
&[("redirect_url", redirect_url.as_str())],
|
&[
|
||||||
|
("redirect_url", redirect_url.as_str()),
|
||||||
|
("jump_to_url", jump_to_url.as_str()),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let url = Url::parse_with_params(
|
let url = Url::parse_with_params(
|
||||||
&format!("{}oauth/authorize", mast_url),
|
&format!("{}oauth/authorize", mast_url),
|
||||||
&[
|
&[
|
||||||
@@ -59,8 +66,8 @@ struct Token {
|
|||||||
struct Account {
|
struct Account {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
#[get("/cs/auth?<code>&<redirect_url>")]
|
#[get("/cs/auth?<code>&<redirect_url>&<jump_to_url>")]
|
||||||
pub async fn cs_auth(code: String, redirect_url: String, db: Db) -> Redirect {
|
pub async fn cs_auth(code: String, redirect_url: String, jump_to_url: String, db: Db) -> Redirect {
|
||||||
let mast_url = env::var("MAST_BASE_URL").unwrap();
|
let mast_url = env::var("MAST_BASE_URL").unwrap();
|
||||||
let mast_cli = env::var("MAST_CLIENT").unwrap();
|
let mast_cli = env::var("MAST_CLIENT").unwrap();
|
||||||
let mast_sec = env::var("MAST_SECRET").unwrap();
|
let mast_sec = env::var("MAST_SECRET").unwrap();
|
||||||
@@ -69,12 +76,15 @@ pub async fn cs_auth(code: String, redirect_url: String, db: Db) -> Redirect {
|
|||||||
// to keep same
|
// to keep same
|
||||||
let redirect_url = Url::parse_with_params(
|
let redirect_url = Url::parse_with_params(
|
||||||
redirect_url.as_str(),
|
redirect_url.as_str(),
|
||||||
&[("redirect_url", redirect_url.as_str())],
|
&[
|
||||||
|
("redirect_url", redirect_url.as_str()),
|
||||||
|
("jump_to_url", jump_to_url.as_str()),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let token: Token = client
|
let r = client
|
||||||
.post(format!("{}oauth/token", &mast_url))
|
.post(format!("{}oauth/token", &mast_url))
|
||||||
.form(&[
|
.form(&[
|
||||||
("client_id", mast_cli.as_str()),
|
("client_id", mast_cli.as_str()),
|
||||||
@@ -86,11 +96,10 @@ pub async fn cs_auth(code: String, redirect_url: String, db: Db) -> Redirect {
|
|||||||
])
|
])
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
//dbg!(&r);
|
||||||
|
|
||||||
|
let token: Token = r.json().await.unwrap();
|
||||||
//dbg!(&token);
|
//dbg!(&token);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
@@ -110,5 +119,19 @@ pub async fn cs_auth(code: String, redirect_url: String, db: Db) -> Redirect {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Redirect::to(format!("/?token={}", tk))
|
Redirect::to(format!(
|
||||||
|
"{}?token={}",
|
||||||
|
{
|
||||||
|
if env::var("FRONTEND_WHITELIST")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split(',')
|
||||||
|
.any(|url| jump_to_url.starts_with(url))
|
||||||
|
{
|
||||||
|
&jump_to_url
|
||||||
|
} else {
|
||||||
|
"/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&tk
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/main.rs
32
src/main.rs
@@ -12,6 +12,7 @@ extern crate log;
|
|||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
mod cors;
|
||||||
mod db_conn;
|
mod db_conn;
|
||||||
mod libs;
|
mod libs;
|
||||||
#[cfg(feature = "mastlogin")]
|
#[cfg(feature = "mastlogin")]
|
||||||
@@ -45,17 +46,27 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
clear_outdate_redis_data(&rconn.clone()).await;
|
clear_outdate_redis_data(&rconn.clone()).await;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
sleep(Duration::from_secs(4 * 60 * 60)).await;
|
sleep(Duration::from_secs(3 * 60 * 60)).await;
|
||||||
models::Post::annealing(establish_connection(), &rconn).await;
|
models::Post::annealing(establish_connection(), &rconn).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let rconn = RdsConn(rmc.clone());
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
for room_id in (0..5).map(Some).chain([None, Some(42)]) {
|
||||||
|
cache::PostListCache::init(room_id, 3, &rconn).clear().await;
|
||||||
|
}
|
||||||
|
sleep(Duration::from_secs(5 * 60)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.mount(
|
.mount(
|
||||||
"/_api/v1",
|
"/_api/v1",
|
||||||
routes![
|
routes![
|
||||||
api::comment::get_comment,
|
api::comment::get_comment,
|
||||||
api::comment::add_comment,
|
api::comment::old_add_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,
|
||||||
@@ -72,6 +83,16 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
api::operation::set_auto_block,
|
api::operation::set_auto_block,
|
||||||
api::vote::vote,
|
api::vote::vote,
|
||||||
api::upload::ipfs_upload,
|
api::upload::ipfs_upload,
|
||||||
|
cors::options_handler,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.mount(
|
||||||
|
"/_api/v2",
|
||||||
|
routes![
|
||||||
|
api::comment::add_comment,
|
||||||
|
api::upload::local_upload,
|
||||||
|
cors::options_handler,
|
||||||
|
api::attention::set_notification,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.mount(
|
.mount(
|
||||||
@@ -90,6 +111,13 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
.manage(RandomHasher::get_random_one())
|
.manage(RandomHasher::get_random_one())
|
||||||
.manage(rmc)
|
.manage(rmc)
|
||||||
.attach(Db::fairing())
|
.attach(Db::fairing())
|
||||||
|
.attach(cors::Cors {
|
||||||
|
whitelist: env::var("FRONTEND_WHITELIST")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
})
|
||||||
.launch()
|
.launch()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ pub struct Post {
|
|||||||
pub is_reported: bool,
|
pub is_reported: bool,
|
||||||
pub hot_score: i32,
|
pub hot_score: i32,
|
||||||
pub allow_search: bool,
|
pub allow_search: bool,
|
||||||
|
pub room_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Insertable, Serialize, Deserialize, Debug)]
|
#[derive(Queryable, Insertable, Serialize, Deserialize, Debug)]
|
||||||
@@ -140,6 +141,7 @@ pub struct NewPost {
|
|||||||
pub is_tmp: bool,
|
pub is_tmp: bool,
|
||||||
pub n_attentions: i32,
|
pub n_attentions: i32,
|
||||||
pub allow_search: bool,
|
pub allow_search: bool,
|
||||||
|
pub room_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Post {
|
impl Post {
|
||||||
@@ -171,7 +173,7 @@ impl Post {
|
|||||||
let missing_ps = Self::_get_multi(db, missing_ids).await?;
|
let missing_ps = Self::_get_multi(db, missing_ids).await?;
|
||||||
// dbg!(&missing_ps);
|
// dbg!(&missing_ps);
|
||||||
|
|
||||||
cacher.sets(&missing_ps.iter().collect()).await;
|
cacher.sets(&missing_ps.iter().collect::<Vec<_>>()).await;
|
||||||
|
|
||||||
for p in missing_ps.into_iter() {
|
for p in missing_ps.into_iter() {
|
||||||
if let Some(op) = id2po.get_mut(&p.id) {
|
if let Some(op) = id2po.get_mut(&p.id) {
|
||||||
@@ -215,19 +217,21 @@ impl Post {
|
|||||||
pub async fn gets_by_page(
|
pub async fn gets_by_page(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
rconn: &RdsConn,
|
rconn: &RdsConn,
|
||||||
|
room_id: Option<i32>,
|
||||||
order_mode: u8,
|
order_mode: u8,
|
||||||
start: i64,
|
start: i64,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> QueryResult<Vec<Self>> {
|
) -> QueryResult<Vec<Self>> {
|
||||||
let mut cacher = PostListCommentCache::init(order_mode, &rconn);
|
let mut cacher = PostListCache::init(room_id, order_mode, &rconn);
|
||||||
if cacher.need_fill().await {
|
if cacher.need_fill().await {
|
||||||
let pids =
|
let pids =
|
||||||
Self::_get_ids_by_page(db, order_mode.clone(), 0, cacher.i64_minlen()).await?;
|
Self::_get_ids_by_page(db, room_id, order_mode.clone(), 0, cacher.i64_minlen())
|
||||||
|
.await?;
|
||||||
let ps = Self::get_multi(db, rconn, &pids).await?;
|
let ps = Self::get_multi(db, rconn, &pids).await?;
|
||||||
cacher.fill(&ps).await;
|
cacher.fill(&ps).await;
|
||||||
}
|
}
|
||||||
let pids = if start + limit > cacher.i64_len() {
|
let pids = if start + limit > cacher.i64_len() {
|
||||||
Self::_get_ids_by_page(db, order_mode, start, limit).await?
|
Self::_get_ids_by_page(db, room_id, order_mode, start, limit).await?
|
||||||
} else {
|
} else {
|
||||||
cacher.get_pids(start, limit).await
|
cacher.get_pids(start, limit).await
|
||||||
};
|
};
|
||||||
@@ -236,6 +240,7 @@ impl Post {
|
|||||||
}
|
}
|
||||||
async fn _get_ids_by_page(
|
async fn _get_ids_by_page(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
|
room_id: Option<i32>,
|
||||||
order_mode: u8,
|
order_mode: u8,
|
||||||
start: i64,
|
start: i64,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
@@ -243,7 +248,11 @@ impl Post {
|
|||||||
db.run(move |c| {
|
db.run(move |c| {
|
||||||
let mut query = base_query!(posts).select(posts::id);
|
let mut query = base_query!(posts).select(posts::id);
|
||||||
if order_mode > 0 {
|
if order_mode > 0 {
|
||||||
query = query.filter(posts::is_reported.eq(false))
|
query = query.filter(posts::is_reported.eq(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ri) = room_id {
|
||||||
|
query = query.filter(posts::room_id.eq(ri));
|
||||||
}
|
}
|
||||||
|
|
||||||
query = match order_mode {
|
query = match order_mode {
|
||||||
@@ -262,6 +271,7 @@ impl Post {
|
|||||||
pub async fn search(
|
pub async fn search(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
rconn: &RdsConn,
|
rconn: &RdsConn,
|
||||||
|
room_id: Option<i32>,
|
||||||
search_mode: u8,
|
search_mode: u8,
|
||||||
search_text: String,
|
search_text: String,
|
||||||
start: i64,
|
start: i64,
|
||||||
@@ -276,29 +286,38 @@ impl Post {
|
|||||||
.distinct()
|
.distinct()
|
||||||
.left_join(comments::table)
|
.left_join(comments::table)
|
||||||
.filter(posts::is_reported.eq(false));
|
.filter(posts::is_reported.eq(false));
|
||||||
|
if let Some(ri) = room_id {
|
||||||
|
query = query.filter(posts::room_id.eq(ri));
|
||||||
|
}
|
||||||
// 先用搜索+缓存,性能有问题了再真的做tag表
|
// 先用搜索+缓存,性能有问题了再真的做tag表
|
||||||
query = match search_mode {
|
query = match search_mode {
|
||||||
0 => {
|
0 => {
|
||||||
pat = format!("%#{}%", &search_text2);
|
pat = format!("%#{}%", &search_text2);
|
||||||
query
|
query.filter(
|
||||||
.filter(posts::cw.eq(&search_text))
|
posts::cw
|
||||||
.or_filter(posts::cw.eq(format!("#{}", &search_text)))
|
.eq(&search_text)
|
||||||
.or_filter(posts::content.like(&pat))
|
.or(posts::cw.eq(format!("#{}", &search_text)))
|
||||||
.or_filter(
|
.or(posts::content.like(&pat))
|
||||||
comments::content
|
.or(comments::content
|
||||||
.like(&pat)
|
.like(&pat)
|
||||||
.and(comments::is_deleted.eq(false)),
|
.and(comments::is_deleted.eq(false))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
pat = format!("%{}%", search_text2.replace(" ", "%"));
|
pat = format!("%{}%", search_text2.replace(" ", "%"));
|
||||||
query
|
query
|
||||||
.filter(posts::content.like(&pat).or(comments::content.like(&pat)))
|
.filter(
|
||||||
|
posts::content.like(&pat).or(comments::content
|
||||||
|
.like(&pat)
|
||||||
|
.and(comments::is_deleted.eq(false))),
|
||||||
|
)
|
||||||
.filter(posts::allow_search.eq(true))
|
.filter(posts::allow_search.eq(true))
|
||||||
}
|
}
|
||||||
2 => query
|
2 => query.filter(
|
||||||
.filter(posts::author_title.eq(&search_text))
|
posts::author_title
|
||||||
.or_filter(comments::author_title.eq(&search_text)),
|
.eq(&search_text)
|
||||||
|
.or(comments::author_title.eq(&search_text)),
|
||||||
|
),
|
||||||
_ => panic!("Wrong search mode!"),
|
_ => panic!("Wrong search mode!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -328,9 +347,12 @@ impl Post {
|
|||||||
join!(
|
join!(
|
||||||
self.set_instance_cache(rconn),
|
self.set_instance_cache(rconn),
|
||||||
future::join_all((if is_new { 0..4 } else { 1..4 }).map(|mode| async move {
|
future::join_all((if is_new { 0..4 } else { 1..4 }).map(|mode| async move {
|
||||||
PostListCommentCache::init(mode, &rconn.clone())
|
PostListCache::init(None, mode, &rconn.clone())
|
||||||
.put(self)
|
.put(self)
|
||||||
.await
|
.await;
|
||||||
|
PostListCache::init(Some(self.room_id), mode, &rconn.clone())
|
||||||
|
.put(self)
|
||||||
|
.await;
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -343,7 +365,9 @@ impl Post {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
PostCache::init(&rconn).clear_all().await;
|
PostCache::init(&rconn).clear_all().await;
|
||||||
PostListCommentCache::init(2, rconn).clear().await
|
for room_id in (0..5).map(Some).chain([None, Some(42)]) {
|
||||||
|
PostListCache::init(room_id, 2, rconn).clear().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use redis::aio::MultiplexedConnection;
|
use redis::aio::MultiplexedConnection;
|
||||||
use rocket::request::{FromRequest, Outcome, Request};
|
use rocket::request::{FromRequest, Outcome, Request};
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
pub struct RdsConn(pub MultiplexedConnection);
|
pub struct RdsConn(pub MultiplexedConnection);
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ impl DerefMut for RdsConn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn init_rds_client() -> MultiplexedConnection {
|
pub async fn init_rds_client() -> MultiplexedConnection {
|
||||||
let redis_url = env::var("REDIS_URL").expect("REDIS_URL must be set");
|
let redis_url = env::var("REDIS_URL").expect("REDIS_URL must be set");
|
||||||
let client = redis::Client::open(redis_url).expect("connect to redis fail");
|
let client = redis::Client::open(redis_url).expect("connect to redis fail");
|
||||||
|
|||||||
@@ -75,7 +75,25 @@ impl Attention {
|
|||||||
self.rconn.smembers(&self.key).await
|
self.rconn.smembers(&self.key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: clear all
|
pub async fn clear_all(rconn: &RdsConn) {
|
||||||
|
let mut rconn = rconn.clone();
|
||||||
|
let mut keys = rconn
|
||||||
|
.scan_match::<&str, String>("hole_v2:attention:*")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut ks_for_del = Vec::new();
|
||||||
|
while let Some(key) = keys.next_item().await {
|
||||||
|
ks_for_del.push(key);
|
||||||
|
}
|
||||||
|
if ks_for_del.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rconn
|
||||||
|
.del(ks_for_del)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| warn!("clear all post cache fail, {}", e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
@@ -86,6 +104,7 @@ pub enum LogType {
|
|||||||
Ban,
|
Ban,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
impl LogType {
|
impl LogType {
|
||||||
pub fn contains_ugc(&self) -> bool {
|
pub fn contains_ugc(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@@ -94,6 +113,7 @@ impl LogType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
@@ -220,7 +240,7 @@ impl AutoBlockRank {
|
|||||||
|
|
||||||
pub async fn get(rconn: &RdsConn, namehash: &str) -> RedisResult<u8> {
|
pub async fn get(rconn: &RdsConn, namehash: &str) -> RedisResult<u8> {
|
||||||
let rank: Option<u8> = rconn.clone().hget(KEY_AUTO_BLOCK_RANK, namehash).await?;
|
let rank: Option<u8> = rconn.clone().hget(KEY_AUTO_BLOCK_RANK, namehash).await?;
|
||||||
Ok(rank.unwrap_or(2))
|
Ok(rank.unwrap_or(4))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear(rconn: &RdsConn) -> RedisResult<()> {
|
pub async fn clear(rconn: &RdsConn) -> RedisResult<()> {
|
||||||
@@ -264,9 +284,10 @@ impl PollVote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear_outdate_redis_data(rconn: &RdsConn) {
|
pub async fn clear_outdate_redis_data(rconn: &RdsConn) {
|
||||||
BannedUsers::clear(&rconn).await.unwrap();
|
BannedUsers::clear(rconn).await.unwrap();
|
||||||
CustomTitle::clear(&rconn).await.unwrap();
|
CustomTitle::clear(rconn).await.unwrap();
|
||||||
AutoBlockRank::clear(&rconn).await.unwrap();
|
AutoBlockRank::clear(rconn).await.unwrap();
|
||||||
|
Attention::clear_all(rconn).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use init;
|
pub(crate) use init;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ table! {
|
|||||||
is_reported -> Bool,
|
is_reported -> Bool,
|
||||||
hot_score -> Int4,
|
hot_score -> Int4,
|
||||||
allow_search -> Bool,
|
allow_search -> Bool,
|
||||||
|
room_id -> Int4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,8 +43,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