docs: add utoipa OpenAPI annotations to all scraper endpoints
Deploy Scraper / build-and-deploy (push) Canceled after 0s

Add #[utoipa::path] + IntoParams to all new category handlers:
- downloader (25 routes): /download, /detect-platform, 23 platform handlers
- misc (4): currency-converter, harga-emas, kurs-bca, server-info
- stalk (8): github, youtube, ml, free-fire, genshin, twitter, tiktok, instagram
- weebs (5): anime-info, manga-info, whatanime, waifu (sfw/nsfw)
- search (5): bmkg, jadwal-sholat, weather, google, yt
- tool (6): whois, ip-location, tinyurl, check-hosting, hargapangan, cek-resi
- image (2): brat, brat-animated
- uploader (2): ryzencdn, serve-file
- ai (3): chat, gemini, image

Register all in ModuleApiDoc paths() with per-category tags.
Fix clippy warnings in touched handlers (expect/unwrap -> map_err, needless lifetimes).

/docs now documents the full scraper API surface (was only anime/komik).
This commit is contained in:
asepharyana
2026-09-01 22:39:02 +07:00
parent e6f543078b
commit bbb8bed389
10 changed files with 879 additions and 58 deletions
+82 -6
View File
@@ -4,7 +4,7 @@ use utoipa::OpenApi;
#[derive(OpenApi)]
#[openapi(
paths(
// Anime module handlers
// ── Anime module ──────────────────────────────────────────
crate::presentation::handler::anime::anime_index,
crate::presentation::handler::anime::genres,
crate::presentation::handler::anime::detail_slug,
@@ -16,7 +16,7 @@ use utoipa::OpenApi;
crate::presentation::handler::anime::search_slug_page,
crate::presentation::handler::anime::genre_slug_index,
crate::presentation::handler::anime::genre_slug_page,
// Anime2 module handlers
// ── Anime2 module ─────────────────────────────────────────
crate::presentation::handler::anime2::index,
crate::presentation::handler::anime2::genre_list,
crate::presentation::handler::anime2::filter,
@@ -28,7 +28,7 @@ use utoipa::OpenApi;
crate::presentation::handler::anime2::latest_slug,
crate::presentation::handler::anime2::ongoing_anime_slug,
crate::presentation::handler::anime2::complete_anime_slug,
// Komik module handlers
// ── Komik module ──────────────────────────────────────────
crate::presentation::handler::komik::genre_list,
crate::presentation::handler::komik::chapter_slug,
crate::presentation::handler::komik::detail_slug,
@@ -40,19 +40,95 @@ use utoipa::OpenApi;
crate::presentation::handler::komik::popular_slug,
crate::presentation::handler::komik::search_slug,
crate::presentation::handler::komik::search_slug_page,
// Proxy module handlers
// ── Download module ────────────────────────────────────────
crate::presentation::handler::downloader::download,
crate::presentation::handler::downloader::detect_platform_handler,
crate::presentation::handler::downloader::download_instagram,
crate::presentation::handler::downloader::download_facebook,
crate::presentation::handler::downloader::download_tiktok,
crate::presentation::handler::downloader::download_youtube,
crate::presentation::handler::downloader::download_youtube_mp3,
crate::presentation::handler::downloader::download_spotify,
crate::presentation::handler::downloader::download_twitter,
crate::presentation::handler::downloader::download_pinterest,
crate::presentation::handler::downloader::download_mega,
crate::presentation::handler::downloader::download_terabox,
crate::presentation::handler::downloader::download_gdrive,
crate::presentation::handler::downloader::download_mediafire,
crate::presentation::handler::downloader::download_pixeldrain,
crate::presentation::handler::downloader::download_threads,
crate::presentation::handler::downloader::download_doodstream,
crate::presentation::handler::downloader::download_krakenfiles,
crate::presentation::handler::downloader::download_danbooru,
crate::presentation::handler::downloader::download_soundcloud,
crate::presentation::handler::downloader::download_dailymotion,
crate::presentation::handler::downloader::download_reddit,
crate::presentation::handler::downloader::download_streamable,
crate::presentation::handler::downloader::download_videy,
crate::presentation::handler::downloader::download_bilibili,
// ── Misc module ────────────────────────────────────────────
crate::presentation::handler::misc::currency_converter_handler,
crate::presentation::handler::misc::harga_emas_handler,
crate::presentation::handler::misc::kurs_bca_handler,
crate::presentation::handler::misc::server_info_handler,
// ── Stalk module ───────────────────────────────────────────
crate::presentation::handler::stalk::github_handler,
crate::presentation::handler::stalk::youtube_handler,
crate::presentation::handler::stalk::mobile_legends_handler,
crate::presentation::handler::stalk::free_fire_handler,
crate::presentation::handler::stalk::genshin_handler,
crate::presentation::handler::stalk::twitter_handler,
crate::presentation::handler::stalk::tiktok_handler,
crate::presentation::handler::stalk::instagram_handler,
// ── Weebs module ───────────────────────────────────────────
crate::presentation::handler::weebs::anime_info_handler,
crate::presentation::handler::weebs::manga_info_handler,
crate::presentation::handler::weebs::whatanime_handler,
crate::presentation::handler::weebs::sfw_waifu_handler,
crate::presentation::handler::weebs::nsfw_waifu_handler,
// ── Search module ──────────────────────────────────────────
crate::presentation::handler::search::bmkg_handler,
crate::presentation::handler::search::jadwal_sholat_handler,
crate::presentation::handler::search::weather_handler,
crate::presentation::handler::search::google_handler,
crate::presentation::handler::search::yt_handler,
// ── Tool module ────────────────────────────────────────────
crate::presentation::handler::tools::whois_handler,
crate::presentation::handler::tools::ip_location_handler,
crate::presentation::handler::tools::tinyurl_handler,
crate::presentation::handler::tools::check_hosting_handler,
crate::presentation::handler::tools::hargapangan_handler,
crate::presentation::handler::tools::cek_resi_handler,
// ── Image module ───────────────────────────────────────────
crate::presentation::handler::image::brat_handler,
crate::presentation::handler::image::brat_animated_handler,
// ── Uploader module ────────────────────────────────────────
crate::presentation::handler::uploader::ryzencdn_handler,
crate::presentation::handler::uploader::serve_file_handler,
// ── AI module ──────────────────────────────────────────────
crate::presentation::handler::ai::chat_handler,
crate::presentation::handler::ai::gemini_handler,
crate::presentation::handler::ai::image_handler,
),
components(
schemas(
// Application response wrapper
crate::presentation::dto::common::ApiResponse<String>,
crate::presentation::dto::downloader::DownloadResponse,
)
),
tags(
(name = "anime", description = "Anime endpoints"),
(name = "anime2", description = "Anime2 endpoints"),
(name = "komik", description = "Komik endpoints"),
(name = "proxy", description = "Proxy endpoints"),
(name = "download", description = "Media download endpoints (23 platforms)"),
(name = "misc", description = "Miscellaneous utilities (currency, gold, server)"),
(name = "stalk", description = "User profile stalking (GitHub, YT, Twitter, TikTok, IG, FF, ML, Genshin)"),
(name = "weebs", description = "Weeb endpoints (anime info, manga, anime search, waifu)"),
(name = "search", description = "Search endpoints (BMKG, prayer times, weather, YouTube, Google)"),
(name = "tool", description = "Utility tools (WHOIS, IP lookup, URL shortener, tracking, hosting)"),
(name = "image", description = "Image generation (brat static & animated)"),
(name = "uploader", description = "File upload and serve"),
(name = "ai", description = "AI endpoints (chat, gemini, image generation)"),
)
)]
pub struct ModuleApiDoc;
+38 -2
View File
@@ -23,6 +23,18 @@ pub struct AiParams {
}
/// GET /ai/chat?text=...&prompt=...&model=...
#[utoipa::path(
get,
path = "/ai/chat",
tag = "ai",
operation_id = "ai_chat_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn chat_handler(Query(params): Query<AiParams>) -> Result<Json<Value>, AppError> {
let text = params
.text
@@ -32,6 +44,18 @@ pub async fn chat_handler(Query(params): Query<AiParams>) -> Result<Json<Value>,
}
/// GET /ai/gemini?text=...&prompt=...&image_url=...
#[utoipa::path(
get,
path = "/ai/gemini",
tag = "ai",
operation_id = "ai_gemini_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn gemini_handler(Query(params): Query<AiParams>) -> Result<Json<Value>, AppError> {
let text = params
.text
@@ -42,6 +66,18 @@ pub async fn gemini_handler(Query(params): Query<AiParams>) -> Result<Json<Value
}
/// GET /ai/image?prompt=...&model=...&width=...&height=... — returns image bytes.
#[utoipa::path(
get,
path = "/ai/image",
tag = "ai",
operation_id = "ai_image_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn image_handler(Query(params): Query<AiParams>) -> Result<Response<Body>, AppError> {
let prompt = params
.prompt
@@ -59,9 +95,9 @@ pub async fn image_handler(Query(params): Query<AiParams>) -> Result<Response<Bo
} else {
"application/octet-stream"
};
Ok(Response::builder()
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime)
.body(Body::from(bytes))
.unwrap())
.map_err(|e| AppError::Internal(format!("build response: {e}")))
}
+356 -34
View File
@@ -7,17 +7,15 @@
use axum::extract::Query;
use axum::Json;
use serde::Deserialize;
use serde_json::Value;
use utoipa::IntoParams;
use crate::application::downloader as use_cases;
use crate::presentation::dto::downloader::DownloadResponse;
use crate::presentation::error::AppError;
// ============================================================================
// DownloadParams + response
// ============================================================================
/// Request params for downloader endpoints.
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, IntoParams)]
pub struct DownloadParams {
pub url: String,
pub cookies: Option<String>,
@@ -25,11 +23,24 @@ pub struct DownloadParams {
pub quality: Option<String>,
}
// ============================================================================
// ========================================================================
// Handlers
// ============================================================================
// ========================================================================
/// All-in-one downloader with auto-detection.
/// All-in-one auto-detect downloader.
/// `/download?url=...`
#[utoipa::path(
get,
path = "/download",
tag = "download",
operation_id = "dl_download",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -37,10 +48,23 @@ pub async fn download(
Ok(Json(DownloadResponse::ok(result)))
}
/// Platform detection endpoint.
/// Detect platform from URL.
/// `/detect-platform?url=...`
#[utoipa::path(
get,
path = "/detect-platform",
tag = "download",
operation_id = "dl_detect_platform_handler",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn detect_platform_handler(
Query(params): Query<DownloadParams>,
) -> Result<Json<serde_json::Value>, AppError> {
) -> Result<Json<Value>, AppError> {
let platform = use_cases::detect_platform(&params.url);
Ok(Json(serde_json::json!({
"platform": platform,
@@ -48,8 +72,20 @@ pub async fn detect_platform_handler(
})))
}
/// Generate handler for each downloader platform.
/// Handler for _instagram.
/// Download Instagram media.
/// `/download/instagram?url=...`
#[utoipa::path(
get,
path = "/download/instagram",
tag = "download",
operation_id = "dl_download_instagram",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_instagram(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -57,7 +93,20 @@ pub async fn download_instagram(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _facebook.
/// Download Facebook video.
/// `/download/facebook?url=...`
#[utoipa::path(
get,
path = "/download/facebook",
tag = "download",
operation_id = "dl_download_facebook",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_facebook(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -65,7 +114,20 @@ pub async fn download_facebook(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _tiktok.
/// Download TikTok video.
/// `/download/tiktok?url=...`
#[utoipa::path(
get,
path = "/download/tiktok",
tag = "download",
operation_id = "dl_download_tiktok",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_tiktok(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -73,7 +135,20 @@ pub async fn download_tiktok(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _youtube.
/// Download YouTube video.
/// `/download/youtube?url=...`
#[utoipa::path(
get,
path = "/download/youtube",
tag = "download",
operation_id = "dl_download_youtube",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_youtube(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -83,7 +158,20 @@ pub async fn download_youtube(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _youtube_mp3.
/// Convert YouTube to MP3.
/// `/download/youtube/mp3?url=...`
#[utoipa::path(
get,
path = "/download/youtube/mp3",
tag = "download",
operation_id = "dl_download_youtube_mp3",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_youtube_mp3(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -91,7 +179,20 @@ pub async fn download_youtube_mp3(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _spotify.
/// Download Spotify track.
/// `/download/spotify?url=...`
#[utoipa::path(
get,
path = "/download/spotify",
tag = "download",
operation_id = "dl_download_spotify",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_spotify(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -99,7 +200,20 @@ pub async fn download_spotify(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _twitter.
/// Download Twitter/X media.
/// `/download/twitter?url=...`
#[utoipa::path(
get,
path = "/download/twitter",
tag = "download",
operation_id = "dl_download_twitter",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_twitter(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -107,7 +221,20 @@ pub async fn download_twitter(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _pinterest.
/// Download Pinterest pin.
/// `/download/pinterest?url=...`
#[utoipa::path(
get,
path = "/download/pinterest",
tag = "download",
operation_id = "dl_download_pinterest",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_pinterest(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -115,7 +242,20 @@ pub async fn download_pinterest(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _mega.
/// Download from Mega.
/// `/download/mega?url=...`
#[utoipa::path(
get,
path = "/download/mega",
tag = "download",
operation_id = "dl_download_mega",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_mega(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -123,7 +263,20 @@ pub async fn download_mega(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _terabox.
/// Download from Terabox.
/// `/download/terabox?url=...`
#[utoipa::path(
get,
path = "/download/terabox",
tag = "download",
operation_id = "dl_download_terabox",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_terabox(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -131,7 +284,20 @@ pub async fn download_terabox(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _gdrive.
/// Download from Google Drive.
/// `/download/gdrive?url=...`
#[utoipa::path(
get,
path = "/download/gdrive",
tag = "download",
operation_id = "dl_download_gdrive",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_gdrive(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -139,7 +305,20 @@ pub async fn download_gdrive(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _mediafire.
/// Download from MediaFire.
/// `/download/mediafire?url=...`
#[utoipa::path(
get,
path = "/download/mediafire",
tag = "download",
operation_id = "dl_download_mediafire",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_mediafire(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -147,7 +326,20 @@ pub async fn download_mediafire(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _pixeldrain.
/// Download from Pixeldrain.
/// `/download/pixeldrain?url=...`
#[utoipa::path(
get,
path = "/download/pixeldrain",
tag = "download",
operation_id = "dl_download_pixeldrain",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_pixeldrain(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -155,7 +347,20 @@ pub async fn download_pixeldrain(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _threads.
/// Download Threads media.
/// `/download/threads?url=...`
#[utoipa::path(
get,
path = "/download/threads",
tag = "download",
operation_id = "dl_download_threads",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_threads(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -163,7 +368,20 @@ pub async fn download_threads(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _doodstream.
/// Download from DoodStream.
/// `/download/doodstream?url=...`
#[utoipa::path(
get,
path = "/download/doodstream",
tag = "download",
operation_id = "dl_download_doodstream",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_doodstream(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -171,7 +389,20 @@ pub async fn download_doodstream(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _krakenfiles.
/// Download from KrakenFiles.
/// `/download/krakenfiles?url=...`
#[utoipa::path(
get,
path = "/download/krakenfiles",
tag = "download",
operation_id = "dl_download_krakenfiles",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_krakenfiles(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -179,7 +410,20 @@ pub async fn download_krakenfiles(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _danbooru.
/// Download from Danbooru.
/// `/download/danbooru?url=...`
#[utoipa::path(
get,
path = "/download/danbooru",
tag = "download",
operation_id = "dl_download_danbooru",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_danbooru(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -187,7 +431,20 @@ pub async fn download_danbooru(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _soundcloud.
/// Download SoundCloud track.
/// `/download/soundcloud?url=...`
#[utoipa::path(
get,
path = "/download/soundcloud",
tag = "download",
operation_id = "dl_download_soundcloud",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_soundcloud(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -195,7 +452,20 @@ pub async fn download_soundcloud(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _dailymotion.
/// Download Dailymotion video.
/// `/download/dailymotion?url=...`
#[utoipa::path(
get,
path = "/download/dailymotion",
tag = "download",
operation_id = "dl_download_dailymotion",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_dailymotion(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -203,7 +473,20 @@ pub async fn download_dailymotion(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _reddit.
/// Download Reddit media.
/// `/download/reddit?url=...`
#[utoipa::path(
get,
path = "/download/reddit",
tag = "download",
operation_id = "dl_download_reddit",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_reddit(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -211,7 +494,20 @@ pub async fn download_reddit(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _streamable.
/// Download Streamable video.
/// `/download/streamable?url=...`
#[utoipa::path(
get,
path = "/download/streamable",
tag = "download",
operation_id = "dl_download_streamable",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_streamable(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -219,7 +515,20 @@ pub async fn download_streamable(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _videy.
/// Download Videy video.
/// `/download/videy?url=...`
#[utoipa::path(
get,
path = "/download/videy",
tag = "download",
operation_id = "dl_download_videy",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_videy(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
@@ -227,7 +536,20 @@ pub async fn download_videy(
Ok(Json(DownloadResponse::ok(result)))
}
/// Handler for _bilibili.
/// Download Bilibili video.
/// `/download/bilibili?url=...`
#[utoipa::path(
get,
path = "/download/bilibili",
tag = "download",
operation_id = "dl_download_bilibili",
params(DownloadParams),
responses(
(status = 200, description = "Download result", body = DownloadResponse),
(status = 400, description = "Bad URL or platform"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn download_bilibili(
Query(params): Query<DownloadParams>,
) -> Result<Json<DownloadResponse>, AppError> {
+32 -5
View File
@@ -7,35 +7,62 @@ use axum::extract::Query;
use axum::http::{header, StatusCode};
use axum::response::Response;
use serde::Deserialize;
use utoipa::IntoParams;
use crate::application::image as use_cases;
use crate::presentation::error::AppError;
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub struct BratParams {
pub text: Option<String>,
}
/// GET /image/brat?text=Hello
#[utoipa::path(
get,
path = "/image/brat",
tag = "image",
operation_id = "image_brat",
params(BratParams),
responses(
(status = 200, description = "Static brat image (PNG)", body = Vec<u8>),
(status = 400, description = "Missing text"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn brat_handler(Query(params): Query<BratParams>) -> Result<Response, AppError> {
let text = params
.text
.ok_or_else(|| AppError::BadRequest("text parameter is required".into()))?;
let bytes = use_cases::brat(&text).await?;
Ok(Response::builder()
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "image/png")
.body(Body::from(bytes))
.expect("valid response"))
.map_err(|e| AppError::Internal(format!("build response: {e}")))
}
/// GET /image/brat/animated?text=Hello
#[utoipa::path(
get,
path = "/image/brat/animated",
tag = "image",
operation_id = "image_brat_animated",
params(BratParams),
responses(
(status = 200, description = "Animated brat image (GIF)", body = Vec<u8>),
(status = 400, description = "Missing text"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn brat_animated_handler(Query(params): Query<BratParams>) -> Result<Response, AppError> {
let text = params
.text
.ok_or_else(|| AppError::BadRequest("text parameter is required".into()))?;
let bytes = use_cases::brat_animated(&text).await?;
Ok(Response::builder()
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "image/gif")
.body(Body::from(bytes))
.expect("valid response"))
.map_err(|e| AppError::Internal(format!("build response: {e}")))
}
+43 -1
View File
@@ -7,12 +7,13 @@ use axum::http::StatusCode;
use axum::Json;
use serde::Deserialize;
use serde_json::Value;
use utoipa::IntoParams;
use crate::application::misc as use_cases;
use crate::presentation::error::AppError;
/// Query params for currency converter.
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub struct CurrencyParams {
pub amount: Option<f64>,
pub from: Option<String>,
@@ -20,6 +21,17 @@ pub struct CurrencyParams {
}
/// GET /misc/currency-converter?amount=100&from=USD&to=IDR
#[utoipa::path(
get,
path = "/misc/currency-converter",
tag = "misc",
operation_id = "currency_converter",
params(CurrencyParams),
responses(
(status = 200, description = "Currency conversion result", body = Value),
(status = 502, description = "Upstream error"),
)
)]
pub async fn currency_converter_handler(
Query(params): Query<CurrencyParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -32,18 +44,48 @@ pub async fn currency_converter_handler(
}
/// GET /misc/harga-emas
#[utoipa::path(
get,
path = "/misc/harga-emas",
tag = "misc",
operation_id = "harga_emas",
responses(
(status = 200, description = "Gold prices (Antam)", body = Value),
(status = 502, description = "Upstream error"),
)
)]
pub async fn harga_emas_handler() -> Result<(StatusCode, Json<Value>), AppError> {
let result = use_cases::harga_emas().await?;
Ok((StatusCode::OK, Json(result)))
}
/// GET /misc/kurs-bca
#[utoipa::path(
get,
path = "/misc/kurs-bca",
tag = "misc",
operation_id = "kurs_bca",
responses(
(status = 200, description = "BCA exchange rates", body = Value),
(status = 502, description = "Upstream error"),
)
)]
pub async fn kurs_bca_handler() -> Result<(StatusCode, Json<Value>), AppError> {
let result = use_cases::kurs_bca().await?;
Ok((StatusCode::OK, Json(result)))
}
/// GET /misc/server-info
#[utoipa::path(
get,
path = "/misc/server-info",
tag = "misc",
operation_id = "server_info",
responses(
(status = 200, description = "Server info", body = Value),
(status = 502, description = "Upstream error"),
)
)]
pub async fn server_info_handler() -> Result<(StatusCode, Json<Value>), AppError> {
let result = use_cases::server_info().await?;
Ok((StatusCode::OK, Json(result)))
+61 -1
View File
@@ -19,17 +19,41 @@ pub struct SearchParams {
pub city: Option<String>,
}
fn require<'a>(v: Option<&'a String>, name: &str) -> Result<String, AppError> {
fn require(v: Option<&String>, name: &str) -> Result<String, AppError> {
v.cloned()
.ok_or_else(|| AppError::BadRequest(format!("Missing '{}' parameter", name)))
}
/// GET /search/bmkg
#[utoipa::path(
get,
path = "/search/bmkg",
tag = "search",
operation_id = "search_bmkg_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn bmkg_handler() -> Result<(StatusCode, Json<Value>), AppError> {
Ok((StatusCode::OK, Json(use_cases::bmkg().await?)))
}
/// GET /search/jadwal-sholat?kota=jakarta
#[utoipa::path(
get,
path = "/search/jadwal-sholat",
tag = "search",
operation_id = "search_jadwal_sholat_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn jadwal_sholat_handler(
Query(p): Query<SearchParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -38,6 +62,18 @@ pub async fn jadwal_sholat_handler(
}
/// GET /search/weather?city=jakarta
#[utoipa::path(
get,
path = "/search/weather",
tag = "search",
operation_id = "search_weather_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn weather_handler(
Query(p): Query<SearchParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -46,6 +82,18 @@ pub async fn weather_handler(
}
/// GET /search/google?query=rust
#[utoipa::path(
get,
path = "/search/google",
tag = "search",
operation_id = "search_google_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn google_handler(
Query(p): Query<SearchParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -54,6 +102,18 @@ pub async fn google_handler(
}
/// GET /search/yt?query=rickroll
#[utoipa::path(
get,
path = "/search/yt",
tag = "search",
operation_id = "search_yt_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn yt_handler(
Query(p): Query<SearchParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
+98 -1
View File
@@ -7,12 +7,13 @@ use axum::http::StatusCode;
use axum::Json;
use serde::Deserialize;
use serde_json::Value;
use utoipa::IntoParams;
use crate::application::stalk as use_cases;
use crate::presentation::error::AppError;
/// Query params for stalk endpoints.
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub struct StalkParams {
pub username: Option<String>,
pub user_id: Option<String>,
@@ -22,6 +23,18 @@ pub struct StalkParams {
}
/// GET /stalk/github?username=asepharyana
#[utoipa::path(
get,
path = "/stalk/github",
tag = "stalk",
operation_id = "stalk_github",
params(StalkParams),
responses(
(status = 200, description = "GitHub user profile", body = Value),
(status = 400, description = "Missing username"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn github_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -33,6 +46,18 @@ pub async fn github_handler(
}
/// GET /stalk/youtube?username=<handle>
#[utoipa::path(
get,
path = "/stalk/youtube",
tag = "stalk",
operation_id = "stalk_youtube",
params(StalkParams),
responses(
(status = 200, description = "YouTube channel profile", body = Value),
(status = 400, description = "Missing username"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn youtube_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -44,6 +69,18 @@ pub async fn youtube_handler(
}
/// GET /stalk/mobile-legends?user_id=...&zone_id=... (aliases: userid, zoneid)
#[utoipa::path(
get,
path = "/stalk/mobile-legends",
tag = "stalk",
operation_id = "stalk_mobile_legends",
params(StalkParams),
responses(
(status = 200, description = "Mobile Legends player info", body = Value),
(status = 400, description = "Missing user_id/zone_id"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn mobile_legends_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -60,6 +97,18 @@ pub async fn mobile_legends_handler(
}
/// GET /stalk/free-fire?user_id=... (alias: userid)
#[utoipa::path(
get,
path = "/stalk/free-fire",
tag = "stalk",
operation_id = "stalk_free_fire",
params(StalkParams),
responses(
(status = 200, description = "Free Fire player info", body = Value),
(status = 400, description = "Missing user_id"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn free_fire_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -72,6 +121,18 @@ pub async fn free_fire_handler(
}
/// GET /stalk/genshin-impact?user_id=... (alias: userid)
#[utoipa::path(
get,
path = "/stalk/genshin-impact",
tag = "stalk",
operation_id = "stalk_genshin",
params(StalkParams),
responses(
(status = 200, description = "Genshin Impact profile", body = Value),
(status = 400, description = "Missing user_id"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn genshin_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -84,6 +145,18 @@ pub async fn genshin_handler(
}
/// GET /stalk/twitter?username=...
#[utoipa::path(
get,
path = "/stalk/twitter",
tag = "stalk",
operation_id = "stalk_twitter",
params(StalkParams),
responses(
(status = 200, description = "Twitter/X user profile", body = Value),
(status = 400, description = "Missing username"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn twitter_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -95,6 +168,18 @@ pub async fn twitter_handler(
}
/// GET /stalk/tiktok?username=...
#[utoipa::path(
get,
path = "/stalk/tiktok",
tag = "stalk",
operation_id = "stalk_tiktok",
params(StalkParams),
responses(
(status = 200, description = "TikTok user profile", body = Value),
(status = 400, description = "Missing username"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn tiktok_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -106,6 +191,18 @@ pub async fn tiktok_handler(
}
/// GET /stalk/instagram?username=...
#[utoipa::path(
get,
path = "/stalk/instagram",
tag = "stalk",
operation_id = "stalk_instagram",
params(StalkParams),
responses(
(status = 200, description = "Instagram user profile", body = Value),
(status = 400, description = "Missing username"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn instagram_handler(
Query(params): Query<StalkParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
+82 -5
View File
@@ -1,35 +1,49 @@
//! Axum handlers for tools.
//! Axum handlers for tool endpoints.
use axum::extract::Query;
use axum::http::StatusCode;
use axum::Json;
use serde::Deserialize;
use serde_json::Value;
use utoipa::IntoParams;
use crate::application::tools as use_cases;
use crate::presentation::error::AppError;
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub struct DomainParams {
pub domain: Option<String>,
}
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub struct IpParams {
pub ip: Option<String>,
}
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub struct UrlParams {
pub url: Option<String>,
}
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams)]
pub struct ResiParams {
pub resi: Option<String>,
pub ekspedisi: Option<String>,
}
/// GET /tool/whois?domain=example.com
#[utoipa::path(
get,
path = "/tool/whois",
tag = "tool",
operation_id = "tool_whois",
params(DomainParams),
responses(
(status = 200, description = "WHOIS info", body = Value),
(status = 400, description = "Missing domain"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn whois_handler(
Query(params): Query<DomainParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -39,6 +53,19 @@ pub async fn whois_handler(
Ok((StatusCode::OK, Json(use_cases::whois(&domain).await?)))
}
/// GET /tool/ip-location?ip=1.1.1.1
#[utoipa::path(
get,
path = "/tool/ip-location",
tag = "tool",
operation_id = "tool_ip_location",
params(IpParams),
responses(
(status = 200, description = "IP geolocation", body = Value),
(status = 400, description = "Missing IP"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn ip_location_handler(
Query(params): Query<IpParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -48,6 +75,19 @@ pub async fn ip_location_handler(
Ok((StatusCode::OK, Json(use_cases::ip_location(&ip).await?)))
}
/// GET /tool/tinyurl?url=https://example.com
#[utoipa::path(
get,
path = "/tool/tinyurl",
tag = "tool",
operation_id = "tool_tinyurl",
params(UrlParams),
responses(
(status = 200, description = "Shortened URL", body = Value),
(status = 400, description = "Missing URL"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn tinyurl_handler(
Query(params): Query<UrlParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -57,6 +97,19 @@ pub async fn tinyurl_handler(
Ok((StatusCode::OK, Json(use_cases::tinyurl(&url).await?)))
}
/// GET /tool/check-hosting?domain=example.com
#[utoipa::path(
get,
path = "/tool/check-hosting",
tag = "tool",
operation_id = "tool_check_hosting",
params(DomainParams),
responses(
(status = 200, description = "Hosting info", body = Value),
(status = 400, description = "Missing domain"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn check_hosting_handler(
Query(params): Query<DomainParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -69,10 +122,34 @@ pub async fn check_hosting_handler(
))
}
/// GET /tool/hargapangan
#[utoipa::path(
get,
path = "/tool/hargapangan",
tag = "tool",
operation_id = "tool_hargapangan",
responses(
(status = 200, description = "Food prices", body = Value),
(status = 502, description = "Upstream error"),
)
)]
pub async fn hargapangan_handler() -> Result<(StatusCode, Json<Value>), AppError> {
Ok((StatusCode::OK, Json(use_cases::hargapangan().await?)))
}
/// GET /tool/cek-resi?resi=...&ekspedisi=...
#[utoipa::path(
get,
path = "/tool/cek-resi",
tag = "tool",
operation_id = "tool_cek_resi",
params(ResiParams),
responses(
(status = 200, description = "Shipment tracking", body = Value),
(status = 400, description = "Missing resi"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn cek_resi_handler(
Query(params): Query<ResiParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
+26 -2
View File
@@ -14,6 +14,18 @@ use crate::application::uploader as use_cases;
use crate::presentation::error::AppError;
/// POST /uploader/ryzencdn — accept a multipart file upload.
#[utoipa::path(
post,
path = "/uploader/ryzencdn",
tag = "uploader",
operation_id = "uploader_ryzencdn_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn ryzencdn_handler(mut multipart: Multipart) -> Result<Json<Value>, AppError> {
// Read the first file field.
let mut file_name = String::from("");
@@ -47,12 +59,24 @@ pub async fn ryzencdn_handler(mut multipart: Multipart) -> Result<Json<Value>, A
}
/// GET /uploader/file/:name — serve an uploaded file.
#[utoipa::path(
get,
path = "/uploader/file/:name",
tag = "uploader",
operation_id = "uploader_serve_file_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn serve_file_handler(Path(name): Path<String>) -> Result<Response<Body>, AppError> {
let (bytes, mime) = use_cases::serve(&name)?;
Ok(Response::builder()
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime)
.header(header::CACHE_CONTROL, "public, max-age=86400")
.body(Body::from(bytes))
.unwrap())
.map_err(|e| AppError::Internal(format!("build response: {e}")))
}
+61 -1
View File
@@ -19,12 +19,24 @@ pub struct WeebsParams {
pub tag: Option<String>,
}
fn require<'a>(v: Option<&'a String>, name: &str) -> Result<String, AppError> {
fn require(v: Option<&String>, name: &str) -> Result<String, AppError> {
v.cloned()
.ok_or_else(|| AppError::BadRequest(format!("Missing '{}' parameter", name)))
}
/// GET /weebs/anime-info?query=naruto
#[utoipa::path(
get,
path = "/weebs/anime-info",
tag = "weebs",
operation_id = "weebs_anime_info_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn anime_info_handler(
Query(p): Query<WeebsParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -33,6 +45,18 @@ pub async fn anime_info_handler(
}
/// GET /weebs/manga-info?query=one%20piece
#[utoipa::path(
get,
path = "/weebs/manga-info",
tag = "weebs",
operation_id = "weebs_manga_info_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn manga_info_handler(
Query(p): Query<WeebsParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -41,6 +65,18 @@ pub async fn manga_info_handler(
}
/// GET /weebs/whatanime?url=<image-url>
#[utoipa::path(
get,
path = "/weebs/whatanime",
tag = "weebs",
operation_id = "weebs_whatanime_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn whatanime_handler(
Query(p): Query<WeebsParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -49,6 +85,18 @@ pub async fn whatanime_handler(
}
/// GET /weebs/sfw-waifu?tag=waifu
#[utoipa::path(
get,
path = "/weebs/sfw-waifu",
tag = "weebs",
operation_id = "weebs_sfw_waifu_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn sfw_waifu_handler(
Query(p): Query<WeebsParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {
@@ -57,6 +105,18 @@ pub async fn sfw_waifu_handler(
}
/// GET /weebs/nsfw-waifu?tag=waifu
#[utoipa::path(
get,
path = "/weebs/nsfw-waifu",
tag = "weebs",
operation_id = "weebs_nsfw_waifu_handler",
responses(
(status = 200, description = "OK", body = serde_json::Value),
(status = 400, description = "Bad Request"),
(status = 502, description = "Upstream error"),
)
)]
pub async fn nsfw_waifu_handler(
Query(p): Query<WeebsParams>,
) -> Result<(StatusCode, Json<Value>), AppError> {