Add folder support

This commit is contained in:
Deep 2025-09-22 02:29:47 +05:30
parent df46ba93d1
commit 42976295df
7 changed files with 172 additions and 10 deletions

View file

@ -147,6 +147,8 @@ pub async fn update_cipher(
cipher.favorite,
cipher.folder_id,
cipher.updated_at,
id,
claims.sub,
).map_err(|_|AppError::Database)?
.run()
.await?;

124
src/handlers/folders.rs Normal file
View file

@ -0,0 +1,124 @@
use axum::{extract::State, Json};
use chrono::Utc;
use std::sync::Arc;
use uuid::Uuid;
use worker::{query, Env};
use crate::auth::Claims;
use crate::db;
use crate::error::AppError;
use crate::models::folder::{CreateFolderRequest, Folder, FolderResponse};
use axum::extract::Path;
#[worker::send]
pub async fn create_folder(
claims: Claims,
State(env): State<Arc<Env>>,
Json(payload): Json<CreateFolderRequest>,
) -> Result<Json<FolderResponse>, AppError> {
let db = db::get_db(&env)?;
let now = Utc::now();
let now = now.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
let folder = Folder {
id: Uuid::new_v4().to_string(),
user_id: claims.sub.clone(),
name: payload.name,
created_at: now.clone(),
updated_at: now.clone(),
};
query!(
&db,
"INSERT INTO folders (id, user_id, name, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, ?5)",
folder.id,
folder.user_id,
folder.name,
folder.created_at,
folder.updated_at
)
.map_err(|_| AppError::Database)?
.run()
.await?;
let response = FolderResponse {
id: folder.id,
name: folder.name,
revision_date: folder.updated_at,
object: "folder".to_string(),
};
Ok(Json(response))
}
#[worker::send]
pub async fn delete_folder(
claims: Claims,
State(env): State<Arc<Env>>,
Path(id): Path<String>,
) -> Result<Json<()>, AppError> {
let db = db::get_db(&env)?;
query!(
&db,
"DELETE FROM folders WHERE id = ?1 AND user_id = ?2",
id,
claims.sub
)
.map_err(|_| AppError::Database)?
.run()
.await?;
Ok(Json(()))
}
#[worker::send]
pub async fn update_folder(
claims: Claims,
State(env): State<Arc<Env>>,
Path(id): Path<String>,
Json(payload): Json<CreateFolderRequest>,
) -> Result<Json<FolderResponse>, AppError> {
let db = db::get_db(&env)?;
let now = Utc::now();
let now = now.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
let existing_folder: Folder = query!(
&db,
"SELECT * FROM folders WHERE id = ?1 AND user_id = ?2",
id,
claims.sub
)
.map_err(|_| AppError::Database)?
.first(None)
.await?
.ok_or(AppError::NotFound("Folder not found".to_string()))?;
let folder = Folder {
id: id.clone(),
user_id: existing_folder.user_id,
name: payload.name,
created_at: existing_folder.created_at,
updated_at: now.clone(),
};
query!(
&db,
"UPDATE folders SET name = ?1, updated_at = ?2 WHERE id = ?3 AND user_id = ?4",
folder.name,
folder.updated_at,
folder.id,
folder.user_id
)
.map_err(|_| AppError::Database)?
.run()
.await?;
let response = FolderResponse {
id: folder.id,
name: folder.name,
revision_date: folder.updated_at,
object: "folder".to_string(),
};
Ok(Json(response))
}

View file

@ -3,3 +3,4 @@ pub mod ciphers;
pub mod config;
pub mod identity;
pub mod sync;
pub mod folders;

View file

@ -9,7 +9,7 @@ use crate::{
error::AppError,
models::{
cipher::{Cipher, CipherDBModel},
folder::Folder,
folder::{Folder, FolderResponse},
sync::{Profile, SyncResponse},
user::User,
},
@ -32,13 +32,15 @@ pub async fn get_sync_data(
.ok_or_else(|| AppError::NotFound("User not found".to_string()))?;
// Fetch folders
let folders: Vec<Folder> = db
let folders_db: Vec<Folder> = db
.prepare("SELECT * FROM folders WHERE user_id = ?1")
.bind(&[user_id.clone().into()])?
.all()
.await?
.results()?;
let folders: Vec<FolderResponse> = folders_db.into_iter().map(|f| f.into()).collect();
// Fetch ciphers
let ciphers: Vec<Value> = db
.prepare("SELECT * FROM ciphers WHERE user_id = ?1")
@ -91,4 +93,4 @@ pub async fn get_sync_data(
};
Ok(Json(response))
}
}

View file

@ -1,7 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Folder {
pub id: String,
pub user_id: String,
@ -10,3 +9,33 @@ pub struct Folder {
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FolderResponse {
pub id: String,
pub name: String,
pub revision_date: String,
#[serde(default = "default_object")]
pub object: String,
}
fn default_object() -> String {
"folder".to_string()
}
impl From<Folder> for FolderResponse {
fn from(folder: Folder) -> Self {
FolderResponse {
id: folder.id,
name: folder.name,
revision_date: folder.updated_at,
object: "folder".to_string(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct CreateFolderRequest {
pub name: String,
}

View file

@ -1,4 +1,4 @@
use super::{cipher::Cipher, folder::Folder};
use super::{cipher::Cipher, folder::FolderResponse};
use serde::Serialize;
use serde_json::Value;
@ -33,10 +33,10 @@ pub struct Profile {
#[derive(Debug, Serialize)]
pub struct SyncResponse {
#[serde(rename = "Profile")]
#[serde(rename = "profile")]
pub profile: Profile,
#[serde(rename = "Folders")]
pub folders: Vec<Folder>,
#[serde(rename = "folders")]
pub folders: Vec<FolderResponse>,
#[serde(rename = "ciphers")]
pub ciphers: Vec<Cipher>,
#[serde(rename = "Domains")]

View file

@ -1,11 +1,11 @@
use axum::{
routing::{get, post, put},
routing::{get, post, put, delete},
Router,
};
use std::sync::Arc;
use worker::Env;
use crate::handlers::{accounts, ciphers, config, identity, sync};
use crate::handlers::{accounts, ciphers, config, identity, sync, folders};
pub fn api_router(env: Env) -> Router {
let app_state = Arc::new(env);
@ -28,6 +28,10 @@ pub fn api_router(env: Env) -> Router {
.route("/api/ciphers/create", post(ciphers::create_cipher))
.route("/api/ciphers/{id}", put(ciphers::update_cipher))
.route("/api/ciphers/{id}/delete", put(ciphers::delete_cipher))
// Folders CRUD
.route("/api/folders", post(folders::create_folder))
.route("/api/folders/{id}", put(folders::update_folder))
.route("/api/folders/{id}", delete(folders::delete_folder))
.route("/api/config", get(config::config))
.with_state(app_state)
}