This commit is contained in:
2023-10-24 08:14:15 +00:00
committed by GitHub
parent 370c042e18
commit d8c0cac94c
16 changed files with 675 additions and 25 deletions

2
Cargo.lock generated
View File

@@ -286,6 +286,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.48.5",
]
@@ -1472,6 +1473,7 @@ dependencies = [
"reqwest",
"rusqlite",
"serde",
"serde_json",
"tokio",
"url",
"urlparse",

View File

@@ -12,7 +12,7 @@ base64 = { version = "0.21", optional = true }
bytes = { version = "1.4", optional = true }
c_fixed_string = { version = "0.2", optional = true }
cfg-if = "1"
chrono = "0.4"
chrono = { version = "0.4", features = ["serde"] }
dateparser = "0.2.0"
derive_more = "0.99"
fancy-regex = "0.11"
@@ -42,6 +42,7 @@ reqwest = { version = "0.11", features = ["brotli", "deflate", "gzip", "socks",
rusqlite = { version = "0.29", features = ["bundled", "chrono"], optional = true }
RustyXML = "0.3"
serde = "1"
serde_json = { version = "1", optional = true }
tokio = { version = "1.27", features = ["rt", "macros", "rt-multi-thread", "time"] }
url = "2.3"
urlparse = "0.7"
@@ -59,7 +60,7 @@ db_all = ["db", "db_sqlite"]
db_sqlite = ["rusqlite"]
docker = []
exif = ["bindgen", "c_fixed_string", "cmake", "link-cplusplus", "utf16string"]
server = ["async-trait", "base64", "db", "hex", "hyper", "multipart", "openssl"]
server = ["async-trait", "base64", "db", "hex", "hyper", "multipart", "openssl", "serde_json"]
ugoira = ["avdict", "bindgen", "cmake", "link-cplusplus"]
[patch.crates-io]

View File

@@ -1,5 +1,7 @@
pub mod config;
pub mod pixiv_artworks;
#[cfg(feature = "server")]
pub mod push_task;
#[cfg(feature = "db_sqlite")]
pub mod sqlite;
#[cfg(feature = "server")]
@@ -13,6 +15,8 @@ pub use config::PixivDownloaderDbConfig;
#[cfg(feature = "db_sqlite")]
pub use config::PixivDownloaderSqliteConfig;
pub use pixiv_artworks::{PixivArtwork, PixivArtworkLock};
#[cfg(feature = "server")]
pub use push_task::{PushConfig, PushTask, PushTaskConfig};
#[cfg(feature = "db_sqlite")]
pub use sqlite::{PixivDownloaderSqlite, SqliteError};
#[cfg(feature = "server")]
@@ -22,15 +26,15 @@ pub use traits::PixivDownloaderDb;
pub use user::User;
#[derive(Debug, derive_more::Display)]
pub struct PixivDownloaderDbError {
e: anyhow::Error,
pub enum PixivDownloaderDbError {
AnyHow(anyhow::Error),
#[cfg(feature = "db_sqlite")]
Sqlite(SqliteError),
}
impl PixivDownloaderDbError {
pub fn msg<S: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static>(msg: S) -> Self {
Self {
e: anyhow::Error::msg(msg),
}
Self::AnyHow(anyhow::Error::msg(msg))
}
}
@@ -39,7 +43,7 @@ where
T: Into<anyhow::Error>,
{
fn from(e: T) -> Self {
Self { e: e.into() }
Self::AnyHow(e.into())
}
}
@@ -48,7 +52,31 @@ use crate::gettext;
#[cfg(feature = "db_sqlite")]
impl From<SqliteError> for PixivDownloaderDbError {
fn from(e: SqliteError) -> Self {
PixivDownloaderDbError::msg(e)
PixivDownloaderDbError::Sqlite(e)
}
}
#[cfg(all(feature = "db_sqlite", feature = "server"))]
pub trait Optional2Extension<T> {
fn optional2(self) -> Result<Option<T>, PixivDownloaderDbError>;
}
#[cfg(all(feature = "db_sqlite", feature = "server"))]
impl<T> Optional2Extension<T> for Result<T, PixivDownloaderDbError> {
fn optional2(self) -> Result<Option<T>, PixivDownloaderDbError> {
match self {
Ok(v) => Ok(Some(v)),
Err(e) => match e {
PixivDownloaderDbError::Sqlite(e) => match e {
SqliteError::DbError(e) => match e {
rusqlite::Error::QueryReturnedNoRows => Ok(None),
_ => Err(PixivDownloaderDbError::Sqlite(SqliteError::DbError(e))),
},
_ => Err(PixivDownloaderDbError::Sqlite(e)),
},
_ => Err(e),
},
}
}
}

76
src/db/push_task.rs Normal file
View File

@@ -0,0 +1,76 @@
use crate::push::every_push::EveryPushTextType;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PushTaskPixivRestrictType {
Public,
Private,
All,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum PushTaskPixivAction {
Follow {
restrict: PushTaskPixivRestrictType,
},
Bookmarks {
restrict: PushTaskPixivRestrictType,
uid: u64,
},
Illusts {
uid: u64,
},
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PushTaskPixivConfig {
pub act: PushTaskPixivAction,
/// Tag translation language
pub lang: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum PushTaskConfig {
Pixiv(PushTaskPixivConfig),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EveryPushConfig {
/// Push server
pub push_server: String,
/// Push token
pub push_token: String,
#[serde(rename = "type")]
/// Push type
pub typ: EveryPushTextType,
/// Push topic ID
pub topic_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum PushConfig {
EveryPush(EveryPushConfig),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PushTask {
/// The task ID
pub id: u64,
/// Configurations of the task
pub config: PushTaskConfig,
/// Push configurations
pub push_configs: Vec<PushConfig>,
#[serde(with = "chrono::serde::ts_seconds")]
/// Last updated time
pub last_updated: DateTime<Utc>,
/// Update interval
pub ttl: u64,
}

View File

@@ -1,8 +1,12 @@
#[cfg(feature = "server")]
use super::super::Optional2Extension;
use super::super::{PixivArtwork, PixivArtworkLock};
use super::super::{
PixivDownloaderDb, PixivDownloaderDbConfig, PixivDownloaderDbError, PixivDownloaderSqliteConfig,
};
#[cfg(feature = "server")]
use super::super::{PushConfig, PushTask, PushTaskConfig};
#[cfg(feature = "server")]
use super::super::{Token, User};
use super::SqliteError;
use bytes::BytesMut;
@@ -51,6 +55,13 @@ id INT,
file_id INT,
page INT
);";
const PUSH_TASK_TABLE: &'static str = "CREATE TABLE push_task (
id INTEGER PRIMARY KEY AUTOINCREMENT,
config TEXT,
push_configs TEXT,
last_updated DATETIME,
ttl INT
);";
const TAGS_TABLE: &'static str = "CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
@@ -82,7 +93,7 @@ v3 INT,
v4 INT,
PRIMARY KEY (id)
);";
const VERSION: [u8; 4] = [1, 0, 0, 6];
const VERSION: [u8; 4] = [1, 0, 0, 7];
pub struct PixivDownloaderSqlite {
db: Mutex<Connection>,
@@ -116,6 +127,29 @@ impl PixivDownloaderSqlite {
Ok(())
}
#[cfg(feature = "server")]
fn _add_push_task(
ts: &Transaction,
config: &PushTaskConfig,
push_configs: &[PushConfig],
ttl: u64,
) -> Result<u64, PixivDownloaderDbError> {
ts.execute(
"INSERT INTO push_task (config, push_configs, last_updated, ttl) VALUES (?, ?, ?, ?);",
(
serde_json::to_string(config)?,
serde_json::to_string(push_configs)?,
DateTime::UNIX_EPOCH,
ttl,
),
)?;
Ok(ts.query_row(
"SELECT seq FROM sqlite_sequence WHERE name = 'push_task';",
[],
|row| Ok(row.get(0)?),
)?)
}
#[cfg(feature = "server")]
async fn _add_root_user(
&self,
@@ -215,6 +249,9 @@ impl PixivDownloaderSqlite {
if db_version < [1, 0, 0, 6] {
tx.execute(CONFIG_TABLE, [])?;
}
if db_version < [1, 0, 0, 7] {
tx.execute(PUSH_TASK_TABLE, [])?;
}
self._write_version(&tx)?;
tx.commit()?;
}
@@ -247,6 +284,9 @@ impl PixivDownloaderSqlite {
if !tables.contains_key("pixiv_files") {
t.execute(PIXIV_FILES_TABLE, [])?;
}
if !tables.contains_key("push_task") {
t.execute(PUSH_TASK_TABLE, [])?;
}
if !tables.contains_key("tags") {
t.execute(TAGS_TABLE, [])?;
}
@@ -342,6 +382,29 @@ impl PixivDownloaderSqlite {
.optional()?)
}
#[cfg(feature = "server")]
async fn get_push_task(&self, id: u64) -> Result<Option<PushTask>, PixivDownloaderDbError> {
let con = self.db.lock().await;
con.query_row_and_then::<PushTask, PixivDownloaderDbError, _, _>(
"SELECT * FROM push_task WHERE id = ?;",
[id],
|row| {
let config: String = row.get(1)?;
let config: PushTaskConfig = serde_json::from_str(&config)?;
let push_configs: String = row.get(2)?;
let push_configs: Vec<PushConfig> = serde_json::from_str(&push_configs)?;
Ok(PushTask {
id: row.get(0)?,
config,
push_configs,
last_updated: row.get(3)?,
ttl: row.get(4)?,
})
},
)
.optional2()
}
#[cfg(feature = "server")]
async fn get_token(&self, id: u64) -> Result<Option<Token>, SqliteError> {
let con = self.db.lock().await;
@@ -554,6 +617,56 @@ impl PixivDownloaderSqlite {
}
}
#[cfg(feature = "server")]
fn _update_push_task(
tx: &Transaction,
id: u64,
config: Option<&PushTaskConfig>,
push_configs: Option<&[PushConfig]>,
ttl: Option<u64>,
) -> Result<(), PixivDownloaderDbError> {
match config {
Some(config) => {
let config = serde_json::to_string(config)?;
tx.execute(
"UPDATE push_task SET config = ? WHERE id = ?;",
(config, id),
)?;
}
None => {}
}
match push_configs {
Some(push_configs) => {
let push_configs = serde_json::to_string(push_configs)?;
tx.execute(
"UPDATE push_task SET push_configs = ? WHERE id = ?;",
(push_configs, id),
)?;
}
None => {}
}
match ttl {
Some(ttl) => {
tx.execute("UPDATE push_task SET ttl = ? WHERE id = ?;", (ttl, id))?;
}
None => {}
}
Ok(())
}
#[cfg(feature = "server")]
fn _update_push_task_last_updated(
tx: &Transaction,
id: u64,
last_updated: &DateTime<Utc>,
) -> Result<(), PixivDownloaderDbError> {
tx.execute(
"UPDATE push_task SET last_updated = ? WHERE id = ?;",
(last_updated, id),
)?;
Ok(())
}
#[cfg(feature = "server")]
async fn _update_user(
&self,
@@ -677,6 +790,23 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
Ok(self.get_pixiv_artwork(id).await?.expect("User not found:"))
}
#[cfg(feature = "server")]
async fn add_push_task(
&self,
config: &PushTaskConfig,
push_configs: &[PushConfig],
ttl: u64,
) -> Result<PushTask, PixivDownloaderDbError> {
let task = {
let mut db = self.db.lock().await;
let tx = db.transaction()?;
let task = Self::_add_push_task(&tx, config, push_configs, ttl)?;
tx.commit()?;
task
};
Ok(self.get_push_task(task).await?.expect("Task not found:"))
}
#[cfg(feature = "server")]
async fn add_root_user(
&self,
@@ -770,6 +900,32 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
Ok(self.get_config(key).await?)
}
async fn get_config_or_set_default(
&self,
key: &str,
default: fn() -> Result<String, PixivDownloaderDbError>,
) -> Result<String, PixivDownloaderDbError> {
let mut db = self.db.lock().await;
let tx = db.transaction()?;
let value = tx
.query_row("SELECT value FROM config WHERE key = ?;", [key], |row| {
row.get(0)
})
.optional()?;
match value {
Some(v) => {
tx.commit()?;
Ok(v)
}
None => {
let v = default()?;
Self::_set_config(&tx, key, &v)?;
tx.commit()?;
Ok(v)
}
}
}
async fn get_pixiv_artwork(
&self,
id: u64,
@@ -777,6 +933,11 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
Ok(self.get_pixiv_artwork(id).await?)
}
#[cfg(feature = "server")]
async fn get_push_task(&self, id: u64) -> Result<Option<PushTask>, PixivDownloaderDbError> {
Ok(self.get_push_task(id).await?)
}
#[cfg(feature = "server")]
async fn get_token(&self, id: u64) -> Result<Option<Token>, PixivDownloaderDbError> {
Ok(self.get_token(id).await?)
@@ -851,6 +1012,38 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
.await?)
}
#[cfg(feature = "server")]
async fn update_push_task(
&self,
id: u64,
config: Option<&PushTaskConfig>,
push_configs: Option<&[PushConfig]>,
ttl: Option<u64>,
) -> Result<PushTask, PixivDownloaderDbError> {
{
let mut db = self.db.lock().await;
let tx = db.transaction()?;
Self::_update_push_task(&tx, id, config, push_configs, ttl)?;
tx.commit()?;
}
Ok(self.get_push_task(id).await?.expect("Task not found:"))
}
#[cfg(feature = "server")]
async fn update_push_task_last_updated(
&self,
id: u64,
last_updated: &DateTime<Utc>,
) -> Result<(), PixivDownloaderDbError> {
{
let mut db = self.db.lock().await;
let mut tx = db.transaction()?;
Self::_update_push_task_last_updated(&mut tx, id, last_updated)?;
tx.commit()?;
}
Ok(())
}
#[cfg(feature = "server")]
async fn update_user(
&self,

View File

@@ -2,6 +2,8 @@ use super::PixivDownloaderDbConfig;
use super::PixivDownloaderDbError;
use super::{PixivArtwork, PixivArtworkLock};
#[cfg(feature = "server")]
use super::{PushConfig, PushTask, PushTaskConfig};
#[cfg(feature = "server")]
use super::{Token, User};
use chrono::{DateTime, Utc};
use flagset::FlagSet;
@@ -36,6 +38,17 @@ pub trait PixivDownloaderDb {
lock: &FlagSet<PixivArtworkLock>,
) -> Result<PixivArtwork, PixivDownloaderDbError>;
#[cfg(feature = "server")]
/// Add a push task
/// * `config` - The task's config
/// * `push_configs` - The task's push configurations
/// * `ttl` - The task's update interval
async fn add_push_task(
&self,
config: &PushTaskConfig,
push_configs: &[PushConfig],
ttl: u64,
) -> Result<PushTask, PixivDownloaderDbError>;
#[cfg(feature = "server")]
/// Add root user to database.
/// * `name` - User name
/// * `username` - Unique user name
@@ -98,6 +111,14 @@ pub trait PixivDownloaderDb {
/// Get a config from database
/// * `key` - The config key
async fn get_config(&self, key: &str) -> Result<Option<String>, PixivDownloaderDbError>;
/// Get a config from database or set a default value to database and return
/// * `key` - The config key
/// * `default` - The function to return default value
async fn get_config_or_set_default(
&self,
key: &str,
default: fn() -> Result<String, PixivDownloaderDbError>,
) -> Result<String, PixivDownloaderDbError>;
/// Get an artwork from database
/// * `id` - The artwork ID
async fn get_pixiv_artwork(
@@ -107,19 +128,21 @@ pub trait PixivDownloaderDb {
#[cfg(feature = "server")]
/// Get proxy pixiv secrets
async fn get_proxy_pixiv_secrets(&self) -> Result<String, PixivDownloaderDbError> {
let key = "proxy_pixiv_secrets";
match self.get_config(key).await? {
Some(v) => Ok(v),
None => {
let mut buf = [0; 32];
openssl::rand::rand_bytes(&mut buf)?;
let v = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &buf);
self.set_config(key, &v).await?;
Ok(v)
}
}
self.get_config_or_set_default("proxy_pixiv_secrets", || {
let mut buf = [0; 32];
openssl::rand::rand_bytes(&mut buf)?;
Ok(base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
&buf,
))
})
.await
}
#[cfg(feature = "server")]
/// Get a push task by ID
/// * `id` - The task's ID
async fn get_push_task(&self, id: u64) -> Result<Option<PushTask>, PixivDownloaderDbError>;
#[cfg(feature = "server")]
/// Get token by ID
/// * `id` - The token ID
async fn get_token(&self, id: u64) -> Result<Option<Token>, PixivDownloaderDbError>;
@@ -181,6 +204,28 @@ pub trait PixivDownloaderDb {
is_admin: bool,
) -> Result<User, PixivDownloaderDbError>;
#[cfg(feature = "server")]
/// Update a push task
/// * `id`: The task's ID
/// * `config`: The task's config
/// * `push_configs`: The task's push configurations
/// * `ttl`: The task's update interval
async fn update_push_task(
&self,
id: u64,
config: Option<&PushTaskConfig>,
push_configs: Option<&[PushConfig]>,
ttl: Option<u64>,
) -> Result<PushTask, PixivDownloaderDbError>;
#[cfg(feature = "server")]
/// Update a push task's last updated time
/// * `id`: The task's ID
/// * `last_updated`: The task's last updated time
async fn update_push_task_last_updated(
&self,
id: u64,
last_updated: &DateTime<Utc>,
) -> Result<(), PixivDownloaderDbError>;
#[cfg(feature = "server")]
/// Update a user's information
/// * `id`: The user's ID
/// * `name`: The user's name

View File

@@ -29,6 +29,8 @@ pub enum PixivDownloaderError {
ParseIntError(std::num::ParseIntError),
ReqwestError(reqwest::Error),
PixivAppError(crate::pixivapp::error::PixivAppError),
#[cfg(feature = "serde_json")]
SerdeJsonError(serde_json::Error),
}
impl std::error::Error for PixivDownloaderError {}

View File

@@ -53,7 +53,7 @@ impl<T: ToJson> ToJson for &T {
}
}
impl<T: ToJson2> ToJson2 for &T {
impl<T: ToJson2 + ?Sized> ToJson2 for &T {
fn to_json2(&self) -> JsonValue {
(*self).to_json2()
}

View File

@@ -1,7 +1,9 @@
use crate::webclient::WebClient;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// Text type
pub enum EveryPushTextType {
Text,
@@ -42,6 +44,7 @@ impl EveryPushClient {
/// * `push_token` - push token
/// * `text` - text
/// * `title` - title
/// * `topic_id` - topic id
/// * `typ` - text type
///
/// For more information, see [API document](https://github.com/PeanutMelonSeedBigAlmond/EveryPush.Server/blob/main/api.md#推送消息)
@@ -49,11 +52,13 @@ impl EveryPushClient {
P: AsRef<str> + ?Sized,
T: AsRef<str> + ?Sized,
I: AsRef<str> + ?Sized,
O: AsRef<str> + ?Sized,
>(
&self,
push_token: &P,
text: &T,
title: Option<&I>,
topic_id: Option<&O>,
typ: Option<EveryPushTextType>,
) -> Result<(), String> {
let mut params = HashMap::new();
@@ -63,6 +68,10 @@ impl EveryPushClient {
Some(t) => params.insert("title", t.as_ref()),
None => None,
};
match topic_id {
Some(t) => params.insert("topicId", t.as_ref()),
None => None,
};
match &typ {
Some(t) => params.insert("type", t.as_ref()),
None => None,
@@ -105,6 +114,7 @@ async fn test_every_push_push() {
&token,
"Push Test",
Some("Push"),
Some("test"),
Some(EveryPushTextType::Text),
)
.await

View File

@@ -1,7 +1,9 @@
use super::auth::RSAKey;
use super::body::hyper::HyperBody;
use super::cors::CorsContext;
use super::params::RequestParams;
use super::result::JSONResult;
use super::preclude::HttpBodyType;
use super::result::{JSONResult, SerdeJSONResult, SerdeJSONResult2};
use crate::db::{open_and_init_database, PixivDownloaderDb, Token, User};
use crate::error::PixivDownloaderError;
use crate::ext::json::ToJson2;
@@ -11,6 +13,7 @@ use futures_util::lock::Mutex;
use hyper::{http::response::Builder, Body, Request, Response};
use json::JsonValue;
use std::collections::BTreeMap;
use std::pin::Pin;
use std::sync::Arc;
pub struct ServerContext {
@@ -53,6 +56,33 @@ impl ServerContext {
Ok(builder.body(re.to_json2())?)
}
pub fn response_serde_json_result(
&self,
builder: Builder,
re: SerdeJSONResult,
) -> Result<Response<Pin<Box<HttpBodyType>>>, PixivDownloaderError> {
let builder = match &re {
Ok(_) => builder,
Err(err) => {
if err.code <= -400 && err.code >= -600 {
builder.status((-err.code) as u16)
} else if err.code < 0 {
builder.status(500)
} else if err.code > 0 {
builder.status(400)
} else {
builder
}
}
};
let s = SerdeJSONResult2::new(re);
Ok(
builder.body::<Pin<Box<HttpBodyType>>>(Box::pin(HyperBody::from(
serde_json::to_string(&s)?,
)))?,
)
}
pub async fn verify_token2(
&self,
req: &Request<Body>,

View File

@@ -11,6 +11,8 @@ pub mod params;
pub mod preclude;
/// Routes about proxy
pub mod proxy;
/// Push tasks management
pub mod push;
/// Base result type for JSON response
pub mod result;
/// Routes

View File

@@ -2,7 +2,7 @@ pub use super::body::hyper::HyperBody;
pub use super::body::response::ResponseBody;
pub use super::context::ServerContext;
pub use super::params::RequestParams;
pub use super::result::JSONResult;
pub use super::result::{JSONResult, SerdeJSONResult};
pub use super::route::ResponseForType;
pub use super::traits::{GetRequestParams, MatchRoute, ResponseFor, ResponseJsonFor};
pub use crate::error::PixivDownloaderError;

221
src/server/push/index.rs Normal file
View File

@@ -0,0 +1,221 @@
use super::super::preclude::*;
use crate::db::{PushConfig, PushTaskConfig};
use crate::ext::try_err::TryErr3;
/// Push task manage action
pub enum PushAction {
/// Add a new push task
Add,
/// Change a exist push task
Change,
/// Get a exist push task
Get,
}
pub struct PushContext {
ctx: Arc<ServerContext>,
action: Option<PushAction>,
is_restful: bool,
}
impl PushContext {
pub fn new(ctx: Arc<ServerContext>, action: Option<PushAction>, is_restful: bool) -> Self {
Self {
ctx,
action,
is_restful,
}
}
async fn handle(&self, mut req: Request<Body>) -> SerdeJSONResult {
let params = req
.get_params()
.await
.try_err3(400, "Failed to get parameters:")?;
let t = self
.ctx
.verify(&req, &params)
.await
.try_err3(401, "Unauthorized")?;
if t.is_some_and(|t| !t.is_admin) {
return Err((403, "Permission denied.").into());
}
match &self.action {
Some(a) => match a {
PushAction::Add => {
let config = params.get("config").ok_or((400, "Missing config."))?;
let config: PushTaskConfig =
serde_json::from_str(config).try_err3(400, "Failed to parse config:")?;
let ttl = params
.get_u64("ttl")
.try_err3(400, "Bad ttl.")?
.unwrap_or(300);
let push_configs = params
.get("push_configs")
.ok_or((400, "Missing push_configs."))?;
let push_configs: Vec<PushConfig> = serde_json::from_str(push_configs)
.try_err3(400, "Failed to parse push_configs:")?;
let re = self
.ctx
.db
.add_push_task(&config, &push_configs, ttl)
.await
.try_err3(500, "Failed to add push task:")?;
Ok(serde_json::to_value(re).try_err3(500, "Failed to serialize result:")?)
}
PushAction::Change => {
let id = params
.get_u64("id")
.try_err3(400, "Bad id.")?
.try_err3(400, "Missing id.")?;
let config = match params.get("config") {
Some(v) => Some(
serde_json::from_str::<PushTaskConfig>(v)
.try_err3(400, "Failed to parse config:")?,
),
None => None,
};
let ttl = params.get_u64("ttl").try_err3(400, "Bad ttl")?;
let push_configs = match params.get("push_configs") {
Some(v) => Some(
serde_json::from_str::<Vec<PushConfig>>(v)
.try_err3(400, "Failed to parse push_configs:")?,
),
None => None,
};
let push_configs = match &push_configs {
Some(v) => Some(v.as_ref()),
None => None,
};
let re = self
.ctx
.db
.update_push_task(id, config.as_ref(), push_configs, ttl)
.await
.try_err3(500, "Failed to change push task:")?;
Ok(serde_json::to_value(re).try_err3(500, "Failed to serialize result:")?)
}
PushAction::Get => {
let id = params
.get_u64("id")
.try_err3(400, "Bad id.")?
.try_err3(400, "Missing id.")?;
let re = self
.ctx
.db
.get_push_task(id)
.await
.try_err3(500, "Failed to get push task:")?;
Ok(serde_json::to_value(re).try_err3(500, "Failed to serialize result:")?)
}
},
None => {
panic!("PushContext::handle: action is None")
}
}
}
}
#[async_trait]
impl ResponseFor<Body, Pin<Box<HttpBodyType>>> for PushContext {
async fn response(
&self,
req: Request<Body>,
) -> Result<Response<Pin<Box<HttpBodyType>>>, PixivDownloaderError> {
let builder = if self.is_restful {
filter_http_methods!(
req,
Box::pin(HyperBody::empty()),
true,
self.ctx,
allow_headers = [CONTENT_TYPE, X_SIGN, X_TOKEN_ID],
typ_def = Pin<Box<HttpBodyType>>,
GET,
OPTIONS,
PATCH,
PUT,
);
builder
} else {
filter_http_methods!(
req,
Box::pin(HyperBody::empty()),
true,
self.ctx,
allow_headers = [CONTENT_TYPE, X_SIGN, X_TOKEN_ID],
typ_def = Pin<Box<HttpBodyType>>,
GET,
OPTIONS,
POST,
);
builder
};
let re = self.handle(req).await;
self.ctx.response_serde_json_result(builder, re)
}
}
pub struct PushRoute {
regex: Regex,
}
impl PushRoute {
pub fn new() -> Self {
Self {
regex: Regex::new(r"^(/+api)?/+push(/+(add|change|get))?$").unwrap(),
}
}
}
impl MatchRoute<Body, Pin<Box<HttpBodyType>>> for PushRoute {
fn match_route(
&self,
ctx: &Arc<ServerContext>,
req: &http::Request<Body>,
) -> Option<Box<ResponseForType>> {
let path = req.uri().path();
let pat = self.regex.captures(path);
match pat {
Some(cap) => {
if req.method() == Method::OPTIONS {
return Some(Box::new(PushContext::new(
Arc::clone(ctx),
None,
cap.get(2).is_none(),
)));
}
let cap2 = cap.get(2);
let is_restful = cap2.is_none();
let action = match cap2 {
Some(m) => {
let m = m.as_str().trim_start_matches("/");
match m {
"add" => Some(PushAction::Add),
"change" => Some(PushAction::Change),
"get" => Some(PushAction::Get),
_ => None,
}
}
None => {
let m = req.method();
if m == Method::PUT {
Some(PushAction::Add)
} else if m == Method::GET {
Some(PushAction::Get)
} else if m == Method::PATCH {
Some(PushAction::Change)
} else {
None
}
}
};
Some(Box::new(PushContext::new(
Arc::clone(ctx),
action,
is_restful,
)))
}
None => None,
}
}
}

3
src/server/push/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod index;
pub use index::{PushContext, PushRoute};

View File

@@ -2,6 +2,8 @@ use crate::ext::json::ToJson2;
#[cfg(test)]
use crate::ext::json::{FromJson, ToJson};
use json::JsonValue;
use serde::ser::SerializeMap;
use serde::Serialize;
#[derive(Clone, Debug)]
/// Error information of a request
@@ -85,6 +87,7 @@ impl From<(i32, String, String)> for JSONError {
}
pub type JSONResult = Result<JsonValue, JSONError>;
pub type SerdeJSONResult = Result<serde_json::Value, JSONError>;
impl ToJson2 for JSONResult {
fn to_json2(&self) -> JsonValue {
@@ -104,6 +107,38 @@ impl ToJson2 for JSONResult {
}
}
pub struct SerdeJSONResult2 {
r: SerdeJSONResult,
}
impl SerdeJSONResult2 {
pub fn new(r: SerdeJSONResult) -> Self {
Self { r }
}
}
impl Serialize for SerdeJSONResult2 {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match &self.r {
Ok(v) => {
let mut s = serializer.serialize_map(Some(3))?;
s.serialize_entry("ok", &true)?;
s.serialize_entry("code", &0)?;
s.serialize_entry("result", v)?;
s.end()
}
Err(e) => {
let mut s = serializer.serialize_map(Some(4))?;
s.serialize_entry("ok", &false)?;
s.serialize_entry("code", &e.code)?;
s.serialize_entry("msg", &e.msg)?;
s.serialize_entry::<_, Option<String>>("debug_msg", &None)?;
s.end()
}
}
}
}
#[cfg(test)]
impl FromJson for JSONResult {
type Err = crate::error::PixivDownloaderError;

View File

@@ -2,6 +2,7 @@ use super::auth::*;
use super::context::ServerContext;
use super::preclude::HttpBodyType;
use super::proxy::*;
use super::push::*;
use super::traits::MatchRoute;
use super::traits::ResponseFor;
use super::version::VersionRoute;
@@ -26,6 +27,7 @@ impl ServerRoutes {
routes.push(Box::new(AuthPubkeyRoute::new()));
routes.push(Box::new(AuthTokenRoute::new()));
routes.push(Box::new(ProxyPixivRoute::new()));
routes.push(Box::new(PushRoute::new()));
Self { routes }
}