diff options
Diffstat (limited to 'makima/src/server/handlers/files.rs')
| -rw-r--r-- | makima/src/server/handlers/files.rs | 53 |
1 files changed, 43 insertions, 10 deletions
diff --git a/makima/src/server/handlers/files.rs b/makima/src/server/handlers/files.rs index c65eed5..9634b73 100644 --- a/makima/src/server/handlers/files.rs +++ b/makima/src/server/handlers/files.rs @@ -10,21 +10,30 @@ use uuid::Uuid; use crate::db::models::{CreateFileRequest, FileListResponse, FileSummary, UpdateFileRequest}; use crate::db::repository::{self, RepositoryError}; +use crate::server::auth::Authenticated; use crate::server::messages::ApiError; use crate::server::state::{FileUpdateNotification, SharedState}; -/// List all files for the current owner. +/// List all files for the authenticated user's owner. #[utoipa::path( get, path = "/api/v1/files", responses( (status = 200, description = "List of files", body = FileListResponse), + (status = 401, description = "Unauthorized", body = ApiError), (status = 503, description = "Database not configured", body = ApiError), (status = 500, description = "Internal server error", body = ApiError), ), + security( + ("bearer_auth" = []), + ("api_key" = []) + ), tag = "Files" )] -pub async fn list_files(State(state): State<SharedState>) -> impl IntoResponse { +pub async fn list_files( + State(state): State<SharedState>, + Authenticated(auth): Authenticated, +) -> impl IntoResponse { let Some(ref pool) = state.db_pool else { return ( StatusCode::SERVICE_UNAVAILABLE, @@ -33,7 +42,7 @@ pub async fn list_files(State(state): State<SharedState>) -> impl IntoResponse { .into_response(); }; - match repository::list_files(pool).await { + match repository::list_files_for_owner(pool, auth.owner_id).await { Ok(files) => { let summaries: Vec<FileSummary> = files.into_iter().map(FileSummary::from).collect(); let total = summaries.len() as i64; @@ -54,7 +63,7 @@ pub async fn list_files(State(state): State<SharedState>) -> impl IntoResponse { } } -/// Get a single file by ID. +/// Get a single file by ID (scoped by owner). #[utoipa::path( get, path = "/api/v1/files/{id}", @@ -63,14 +72,20 @@ pub async fn list_files(State(state): State<SharedState>) -> impl IntoResponse { ), responses( (status = 200, description = "File details", body = crate::db::models::File), + (status = 401, description = "Unauthorized", body = ApiError), (status = 404, description = "File not found", body = ApiError), (status = 503, description = "Database not configured", body = ApiError), (status = 500, description = "Internal server error", body = ApiError), ), + security( + ("bearer_auth" = []), + ("api_key" = []) + ), tag = "Files" )] pub async fn get_file( State(state): State<SharedState>, + Authenticated(auth): Authenticated, Path(id): Path<Uuid>, ) -> impl IntoResponse { let Some(ref pool) = state.db_pool else { @@ -81,7 +96,7 @@ pub async fn get_file( .into_response(); }; - match repository::get_file(pool, id).await { + match repository::get_file_for_owner(pool, id, auth.owner_id).await { Ok(Some(file)) => Json(file).into_response(), Ok(None) => ( StatusCode::NOT_FOUND, @@ -107,13 +122,19 @@ pub async fn get_file( responses( (status = 201, description = "File created", body = crate::db::models::File), (status = 400, description = "Invalid request", body = ApiError), + (status = 401, description = "Unauthorized", body = ApiError), (status = 503, description = "Database not configured", body = ApiError), (status = 500, description = "Internal server error", body = ApiError), ), + security( + ("bearer_auth" = []), + ("api_key" = []) + ), tag = "Files" )] pub async fn create_file( State(state): State<SharedState>, + Authenticated(auth): Authenticated, Json(req): Json<CreateFileRequest>, ) -> impl IntoResponse { let Some(ref pool) = state.db_pool else { @@ -124,7 +145,7 @@ pub async fn create_file( .into_response(); }; - match repository::create_file(pool, req).await { + match repository::create_file_for_owner(pool, auth.owner_id, req).await { Ok(file) => (StatusCode::CREATED, Json(file)).into_response(), Err(e) => { tracing::error!("Failed to create file: {}", e); @@ -137,7 +158,7 @@ pub async fn create_file( } } -/// Update an existing file. +/// Update an existing file (scoped by owner). #[utoipa::path( put, path = "/api/v1/files/{id}", @@ -147,15 +168,21 @@ pub async fn create_file( request_body = UpdateFileRequest, responses( (status = 200, description = "File updated", body = crate::db::models::File), + (status = 401, description = "Unauthorized", body = ApiError), (status = 404, description = "File not found", body = ApiError), (status = 409, description = "Version conflict", body = ApiError), (status = 503, description = "Database not configured", body = ApiError), (status = 500, description = "Internal server error", body = ApiError), ), + security( + ("bearer_auth" = []), + ("api_key" = []) + ), tag = "Files" )] pub async fn update_file( State(state): State<SharedState>, + Authenticated(auth): Authenticated, Path(id): Path<Uuid>, Json(req): Json<UpdateFileRequest>, ) -> impl IntoResponse { @@ -185,7 +212,7 @@ pub async fn update_file( updated_fields.push("body".to_string()); } - match repository::update_file(pool, id, req).await { + match repository::update_file_for_owner(pool, id, auth.owner_id, req).await { Ok(Some(file)) => { // Broadcast update notification state.broadcast_file_update(FileUpdateNotification { @@ -233,7 +260,7 @@ pub async fn update_file( } } -/// Delete a file. +/// Delete a file (scoped by owner). #[utoipa::path( delete, path = "/api/v1/files/{id}", @@ -242,14 +269,20 @@ pub async fn update_file( ), responses( (status = 204, description = "File deleted"), + (status = 401, description = "Unauthorized", body = ApiError), (status = 404, description = "File not found", body = ApiError), (status = 503, description = "Database not configured", body = ApiError), (status = 500, description = "Internal server error", body = ApiError), ), + security( + ("bearer_auth" = []), + ("api_key" = []) + ), tag = "Files" )] pub async fn delete_file( State(state): State<SharedState>, + Authenticated(auth): Authenticated, Path(id): Path<Uuid>, ) -> impl IntoResponse { let Some(ref pool) = state.db_pool else { @@ -260,7 +293,7 @@ pub async fn delete_file( .into_response(); }; - match repository::delete_file(pool, id).await { + match repository::delete_file_for_owner(pool, id, auth.owner_id).await { Ok(true) => StatusCode::NO_CONTENT.into_response(), Ok(false) => ( StatusCode::NOT_FOUND, |
