use axum::{ extract::{Path, Query, State}, http::StatusCode, Json, }; use utoipa::OpenApi; use uuid::Uuid; use crate::{ auth::{passkeys::PasskeySummary, TenantScopedConn}, error::AppResult, http::responders::JsonResponse, services::profile::{ ApiTokenCreatedResponse, ApiTokenResponse, CreateApiTokenRequest, ProfileService, RevokePasskeyQuery, }, state::AppState, }; #[utoipa::path( get, path = "/api/profile/passkeys", responses((status = 200, description = "List registered passkeys", body = [PasskeySummary])), tag = "Profile" )] pub async fn list_passkeys( State(state): State, TenantScopedConn { mut conn, user_id, .. }: TenantScopedConn, ) -> AppResult>> { ProfileService::new(&state).list_passkeys(&mut conn, user_id) } #[utoipa::path( get, path = "/api/profile/api-tokens", responses((status = 200, description = "List API tokens", body = [ApiTokenResponse])), tag = "Profile" )] pub async fn list_api_tokens( State(state): State, TenantScopedConn { mut conn, tenant_id, user_id, .. }: TenantScopedConn, ) -> AppResult>> { ProfileService::new(&state).list_api_tokens(&mut conn, tenant_id, user_id) } #[utoipa::path( post, path = "/api/profile/api-tokens", request_body = CreateApiTokenRequest, responses((status = 201, description = "API token created", body = ApiTokenCreatedResponse)), tag = "Profile" )] pub async fn create_api_token( State(state): State, TenantScopedConn { mut conn, tenant_id, user_id, .. }: TenantScopedConn, Json(payload): Json, ) -> AppResult> { ProfileService::new(&state).create_api_token(&mut conn, tenant_id, user_id, payload) } #[utoipa::path( post, path = "/api/profile/api-tokens/{id}/regenerate", params(("id" = Uuid, Path, description = "API token ID")), responses((status = 200, description = "API token regenerated", body = ApiTokenCreatedResponse)), tag = "Profile" )] pub async fn regenerate_api_token( State(state): State, TenantScopedConn { mut conn, tenant_id, user_id, .. }: TenantScopedConn, Path(token_id): Path, ) -> AppResult> { ProfileService::new(&state).regenerate_api_token(&mut conn, tenant_id, user_id, token_id) } #[utoipa::path( delete, path = "/api/profile/api-tokens/{id}", params(("id" = Uuid, Path, description = "API token ID")), responses((status = 204, description = "API token revoked")), tag = "Profile" )] pub async fn delete_api_token( State(state): State, TenantScopedConn { mut conn, user_id, .. }: TenantScopedConn, Path(token_id): Path, ) -> AppResult { ProfileService::new(&state).delete_api_token(&mut conn, user_id, token_id) } #[utoipa::path( delete, path = "/api/profile/passkeys/{id}", params( ("id" = Uuid, Path, description = "Passkey ID"), ("reason" = Option, Query, description = "Optional reason for revoking the passkey") ), responses((status = 204, description = "Passkey revoked")), tag = "Profile" )] pub async fn delete_passkey( State(state): State, TenantScopedConn { mut conn, user_id, .. }: TenantScopedConn, Path(passkey_id): Path, Query(query): Query, ) -> AppResult { ProfileService::new(&state).delete_passkey(&mut conn, user_id, passkey_id, query.reason) } #[derive(OpenApi)] #[openapi( paths( crate::routes::profile::list_api_tokens, crate::routes::profile::create_api_token, crate::routes::profile::regenerate_api_token, crate::routes::profile::delete_api_token, crate::routes::profile::list_passkeys, crate::routes::profile::delete_passkey ), components(schemas( crate::models::ApiCapability, crate::services::profile::ApiTokenResponse, crate::services::profile::ApiTokenCreatedResponse, crate::services::profile::CreateApiTokenRequest, crate::services::profile::RevokePasskeyQuery, crate::auth::passkeys::PasskeySummary )) )] pub struct ProfileApiDoc;