Write OpenAPI documentation
This commit is contained in:
parent
9247a39094
commit
bc3469fa88
209
Cargo.lock
generated
209
Cargo.lock
generated
@ -49,7 +49,17 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"utoipa",
|
||||||
|
"utoipa-swagger-ui",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -178,6 +188,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -244,6 +260,15 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
@ -280,6 +305,17 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@ -362,6 +398,16 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -792,6 +838,7 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -863,6 +910,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.27"
|
version = "0.4.27"
|
||||||
@ -897,6 +950,16 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.8"
|
version = "0.8.8"
|
||||||
@ -1199,6 +1262,40 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed"
|
||||||
|
version = "8.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5fbc0ee50fcb99af7cebb442e5df7b5b45e9460ffa3f8f549cd26b862bec49d"
|
||||||
|
dependencies = [
|
||||||
|
"rust-embed-impl",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-impl"
|
||||||
|
version = "8.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6bf418c9a2e3f6663ca38b8a7134cc2c2167c9d69688860e8961e3faa731702e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"syn",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-utils"
|
||||||
|
version = "8.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d55b95147fe01265d06b3955db798bdaed52e60e2211c41137701b3aba8e21"
|
||||||
|
dependencies = [
|
||||||
|
"sha2",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
@ -1230,6 +1327,15 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -1365,6 +1471,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 = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@ -1886,6 +1998,12 @@ version = "1.18.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
@ -1937,12 +2055,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "utoipa"
|
||||||
version = "1.16.0"
|
version = "5.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.2",
|
"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",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-swagger-ui"
|
||||||
|
version = "9.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29519b3c485df6b13f4478ac909a491387e9ef70204487c3b64b53749aec0be"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"base64",
|
||||||
|
"mime_guess",
|
||||||
|
"regex",
|
||||||
|
"rust-embed",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
"utoipa",
|
||||||
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1963,6 +2114,16 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
@ -2010,6 +2171,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -2286,3 +2456,32 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"crc32fast",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"flate2",
|
||||||
|
"indexmap",
|
||||||
|
"memchr",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
@ -22,4 +22,5 @@ toml = "0.8.20"
|
|||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
uuid = { version = "1.16.0", features = ["v4"] }
|
utoipa = { version = "5.3.1", features = ["axum_extras"] }
|
||||||
|
utoipa-swagger-ui = { version = "9.0.1", features = ["axum"] }
|
||||||
|
7
TODO.md
7
TODO.md
@ -4,5 +4,10 @@ Tasks that have been completed in commits before this one have been omitted from
|
|||||||
|
|
||||||
- [x] /video PUT route
|
- [x] /video PUT route
|
||||||
- [x] /channel PUT route
|
- [x] /channel PUT route
|
||||||
- [ ] OpenAPI documentation
|
- [x] OpenAPI documentation
|
||||||
|
|
||||||
|
## Planned features
|
||||||
|
|
||||||
- [ ] Video versioning
|
- [ ] Video versioning
|
||||||
|
- [ ] Subtitles support
|
||||||
|
- [ ] Cookies support
|
||||||
|
@ -6,6 +6,7 @@ use thiserror::Error;
|
|||||||
use tokio::{fs, process::Command};
|
use tokio::{fs, process::Command};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::string::ToUnquotedString;
|
use crate::string::ToUnquotedString;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ pub enum ChannelError {
|
|||||||
JsonKey(String),
|
JsonKey(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, ToSchema)]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
@ -5,6 +5,7 @@ use serde_json::Value;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::string::ToUnquotedString;
|
use crate::string::ToUnquotedString;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ pub enum CommentsError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, ToSchema)]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
|
45
src/doc.rs
Executable file
45
src/doc.rs
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
use utoipa_swagger_ui::SwaggerUi;
|
||||||
|
|
||||||
|
// Because apparently the OpenApi macro can't import these on its own
|
||||||
|
// if the routes are not in the same location as the router
|
||||||
|
use crate::routes::{
|
||||||
|
__path_index,
|
||||||
|
channel::{
|
||||||
|
__path_delete_channel, __path_get_channel, __path_get_channel_videos, __path_list_channels,
|
||||||
|
__path_update_channel, __path_upload_channel,
|
||||||
|
},
|
||||||
|
comment::__path_get_video_comments,
|
||||||
|
video::{
|
||||||
|
__path_delete_video, __path_get_video, __path_list_videos, __path_update_video,
|
||||||
|
__path_upload_video,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
index,
|
||||||
|
list_channels,
|
||||||
|
list_videos,
|
||||||
|
upload_video,
|
||||||
|
upload_channel,
|
||||||
|
update_channel,
|
||||||
|
update_video,
|
||||||
|
delete_channel,
|
||||||
|
delete_video,
|
||||||
|
get_video,
|
||||||
|
get_channel,
|
||||||
|
get_video_comments,
|
||||||
|
get_channel_videos
|
||||||
|
),
|
||||||
|
info(
|
||||||
|
title = "Almond API",
|
||||||
|
description = "Interface to archive YouTube videos."
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
struct ApiDoc;
|
||||||
|
|
||||||
|
pub fn docs_router() -> SwaggerUi {
|
||||||
|
SwaggerUi::new("/docs").url("/api-doc/openapi.json", ApiDoc::openapi())
|
||||||
|
}
|
15
src/main.rs
15
src/main.rs
@ -4,13 +4,18 @@ use axum::{
|
|||||||
Router,
|
Router,
|
||||||
routing::{delete, get, post, put},
|
routing::{delete, get, post, put},
|
||||||
};
|
};
|
||||||
|
use doc::docs_router;
|
||||||
use instance::Instance;
|
use instance::Instance;
|
||||||
|
|
||||||
use routes::{
|
use routes::{
|
||||||
channel::{delete_channel, get_channel, list_channels, update_channel, upload_channel},
|
channel::{
|
||||||
|
delete_channel, get_channel, get_channel_videos, list_channels, update_channel,
|
||||||
|
upload_channel,
|
||||||
|
},
|
||||||
comment::get_video_comments,
|
comment::get_video_comments,
|
||||||
index,
|
index,
|
||||||
middleware::auth,
|
middleware::auth,
|
||||||
video::{delete_video, get_channel_videos, get_video, list_videos, update_video, upload_video},
|
video::{delete_video, get_video, list_videos, update_video, upload_video},
|
||||||
};
|
};
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@ -18,6 +23,7 @@ use tracing::info;
|
|||||||
mod channel;
|
mod channel;
|
||||||
mod comment;
|
mod comment;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod doc;
|
||||||
mod instance;
|
mod instance;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod string;
|
mod string;
|
||||||
@ -60,7 +66,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.route("/channel/{id}", delete(delete_channel))
|
.route("/channel/{id}", delete(delete_channel))
|
||||||
.route_layer(axum::middleware::from_fn_with_state(instance.clone(), auth));
|
.route_layer(axum::middleware::from_fn_with_state(instance.clone(), auth));
|
||||||
|
|
||||||
let almond = router.merge(protected).with_state(instance);
|
let almond = router
|
||||||
|
.merge(protected)
|
||||||
|
.merge(docs_router())
|
||||||
|
.with_state(instance);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(address).await?;
|
let listener = tokio::net::TcpListener::bind(address).await?;
|
||||||
|
|
||||||
|
@ -6,19 +6,21 @@ use axum::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
channel::{Channel, ChannelError},
|
channel::{Channel, ChannelError},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
url::is_youtube_url,
|
url::is_youtube_url,
|
||||||
|
video::Video,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct ListChannelsQuery {
|
pub struct ListChannelsQuery {
|
||||||
page: Option<usize>,
|
page: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct ListChannelsResponse {
|
pub struct ListChannelsResponse {
|
||||||
channels: Vec<Channel>,
|
channels: Vec<Channel>,
|
||||||
page: usize,
|
page: usize,
|
||||||
@ -28,6 +30,7 @@ pub struct ListChannelsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve video list as JSON (paged)
|
/// Retrieve video list as JSON (paged)
|
||||||
|
#[utoipa::path(get, path = "/channel", request_body = ListChannelsQuery, params(("page" = Option<usize>, Query)), tags = ["channel"], responses((status = 200, body = ListChannelsResponse), (status = 500, description = "Error fetching channels from database")))]
|
||||||
pub async fn list_channels(
|
pub async fn list_channels(
|
||||||
State(state): State<Instance>,
|
State(state): State<Instance>,
|
||||||
Query(query): Query<ListChannelsQuery>,
|
Query(query): Query<ListChannelsQuery>,
|
||||||
@ -64,6 +67,7 @@ pub async fn list_channels(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a single channel from the database by its ID
|
/// Get a single channel from the database by its ID
|
||||||
|
#[utoipa::path(get, path = "/channel/{id}", tags = ["channel"], responses((status = 200, body = Channel), (status = 404, description = "Not found"), (status = 500, description = "Failed to get channel from database")))]
|
||||||
pub async fn get_channel(
|
pub async fn get_channel(
|
||||||
State(state): State<Instance>,
|
State(state): State<Instance>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
@ -76,12 +80,31 @@ pub async fn get_channel(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
/// Get all videos belonging to a channel ID from the database
|
||||||
|
#[utoipa::path(get, path = "/channel/{id}/videos", tags = ["channel"], responses((status = 200, body = Vec<Video>), (status = 404, description = "Not found"), (status = 500, description = "Error fetching videos from database")))]
|
||||||
|
pub async fn get_channel_videos(
|
||||||
|
State(state): State<Instance>,
|
||||||
|
Path(id): Path<String>,
|
||||||
|
) -> Result<Json<Vec<Video>>, StatusCode> {
|
||||||
|
sqlx::query_as!(Video, "SELECT * FROM video WHERE author_id = ?", id)
|
||||||
|
.fetch_all(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_or(Err(StatusCode::INTERNAL_SERVER_ERROR), |videos| {
|
||||||
|
if videos.is_empty() {
|
||||||
|
Err(StatusCode::NOT_FOUND)
|
||||||
|
} else {
|
||||||
|
Ok(Json(videos))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct UploadChannelQuery {
|
pub struct UploadChannelQuery {
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a channel's metadata to the database
|
/// Upload a channel's metadata to the database
|
||||||
|
#[utoipa::path(post, path = "/channel", request_body = UploadChannelQuery, params(("url" = String, Query), ("almond-api-key" = String, description = "API key")), tags = ["channel"], responses((status = 200, description = "Channel already exists, skipping"), (status = 201, description = "Channel uploaded successfully"), (status = 400, description = "Bad request, likely a malformed URL"), (status = 401, description = "Unauthorized"), (status = 500, description = "Failed to upload channel to database")))]
|
||||||
pub async fn upload_channel(
|
pub async fn upload_channel(
|
||||||
State(state): State<Instance>,
|
State(state): State<Instance>,
|
||||||
Query(query): Query<UploadChannelQuery>,
|
Query(query): Query<UploadChannelQuery>,
|
||||||
@ -141,7 +164,7 @@ pub async fn upload_channel(
|
|||||||
{
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
info!("Inserted channel to database successfully! {result:?}");
|
info!("Inserted channel to database successfully! {result:?}");
|
||||||
StatusCode::OK
|
StatusCode::CREATED
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error inserting channel to database: {e:?}");
|
error!("Error inserting channel to database: {e:?}");
|
||||||
@ -154,6 +177,7 @@ pub async fn upload_channel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update an existing channel from the database
|
/// Update an existing channel from the database
|
||||||
|
#[utoipa::path(put, path = "/channel/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["channel"], responses((status = 204, description = "Channel updated successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to update channel from database")))]
|
||||||
pub async fn update_channel(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
pub async fn update_channel(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
||||||
match sqlx::query_as!(Channel, "SELECT * FROM channel WHERE youtube_id = ?", id)
|
match sqlx::query_as!(Channel, "SELECT * FROM channel WHERE youtube_id = ?", id)
|
||||||
.fetch_optional(&state.pool)
|
.fetch_optional(&state.pool)
|
||||||
@ -175,7 +199,7 @@ pub async fn update_channel(State(state): State<Instance>, Path(id): Path<String
|
|||||||
).execute(&state.pool).await {
|
).execute(&state.pool).await {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
info!("Updated channel '{id}' successfully: {r:?}");
|
info!("Updated channel '{id}' successfully: {r:?}");
|
||||||
StatusCode::OK
|
StatusCode::NO_CONTENT
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Update channel failed: {e:?}");
|
error!("Update channel failed: {e:?}");
|
||||||
@ -198,6 +222,7 @@ pub async fn update_channel(State(state): State<Instance>, Path(id): Path<String
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a channel from the database
|
/// Delete a channel from the database
|
||||||
|
#[utoipa::path(delete, path = "/channel/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["channel"], responses((status = 204, description = "Channel deleted successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to delete channel from database")))]
|
||||||
pub async fn delete_channel(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
pub async fn delete_channel(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
||||||
match sqlx::query!("DELETE FROM channel WHERE youtube_id = ?", id)
|
match sqlx::query!("DELETE FROM channel WHERE youtube_id = ?", id)
|
||||||
.fetch_optional(&state.pool)
|
.fetch_optional(&state.pool)
|
||||||
|
@ -5,6 +5,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
comment::{Comment, CommentsError, get_comments_from_video},
|
comment::{Comment, CommentsError, get_comments_from_video},
|
||||||
@ -16,7 +17,7 @@ pub struct VideoCommentsQuery {
|
|||||||
page: Option<usize>,
|
page: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct VideoCommentsResponse {
|
pub struct VideoCommentsResponse {
|
||||||
comments: Vec<Comment>,
|
comments: Vec<Comment>,
|
||||||
page: usize,
|
page: usize,
|
||||||
@ -26,6 +27,7 @@ pub struct VideoCommentsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the comments from a video, will return an empty vec if the video has no comments
|
/// Fetches the comments from a video, will return an empty vec if the video has no comments
|
||||||
|
#[utoipa::path(get, path = "/video/{id}/comments", tags = ["comment"], responses((status = 200, body = VideoCommentsResponse), (status = 404, description = "Not found"), (status = 500, description = "Failed to get video comments")))]
|
||||||
pub async fn get_video_comments(
|
pub async fn get_video_comments(
|
||||||
State(state): State<Instance>,
|
State(state): State<Instance>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
use axum::Json;
|
use axum::Json;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
pub mod video;
|
pub mod video;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct IndexResponse {
|
pub struct IndexResponse {
|
||||||
app_name: String,
|
app_name: String,
|
||||||
version: String,
|
version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get general information from the current Almond instance
|
/// Get general information from the current Almond instance
|
||||||
|
#[utoipa::path(get, path = "/", tags = ["application"], responses((status = 200, body = IndexResponse)))]
|
||||||
pub async fn index() -> Json<IndexResponse> {
|
pub async fn index() -> Json<IndexResponse> {
|
||||||
let app_name = "Almond".into();
|
let app_name = "Almond".into();
|
||||||
let version = env!("CARGO_PKG_VERSION").into();
|
let version = env!("CARGO_PKG_VERSION").into();
|
||||||
|
@ -6,6 +6,7 @@ use axum::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
@ -13,12 +14,12 @@ use crate::{
|
|||||||
video::{Video, VideoError},
|
video::{Video, VideoError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct ListVideosQuery {
|
pub struct ListVideosQuery {
|
||||||
page: Option<usize>,
|
page: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct ListVideosResponse {
|
pub struct ListVideosResponse {
|
||||||
videos: Vec<Video>,
|
videos: Vec<Video>,
|
||||||
page: usize,
|
page: usize,
|
||||||
@ -28,6 +29,7 @@ pub struct ListVideosResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve video list as JSON (paged)
|
/// Retrieve video list as JSON (paged)
|
||||||
|
#[utoipa::path(get, path = "/video", request_body = ListVideosQuery, params(("page" = Option<usize>, Query)), tags = ["video"], responses((status = 200, body = ListVideosResponse), (status = 500, description = "Error fetching videos from database")))]
|
||||||
pub async fn list_videos(
|
pub async fn list_videos(
|
||||||
State(state): State<Instance>,
|
State(state): State<Instance>,
|
||||||
Query(query): Query<ListVideosQuery>,
|
Query(query): Query<ListVideosQuery>,
|
||||||
@ -63,24 +65,8 @@ pub async fn list_videos(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all videos belonging to a channel ID from the database
|
|
||||||
pub async fn get_channel_videos(
|
|
||||||
State(state): State<Instance>,
|
|
||||||
Path(id): Path<String>,
|
|
||||||
) -> Result<Json<Vec<Video>>, StatusCode> {
|
|
||||||
sqlx::query_as!(Video, "SELECT * FROM video WHERE author_id = ?", id)
|
|
||||||
.fetch_all(&state.pool)
|
|
||||||
.await
|
|
||||||
.map_or(Err(StatusCode::INTERNAL_SERVER_ERROR), |videos| {
|
|
||||||
if videos.is_empty() {
|
|
||||||
Err(StatusCode::NOT_FOUND)
|
|
||||||
} else {
|
|
||||||
Ok(Json(videos))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a single video from the database by its ID
|
/// Get a single video from the database by its ID
|
||||||
|
#[utoipa::path(get, path = "/video/{id}", tags = ["video"], responses((status = 200, body = Video), (status = 404, description = "Not found"), (status = 500, description = "Failed to get video from database")))]
|
||||||
pub async fn get_video(
|
pub async fn get_video(
|
||||||
State(state): State<Instance>,
|
State(state): State<Instance>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
@ -93,12 +79,13 @@ pub async fn get_video(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct UploadVideoQuery {
|
pub struct UploadVideoQuery {
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a video to the database
|
/// Upload a video to the database
|
||||||
|
#[utoipa::path(post, path = "/video", request_body = UploadVideoQuery, params(("url" = String, Query), ("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 200, description = "Video already exists, skipping"), (status = 201, description = "Video uploaded successfully"), (status = 400, description = "Bad request, likely a malformed URL"), (status = 401, description = "Unauthorized"), (status = 500, description = "Failed to upload video to database")))]
|
||||||
pub async fn upload_video(
|
pub async fn upload_video(
|
||||||
State(state): State<Instance>,
|
State(state): State<Instance>,
|
||||||
Query(query): Query<UploadVideoQuery>,
|
Query(query): Query<UploadVideoQuery>,
|
||||||
@ -162,7 +149,7 @@ pub async fn upload_video(
|
|||||||
{
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
info!("Inserted video to database successfully! {result:?}");
|
info!("Inserted video to database successfully! {result:?}");
|
||||||
StatusCode::OK
|
StatusCode::CREATED
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error inserting video to database: {e:?}");
|
error!("Error inserting video to database: {e:?}");
|
||||||
@ -175,6 +162,7 @@ pub async fn upload_video(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update an existing video from the database
|
/// Update an existing video from the database
|
||||||
|
#[utoipa::path(put, path = "/video/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 204, description = "Video updated successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to update video from database")))]
|
||||||
pub async fn update_video(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
pub async fn update_video(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
||||||
match sqlx::query_as!(Video, "SELECT * FROM video WHERE youtube_id = ?", id)
|
match sqlx::query_as!(Video, "SELECT * FROM video WHERE youtube_id = ?", id)
|
||||||
.fetch_optional(&state.pool)
|
.fetch_optional(&state.pool)
|
||||||
@ -194,7 +182,7 @@ pub async fn update_video(State(state): State<Instance>, Path(id): Path<String>)
|
|||||||
).execute(&state.pool).await {
|
).execute(&state.pool).await {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
info!("Updated video '{id}' successfully: {r:?}");
|
info!("Updated video '{id}' successfully: {r:?}");
|
||||||
StatusCode::OK
|
StatusCode::NO_CONTENT
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Update video failed: {e:?}");
|
error!("Update video failed: {e:?}");
|
||||||
@ -217,6 +205,7 @@ pub async fn update_video(State(state): State<Instance>, Path(id): Path<String>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a video from the database
|
/// Delete a video from the database
|
||||||
|
#[utoipa::path(delete, path = "/video/{id}", params(("almond-api-key" = String, description = "API key")), tags = ["video"], responses((status = 204, description = "Video deleted successfully"), (status = 404, description = "Video with specified ID not found"), (status = 500, description = "Failed to delete video from database")))]
|
||||||
pub async fn delete_video(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
pub async fn delete_video(State(state): State<Instance>, Path(id): Path<String>) -> StatusCode {
|
||||||
match sqlx::query!("DELETE FROM video WHERE youtube_id = ?", id)
|
match sqlx::query!("DELETE FROM video WHERE youtube_id = ?", id)
|
||||||
.fetch_optional(&state.pool)
|
.fetch_optional(&state.pool)
|
||||||
@ -226,7 +215,7 @@ pub async fn delete_video(State(state): State<Instance>, Path(id): Path<String>)
|
|||||||
}) {
|
}) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Deleted video '{id}' successfully");
|
info!("Deleted video '{id}' successfully");
|
||||||
StatusCode::OK
|
StatusCode::NO_CONTENT
|
||||||
}
|
}
|
||||||
Err(status) => status,
|
Err(status) => status,
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ use thiserror::Error;
|
|||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::string::ToUnquotedString;
|
use crate::string::ToUnquotedString;
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ pub enum VideoError {
|
|||||||
MissingVideoFile,
|
MissingVideoFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, ToSchema)]
|
||||||
pub struct Video {
|
pub struct Video {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user