use axum::{
extract::{Path, Query},
Json,
};
use utoipa::IntoParams;
use crate::auth::AuthenticatedUser;
use crate::error::Result;
use crate::models::{
AddAssetRequest, Collection, CreateCollectionRequest, PaginatedResponse, PaginationParams,
UpdateCollectionRequest,
};
use crate::service::CollectionService;
#[derive(Debug, serde::Deserialize, IntoParams)]
#[into_params(parameter_in = Query)]
pub struct ListCollectionsParams {
#[serde(default = "default_limit")]
pub limit: i64,
#[serde(default)]
pub cursor: Option<String>,
}
fn default_limit() -> i64 {
20
}
#[utoipa::path(
post,
path = "/api/collections",
request_body = CreateCollectionRequest,
responses(
(status = 200, description = "Collection created successfully", body = Collection),
),
tag = "collections",
)]
pub async fn create_collection(
tx: crate::cedar::enforcement::TenantState,
user: AuthenticatedUser,
Json(request): Json<CreateCollectionRequest>,
) -> Result<Json<Collection>> {
let user_id = &user.user_id;
let collection = CollectionService::create(tx.services(), request, user_id).await?;
Ok(Json(collection))
}
#[utoipa::path(
get,
path = "/api/collections/{id}",
params(
("id" = String, Path, description = "Collection ID"),
),
responses(
(status = 200, description = "Collection found", body = Collection),
(status = 404, description = "Collection not found"),
),
tag = "collections",
)]
pub async fn get_collection(
tx: crate::cedar::enforcement::TenantState,
Path(id): Path<String>,
) -> Result<Json<Collection>> {
let collection = CollectionService::get(tx.services(), &id).await?;
Ok(Json(collection))
}
#[utoipa::path(
put,
path = "/api/collections/{id}",
params(
("id" = String, Path, description = "Collection ID"),
),
request_body = UpdateCollectionRequest,
responses(
(status = 200, description = "Collection updated successfully", body = Collection),
(status = 404, description = "Collection not found"),
),
tag = "collections",
)]
pub async fn update_collection(
tx: crate::cedar::enforcement::TenantState,
user: AuthenticatedUser,
Path(id): Path<String>,
Json(request): Json<UpdateCollectionRequest>,
) -> Result<Json<Collection>> {
let user_id = &user.user_id;
let collection = CollectionService::update(tx.services(), &id, request, user_id).await?;
Ok(Json(collection))
}
#[utoipa::path(
delete,
path = "/api/collections/{id}",
params(
("id" = String, Path, description = "Collection ID"),
),
responses(
(status = 200, description = "Collection deleted successfully"),
(status = 404, description = "Collection not found"),
),
tag = "collections",
)]
pub async fn delete_collection(
tx: crate::cedar::enforcement::TenantState,
user: AuthenticatedUser,
Path(id): Path<String>,
) -> Result<Json<serde_json::Value>> {
let user_id = &user.user_id;
CollectionService::delete(tx.services(), &id, user_id).await?;
Ok(Json(serde_json::json!({ "deleted": true })))
}
#[utoipa::path(
get,
path = "/api/collections",
params(ListCollectionsParams),
responses(
(status = 200, description = "List of collections", body = PaginatedResponse<Collection>),
),
tag = "collections",
)]
pub async fn list_collections(
tx: crate::cedar::enforcement::TenantState,
Query(params): Query<PaginationParams>,
) -> Result<Json<PaginatedResponse<Collection>>> {
let (collections, next_cursor) =
CollectionService::list(tx.services(), params.limit, params.cursor.as_deref()).await?;
Ok(Json(PaginatedResponse {
data: collections,
next_cursor,
total: None,
}))
}
#[utoipa::path(
post,
path = "/api/collections/{id}/assets",
params(
("id" = String, Path, description = "Collection ID"),
),
request_body = AddAssetRequest,
responses(
(status = 200, description = "Asset added to collection successfully"),
(status = 404, description = "Collection or asset not found"),
),
tag = "collections",
)]
pub async fn add_asset(
tx: crate::cedar::enforcement::TenantState,
user: AuthenticatedUser,
Path(id): Path<String>,
Json(request): Json<AddAssetRequest>,
) -> Result<Json<serde_json::Value>> {
let user_id = &user.user_id;
CollectionService::add_asset(tx.services(), &id, &request.asset_id, user_id).await?;
Ok(Json(serde_json::json!({ "added": true })))
}
#[utoipa::path(
delete,
path = "/api/collections/{id}/assets/{asset_id}",
params(
("id" = String, Path, description = "Collection ID"),
("asset_id" = String, Path, description = "Asset ID to remove"),
),
responses(
(status = 200, description = "Asset removed from collection successfully"),
(status = 404, description = "Collection or asset not found"),
),
tag = "collections",
)]
pub async fn remove_asset(
tx: crate::cedar::enforcement::TenantState,
user: AuthenticatedUser,
Path((id, asset_id)): Path<(String, String)>,
) -> Result<Json<serde_json::Value>> {
let user_id = &user.user_id;
CollectionService::remove_asset(tx.services(), &id, &asset_id, user_id).await?;
Ok(Json(serde_json::json!({ "removed": true })))
}