use chrono::Utc;
use uuid::Uuid;
use crate::core::context::KeychainContext;
use crate::core::error::{AppError, AppResult};
use crate::core::models::folder::{Folder, FolderCreateRequest, FolderUpdateRequest};
use crate::core::store::folders as store;
use crate::core::store::folders::FolderRow;
fn to_api(row: FolderRow) -> Folder {
Folder {
id: row.uuid,
name: row.name,
revision_date: row.updated_at,
}
}
fn validated_name(name: &str) -> AppResult<String> {
let trimmed = name.trim();
if trimmed.is_empty() {
return Err(AppError::BadRequest("folder name is required".into()));
}
Ok(trimmed.to_string())
}
async fn owned(ctx: &KeychainContext, user_uuid: &str, id: &str) -> AppResult<FolderRow> {
let row = store::find(ctx.db(), id)
.await?
.ok_or_else(|| AppError::NotFound("folder not found".into()))?;
if row.user_uuid != user_uuid {
return Err(AppError::Forbidden);
}
Ok(row)
}
pub async fn list(ctx: &KeychainContext, user_uuid: &str) -> AppResult<Vec<Folder>> {
let rows = store::find_by_user(ctx.db(), user_uuid).await?;
Ok(rows.into_iter().map(to_api).collect())
}
pub async fn create(
ctx: &KeychainContext,
user_uuid: &str,
req: FolderCreateRequest,
) -> AppResult<Folder> {
let name = validated_name(&req.name)?;
let now = Utc::now();
let row = FolderRow {
uuid: Uuid::new_v4().to_string(),
user_uuid: user_uuid.to_string(),
name,
created_at: now,
updated_at: now,
};
store::insert(ctx.db(), &row).await?;
Ok(to_api(row))
}
pub async fn get(ctx: &KeychainContext, user_uuid: &str, id: &str) -> AppResult<Folder> {
Ok(to_api(owned(ctx, user_uuid, id).await?))
}
pub async fn update(
ctx: &KeychainContext,
user_uuid: &str,
id: &str,
req: FolderUpdateRequest,
) -> AppResult<Folder> {
let mut row = owned(ctx, user_uuid, id).await?;
let name = validated_name(&req.name)?;
let now = Utc::now();
store::update_name(ctx.db(), id, &name, now).await?;
row.name = name;
row.updated_at = now;
Ok(to_api(row))
}
pub async fn delete(ctx: &KeychainContext, user_uuid: &str, id: &str) -> AppResult<()> {
owned(ctx, user_uuid, id).await?;
store::delete(ctx.db(), id).await?;
Ok(())
}