summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/files.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server/handlers/files.rs')
-rw-r--r--makima/src/server/handlers/files.rs53
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,