mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-09-05 23:49:53 +08:00
feat: 加入带有详细类型注释的 swagger 文档 (#257)
This commit is contained in:
Generated
+128
-17
@@ -121,6 +121,15 @@ dependencies = [
|
|||||||
"backtrace",
|
"backtrace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.7.1"
|
version = "1.7.1"
|
||||||
@@ -486,9 +495,10 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"utoipa",
|
||||||
|
"utoipa-swagger-ui",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -585,9 +595,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.15.4"
|
version = "3.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecheck"
|
name = "bytecheck"
|
||||||
@@ -874,6 +884,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_arbitrary"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.96",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder"
|
name = "derive_builder"
|
||||||
version = "0.20.2"
|
version = "0.20.2"
|
||||||
@@ -938,6 +959,17 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "displaydoc"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.96",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenvy"
|
||||||
version = "0.15.7"
|
version = "0.15.7"
|
||||||
@@ -1555,6 +1587,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.2",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1676,6 +1709,12 @@ dependencies = [
|
|||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lockfree-object-pool"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.22"
|
version = "0.4.22"
|
||||||
@@ -2905,6 +2944,12 @@ dependencies = [
|
|||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simdutf8"
|
name = "simdutf8"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -3502,20 +3547,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-http"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.5.0",
|
|
||||||
"bytes",
|
|
||||||
"http",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -3659,6 +3690,55 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa"
|
||||||
|
version = "5.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"utoipa-gen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-gen"
|
||||||
|
version = "5.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn 2.0.96",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-swagger-ui"
|
||||||
|
version = "9.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "161166ec520c50144922a625d8bc4925cc801b2dda958ab69878527c0e5c5d61"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"base64",
|
||||||
|
"mime_guess",
|
||||||
|
"regex",
|
||||||
|
"rust-embed",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
"utoipa",
|
||||||
|
"utoipa-swagger-ui-vendored",
|
||||||
|
"zip",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-swagger-ui-vendored"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -4087,3 +4167,34 @@ name = "zeroize"
|
|||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd56a4d5921bc2f99947ac5b3abe5f510b1be7376fdc5e9fce4a23c6a93e87c"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"crc32fast",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"displaydoc",
|
||||||
|
"flate2",
|
||||||
|
"indexmap",
|
||||||
|
"memchr",
|
||||||
|
"thiserror 1.0.63",
|
||||||
|
"zopfli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zopfli"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"crc32fast",
|
||||||
|
"lockfree-object-pool",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|||||||
+2
-1
@@ -67,9 +67,10 @@ tokio = { version = "1.43.0", features = ["full"] }
|
|||||||
tokio-util = { version = "0.7.13", features = ["io", "rt"] }
|
tokio-util = { version = "0.7.13", features = ["io", "rt"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
tower = "0.5.2"
|
tower = "0.5.2"
|
||||||
tower-http = { version = "0.6.2", features = ["normalize-path"] }
|
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["chrono"] }
|
tracing-subscriber = { version = "0.3.19", features = ["chrono"] }
|
||||||
|
utoipa = { version = "5", features = ["axum_extras"] }
|
||||||
|
utoipa-swagger-ui = { version = "9.0.0", features = ["axum", "vendored"] }
|
||||||
|
|
||||||
[workspace.metadata.release]
|
[workspace.metadata.release]
|
||||||
release = false
|
release = false
|
||||||
|
|||||||
@@ -47,9 +47,10 @@ tokio = { workspace = true }
|
|||||||
tokio-util = { workspace = true }
|
tokio-util = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tower = { workspace = true }
|
tower = { workspace = true }
|
||||||
tower-http = { workspace = true }
|
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
|
utoipa = { workspace = true }
|
||||||
|
utoipa-swagger-ui = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_matches = { workspace = true }
|
assert_matches = { workspace = true }
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ use axum::http::HeaderMap;
|
|||||||
use axum::middleware::Next;
|
use axum::middleware::Next;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
|
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
|
||||||
|
use utoipa::Modify;
|
||||||
|
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
|
|
||||||
pub async fn auth(headers: HeaderMap, request: Request, next: Next) -> Result<Response, StatusCode> {
|
pub async fn auth(headers: HeaderMap, request: Request, next: Next) -> Result<Response, StatusCode> {
|
||||||
if request.uri().path().starts_with("/api") && get_token(&headers) != CONFIG.auth_token {
|
if request.uri().path().starts_with("/api/") && get_token(&headers) != CONFIG.auth_token {
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
return Err(StatusCode::UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
Ok(next.run(request).await)
|
Ok(next.run(request).await)
|
||||||
@@ -19,3 +21,19 @@ fn get_token(headers: &HeaderMap) -> Option<String> {
|
|||||||
.and_then(|v| v.to_str().ok())
|
.and_then(|v| v.to_str().ok())
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct OpenAPIAuth;
|
||||||
|
|
||||||
|
impl Modify for OpenAPIAuth {
|
||||||
|
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||||
|
if let Some(schema) = openapi.components.as_mut() {
|
||||||
|
schema.add_security_scheme(
|
||||||
|
"Token",
|
||||||
|
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
|
||||||
|
"Authorization",
|
||||||
|
"与配置文件中的 auth_token 相同",
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
@@ -7,71 +6,100 @@ use axum::Json;
|
|||||||
use bili_sync_entity::*;
|
use bili_sync_entity::*;
|
||||||
use bili_sync_migration::Expr;
|
use bili_sync_migration::Expr;
|
||||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
|
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
use crate::api::auth::OpenAPIAuth;
|
||||||
use crate::api::error::ApiError;
|
use crate::api::error::ApiError;
|
||||||
use crate::api::payload::{PageInfo, VideoDetail, VideoInfo, VideoList, VideoListModel, VideoListModelItem};
|
use crate::api::request::VideosRequest;
|
||||||
|
use crate::api::response::{PageInfo, VideoInfo, VideoResponse, VideoSource, VideoSourcesResponse, VideosResponse};
|
||||||
|
|
||||||
/// 列出所有视频列表
|
#[derive(OpenApi)]
|
||||||
pub async fn get_video_list_models(
|
#[openapi(
|
||||||
|
paths(get_video_sources, get_videos, get_video),
|
||||||
|
modifiers(&OpenAPIAuth),
|
||||||
|
security(
|
||||||
|
("Token" = []),
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub struct ApiDoc;
|
||||||
|
|
||||||
|
/// 列出所有视频来源
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/video-sources",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = VideoSourcesResponse),
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn get_video_sources(
|
||||||
Extension(db): Extension<Arc<DatabaseConnection>>,
|
Extension(db): Extension<Arc<DatabaseConnection>>,
|
||||||
) -> Result<Json<VideoListModel>, ApiError> {
|
) -> Result<Json<VideoSourcesResponse>, ApiError> {
|
||||||
Ok(Json(VideoListModel {
|
Ok(Json(VideoSourcesResponse {
|
||||||
collection: collection::Entity::find()
|
collection: collection::Entity::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
.columns([collection::Column::Id, collection::Column::Name])
|
.columns([collection::Column::Id, collection::Column::Name])
|
||||||
.into_model::<VideoListModelItem>()
|
.into_model::<VideoSource>()
|
||||||
.all(db.as_ref())
|
.all(db.as_ref())
|
||||||
.await?,
|
.await?,
|
||||||
favorite: favorite::Entity::find()
|
favorite: favorite::Entity::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
.columns([favorite::Column::Id, favorite::Column::Name])
|
.columns([favorite::Column::Id, favorite::Column::Name])
|
||||||
.into_model::<VideoListModelItem>()
|
.into_model::<VideoSource>()
|
||||||
.all(db.as_ref())
|
.all(db.as_ref())
|
||||||
.await?,
|
.await?,
|
||||||
submission: submission::Entity::find()
|
submission: submission::Entity::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
.column(submission::Column::Id)
|
.column(submission::Column::Id)
|
||||||
.column_as(submission::Column::UpperName, "name")
|
.column_as(submission::Column::UpperName, "name")
|
||||||
.into_model::<VideoListModelItem>()
|
.into_model::<VideoSource>()
|
||||||
.all(db.as_ref())
|
.all(db.as_ref())
|
||||||
.await?,
|
.await?,
|
||||||
watch_later: watch_later::Entity::find()
|
watch_later: watch_later::Entity::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
.column(watch_later::Column::Id)
|
.column(watch_later::Column::Id)
|
||||||
.column_as(Expr::value("稍后再看"), "name")
|
.column_as(Expr::value("稍后再看"), "name")
|
||||||
.into_model::<VideoListModelItem>()
|
.into_model::<VideoSource>()
|
||||||
.all(db.as_ref())
|
.all(db.as_ref())
|
||||||
.await?,
|
.await?,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 列出所有视频的基本信息(支持根据视频列表筛选,支持分页)
|
/// 列出视频的基本信息,支持根据视频来源筛选、名称查找和分页
|
||||||
pub async fn list_videos(
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/videos",
|
||||||
|
params(
|
||||||
|
VideosRequest,
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, body = VideosResponse),
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn get_videos(
|
||||||
Extension(db): Extension<Arc<DatabaseConnection>>,
|
Extension(db): Extension<Arc<DatabaseConnection>>,
|
||||||
Query(params): Query<HashMap<String, String>>,
|
Query(params): Query<VideosRequest>,
|
||||||
) -> Result<Json<VideoList>, ApiError> {
|
) -> Result<Json<VideosResponse>, ApiError> {
|
||||||
let mut query = video::Entity::find();
|
let mut query = video::Entity::find();
|
||||||
for (query_key, filter_column) in [
|
for (field, column) in [
|
||||||
("collection", video::Column::CollectionId),
|
(params.collection, video::Column::CollectionId),
|
||||||
("favorite", video::Column::FavoriteId),
|
(params.favorite, video::Column::FavoriteId),
|
||||||
("submission", video::Column::SubmissionId),
|
(params.submission, video::Column::SubmissionId),
|
||||||
("watch_later", video::Column::WatchLaterId),
|
(params.watch_later, video::Column::WatchLaterId),
|
||||||
] {
|
] {
|
||||||
if let Some(value) = params.get(query_key) {
|
if let Some(id) = field {
|
||||||
query = query.filter(filter_column.eq(value));
|
query = query.filter(column.eq(id));
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(query_word) = params.get("q") {
|
if let Some(query_word) = params.query {
|
||||||
query = query.filter(video::Column::Name.contains(query_word));
|
query = query.filter(video::Column::Name.contains(query_word));
|
||||||
}
|
}
|
||||||
let total_count = query.clone().count(db.as_ref()).await?;
|
let total_count = query.clone().count(db.as_ref()).await?;
|
||||||
let (page, page_size) = if let (Some(page), Some(page_size)) = (params.get("page"), params.get("page_size")) {
|
let (page, page_size) = if let (Some(page), Some(page_size)) = (params.page, params.page_size) {
|
||||||
(page.parse::<u64>()?, page_size.parse::<u64>()?)
|
(page, page_size)
|
||||||
} else {
|
} else {
|
||||||
(1, 10)
|
(1, 10)
|
||||||
};
|
};
|
||||||
Ok(Json(VideoList {
|
Ok(Json(VideosResponse {
|
||||||
videos: query
|
videos: query
|
||||||
.order_by_desc(video::Column::Id)
|
.order_by_desc(video::Column::Id)
|
||||||
.into_partial_model::<VideoInfo>()
|
.into_partial_model::<VideoInfo>()
|
||||||
@@ -82,11 +110,18 @@ pub async fn list_videos(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据 id 获取视频详细信息,包括关联的所有 page
|
/// 获取视频详细信息,包括关联的所有 page
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/videos/{id}",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = VideoResponse),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn get_video(
|
pub async fn get_video(
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
Extension(db): Extension<Arc<DatabaseConnection>>,
|
Extension(db): Extension<Arc<DatabaseConnection>>,
|
||||||
) -> Result<Json<VideoDetail>, ApiError> {
|
) -> Result<Json<VideoResponse>, ApiError> {
|
||||||
let video_info = video::Entity::find_by_id(id)
|
let video_info = video::Entity::find_by_id(id)
|
||||||
.into_partial_model::<VideoInfo>()
|
.into_partial_model::<VideoInfo>()
|
||||||
.one(db.as_ref())
|
.one(db.as_ref())
|
||||||
@@ -100,7 +135,7 @@ pub async fn get_video(
|
|||||||
.into_partial_model::<PageInfo>()
|
.into_partial_model::<PageInfo>()
|
||||||
.all(db.as_ref())
|
.all(db.as_ref())
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Json(VideoDetail {
|
Ok(Json(VideoResponse {
|
||||||
video: video_info,
|
video: video_info,
|
||||||
pages,
|
pages,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod payload;
|
|
||||||
|
mod request;
|
||||||
|
mod response;
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
use bili_sync_entity::*;
|
|
||||||
use sea_orm::{DerivePartialModel, FromQueryResult};
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(FromQueryResult, Serialize)]
|
|
||||||
pub struct VideoListModelItem {
|
|
||||||
id: i32,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct VideoListModel {
|
|
||||||
pub collection: Vec<VideoListModelItem>,
|
|
||||||
pub favorite: Vec<VideoListModelItem>,
|
|
||||||
pub submission: Vec<VideoListModelItem>,
|
|
||||||
pub watch_later: Vec<VideoListModelItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(DerivePartialModel, FromQueryResult, Serialize)]
|
|
||||||
#[sea_orm(entity = "video::Entity")]
|
|
||||||
pub struct VideoInfo {
|
|
||||||
id: i32,
|
|
||||||
name: String,
|
|
||||||
upper_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct VideoList {
|
|
||||||
pub videos: Vec<VideoInfo>,
|
|
||||||
pub total_count: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(DerivePartialModel, FromQueryResult, Serialize)]
|
|
||||||
#[sea_orm(entity = "page::Entity")]
|
|
||||||
pub struct PageInfo {
|
|
||||||
id: i32,
|
|
||||||
pid: i32,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct VideoDetail {
|
|
||||||
pub video: VideoInfo,
|
|
||||||
pub pages: Vec<PageInfo>,
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use utoipa::IntoParams;
|
||||||
|
|
||||||
|
#[derive(Deserialize, IntoParams)]
|
||||||
|
pub struct VideosRequest {
|
||||||
|
pub collection: Option<i32>,
|
||||||
|
pub favorite: Option<i32>,
|
||||||
|
pub submission: Option<i32>,
|
||||||
|
pub watch_later: Option<i32>,
|
||||||
|
pub query: Option<String>,
|
||||||
|
pub page: Option<u64>,
|
||||||
|
pub page_size: Option<u64>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use bili_sync_entity::*;
|
||||||
|
use sea_orm::{DerivePartialModel, FromQueryResult};
|
||||||
|
use serde::Serialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
#[derive(Serialize, ToSchema)]
|
||||||
|
pub struct VideoSourcesResponse {
|
||||||
|
pub collection: Vec<VideoSource>,
|
||||||
|
pub favorite: Vec<VideoSource>,
|
||||||
|
pub submission: Vec<VideoSource>,
|
||||||
|
pub watch_later: Vec<VideoSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, ToSchema)]
|
||||||
|
pub struct VideosResponse {
|
||||||
|
pub videos: Vec<VideoInfo>,
|
||||||
|
pub total_count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, ToSchema)]
|
||||||
|
pub struct VideoResponse {
|
||||||
|
pub video: VideoInfo,
|
||||||
|
pub pages: Vec<PageInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromQueryResult, Serialize, ToSchema)]
|
||||||
|
pub struct VideoSource {
|
||||||
|
id: i32,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DerivePartialModel, FromQueryResult, Serialize, ToSchema)]
|
||||||
|
#[sea_orm(entity = "page::Entity")]
|
||||||
|
pub struct PageInfo {
|
||||||
|
id: i32,
|
||||||
|
pid: i32,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DerivePartialModel, FromQueryResult, Serialize, ToSchema)]
|
||||||
|
#[sea_orm(entity = "video::Entity")]
|
||||||
|
pub struct VideoInfo {
|
||||||
|
id: i32,
|
||||||
|
name: String,
|
||||||
|
upper_name: String,
|
||||||
|
}
|
||||||
@@ -9,11 +9,11 @@ use axum::{middleware, Extension, Router, ServiceExt};
|
|||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use rust_embed::Embed;
|
use rust_embed::Embed;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use tower::Layer;
|
use utoipa::OpenApi;
|
||||||
use tower_http::normalize_path::NormalizePathLayer;
|
use utoipa_swagger_ui::{Config, SwaggerUi};
|
||||||
|
|
||||||
use crate::api::auth;
|
use crate::api::auth;
|
||||||
use crate::api::handler::{get_video, get_video_list_models, list_videos};
|
use crate::api::handler::{get_video, get_video_sources, get_videos, ApiDoc};
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
|
|
||||||
#[derive(Embed)]
|
#[derive(Embed)]
|
||||||
@@ -22,13 +22,22 @@ struct Asset;
|
|||||||
|
|
||||||
pub async fn http_server(database_connection: Arc<DatabaseConnection>) -> Result<()> {
|
pub async fn http_server(database_connection: Arc<DatabaseConnection>) -> Result<()> {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/api/videos", get(list_videos))
|
.route("/api/video-sources", get(get_video_sources))
|
||||||
.route("/api/videos/{video_id}", get(get_video))
|
.route("/api/videos", get(get_videos))
|
||||||
.route("/api/video-list-models", get(get_video_list_models))
|
.route("/api/video/{id}", get(get_video))
|
||||||
|
.merge(
|
||||||
|
SwaggerUi::new("/swagger-ui/")
|
||||||
|
.url("/api-docs/openapi.json", ApiDoc::openapi())
|
||||||
|
.config(
|
||||||
|
Config::default()
|
||||||
|
.try_it_out_enabled(true)
|
||||||
|
.persist_authorization(true)
|
||||||
|
.validator_url("none"),
|
||||||
|
),
|
||||||
|
)
|
||||||
.fallback_service(get(frontend_files))
|
.fallback_service(get(frontend_files))
|
||||||
.layer(Extension(database_connection))
|
.layer(Extension(database_connection))
|
||||||
.layer(middleware::from_fn(auth::auth));
|
.layer(middleware::from_fn(auth::auth));
|
||||||
let app = NormalizePathLayer::trim_trailing_slash().layer(app);
|
|
||||||
let listener = tokio::net::TcpListener::bind(&CONFIG.bind_address)
|
let listener = tokio::net::TcpListener::bind(&CONFIG.bind_address)
|
||||||
.await
|
.await
|
||||||
.context("bind address failed")?;
|
.context("bind address failed")?;
|
||||||
|
|||||||
Reference in New Issue
Block a user