refactor: single refusal shape with openapi contract test
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::extractors::{Body, Params, PathId};
|
||||
use crate::{errors::ApiError, extractors::AuthenticatedUser, state::AppState};
|
||||
use api_types::{
|
||||
ConfirmWatchRequest, ConfirmWatchResponse, DismissWatchRequest, DismissWatchResponse,
|
||||
@@ -16,7 +17,7 @@ use application::integrations::{
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Multipart, Path, State},
|
||||
extract::{Multipart, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
@@ -36,6 +37,14 @@ pub struct WebhookTokenQuery {
|
||||
pub token: Option<String>,
|
||||
}
|
||||
|
||||
fn missing_token() -> axum::response::Response {
|
||||
crate::errors::refuse(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
crate::errors::UNAUTHORIZED,
|
||||
"missing webhook token",
|
||||
)
|
||||
}
|
||||
|
||||
fn extract_webhook_token(headers: &HeaderMap, query: &WebhookTokenQuery) -> Option<String> {
|
||||
extract_bearer_token(headers).or_else(|| query.token.clone())
|
||||
}
|
||||
@@ -45,20 +54,20 @@ fn extract_webhook_token(headers: &HeaderMap, query: &WebhookTokenQuery) -> Opti
|
||||
request_body(content = String, description = "Jellyfin webhook JSON payload (SendAllProperties=true)", content_type = "application/json"),
|
||||
responses(
|
||||
(status = 200, description = "Event accepted or ignored"),
|
||||
(status = 400, description = "Invalid payload"),
|
||||
(status = 401, description = "Invalid or missing webhook token"),
|
||||
(status = 400, description = "Invalid payload", body = api_types::ErrorResponse),
|
||||
(status = 401, description = "Invalid or missing webhook token", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn post_jellyfin_webhook(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
axum::extract::Query(query): axum::extract::Query<WebhookTokenQuery>,
|
||||
Params(query): Params<WebhookTokenQuery>,
|
||||
body: axum::body::Bytes,
|
||||
) -> impl IntoResponse {
|
||||
let token = match extract_webhook_token(&headers, &query) {
|
||||
Some(t) => t,
|
||||
None => return StatusCode::UNAUTHORIZED,
|
||||
None => return missing_token(),
|
||||
};
|
||||
|
||||
let cmd = IngestWatchEventCommand {
|
||||
@@ -80,20 +89,20 @@ pub async fn post_jellyfin_webhook(
|
||||
request_body(content = String, description = "Plex webhook multipart form (payload field contains JSON)", content_type = "multipart/form-data"),
|
||||
responses(
|
||||
(status = 200, description = "Event accepted or ignored"),
|
||||
(status = 400, description = "Invalid payload"),
|
||||
(status = 401, description = "Invalid or missing webhook token"),
|
||||
(status = 400, description = "Invalid payload", body = api_types::ErrorResponse),
|
||||
(status = 401, description = "Invalid or missing webhook token", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn post_plex_webhook(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
axum::extract::Query(query): axum::extract::Query<WebhookTokenQuery>,
|
||||
Params(query): Params<WebhookTokenQuery>,
|
||||
mut multipart: Multipart,
|
||||
) -> impl IntoResponse {
|
||||
let token = match extract_webhook_token(&headers, &query) {
|
||||
Some(t) => t,
|
||||
None => return StatusCode::UNAUTHORIZED,
|
||||
None => return missing_token(),
|
||||
};
|
||||
|
||||
let mut payload_bytes: Option<Vec<u8>> = None;
|
||||
@@ -108,7 +117,13 @@ pub async fn post_plex_webhook(
|
||||
|
||||
let raw_payload = match payload_bytes {
|
||||
Some(b) => b,
|
||||
None => return StatusCode::BAD_REQUEST,
|
||||
None => {
|
||||
return crate::errors::refuse(
|
||||
StatusCode::BAD_REQUEST,
|
||||
crate::errors::INVALID_INPUT,
|
||||
"multipart form has no payload field",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let cmd = IngestWatchEventCommand {
|
||||
@@ -124,7 +139,7 @@ async fn run_ingest(
|
||||
state: &AppState,
|
||||
cmd: IngestWatchEventCommand,
|
||||
parser: &dyn domain::ports::MediaServerParser,
|
||||
) -> StatusCode {
|
||||
) -> axum::response::Response {
|
||||
match ingest_watch_event::execute(
|
||||
&state.app_ctx.deps.integrations.ingest_watch_event,
|
||||
cmd,
|
||||
@@ -132,8 +147,8 @@ async fn run_ingest(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => StatusCode::OK,
|
||||
Err(e) => crate::errors::domain_error_status(&e),
|
||||
Ok(()) => StatusCode::OK.into_response(),
|
||||
Err(e) => crate::errors::ApiError(e).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,14 +157,14 @@ async fn run_ingest(
|
||||
request_body = GenerateTokenRequest,
|
||||
responses(
|
||||
(status = 200, body = GenerateTokenResponse),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 401, description = "Unauthorized", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn post_generate_webhook_token(
|
||||
State(state): State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
Json(req): Json<GenerateTokenRequest>,
|
||||
Body(req): Body<GenerateTokenRequest>,
|
||||
) -> Result<Json<GenerateTokenResponse>, ApiError> {
|
||||
let provider: WatchEventSource = req
|
||||
.provider
|
||||
@@ -182,7 +197,7 @@ pub async fn post_generate_webhook_token(
|
||||
get, path = "/api/v1/settings/webhook-tokens",
|
||||
responses(
|
||||
(status = 200, body = Vec<WebhookTokenDto>),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 401, description = "Unauthorized", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
@@ -216,15 +231,15 @@ pub async fn get_webhook_tokens(
|
||||
params(("id" = Uuid, Path, description = "Webhook token ID")),
|
||||
responses(
|
||||
(status = 204, description = "Token revoked"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Token not found"),
|
||||
(status = 401, description = "Unauthorized", body = api_types::ErrorResponse),
|
||||
(status = 404, description = "Token not found", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn delete_webhook_token(
|
||||
State(state): State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
Path(id): Path<Uuid>,
|
||||
PathId(id): PathId<Uuid>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
let cmd = RevokeWebhookTokenCommand {
|
||||
user_id: user.0.value(),
|
||||
@@ -239,7 +254,7 @@ pub async fn delete_webhook_token(
|
||||
get, path = "/api/v1/watch-queue",
|
||||
responses(
|
||||
(status = 200, body = Vec<WatchQueueEntryDto>),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 401, description = "Unauthorized", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
@@ -273,15 +288,15 @@ pub async fn get_watch_queue(
|
||||
request_body = ConfirmWatchRequest,
|
||||
responses(
|
||||
(status = 200, body = ConfirmWatchResponse),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 403, description = "Forbidden — not your watch event"),
|
||||
(status = 401, description = "Unauthorized", body = api_types::ErrorResponse),
|
||||
(status = 403, description = "Forbidden — not your watch event", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn post_confirm_watch_events(
|
||||
State(state): State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
Json(req): Json<ConfirmWatchRequest>,
|
||||
Body(req): Body<ConfirmWatchRequest>,
|
||||
) -> Result<Json<ConfirmWatchResponse>, ApiError> {
|
||||
let cmd = ConfirmWatchEventsCommand {
|
||||
user_id: user.0.value(),
|
||||
@@ -307,15 +322,15 @@ pub async fn post_confirm_watch_events(
|
||||
request_body = DismissWatchRequest,
|
||||
responses(
|
||||
(status = 200, body = DismissWatchResponse),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 403, description = "Forbidden — not your watch event"),
|
||||
(status = 401, description = "Unauthorized", body = api_types::ErrorResponse),
|
||||
(status = 403, description = "Forbidden — not your watch event", body = api_types::ErrorResponse),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn post_dismiss_watch_events(
|
||||
State(state): State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
Json(req): Json<DismissWatchRequest>,
|
||||
Body(req): Body<DismissWatchRequest>,
|
||||
) -> Result<Json<DismissWatchResponse>, ApiError> {
|
||||
let cmd = DismissWatchEventsCommand {
|
||||
user_id: user.0.value(),
|
||||
|
||||
Reference in New Issue
Block a user