feat: add basic graphql support
This commit is contained in:
parent
caaa5dc0cc
commit
40cbf86f0f
40
Cargo.lock
generated
40
Cargo.lock
generated
@ -220,18 +220,22 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"fast_chemail",
|
"fast_chemail",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
"futures-channel",
|
||||||
"futures-timer",
|
"futures-timer",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"http 1.2.0",
|
"http 1.2.0",
|
||||||
"indexmap 2.7.0",
|
"indexmap 2.7.0",
|
||||||
|
"lru",
|
||||||
"mime",
|
"mime",
|
||||||
"multer",
|
"multer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rust_decimal",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@ -1760,6 +1764,12 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -2071,6 +2081,11 @@ name = "hashbrown"
|
|||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
@ -3315,6 +3330,15 @@ version = "0.4.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-cache"
|
name = "lru-cache"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@ -4522,6 +4546,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"eyre",
|
"eyre",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
|
"fastrand",
|
||||||
"figment",
|
"figment",
|
||||||
"futures",
|
"futures",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
@ -4548,6 +4573,7 @@ dependencies = [
|
|||||||
"scraper",
|
"scraper",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"sea-orm-migration",
|
"sea-orm-migration",
|
||||||
|
"seaography",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
@ -5286,6 +5312,20 @@ version = "4.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "seaography"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7bca7168531927846a9da73b20e65aa36cc258b377035286e70ebb34874097b1"
|
||||||
|
dependencies = [
|
||||||
|
"async-graphql",
|
||||||
|
"fnv",
|
||||||
|
"heck 0.4.1",
|
||||||
|
"itertools 0.12.1",
|
||||||
|
"sea-orm",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.11.1"
|
version = "2.11.1"
|
||||||
|
@ -85,8 +85,10 @@ testcontainers-modules = { version = "0.11.4", optional = true }
|
|||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0.95"
|
||||||
bollard = { version = "0.18", optional = true }
|
bollard = { version = "0.18", optional = true }
|
||||||
async-graphql = "7.0.13"
|
async-graphql = { version = "7.0.13", features = [] }
|
||||||
async-graphql-axum = "7.0.13"
|
async-graphql-axum = "7.0.13"
|
||||||
|
fastrand = "2.3.0"
|
||||||
|
seaography = "1.1.2"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -16,9 +16,10 @@ use sea_orm::DatabaseConnection;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::service::AppAuthService,
|
auth::service::AppAuthService,
|
||||||
controllers,
|
controllers::{self},
|
||||||
dal::{AppDalClient, AppDalInitalizer},
|
dal::{AppDalClient, AppDalInitalizer},
|
||||||
extract::mikan::{client::AppMikanClientInitializer, AppMikanClient},
|
extract::mikan::{client::AppMikanClientInitializer, AppMikanClient},
|
||||||
|
graphql::service::{AppGraphQLService, AppGraphQLServiceInitializer},
|
||||||
migrations::Migrator,
|
migrations::Migrator,
|
||||||
models::subscribers,
|
models::subscribers,
|
||||||
workers::subscription_worker::SubscriptionWorker,
|
workers::subscription_worker::SubscriptionWorker,
|
||||||
@ -36,6 +37,10 @@ pub trait AppContextExt {
|
|||||||
fn get_auth_service(&self) -> &AppAuthService {
|
fn get_auth_service(&self) -> &AppAuthService {
|
||||||
AppAuthService::app_instance()
|
AppAuthService::app_instance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_graphql_service(&self) -> &AppGraphQLService {
|
||||||
|
AppGraphQLService::app_instance()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppContextExt for AppContext {}
|
impl AppContextExt for AppContext {}
|
||||||
@ -52,6 +57,7 @@ impl Hooks for App {
|
|||||||
let initializers: Vec<Box<dyn Initializer>> = vec![
|
let initializers: Vec<Box<dyn Initializer>> = vec![
|
||||||
Box::new(AppDalInitalizer),
|
Box::new(AppDalInitalizer),
|
||||||
Box::new(AppMikanClientInitializer),
|
Box::new(AppMikanClientInitializer),
|
||||||
|
Box::new(AppGraphQLServiceInitializer),
|
||||||
];
|
];
|
||||||
|
|
||||||
Ok(initializers)
|
Ok(initializers)
|
||||||
@ -71,10 +77,11 @@ impl Hooks for App {
|
|||||||
create_app::<Self, Migrator>(mode, environment).await
|
create_app::<Self, Migrator>(mode, environment).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn routes(_ctx: &AppContext) -> AppRoutes {
|
fn routes(ctx: &AppContext) -> AppRoutes {
|
||||||
AppRoutes::with_default_routes()
|
AppRoutes::with_default_routes()
|
||||||
.prefix("/api")
|
.prefix("/api")
|
||||||
.add_route(controllers::subscribers::routes())
|
.add_route(controllers::subscribers::routes())
|
||||||
|
.add_route(controllers::graphql::routes(ctx.get_graphql_service()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> {
|
async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use axum::{http::request::Parts, RequestPartsExt};
|
use axum::{http::request::Parts, RequestPartsExt};
|
||||||
use axum_auth::AuthBasic;
|
use axum_auth::AuthBasic;
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ pub struct BasicAuthService {
|
|||||||
pub config: BasicAuthConfig,
|
pub config: BasicAuthConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl AuthService for BasicAuthService {
|
impl AuthService for BasicAuthService {
|
||||||
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError> {
|
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError> {
|
||||||
if let Ok(AuthBasic((found_user, found_password))) = request.extract().await {
|
if let Ok(AuthBasic((found_user, found_password))) = request.extract().await {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use axum::http::request::Parts;
|
use axum::http::request::Parts;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use jwt_authorizer::{authorizer::Authorizer, NumericDate, OneOrArray};
|
use jwt_authorizer::{authorizer::Authorizer, NumericDate, OneOrArray};
|
||||||
@ -80,7 +81,7 @@ pub struct OidcAuthService {
|
|||||||
pub authorizer: Authorizer<OidcAuthClaims>,
|
pub authorizer: Authorizer<OidcAuthClaims>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl AuthService for OidcAuthService {
|
impl AuthService for OidcAuthService {
|
||||||
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError> {
|
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError> {
|
||||||
let config = &self.config;
|
let config = &self.config;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::FromRequestParts,
|
extract::FromRequestParts,
|
||||||
http::request::Parts,
|
http::request::Parts,
|
||||||
@ -21,7 +22,7 @@ pub struct AuthUserInfo {
|
|||||||
pub auth_type: AuthType,
|
pub auth_type: AuthType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl<S> FromRequestParts<S> for AuthUserInfo
|
impl<S> FromRequestParts<S> for AuthUserInfo
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
@ -42,7 +43,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
pub trait AuthService {
|
pub trait AuthService {
|
||||||
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError>;
|
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError>;
|
||||||
}
|
}
|
||||||
@ -84,7 +85,7 @@ impl AppAuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl AuthService for AppAuthService {
|
impl AuthService for AppAuthService {
|
||||||
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError> {
|
async fn extract_user_info(&self, request: &mut Parts) -> Result<AuthUserInfo, AuthError> {
|
||||||
match self {
|
match self {
|
||||||
@ -96,7 +97,7 @@ impl AuthService for AppAuthService {
|
|||||||
|
|
||||||
pub struct AppAuthServiceInitializer;
|
pub struct AppAuthServiceInitializer;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl Initializer for AppAuthServiceInitializer {
|
impl Initializer for AppAuthServiceInitializer {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
String::from("AppAuthServiceInitializer")
|
String::from("AppAuthServiceInitializer")
|
||||||
|
@ -4,7 +4,10 @@ use figment::{
|
|||||||
};
|
};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{auth::AppAuthConfig, dal::config::AppDalConfig, extract::mikan::AppMikanConfig};
|
use crate::{
|
||||||
|
auth::AppAuthConfig, dal::config::AppDalConfig, extract::mikan::AppMikanConfig,
|
||||||
|
graphql::config::AppGraphQLConfig,
|
||||||
|
};
|
||||||
|
|
||||||
const DEFAULT_APP_SETTINGS_MIXIN: &str = include_str!("./settings_mixin.yaml");
|
const DEFAULT_APP_SETTINGS_MIXIN: &str = include_str!("./settings_mixin.yaml");
|
||||||
|
|
||||||
@ -13,6 +16,7 @@ pub struct AppConfig {
|
|||||||
pub auth: AppAuthConfig,
|
pub auth: AppAuthConfig,
|
||||||
pub dal: AppDalConfig,
|
pub dal: AppDalConfig,
|
||||||
pub mikan: AppMikanConfig,
|
pub mikan: AppMikanConfig,
|
||||||
|
pub graphql: AppGraphQLConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_key_path_from_json_value<T: DeserializeOwned>(
|
pub fn deserialize_key_path_from_json_value<T: DeserializeOwned>(
|
||||||
|
@ -4,9 +4,12 @@ dal:
|
|||||||
mikan:
|
mikan:
|
||||||
http_client:
|
http_client:
|
||||||
exponential_backoff_max_retries: 3
|
exponential_backoff_max_retries: 3
|
||||||
leaky_bucket_max_tokens: 3
|
leaky_bucket_max_tokens: 2
|
||||||
leaky_bucket_initial_tokens: 0
|
leaky_bucket_initial_tokens: 0
|
||||||
leaky_bucket_refill_tokens: 1
|
leaky_bucket_refill_tokens: 1
|
||||||
leaky_bucket_refill_interval: 500
|
leaky_bucket_refill_interval: 500
|
||||||
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"
|
|
||||||
base_url: "https://mikanani.me/"
|
base_url: "https://mikanani.me/"
|
||||||
|
|
||||||
|
graphql:
|
||||||
|
depth_limit: null
|
||||||
|
complexity_limit: null
|
||||||
|
19
apps/recorder/src/controllers/graphql.rs
Normal file
19
apps/recorder/src/controllers/graphql.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
|
||||||
|
use async_graphql_axum::GraphQL;
|
||||||
|
use axum::response::Html;
|
||||||
|
use loco_rs::prelude::*;
|
||||||
|
|
||||||
|
use crate::graphql::service::AppGraphQLService;
|
||||||
|
|
||||||
|
pub async fn graphql_playground() -> impl IntoResponse {
|
||||||
|
Html(playground_source(GraphQLPlaygroundConfig::new(
|
||||||
|
"/api/graphql",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes(graphql_service: &AppGraphQLService) -> Routes {
|
||||||
|
Routes::new().prefix("/graphql").add(
|
||||||
|
"/",
|
||||||
|
get(graphql_playground).post_service(GraphQL::new(graphql_service.schema.clone())),
|
||||||
|
)
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
pub mod graphql;
|
||||||
pub mod subscribers;
|
pub mod subscribers;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use loco_rs::app::{AppContext, Initializer};
|
use loco_rs::app::{AppContext, Initializer};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@ -184,7 +185,7 @@ impl AppDalClient {
|
|||||||
|
|
||||||
pub struct AppDalInitalizer;
|
pub struct AppDalInitalizer;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl Initializer for AppDalInitalizer {
|
impl Initializer for AppDalInitalizer {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
String::from("AppDalInitalizer")
|
String::from("AppDalInitalizer")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use loco_rs::app::{AppContext, Initializer};
|
use loco_rs::app::{AppContext, Initializer};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ impl Deref for AppMikanClient {
|
|||||||
|
|
||||||
pub struct AppMikanClientInitializer;
|
pub struct AppMikanClientInitializer;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl Initializer for AppMikanClientInitializer {
|
impl Initializer for AppMikanClientInitializer {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"AppMikanClientInitializer".to_string()
|
"AppMikanClientInitializer".to_string()
|
||||||
|
@ -6,6 +6,12 @@ use super::HttpClient;
|
|||||||
pub async fn fetch_bytes<T: IntoUrl>(client: Option<&HttpClient>, url: T) -> eyre::Result<Bytes> {
|
pub async fn fetch_bytes<T: IntoUrl>(client: Option<&HttpClient>, url: T) -> eyre::Result<Bytes> {
|
||||||
let client = client.unwrap_or_default();
|
let client = client.unwrap_or_default();
|
||||||
|
|
||||||
let bytes = client.get(url).send().await?.bytes().await?;
|
let bytes = client
|
||||||
|
.get(url)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.bytes()
|
||||||
|
.await?;
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,9 @@ use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
|||||||
use reqwest_tracing::TracingMiddleware;
|
use reqwest_tracing::TracingMiddleware;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::fetch::DEFAULT_HTTP_CLIENT_USER_AGENT;
|
use super::get_random_mobile_ua;
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
@ -31,9 +32,9 @@ pub struct HttpClient {
|
|||||||
pub config: HttpClientConfig,
|
pub config: HttpClientConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<ClientWithMiddleware> for HttpClient {
|
impl From<HttpClient> for ClientWithMiddleware {
|
||||||
fn into(self) -> ClientWithMiddleware {
|
fn from(val: HttpClient) -> Self {
|
||||||
self.client
|
val.client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ pub struct RateLimiterMiddleware {
|
|||||||
rate_limiter: RateLimiter,
|
rate_limiter: RateLimiter,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl reqwest_middleware::Middleware for RateLimiterMiddleware {
|
impl reqwest_middleware::Middleware for RateLimiterMiddleware {
|
||||||
async fn handle(
|
async fn handle(
|
||||||
&self,
|
&self,
|
||||||
@ -68,7 +69,7 @@ impl HttpClient {
|
|||||||
config
|
config
|
||||||
.user_agent
|
.user_agent
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or(DEFAULT_HTTP_CLIENT_USER_AGENT),
|
.unwrap_or_else(|| get_random_mobile_ua()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let reqwest_client = reqwest_client_builder.build()?;
|
let reqwest_client = reqwest_client_builder.build()?;
|
||||||
|
@ -1 +1,11 @@
|
|||||||
pub const DEFAULT_HTTP_CLIENT_USER_AGENT: &str = "Wget/1.13.4 (linux-gnu)";
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref DEFAULT_HTTP_CLIENT_USER_AGENT: Vec<String> =
|
||||||
|
serde_json::from_str::<Vec<String>>(include_str!("./ua.json")).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_random_mobile_ua() -> &'static str {
|
||||||
|
DEFAULT_HTTP_CLIENT_USER_AGENT[fastrand::usize(0..DEFAULT_HTTP_CLIENT_USER_AGENT.len())]
|
||||||
|
.as_str()
|
||||||
|
}
|
||||||
|
@ -4,7 +4,13 @@ use super::HttpClient;
|
|||||||
|
|
||||||
pub async fn fetch_html<T: IntoUrl>(client: Option<&HttpClient>, url: T) -> eyre::Result<String> {
|
pub async fn fetch_html<T: IntoUrl>(client: Option<&HttpClient>, url: T) -> eyre::Result<String> {
|
||||||
let client = client.unwrap_or_default();
|
let client = client.unwrap_or_default();
|
||||||
let content = client.get(url).send().await?.text().await?;
|
let content = client
|
||||||
|
.get(url)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ pub mod core;
|
|||||||
pub mod html;
|
pub mod html;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
|
||||||
pub use core::DEFAULT_HTTP_CLIENT_USER_AGENT;
|
pub use core::get_random_mobile_ua;
|
||||||
|
|
||||||
pub use bytes::fetch_bytes;
|
pub use bytes::fetch_bytes;
|
||||||
pub use client::{HttpClient, HttpClientConfig};
|
pub use client::{HttpClient, HttpClientConfig};
|
||||||
|
15
apps/recorder/src/fetch/ua.json
Normal file
15
apps/recorder/src/fetch/ua.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.1",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.3",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Herring/97.1.8280.8",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 AtContent/95.5.5462.5",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.3",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.3"
|
||||||
|
]
|
7
apps/recorder/src/graphql/config.rs
Normal file
7
apps/recorder/src/graphql/config.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct AppGraphQLConfig {
|
||||||
|
pub depth_limit: Option<usize>,
|
||||||
|
pub complexity_limit: Option<usize>,
|
||||||
|
}
|
5
apps/recorder/src/graphql/mod.rs
Normal file
5
apps/recorder/src/graphql/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod query_root;
|
||||||
|
pub mod service;
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
pub use query_root::schema;
|
53
apps/recorder/src/graphql/query_root.rs
Normal file
53
apps/recorder/src/graphql/query_root.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use async_graphql::dynamic::*;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
use seaography::{Builder, BuilderContext};
|
||||||
|
|
||||||
|
lazy_static::lazy_static! { static ref CONTEXT : BuilderContext = BuilderContext :: default () ; }
|
||||||
|
|
||||||
|
pub fn schema(
|
||||||
|
database: DatabaseConnection,
|
||||||
|
depth: Option<usize>,
|
||||||
|
complexity: Option<usize>,
|
||||||
|
) -> Result<Schema, SchemaError> {
|
||||||
|
use crate::models::*;
|
||||||
|
let mut builder = Builder::new(&CONTEXT, database.clone());
|
||||||
|
|
||||||
|
seaography::register_entities!(
|
||||||
|
builder,
|
||||||
|
[
|
||||||
|
auth,
|
||||||
|
bangumi,
|
||||||
|
downloaders,
|
||||||
|
downloads,
|
||||||
|
episodes,
|
||||||
|
subscribers,
|
||||||
|
subscription_bangumi,
|
||||||
|
subscription_episode,
|
||||||
|
subscriptions
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
builder.register_enumeration::<auth::AuthType>();
|
||||||
|
builder.register_enumeration::<downloads::DownloadStatus>();
|
||||||
|
builder.register_enumeration::<subscriptions::SubscriptionCategory>();
|
||||||
|
builder.register_enumeration::<downloaders::DownloaderCategory>();
|
||||||
|
builder.register_enumeration::<downloads::DownloadMime>();
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = builder.schema_builder();
|
||||||
|
let schema = if let Some(depth) = depth {
|
||||||
|
schema.limit_depth(depth)
|
||||||
|
} else {
|
||||||
|
schema
|
||||||
|
};
|
||||||
|
let schema = if let Some(complexity) = complexity {
|
||||||
|
schema.limit_complexity(complexity)
|
||||||
|
} else {
|
||||||
|
schema
|
||||||
|
};
|
||||||
|
schema
|
||||||
|
.data(database)
|
||||||
|
.finish()
|
||||||
|
.inspect_err(|e| tracing::error!(e = ?e))
|
||||||
|
}
|
51
apps/recorder/src/graphql/service.rs
Normal file
51
apps/recorder/src/graphql/service.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use async_graphql::dynamic::{Schema, SchemaError};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use loco_rs::app::{AppContext, Initializer};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
use super::{config::AppGraphQLConfig, query_root};
|
||||||
|
use crate::config::AppConfigExt;
|
||||||
|
|
||||||
|
static APP_GRAPHQL_SERVICE: OnceCell<AppGraphQLService> = OnceCell::new();
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AppGraphQLService {
|
||||||
|
pub schema: Schema,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppGraphQLService {
|
||||||
|
pub fn new(config: AppGraphQLConfig, db: DatabaseConnection) -> Result<Self, SchemaError> {
|
||||||
|
let schema = query_root::schema(db, config.depth_limit, config.complexity_limit)?;
|
||||||
|
Ok(Self { schema })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn app_instance() -> &'static Self {
|
||||||
|
APP_GRAPHQL_SERVICE
|
||||||
|
.get()
|
||||||
|
.expect("AppGraphQLService is not initialized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AppGraphQLServiceInitializer;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Initializer for AppGraphQLServiceInitializer {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
String::from("AppGraphQLServiceInitializer")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn before_run(&self, app_context: &AppContext) -> loco_rs::Result<()> {
|
||||||
|
APP_GRAPHQL_SERVICE.get_or_try_init(|| {
|
||||||
|
let config = app_context
|
||||||
|
.config
|
||||||
|
.get_app_conf()
|
||||||
|
.map_err(loco_rs::Error::wrap)?
|
||||||
|
.graphql;
|
||||||
|
let db = &app_context.db;
|
||||||
|
AppGraphQLService::new(config, db.clone()).map_err(loco_rs::Error::wrap)
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ pub mod controllers;
|
|||||||
pub mod dal;
|
pub mod dal;
|
||||||
pub mod extract;
|
pub mod extract;
|
||||||
pub mod fetch;
|
pub mod fetch;
|
||||||
|
pub mod graphql;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::{DeriveIden, Statement};
|
use sea_orm::{DeriveIden, Statement};
|
||||||
use sea_orm_migration::prelude::{extension::postgres::IntoTypeRef, *};
|
use sea_orm_migration::prelude::{extension::postgres::IntoTypeRef, *};
|
||||||
|
|
||||||
@ -143,7 +144,7 @@ macro_rules! create_postgres_enum_for_active_enum {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
pub trait CustomSchemaManagerExt {
|
pub trait CustomSchemaManagerExt {
|
||||||
async fn create_postgres_auto_update_ts_fn(&self, col_name: &str) -> Result<(), DbErr>;
|
async fn create_postgres_auto_update_ts_fn(&self, col_name: &str) -> Result<(), DbErr>;
|
||||||
async fn create_postgres_auto_update_ts_fn_for_col<C: IntoIden + 'static + Send>(
|
async fn create_postgres_auto_update_ts_fn_for_col<C: IntoIden + 'static + Send>(
|
||||||
@ -250,7 +251,7 @@ pub trait CustomSchemaManagerExt {
|
|||||||
) -> Result<HashSet<String>, DbErr>;
|
) -> Result<HashSet<String>, DbErr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl CustomSchemaManagerExt for SchemaManager<'_> {
|
impl CustomSchemaManagerExt for SchemaManager<'_> {
|
||||||
async fn create_postgres_auto_update_ts_fn(&self, col_name: &str) -> Result<(), DbErr> {
|
async fn create_postgres_auto_update_ts_fn(&self, col_name: &str) -> Result<(), DbErr> {
|
||||||
let sql = format!(
|
let sql = format!(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use loco_rs::schema::jsonb_null;
|
use loco_rs::schema::jsonb_null;
|
||||||
use sea_orm_migration::{prelude::*, schema::*};
|
use sea_orm_migration::{prelude::*, schema::*};
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ use crate::models::{
|
|||||||
#[derive(DeriveMigrationName)]
|
#[derive(DeriveMigrationName)]
|
||||||
pub struct Migration;
|
pub struct Migration;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl MigrationTrait for Migration {
|
impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
manager
|
manager
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use loco_rs::schema::table_auto;
|
use loco_rs::schema::table_auto;
|
||||||
use sea_orm_migration::{prelude::*, schema::*};
|
use sea_orm_migration::{prelude::*, schema::*};
|
||||||
|
|
||||||
use super::defs::*;
|
use super::defs::*;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
downloaders::DownloaderCategoryEnum,
|
downloaders::{DownloaderCategory, DownloaderCategoryEnum},
|
||||||
prelude::{
|
downloads::{DownloadMime, DownloadMimeEnum, DownloadStatus, DownloadStatusEnum},
|
||||||
downloads::{DownloadMimeEnum, DownloadStatusEnum},
|
|
||||||
DownloadMime, DownloadStatus, DownloaderCategory,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(DeriveMigrationName)]
|
#[derive(DeriveMigrationName)]
|
||||||
pub struct Migration;
|
pub struct Migration;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl MigrationTrait for Migration {
|
impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
create_postgres_enum_for_active_enum!(
|
create_postgres_enum_for_active_enum!(
|
||||||
manager,
|
manager,
|
||||||
DownloaderCategoryEnum,
|
DownloaderCategoryEnum,
|
||||||
DownloaderCategory::QBittorrent
|
DownloaderCategory::QBittorrent,
|
||||||
|
DownloaderCategory::Dandanplay
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||||||
#[derive(DeriveMigrationName)]
|
#[derive(DeriveMigrationName)]
|
||||||
pub struct Migration;
|
pub struct Migration;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl MigrationTrait for Migration {
|
impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
create_postgres_enum_for_active_enum!(
|
create_postgres_enum_for_active_enum!(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm_migration::{prelude::*, schema::*};
|
use sea_orm_migration::{prelude::*, schema::*};
|
||||||
|
|
||||||
use super::defs::Auth;
|
use super::defs::Auth;
|
||||||
@ -12,7 +13,7 @@ use crate::{
|
|||||||
#[derive(DeriveMigrationName)]
|
#[derive(DeriveMigrationName)]
|
||||||
pub struct Migration;
|
pub struct Migration;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl MigrationTrait for Migration {
|
impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
create_postgres_enum_for_active_enum!(
|
create_postgres_enum_for_active_enum!(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
pub use sea_orm_migration::prelude::*;
|
pub use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -8,7 +9,7 @@ pub mod m20241231_000001_auth;
|
|||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl MigratorTrait for Migrator {
|
impl MigratorTrait for Migrator {
|
||||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
vec![
|
vec![
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -50,5 +51,5 @@ pub enum RelatedEntity {
|
|||||||
Subscriber,
|
Subscriber,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use loco_rs::app::AppContext;
|
use loco_rs::app::AppContext;
|
||||||
use sea_orm::{entity::prelude::*, sea_query::OnConflict, ActiveValue, FromJsonQueryResult};
|
use sea_orm::{entity::prelude::*, sea_query::OnConflict, ActiveValue, FromJsonQueryResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -59,6 +60,8 @@ pub enum Relation {
|
|||||||
Subscriber,
|
Subscriber,
|
||||||
#[sea_orm(has_many = "super::episodes::Entity")]
|
#[sea_orm(has_many = "super::episodes::Entity")]
|
||||||
Episode,
|
Episode,
|
||||||
|
#[sea_orm(has_many = "super::subscription_bangumi::Entity")]
|
||||||
|
SubscriptionBangumi,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Related<super::episodes::Entity> for Entity {
|
impl Related<super::episodes::Entity> for Entity {
|
||||||
@ -67,6 +70,12 @@ impl Related<super::episodes::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::subscription_bangumi::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SubscriptionBangumi.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Related<super::subscriptions::Entity> for Entity {
|
impl Related<super::subscriptions::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
super::subscription_bangumi::Relation::Subscription.def()
|
super::subscription_bangumi::Relation::Subscription.def()
|
||||||
@ -83,6 +92,18 @@ impl Related<super::subscribers::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscriptions::Entity")]
|
||||||
|
Subscription,
|
||||||
|
#[sea_orm(entity = "super::subscribers::Entity")]
|
||||||
|
Subscriber,
|
||||||
|
#[sea_orm(entity = "super::episodes::Entity")]
|
||||||
|
Episode,
|
||||||
|
#[sea_orm(entity = "super::subscription_bangumi::Entity")]
|
||||||
|
SubscriptionBangumi,
|
||||||
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub async fn get_or_insert_from_mikan<F>(
|
pub async fn get_or_insert_from_mikan<F>(
|
||||||
ctx: &AppContext,
|
ctx: &AppContext,
|
||||||
@ -146,5 +167,5 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -5,11 +6,17 @@ use url::Url;
|
|||||||
#[derive(
|
#[derive(
|
||||||
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay, Serialize, Deserialize,
|
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay, Serialize, Deserialize,
|
||||||
)]
|
)]
|
||||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "downloader_type")]
|
#[sea_orm(
|
||||||
|
rs_type = "String",
|
||||||
|
db_type = "Enum",
|
||||||
|
enum_name = "downloader_category"
|
||||||
|
)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum DownloaderCategory {
|
pub enum DownloaderCategory {
|
||||||
#[sea_orm(string_value = "qbittorrent")]
|
#[sea_orm(string_value = "qbittorrent")]
|
||||||
QBittorrent,
|
QBittorrent,
|
||||||
|
#[sea_orm(string_value = "dandanplay")]
|
||||||
|
Dandanplay,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
@ -55,7 +62,15 @@ impl Related<super::downloads::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscribers::Entity")]
|
||||||
|
Subscriber,
|
||||||
|
#[sea_orm(entity = "super::downloads::Entity")]
|
||||||
|
Download,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -101,7 +102,17 @@ impl Related<super::episodes::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscribers::Entity")]
|
||||||
|
Subscriber,
|
||||||
|
#[sea_orm(entity = "super::downloaders::Entity")]
|
||||||
|
Downloader,
|
||||||
|
#[sea_orm(entity = "super::episodes::Entity")]
|
||||||
|
Episode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl ActiveModel {}
|
impl ActiveModel {}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "auth_type")]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum AuthType {
|
|
||||||
#[sea_orm(string_value = "basic")]
|
|
||||||
Basic,
|
|
||||||
#[sea_orm(string_value = "oidc")]
|
|
||||||
Oidc,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, DeriveEntityModel)]
|
|
||||||
#[sea_orm(table_name = "auth")]
|
|
||||||
pub struct Model {
|
|
||||||
pub created_at: DateTime,
|
|
||||||
pub updated_at: DateTime,
|
|
||||||
#[sea_orm(primary_key)]
|
|
||||||
pub id: i32,
|
|
||||||
pub pid: String,
|
|
||||||
pub subscriber_id: i32,
|
|
||||||
pub auth_type: AuthType,
|
|
||||||
pub avatar_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscribers::Entity",
|
|
||||||
from = "Column::SubscriberId",
|
|
||||||
to = "super::subscribers::Column::Id"
|
|
||||||
)]
|
|
||||||
SubscriberId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscribers::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::SubscriberId.def()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
use sea_orm::{entity::prelude::*, FromJsonQueryResult};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
|
|
||||||
pub struct BangumiFilter {
|
|
||||||
pub name: Option<Vec<String>>,
|
|
||||||
pub group: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
|
|
||||||
pub struct BangumiExtra {
|
|
||||||
pub name_zh: Option<String>,
|
|
||||||
pub s_name_zh: Option<String>,
|
|
||||||
pub name_en: Option<String>,
|
|
||||||
pub s_name_en: Option<String>,
|
|
||||||
pub name_jp: Option<String>,
|
|
||||||
pub s_name_jp: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
|
||||||
#[sea_orm(table_name = "bangumi")]
|
|
||||||
pub struct Model {
|
|
||||||
pub created_at: DateTime,
|
|
||||||
pub updated_at: DateTime,
|
|
||||||
#[sea_orm(primary_key)]
|
|
||||||
pub id: i32,
|
|
||||||
pub mikan_bangumi_id: Option<String>,
|
|
||||||
pub subscription_id: i32,
|
|
||||||
pub subscriber_id: i32,
|
|
||||||
pub display_name: String,
|
|
||||||
pub raw_name: String,
|
|
||||||
pub season: i32,
|
|
||||||
pub season_raw: Option<String>,
|
|
||||||
pub fansub: Option<String>,
|
|
||||||
pub mikan_fansub_id: Option<String>,
|
|
||||||
pub filter: Option<BangumiFilter>,
|
|
||||||
pub rss_link: Option<String>,
|
|
||||||
pub poster_link: Option<String>,
|
|
||||||
pub save_path: Option<String>,
|
|
||||||
#[sea_orm(default = "false")]
|
|
||||||
pub deleted: bool,
|
|
||||||
pub homepage: Option<String>,
|
|
||||||
pub extra: Option<BangumiExtra>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscriptions::Entity",
|
|
||||||
from = "Column::SubscriptionId",
|
|
||||||
to = "super::subscriptions::Column::Id"
|
|
||||||
)]
|
|
||||||
Subscription,
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscribers::Entity",
|
|
||||||
from = "Column::SubscriberId",
|
|
||||||
to = "super::subscribers::Column::Id"
|
|
||||||
)]
|
|
||||||
Subscriber,
|
|
||||||
#[sea_orm(has_many = "super::episodes::Entity")]
|
|
||||||
Episode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::episodes::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Episode.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscriptions::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscription.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscribers::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscriber.def()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "downloader_type")]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum DownloaderCategory {
|
|
||||||
#[sea_orm(string_value = "qbittorrent")]
|
|
||||||
QBittorrent,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
|
||||||
#[sea_orm(table_name = "downloaders")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(column_type = "Timestamp")]
|
|
||||||
pub created_at: DateTime,
|
|
||||||
#[sea_orm(column_type = "Timestamp")]
|
|
||||||
pub updated_at: DateTime,
|
|
||||||
#[sea_orm(primary_key)]
|
|
||||||
pub id: i32,
|
|
||||||
pub category: DownloaderCategory,
|
|
||||||
pub endpoint: String,
|
|
||||||
pub password: String,
|
|
||||||
pub username: String,
|
|
||||||
pub subscriber_id: i32,
|
|
||||||
pub save_path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscribers::Entity",
|
|
||||||
from = "Column::SubscriberId",
|
|
||||||
to = "super::subscribers::Column::Id"
|
|
||||||
)]
|
|
||||||
Subscriber,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscribers::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscriber.def()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "download_status")]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum DownloadStatus {
|
|
||||||
#[sea_orm(string_value = "pending")]
|
|
||||||
Pending,
|
|
||||||
#[sea_orm(string_value = "downloading")]
|
|
||||||
Downloading,
|
|
||||||
#[sea_orm(string_value = "paused")]
|
|
||||||
Paused,
|
|
||||||
#[sea_orm(string_value = "completed")]
|
|
||||||
Completed,
|
|
||||||
#[sea_orm(string_value = "failed")]
|
|
||||||
Failed,
|
|
||||||
#[sea_orm(string_value = "deleted")]
|
|
||||||
Deleted,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, DeriveDisplay, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "download_mime")]
|
|
||||||
pub enum DownloadMime {
|
|
||||||
#[sea_orm(string_value = "application/octet-stream")]
|
|
||||||
#[serde(rename = "application/octet-stream")]
|
|
||||||
OctetStream,
|
|
||||||
#[sea_orm(string_value = "application/x-bittorrent")]
|
|
||||||
#[serde(rename = "application/x-bittorrent")]
|
|
||||||
BitTorrent,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
|
||||||
#[sea_orm(table_name = "downloads")]
|
|
||||||
pub struct Model {
|
|
||||||
pub created_at: DateTime,
|
|
||||||
pub updated_at: DateTime,
|
|
||||||
#[sea_orm(primary_key)]
|
|
||||||
pub id: i32,
|
|
||||||
pub origin_name: String,
|
|
||||||
pub display_name: String,
|
|
||||||
pub subscription_id: i32,
|
|
||||||
pub status: DownloadStatus,
|
|
||||||
pub mime: DownloadMime,
|
|
||||||
pub url: String,
|
|
||||||
pub all_size: Option<u64>,
|
|
||||||
pub curr_size: Option<u64>,
|
|
||||||
pub homepage: Option<String>,
|
|
||||||
pub save_path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscriptions::Entity",
|
|
||||||
from = "Column::SubscriptionId",
|
|
||||||
to = "super::subscriptions::Column::Id"
|
|
||||||
)]
|
|
||||||
Subscription,
|
|
||||||
#[sea_orm(has_many = "super::episodes::Entity")]
|
|
||||||
Episode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscriptions::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscription.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::episodes::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Episode.def()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
|
||||||
use sea_orm::{entity::prelude::*, FromJsonQueryResult};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult, Default)]
|
|
||||||
pub struct EpisodeExtra {
|
|
||||||
pub name_zh: Option<String>,
|
|
||||||
pub s_name_zh: Option<String>,
|
|
||||||
pub name_en: Option<String>,
|
|
||||||
pub s_name_en: Option<String>,
|
|
||||||
pub name_jp: Option<String>,
|
|
||||||
pub s_name_jp: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
|
||||||
#[sea_orm(table_name = "episodes")]
|
|
||||||
pub struct Model {
|
|
||||||
pub created_at: DateTime,
|
|
||||||
pub updated_at: DateTime,
|
|
||||||
#[sea_orm(primary_key)]
|
|
||||||
pub id: i32,
|
|
||||||
#[sea_orm(indexed)]
|
|
||||||
pub mikan_episode_id: Option<String>,
|
|
||||||
pub raw_name: String,
|
|
||||||
pub display_name: String,
|
|
||||||
pub bangumi_id: i32,
|
|
||||||
pub subscription_id: i32,
|
|
||||||
pub subscriber_id: i32,
|
|
||||||
pub download_id: Option<i32>,
|
|
||||||
pub save_path: Option<String>,
|
|
||||||
pub resolution: Option<String>,
|
|
||||||
pub season: i32,
|
|
||||||
pub season_raw: Option<String>,
|
|
||||||
pub fansub: Option<String>,
|
|
||||||
pub poster_link: Option<String>,
|
|
||||||
pub episode_index: i32,
|
|
||||||
pub homepage: Option<String>,
|
|
||||||
pub subtitle: Option<Vec<String>>,
|
|
||||||
#[sea_orm(default = "false")]
|
|
||||||
pub deleted: bool,
|
|
||||||
pub source: Option<String>,
|
|
||||||
pub extra: EpisodeExtra,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::bangumi::Entity",
|
|
||||||
from = "Column::BangumiId",
|
|
||||||
to = "super::bangumi::Column::Id"
|
|
||||||
)]
|
|
||||||
Bangumi,
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::downloads::Entity",
|
|
||||||
from = "Column::DownloadId",
|
|
||||||
to = "super::downloads::Column::Id"
|
|
||||||
)]
|
|
||||||
Downloads,
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscriptions::Entity",
|
|
||||||
from = "Column::SubscriptionId",
|
|
||||||
to = "super::subscriptions::Column::Id"
|
|
||||||
)]
|
|
||||||
Subscriptions,
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscribers::Entity",
|
|
||||||
from = "Column::SubscriberId",
|
|
||||||
to = "super::subscribers::Column::Id"
|
|
||||||
)]
|
|
||||||
Subscriber,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::bangumi::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Bangumi.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::downloads::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Downloads.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscriptions::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscriptions.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscribers::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscriber.def()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
|
||||||
pub mod auth;
|
|
||||||
pub mod bangumi;
|
|
||||||
pub mod downloaders;
|
|
||||||
pub mod downloads;
|
|
||||||
pub mod episodes;
|
|
||||||
pub mod subscribers;
|
|
||||||
pub mod subscriptions;
|
|
@ -1,71 +0,0 @@
|
|||||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
|
|
||||||
|
|
||||||
use sea_orm::{entity::prelude::*, FromJsonQueryResult};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
|
|
||||||
pub struct SubscriberBangumiConfig {
|
|
||||||
pub leading_group_tag: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
|
||||||
#[sea_orm(table_name = "subscribers")]
|
|
||||||
pub struct Model {
|
|
||||||
pub created_at: DateTime,
|
|
||||||
pub updated_at: DateTime,
|
|
||||||
#[sea_orm(primary_key)]
|
|
||||||
pub id: i32,
|
|
||||||
#[sea_orm(unique)]
|
|
||||||
pub pid: String,
|
|
||||||
pub display_name: String,
|
|
||||||
pub downloader_id: Option<i32>,
|
|
||||||
pub bangumi_conf: Option<SubscriberBangumiConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {
|
|
||||||
#[sea_orm(has_many = "super::subscriptions::Entity")]
|
|
||||||
Subscription,
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::downloaders::Entity",
|
|
||||||
from = "Column::DownloaderId",
|
|
||||||
to = "super::downloaders::Column::Id"
|
|
||||||
)]
|
|
||||||
Downloader,
|
|
||||||
#[sea_orm(has_many = "super::bangumi::Entity")]
|
|
||||||
Bangumi,
|
|
||||||
#[sea_orm(has_many = "super::episodes::Entity")]
|
|
||||||
Episode,
|
|
||||||
#[sea_orm(has_many = "super::auth::Entity")]
|
|
||||||
Auth,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscriptions::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscription.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::downloaders::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Downloader.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::bangumi::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Bangumi.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::episodes::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Episode.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::auth::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Auth.def()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize, DeriveDisplay,
|
|
||||||
)]
|
|
||||||
#[sea_orm(
|
|
||||||
rs_type = "String",
|
|
||||||
db_type = "Enum",
|
|
||||||
enum_name = "subscription_category"
|
|
||||||
)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum SubscriptionCategory {
|
|
||||||
#[sea_orm(string_value = "mikan")]
|
|
||||||
Mikan,
|
|
||||||
#[sea_orm(string_value = "manual")]
|
|
||||||
Manual,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
|
||||||
#[sea_orm(table_name = "subscriptions")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(column_type = "Timestamp")]
|
|
||||||
pub created_at: DateTime,
|
|
||||||
#[sea_orm(column_type = "Timestamp")]
|
|
||||||
pub updated_at: DateTime,
|
|
||||||
#[sea_orm(primary_key)]
|
|
||||||
pub id: i32,
|
|
||||||
pub display_name: String,
|
|
||||||
pub subscriber_id: i32,
|
|
||||||
pub category: SubscriptionCategory,
|
|
||||||
pub source_url: String,
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
|
||||||
pub enum Relation {
|
|
||||||
#[sea_orm(
|
|
||||||
belongs_to = "super::subscribers::Entity",
|
|
||||||
from = "Column::SubscriberId",
|
|
||||||
to = "super::subscribers::Column::Id"
|
|
||||||
)]
|
|
||||||
Subscriber,
|
|
||||||
#[sea_orm(has_many = "super::bangumi::Entity")]
|
|
||||||
Bangumi,
|
|
||||||
#[sea_orm(has_many = "super::episodes::Entity")]
|
|
||||||
Episodes,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscribers::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscriber.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::bangumi::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Bangumi.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::episodes::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Episodes.def()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use loco_rs::app::AppContext;
|
use loco_rs::app::AppContext;
|
||||||
use sea_orm::{entity::prelude::*, sea_query::OnConflict, ActiveValue, FromJsonQueryResult};
|
use sea_orm::{entity::prelude::*, sea_query::OnConflict, ActiveValue, FromJsonQueryResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -70,9 +71,11 @@ pub enum Relation {
|
|||||||
)]
|
)]
|
||||||
Bangumi,
|
Bangumi,
|
||||||
#[sea_orm(has_many = "super::subscriptions::Entity")]
|
#[sea_orm(has_many = "super::subscriptions::Entity")]
|
||||||
Subscriptions,
|
Subscription,
|
||||||
#[sea_orm(has_one = "super::downloads::Entity")]
|
#[sea_orm(has_one = "super::downloads::Entity")]
|
||||||
Downloads,
|
Download,
|
||||||
|
#[sea_orm(has_many = "super::subscription_episode::Entity")]
|
||||||
|
SubscriptionEpisode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Related<super::bangumi::Entity> for Entity {
|
impl Related<super::bangumi::Entity> for Entity {
|
||||||
@ -83,13 +86,7 @@ impl Related<super::bangumi::Entity> for Entity {
|
|||||||
|
|
||||||
impl Related<super::downloads::Entity> for Entity {
|
impl Related<super::downloads::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::Downloads.def()
|
Relation::Download.def()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::subscriptions::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::Subscriptions.def()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +96,36 @@ impl Related<super::subscribers::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::subscription_episode::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SubscriptionEpisode.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::subscriptions::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
super::subscription_episode::Relation::Subscription.def()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn via() -> Option<RelationDef> {
|
||||||
|
Some(Relation::Subscription.def())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscribers::Entity")]
|
||||||
|
Subscriber,
|
||||||
|
#[sea_orm(entity = "super::downloads::Entity")]
|
||||||
|
Subscription,
|
||||||
|
#[sea_orm(entity = "super::bangumi::Entity")]
|
||||||
|
Bangumi,
|
||||||
|
#[sea_orm(entity = "super::subscriptions::Entity")]
|
||||||
|
Download,
|
||||||
|
#[sea_orm(entity = "super::subscription_episode::Entity")]
|
||||||
|
SubscriptionEpisode,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct MikanEpsiodeCreation {
|
pub struct MikanEpsiodeCreation {
|
||||||
pub episode: MikanEpisodeMeta,
|
pub episode: MikanEpisodeMeta,
|
||||||
@ -206,5 +233,5 @@ impl ActiveModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
@ -3,7 +3,6 @@ pub mod bangumi;
|
|||||||
pub mod downloaders;
|
pub mod downloaders;
|
||||||
pub mod downloads;
|
pub mod downloads;
|
||||||
pub mod episodes;
|
pub mod episodes;
|
||||||
pub mod prelude;
|
|
||||||
pub mod query;
|
pub mod query;
|
||||||
pub mod subscribers;
|
pub mod subscribers;
|
||||||
pub mod subscription_bangumi;
|
pub mod subscription_bangumi;
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct Notification {
|
|
||||||
season: i32,
|
|
||||||
episode_size: u32,
|
|
||||||
poster_url: Option<String>,
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
pub use super::{
|
|
||||||
bangumi::{self, Entity as Bangumi},
|
|
||||||
downloaders::{self, DownloaderCategory, Entity as Downloader},
|
|
||||||
downloads::{self, DownloadMime, DownloadStatus, Entity as Download},
|
|
||||||
episodes::{self, Entity as Episode},
|
|
||||||
subscribers::{self, Entity as Subscriber},
|
|
||||||
subscriptions::{self, Entity as Subscription, SubscriptionCategory},
|
|
||||||
};
|
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
prelude::Expr,
|
prelude::Expr,
|
||||||
sea_query::{Alias, IntoColumnRef, IntoTableRef, Query, SelectStatement},
|
sea_query::{Alias, IntoColumnRef, IntoTableRef, Query, SelectStatement},
|
||||||
@ -26,7 +27,7 @@ pub fn filter_values_in<
|
|||||||
.to_owned()
|
.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
pub trait InsertManyReturningExt<A>: Sized
|
pub trait InsertManyReturningExt<A>: Sized
|
||||||
where
|
where
|
||||||
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
|
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
|
||||||
@ -49,7 +50,7 @@ where
|
|||||||
I: IntoIterator<Item = <A::Entity as EntityTrait>::Column> + Send;
|
I: IntoIterator<Item = <A::Entity as EntityTrait>::Column> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl<A> InsertManyReturningExt<A> for Insert<A>
|
impl<A> InsertManyReturningExt<A> for Insert<A>
|
||||||
where
|
where
|
||||||
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
|
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use loco_rs::{
|
use loco_rs::{
|
||||||
app::AppContext,
|
app::AppContext,
|
||||||
model::{ModelError, ModelResult},
|
model::{ModelError, ModelResult},
|
||||||
@ -69,12 +70,26 @@ impl Related<super::auth::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscriptions::Entity")]
|
||||||
|
Subscription,
|
||||||
|
#[sea_orm(entity = "super::downloaders::Entity")]
|
||||||
|
Downloader,
|
||||||
|
#[sea_orm(entity = "super::bangumi::Entity")]
|
||||||
|
Bangumi,
|
||||||
|
#[sea_orm(entity = "super::episodes::Entity")]
|
||||||
|
Episode,
|
||||||
|
#[sea_orm(entity = "super::auth::Entity")]
|
||||||
|
Auth,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct SubscriberIdParams {
|
pub struct SubscriberIdParams {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {
|
impl ActiveModelBehavior for ActiveModel {
|
||||||
async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
|
async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
|
||||||
where
|
where
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::{entity::prelude::*, ActiveValue};
|
use sea_orm::{entity::prelude::*, ActiveValue};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -42,7 +43,15 @@ impl Related<super::bangumi::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscriptions::Entity")]
|
||||||
|
Subscription,
|
||||||
|
#[sea_orm(entity = "super::bangumi::Entity")]
|
||||||
|
Bangumi,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl ActiveModel {
|
impl ActiveModel {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::{entity::prelude::*, ActiveValue};
|
use sea_orm::{entity::prelude::*, ActiveValue};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -42,7 +43,15 @@ impl Related<super::episodes::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscriptions::Entity")]
|
||||||
|
Subscription,
|
||||||
|
#[sea_orm(entity = "super::episodes::Entity")]
|
||||||
|
Episode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl ActiveModel {
|
impl ActiveModel {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::HashSet, sync::Arc};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use loco_rs::app::AppContext;
|
use loco_rs::app::AppContext;
|
||||||
use sea_orm::{entity::prelude::*, ActiveValue};
|
use sea_orm::{entity::prelude::*, ActiveValue};
|
||||||
@ -81,6 +82,18 @@ impl Related<super::subscribers::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::subscription_bangumi::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SubscriptionBangumi.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::subscription_episode::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SubscriptionEpisode.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Related<super::bangumi::Entity> for Entity {
|
impl Related<super::bangumi::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
super::subscription_bangumi::Relation::Bangumi.def()
|
super::subscription_bangumi::Relation::Bangumi.def()
|
||||||
@ -109,6 +122,20 @@ impl Related<super::episodes::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
|
||||||
|
pub enum RelatedEntity {
|
||||||
|
#[sea_orm(entity = "super::subscribers::Entity")]
|
||||||
|
Subscriber,
|
||||||
|
#[sea_orm(entity = "super::bangumi::Entity")]
|
||||||
|
Bangumi,
|
||||||
|
#[sea_orm(entity = "super::episodes::Entity")]
|
||||||
|
Episode,
|
||||||
|
#[sea_orm(entity = "super::subscription_episode::Entity")]
|
||||||
|
SubscriptionEpisode,
|
||||||
|
#[sea_orm(entity = "super::subscription_bangumi::Entity")]
|
||||||
|
SubscriptionBangumi,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct SubscriptionCreateFromRssDto {
|
pub struct SubscriptionCreateFromRssDto {
|
||||||
pub rss_link: String,
|
pub rss_link: String,
|
||||||
@ -122,7 +149,7 @@ pub enum SubscriptionCreateDto {
|
|||||||
Mikan(SubscriptionCreateFromRssDto),
|
Mikan(SubscriptionCreateFromRssDto),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl ActiveModel {
|
impl ActiveModel {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use librqbit_core::{
|
use librqbit_core::{
|
||||||
@ -238,7 +239,7 @@ impl Torrent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
pub trait TorrentDownloader {
|
pub trait TorrentDownloader {
|
||||||
async fn get_torrents_info(
|
async fn get_torrents_info(
|
||||||
&self,
|
&self,
|
||||||
|
@ -2,6 +2,7 @@ use std::{
|
|||||||
borrow::Cow, collections::HashSet, fmt::Debug, future::Future, sync::Arc, time::Duration,
|
borrow::Cow, collections::HashSet, fmt::Debug, future::Future, sync::Arc, time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use eyre::OptionExt;
|
use eyre::OptionExt;
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
pub use qbit_rs::model::{
|
pub use qbit_rs::model::{
|
||||||
@ -218,7 +219,7 @@ impl QBittorrentDownloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait]
|
||||||
impl TorrentDownloader for QBittorrentDownloader {
|
impl TorrentDownloader for QBittorrentDownloader {
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
async fn get_torrents_info(
|
async fn get_torrents_info(
|
||||||
@ -472,7 +473,6 @@ impl Debug for QBittorrentDownloader {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use testcontainers_modules::testcontainers::ImageExt;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -495,6 +495,7 @@ pub mod tests {
|
|||||||
},
|
},
|
||||||
GenericImage,
|
GenericImage,
|
||||||
};
|
};
|
||||||
|
use testcontainers_modules::testcontainers::ImageExt;
|
||||||
|
|
||||||
use crate::test_utils::testcontainers::ContainerRequestEnhancedExt;
|
use crate::test_utils::testcontainers::ContainerRequestEnhancedExt;
|
||||||
|
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
#[cfg(feature = "testcontainers")]
|
|
||||||
pub mod testcontainers {
|
|
||||||
use bollard::container::ListContainersOptions;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use testcontainers::{
|
|
||||||
core::logs::consumer::logging_consumer::LoggingConsumer, ContainerRequest, Image, ImageExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const TESTCONTAINERS_PROJECT_KEY: &str = "tech.enfw.testcontainers.project";
|
|
||||||
pub const TESTCONTAINERS_CONTAINER_KEY: &str = "tech.enfw.testcontainers.container";
|
|
||||||
pub const TESTCONTAINERS_PRUNE_KEY: &str = "tech.enfw.testcontainers.prune";
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
pub trait ContainerRequestEnhancedExt<I>: Sized + ImageExt<I>
|
|
||||||
where
|
|
||||||
I: Image,
|
|
||||||
{
|
|
||||||
async fn with_prune_existed_label(
|
|
||||||
self,
|
|
||||||
container_label: &str,
|
|
||||||
prune: bool,
|
|
||||||
force: bool,
|
|
||||||
) -> eyre::Result<Self>;
|
|
||||||
|
|
||||||
fn with_default_log_consumer(self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<I> ContainerRequestEnhancedExt<I> for ContainerRequest<I>
|
|
||||||
where
|
|
||||||
I: Image,
|
|
||||||
{
|
|
||||||
async fn with_prune_existed_label(
|
|
||||||
self,
|
|
||||||
container_label: &str,
|
|
||||||
prune: bool,
|
|
||||||
force: bool,
|
|
||||||
) -> eyre::Result<Self> {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use bollard::container::PruneContainersOptions;
|
|
||||||
use testcontainers::core::client::docker_client_instance;
|
|
||||||
|
|
||||||
if prune {
|
|
||||||
let client = docker_client_instance().await?;
|
|
||||||
|
|
||||||
let mut filters = HashMap::<String, Vec<String>>::new();
|
|
||||||
|
|
||||||
filters.insert(
|
|
||||||
String::from("label"),
|
|
||||||
vec![
|
|
||||||
format!("{TESTCONTAINERS_PRUNE_KEY}=true"),
|
|
||||||
format!("{}={}", TESTCONTAINERS_PROJECT_KEY, "konobangu"),
|
|
||||||
format!("{}={}", TESTCONTAINERS_CONTAINER_KEY, container_label),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if force {
|
|
||||||
let result = client
|
|
||||||
.list_containers(Some(ListContainersOptions {
|
|
||||||
all: false,
|
|
||||||
filters: filters.clone(),
|
|
||||||
..Default::default()
|
|
||||||
}))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let remove_containers = result
|
|
||||||
.iter()
|
|
||||||
.filter(|c| matches!(c.state.as_deref(), Some("running")))
|
|
||||||
.flat_map(|c| c.id.as_deref())
|
|
||||||
.collect_vec();
|
|
||||||
|
|
||||||
futures::future::try_join_all(
|
|
||||||
remove_containers
|
|
||||||
.iter()
|
|
||||||
.map(|c| client.stop_container(c, None)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
tracing::warn!(name = "stop running containers", result = ?remove_containers);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = client
|
|
||||||
.prune_containers(Some(PruneContainersOptions { filters }))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
tracing::warn!(name = "prune existed containers", result = ?result);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = self.with_labels([
|
|
||||||
(TESTCONTAINERS_PRUNE_KEY, "true"),
|
|
||||||
(TESTCONTAINERS_PROJECT_KEY, "konobangu"),
|
|
||||||
(TESTCONTAINERS_CONTAINER_KEY, container_label),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_default_log_consumer(self) -> Self {
|
|
||||||
self.with_log_consumer(
|
|
||||||
LoggingConsumer::new()
|
|
||||||
.with_stdout_level(log::Level::Info)
|
|
||||||
.with_stderr_level(log::Level::Error),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
2
apps/recorder/src/test_utils/mod.rs
Normal file
2
apps/recorder/src/test_utils/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#[cfg(feature = "testcontainers")]
|
||||||
|
pub mod testcontainers;
|
105
apps/recorder/src/test_utils/testcontainers.rs
Normal file
105
apps/recorder/src/test_utils/testcontainers.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use bollard::container::ListContainersOptions;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use testcontainers::{
|
||||||
|
core::logs::consumer::logging_consumer::LoggingConsumer, ContainerRequest, Image, ImageExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TESTCONTAINERS_PROJECT_KEY: &str = "tech.enfw.testcontainers.project";
|
||||||
|
pub const TESTCONTAINERS_CONTAINER_KEY: &str = "tech.enfw.testcontainers.container";
|
||||||
|
pub const TESTCONTAINERS_PRUNE_KEY: &str = "tech.enfw.testcontainers.prune";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ContainerRequestEnhancedExt<I>: Sized + ImageExt<I>
|
||||||
|
where
|
||||||
|
I: Image,
|
||||||
|
{
|
||||||
|
async fn with_prune_existed_label(
|
||||||
|
self,
|
||||||
|
container_label: &str,
|
||||||
|
prune: bool,
|
||||||
|
force: bool,
|
||||||
|
) -> eyre::Result<Self>;
|
||||||
|
|
||||||
|
fn with_default_log_consumer(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<I> ContainerRequestEnhancedExt<I> for ContainerRequest<I>
|
||||||
|
where
|
||||||
|
I: Image,
|
||||||
|
{
|
||||||
|
async fn with_prune_existed_label(
|
||||||
|
self,
|
||||||
|
container_label: &str,
|
||||||
|
prune: bool,
|
||||||
|
force: bool,
|
||||||
|
) -> eyre::Result<Self> {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use bollard::container::PruneContainersOptions;
|
||||||
|
use testcontainers::core::client::docker_client_instance;
|
||||||
|
|
||||||
|
if prune {
|
||||||
|
let client = docker_client_instance().await?;
|
||||||
|
|
||||||
|
let mut filters = HashMap::<String, Vec<String>>::new();
|
||||||
|
|
||||||
|
filters.insert(
|
||||||
|
String::from("label"),
|
||||||
|
vec![
|
||||||
|
format!("{TESTCONTAINERS_PRUNE_KEY}=true"),
|
||||||
|
format!("{}={}", TESTCONTAINERS_PROJECT_KEY, "konobangu"),
|
||||||
|
format!("{}={}", TESTCONTAINERS_CONTAINER_KEY, container_label),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if force {
|
||||||
|
let result = client
|
||||||
|
.list_containers(Some(ListContainersOptions {
|
||||||
|
all: false,
|
||||||
|
filters: filters.clone(),
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let remove_containers = result
|
||||||
|
.iter()
|
||||||
|
.filter(|c| matches!(c.state.as_deref(), Some("running")))
|
||||||
|
.flat_map(|c| c.id.as_deref())
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
futures::future::try_join_all(
|
||||||
|
remove_containers
|
||||||
|
.iter()
|
||||||
|
.map(|c| client.stop_container(c, None)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::warn!(name = "stop running containers", result = ?remove_containers);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = client
|
||||||
|
.prune_containers(Some(PruneContainersOptions { filters }))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::warn!(name = "prune existed containers", result = ?result);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = self.with_labels([
|
||||||
|
(TESTCONTAINERS_PRUNE_KEY, "true"),
|
||||||
|
(TESTCONTAINERS_PROJECT_KEY, "konobangu"),
|
||||||
|
(TESTCONTAINERS_CONTAINER_KEY, container_label),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_default_log_consumer(self) -> Self {
|
||||||
|
self.with_log_consumer(
|
||||||
|
LoggingConsumer::new()
|
||||||
|
.with_stdout_level(log::Level::Info)
|
||||||
|
.with_stderr_level(log::Level::Error),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
3487
apps/recorder/user_agents
Normal file
3487
apps/recorder/user_agents
Normal file
File diff suppressed because it is too large
Load Diff
@ -117,14 +117,13 @@ settings:
|
|||||||
data_dir: ./data
|
data_dir: ./data
|
||||||
|
|
||||||
mikan:
|
mikan:
|
||||||
|
base_url: "https://mikanani.me/"
|
||||||
http_client:
|
http_client:
|
||||||
exponential_backoff_max_retries: 3
|
exponential_backoff_max_retries: 3
|
||||||
leaky_bucket_max_tokens: 2
|
leaky_bucket_max_tokens: 2
|
||||||
leaky_bucket_initial_tokens: 0
|
leaky_bucket_initial_tokens: 0
|
||||||
leaky_bucket_refill_tokens: 1
|
leaky_bucket_refill_tokens: 1
|
||||||
leaky_bucket_refill_interval: 500
|
leaky_bucket_refill_interval: 500
|
||||||
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"
|
|
||||||
base_url: "https://mikanani.me/"
|
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
auth_type: "oidc" # or "basic"
|
auth_type: "oidc" # or "basic"
|
||||||
|
@ -30,13 +30,14 @@
|
|||||||
"commander": "^12.1.0"
|
"commander": "^12.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"rimraf": "^6.0.1",
|
|
||||||
"shx": "^0.3.4",
|
|
||||||
"@auto-it/all-contributors": "^11.3.0",
|
"@auto-it/all-contributors": "^11.3.0",
|
||||||
"@auto-it/first-time-contributor": "^11.3.0",
|
"@auto-it/first-time-contributor": "^11.3.0",
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@konobangu/typescript-config": "workspace:*",
|
"@konobangu/typescript-config": "workspace:*",
|
||||||
"@turbo/gen": "^2.3.3",
|
"@turbo/gen": "^2.3.3",
|
||||||
|
"@types/jsdom": "^21.1.7",
|
||||||
|
"rimraf": "^6.0.1",
|
||||||
|
"shx": "^0.3.4",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"turbo": "^2.3.3",
|
"turbo": "^2.3.3",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
|
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@ -30,6 +30,9 @@ importers:
|
|||||||
'@turbo/gen':
|
'@turbo/gen':
|
||||||
specifier: ^2.3.3
|
specifier: ^2.3.3
|
||||||
version: 2.3.3(@types/node@22.10.1)(typescript@5.7.2)
|
version: 2.3.3(@types/node@22.10.1)(typescript@5.7.2)
|
||||||
|
'@types/jsdom':
|
||||||
|
specifier: ^21.1.7
|
||||||
|
version: 21.1.7
|
||||||
rimraf:
|
rimraf:
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1
|
version: 6.0.1
|
||||||
@ -4605,6 +4608,9 @@ packages:
|
|||||||
'@types/inquirer@6.5.0':
|
'@types/inquirer@6.5.0':
|
||||||
resolution: {integrity: sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==}
|
resolution: {integrity: sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==}
|
||||||
|
|
||||||
|
'@types/jsdom@21.1.7':
|
||||||
|
resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@ -4676,6 +4682,9 @@ packages:
|
|||||||
'@types/tinycolor2@1.4.6':
|
'@types/tinycolor2@1.4.6':
|
||||||
resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==}
|
resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==}
|
||||||
|
|
||||||
|
'@types/tough-cookie@4.0.5':
|
||||||
|
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
|
||||||
|
|
||||||
'@types/unist@2.0.11':
|
'@types/unist@2.0.11':
|
||||||
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
||||||
|
|
||||||
@ -14170,6 +14179,12 @@ snapshots:
|
|||||||
'@types/through': 0.0.33
|
'@types/through': 0.0.33
|
||||||
rxjs: 6.6.7
|
rxjs: 6.6.7
|
||||||
|
|
||||||
|
'@types/jsdom@21.1.7':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.10.1
|
||||||
|
'@types/tough-cookie': 4.0.5
|
||||||
|
parse5: 7.2.1
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/lodash.merge@4.6.9':
|
'@types/lodash.merge@4.6.9':
|
||||||
@ -14244,6 +14259,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/tinycolor2@1.4.6': {}
|
'@types/tinycolor2@1.4.6': {}
|
||||||
|
|
||||||
|
'@types/tough-cookie@4.0.5': {}
|
||||||
|
|
||||||
'@types/unist@2.0.11': {}
|
'@types/unist@2.0.11': {}
|
||||||
|
|
||||||
'@types/unist@3.0.3': {}
|
'@types/unist@3.0.3': {}
|
||||||
|
Loading…
Reference in New Issue
Block a user