removed verbose comments
This commit is contained in:
parent
197be28e46
commit
0f9b390ced
|
@ -50,20 +50,17 @@ impl File {
|
||||||
encryption_key: Option<String>,
|
encryption_key: Option<String>,
|
||||||
encryption_iv: Option<String>,
|
encryption_iv: Option<String>,
|
||||||
) -> Result<Self, AppError> {
|
) -> Result<Self, AppError> {
|
||||||
// Validate parent directory if provided
|
|
||||||
if let Some(parent_id) = dto.parent_id {
|
if let Some(parent_id) = dto.parent_id {
|
||||||
let parent = Self::find_by_id(pool, parent_id).await?;
|
let parent = Self::find_by_id(pool, parent_id).await?;
|
||||||
if parent.file_type != FileType::Directory {
|
if parent.file_type != FileType::Directory {
|
||||||
return Err(AppError::NotADirectory);
|
return Err(AppError::NotADirectory);
|
||||||
}
|
}
|
||||||
// Check if user has access to parent directory
|
|
||||||
if parent.owner_id != owner_id {
|
if parent.owner_id != owner_id {
|
||||||
// TODO: Check if user has write permission through sharing
|
// TODO: Check if user has write permission through sharing
|
||||||
return Err(AppError::AccessDenied);
|
return Err(AppError::AccessDenied);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate filename in the same directory
|
|
||||||
let existing_file = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3",
|
let existing_file = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3",
|
||||||
dto.name, dto.parent_id, owner_id)
|
dto.name, dto.parent_id, owner_id)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
|
@ -73,7 +70,6 @@ impl File {
|
||||||
return Err(AppError::FileAlreadyExists);
|
return Err(AppError::FileAlreadyExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the file record
|
|
||||||
let file = sqlx::query_as!(File,
|
let file = sqlx::query_as!(File,
|
||||||
r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at)
|
r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
|
@ -101,20 +97,17 @@ impl File {
|
||||||
dto: CreateDirectoryDto,
|
dto: CreateDirectoryDto,
|
||||||
owner_id: Uuid,
|
owner_id: Uuid,
|
||||||
) -> Result<Self, AppError> {
|
) -> Result<Self, AppError> {
|
||||||
// Validate parent directory if provided
|
|
||||||
if let Some(parent_id) = dto.parent_id {
|
if let Some(parent_id) = dto.parent_id {
|
||||||
let parent = Self::find_by_id(pool, parent_id).await?;
|
let parent = Self::find_by_id(pool, parent_id).await?;
|
||||||
if parent.file_type != FileType::Directory {
|
if parent.file_type != FileType::Directory {
|
||||||
return Err(AppError::NotADirectory);
|
return Err(AppError::NotADirectory);
|
||||||
}
|
}
|
||||||
// Check if user has access to parent directory
|
|
||||||
if parent.owner_id != owner_id {
|
if parent.owner_id != owner_id {
|
||||||
// TODO: Check if user has write permission through sharing
|
// TODO: Check if user has write permission through sharing
|
||||||
return Err(AppError::AccessDenied);
|
return Err(AppError::AccessDenied);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate directory name in the same parent
|
|
||||||
let existing_dir = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3 AND file_type = 'directory'",
|
let existing_dir = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3 AND file_type = 'directory'",
|
||||||
dto.name, dto.parent_id, owner_id)
|
dto.name, dto.parent_id, owner_id)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
|
@ -124,7 +117,6 @@ impl File {
|
||||||
return Err(AppError::DirectoryAlreadyExists);
|
return Err(AppError::DirectoryAlreadyExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the directory record
|
|
||||||
let directory = sqlx::query_as!(File,
|
let directory = sqlx::query_as!(File,
|
||||||
r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at)
|
r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
|
@ -165,17 +157,13 @@ impl File {
|
||||||
directory_id: Option<Uuid>,
|
directory_id: Option<Uuid>,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
) -> Result<Vec<Self>, AppError> {
|
) -> Result<Vec<Self>, AppError> {
|
||||||
// If directory_id is None, list files at root level for the user
|
|
||||||
let files = if let Some(dir_id) = directory_id {
|
let files = if let Some(dir_id) = directory_id {
|
||||||
// Verify directory exists and user has access
|
|
||||||
let directory = Self::find_by_id(pool, dir_id).await?;
|
let directory = Self::find_by_id(pool, dir_id).await?;
|
||||||
if directory.file_type != FileType::Directory {
|
if directory.file_type != FileType::Directory {
|
||||||
return Err(AppError::NotADirectory);
|
return Err(AppError::NotADirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has access to this directory
|
|
||||||
if directory.owner_id != user_id {
|
if directory.owner_id != user_id {
|
||||||
// TODO: Check if user has read permission through sharing
|
|
||||||
return Err(AppError::AccessDenied);
|
return Err(AppError::AccessDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +175,6 @@ impl File {
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
// List root level files for the user
|
|
||||||
sqlx::query_as!(File,
|
sqlx::query_as!(File,
|
||||||
r#"SELECT id, name, file_type as "file_type: FileType", mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at
|
r#"SELECT id, name, file_type as "file_type: FileType", mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at
|
||||||
FROM files WHERE parent_id IS NULL AND owner_id = $1 ORDER BY file_type, name"#,
|
FROM files WHERE parent_id IS NULL AND owner_id = $1 ORDER BY file_type, name"#,
|
||||||
|
@ -205,27 +192,20 @@ impl File {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
) -> Result<(), AppError> {
|
) -> Result<(), AppError> {
|
||||||
// Find the file/directory
|
|
||||||
let file = Self::find_by_id(pool, id).await?;
|
let file = Self::find_by_id(pool, id).await?;
|
||||||
|
|
||||||
// Check if user has permission to delete
|
|
||||||
if file.owner_id != user_id {
|
if file.owner_id != user_id {
|
||||||
// TODO: Check if user has write permission through sharing
|
|
||||||
return Err(AppError::AccessDenied);
|
return Err(AppError::AccessDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a directory, recursively delete all contents
|
|
||||||
if file.file_type == FileType::Directory {
|
if file.file_type == FileType::Directory {
|
||||||
// Get all files in this directory
|
|
||||||
let files_in_dir = Self::list_directory(pool, Some(id), user_id).await?;
|
let files_in_dir = Self::list_directory(pool, Some(id), user_id).await?;
|
||||||
|
|
||||||
// Recursively delete each file/subdirectory
|
|
||||||
for file in files_in_dir {
|
for file in files_in_dir {
|
||||||
Self::delete(pool, file.id, user_id).await?;
|
Self::delete(pool, file.id, user_id).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the file/directory record
|
|
||||||
sqlx::query!("DELETE FROM files WHERE id = $1", id)
|
sqlx::query!("DELETE FROM files WHERE id = $1", id)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -236,11 +216,9 @@ impl File {
|
||||||
pub async fn get_file_path(pool: &PgPool, id: Uuid) -> Result<String, AppError> {
|
pub async fn get_file_path(pool: &PgPool, id: Uuid) -> Result<String, AppError> {
|
||||||
let file = Self::find_by_id(pool, id).await?;
|
let file = Self::find_by_id(pool, id).await?;
|
||||||
|
|
||||||
// Build the path by traversing parent directories
|
|
||||||
let mut path_parts = vec![file.name.clone()];
|
let mut path_parts = vec![file.name.clone()];
|
||||||
let mut current_parent_id = file.parent_id;
|
let mut current_parent_id = file.parent_id;
|
||||||
|
|
||||||
// Prevent infinite loops by limiting depth
|
|
||||||
let mut depth = 0;
|
let mut depth = 0;
|
||||||
const MAX_DEPTH: usize = 100;
|
const MAX_DEPTH: usize = 100;
|
||||||
|
|
||||||
|
@ -255,10 +233,7 @@ impl File {
|
||||||
depth += 1;
|
depth += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse to get the correct order (root -> leaf)
|
|
||||||
path_parts.reverse();
|
path_parts.reverse();
|
||||||
|
|
||||||
// Join with path separator
|
|
||||||
Ok(path_parts.join("/"))
|
Ok(path_parts.join("/"))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -92,7 +92,6 @@ async fn upload_file(
|
||||||
return Err(AppError::StorageQuotaExceeded("Storage quota exceeded".to_string()));
|
return Err(AppError::StorageQuotaExceeded("Storage quota exceeded".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create storage service
|
|
||||||
let storage_service = StorageService::new(&config.storage_path);
|
let storage_service = StorageService::new(&config.storage_path);
|
||||||
|
|
||||||
// Create file record in database
|
// Create file record in database
|
||||||
|
@ -122,29 +121,22 @@ async fn download_file(
|
||||||
Extension(encryption_service): Extension<EncryptionService>,
|
Extension(encryption_service): Extension<EncryptionService>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, AppError> {
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
// Get file metadata
|
|
||||||
let file = FileModel::find_by_id(&pool, id).await?;
|
let file = FileModel::find_by_id(&pool, id).await?;
|
||||||
|
|
||||||
// Check if user has access to this file
|
|
||||||
if file.owner_id != auth_user.id {
|
if file.owner_id != auth_user.id {
|
||||||
return Err(AppError::AccessDenied("You don't have access to this file".to_string()));
|
return Err(AppError::AccessDenied("You don't have access to this file".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a file (not a directory)
|
|
||||||
if file.file_type != FileType::File {
|
if file.file_type != FileType::File {
|
||||||
return Err(AppError::InvalidInput("Cannot download a directory".to_string()));
|
return Err(AppError::InvalidInput("Cannot download a directory".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create storage service
|
|
||||||
let storage_service = StorageService::new(&config.storage_path);
|
let storage_service = StorageService::new(&config.storage_path);
|
||||||
|
|
||||||
// Read encrypted file from disk
|
|
||||||
let encrypted_data = storage_service.read_file(auth_user.id, file.id).await?;
|
let encrypted_data = storage_service.read_file(auth_user.id, file.id).await?;
|
||||||
|
|
||||||
// Decrypt file
|
|
||||||
let decrypted_data = encryption_service.decrypt(&encrypted_data)?;
|
let decrypted_data = encryption_service.decrypt(&encrypted_data)?;
|
||||||
|
|
||||||
// Set up response headers
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
axum::http::header::CONTENT_TYPE,
|
axum::http::header::CONTENT_TYPE,
|
||||||
|
@ -155,7 +147,6 @@ async fn download_file(
|
||||||
format!("attachment; filename=\"{}\"", file.name).parse().unwrap(),
|
format!("attachment; filename=\"{}\"", file.name).parse().unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a stream from the decrypted data
|
|
||||||
let stream = tokio_util::io::ReaderStream::new(std::io::Cursor::new(decrypted_data));
|
let stream = tokio_util::io::ReaderStream::new(std::io::Cursor::new(decrypted_data));
|
||||||
let body = StreamBody::new(stream);
|
let body = StreamBody::new(stream);
|
||||||
|
|
||||||
|
@ -177,27 +168,16 @@ async fn delete_file(
|
||||||
Extension(config): Extension<Arc<Config>>,
|
Extension(config): Extension<Arc<Config>>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, AppError> {
|
) -> Result<StatusCode, AppError> {
|
||||||
// Get file metadata
|
|
||||||
let file = FileModel::find_by_id(&pool, id).await?;
|
let file = FileModel::find_by_id(&pool, id).await?;
|
||||||
|
|
||||||
// Check if user has access to this file
|
|
||||||
if file.owner_id != auth_user.id {
|
if file.owner_id != auth_user.id {
|
||||||
return Err(AppError::AccessDenied("You don't have access to this file".to_string()));
|
return Err(AppError::AccessDenied("You don't have access to this file".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create storage service
|
|
||||||
let storage_service = StorageService::new(&config.storage_path);
|
let storage_service = StorageService::new(&config.storage_path);
|
||||||
|
|
||||||
// Delete file or directory
|
|
||||||
if file.file_type == FileType::Directory {
|
if file.file_type == FileType::Directory {
|
||||||
// For directories, recursively delete all contents
|
|
||||||
FileModel::delete_directory_recursive(&pool, id, auth_user.id, &storage_service).await?;
|
FileModel::delete_directory_recursive(&pool, id, auth_user.id, &storage_service).await?;
|
||||||
} else {
|
} else {
|
||||||
// For files, delete the file from storage and update user quota
|
|
||||||
storage_service.delete_file(auth_user.id, file.id).await?;
|
storage_service.delete_file(auth_user.id, file.id).await?;
|
||||||
FileModel::delete(&pool, id).await?;
|
FileModel::delete(&pool, id).await?;
|
||||||
|
|
||||||
// Update user storage used
|
|
||||||
crate::models::user::User::update_storage_used(&pool, auth_user.id, -file.size).await?;
|
crate::models::user::User::update_storage_used(&pool, auth_user.id, -file.size).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,8 @@ async fn list_shares(
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
Extension(pool): Extension<PgPool>,
|
Extension(pool): Extension<PgPool>,
|
||||||
) -> Result<Json<Vec<ShareResponse>>, AppError> {
|
) -> Result<Json<Vec<ShareResponse>>, AppError> {
|
||||||
// Get shares owned by the user
|
|
||||||
let owned_shares = Share::list_by_owner(&pool, auth_user.id).await?;
|
let owned_shares = Share::list_by_owner(&pool, auth_user.id).await?;
|
||||||
|
|
||||||
// Get shares shared with the user
|
|
||||||
let received_shares = Share::list_by_recipient(&pool, auth_user.id).await?;
|
let received_shares = Share::list_by_recipient(&pool, auth_user.id).await?;
|
||||||
|
|
||||||
// Combine and get full details for each share
|
|
||||||
let mut share_responses = Vec::new();
|
let mut share_responses = Vec::new();
|
||||||
|
|
||||||
for share in owned_shares {
|
for share in owned_shares {
|
||||||
|
@ -61,8 +56,6 @@ async fn get_share(
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<Json<ShareResponse>, AppError> {
|
) -> Result<Json<ShareResponse>, AppError> {
|
||||||
let share = Share::find_by_id(&pool, id).await?;
|
let share = Share::find_by_id(&pool, id).await?;
|
||||||
|
|
||||||
// Check if user has access to this share
|
|
||||||
if share.owner_id != auth_user.id && share.recipient_id != Some(auth_user.id) {
|
if share.owner_id != auth_user.id && share.recipient_id != Some(auth_user.id) {
|
||||||
return Err(AppError::AccessDenied("You don't have access to this share".to_string()));
|
return Err(AppError::AccessDenied("You don't have access to this share".to_string()));
|
||||||
}
|
}
|
||||||
|
@ -85,8 +78,6 @@ async fn access_shared_file(
|
||||||
Path(access_key): Path<String>,
|
Path(access_key): Path<String>,
|
||||||
) -> Result<Json<ShareResponse>, AppError> {
|
) -> Result<Json<ShareResponse>, AppError> {
|
||||||
let share = Share::find_by_access_key(&pool, &access_key).await?;
|
let share = Share::find_by_access_key(&pool, &access_key).await?;
|
||||||
|
|
||||||
// Check if share is valid (not expired)
|
|
||||||
if let Some(expires_at) = share.expires_at {
|
if let Some(expires_at) = share.expires_at {
|
||||||
if expires_at < time::OffsetDateTime::now_utc() {
|
if expires_at < time::OffsetDateTime::now_utc() {
|
||||||
return Err(AppError::AccessDenied("This share has expired".to_string()));
|
return Err(AppError::AccessDenied("This share has expired".to_string()));
|
||||||
|
|
|
@ -36,7 +36,6 @@ async fn update_password(
|
||||||
Extension(pool): Extension<PgPool>,
|
Extension(pool): Extension<PgPool>,
|
||||||
Json(update_dto): Json<UpdatePasswordDto>,
|
Json(update_dto): Json<UpdatePasswordDto>,
|
||||||
) -> Result<StatusCode, AppError> {
|
) -> Result<StatusCode, AppError> {
|
||||||
// Verify current password
|
|
||||||
let user = User::find_by_id(&pool, auth_user.id).await?;
|
let user = User::find_by_id(&pool, auth_user.id).await?;
|
||||||
let is_valid = crate::utils::password::verify_password(
|
let is_valid = crate::utils::password::verify_password(
|
||||||
&update_dto.current_password,
|
&update_dto.current_password,
|
||||||
|
@ -47,10 +46,8 @@ async fn update_password(
|
||||||
return Err(AppError::AuthenticationError("Current password is incorrect".to_string()));
|
return Err(AppError::AuthenticationError("Current password is incorrect".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash new password
|
|
||||||
let new_password_hash = crate::utils::password::hash_password(&update_dto.new_password)?;
|
let new_password_hash = crate::utils::password::hash_password(&update_dto.new_password)?;
|
||||||
|
|
||||||
// Update password in database
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE users
|
UPDATE users
|
||||||
|
|
|
@ -10,12 +10,9 @@ pub struct EncryptionService {
|
||||||
|
|
||||||
impl EncryptionService {
|
impl EncryptionService {
|
||||||
pub fn new(master_key: &str) -> Self {
|
pub fn new(master_key: &str) -> Self {
|
||||||
// Decode the base64 master key
|
|
||||||
let key_bytes = general_purpose::STANDARD
|
let key_bytes = general_purpose::STANDARD
|
||||||
.decode(master_key)
|
.decode(master_key)
|
||||||
.expect("Invalid master key format");
|
.expect("Invalid master key format");
|
||||||
|
|
||||||
// Create the cipher
|
|
||||||
let cipher = Aes256Gcm::new_from_slice(&key_bytes)
|
let cipher = Aes256Gcm::new_from_slice(&key_bytes)
|
||||||
.expect("Invalid key length");
|
.expect("Invalid key length");
|
||||||
|
|
||||||
|
@ -23,17 +20,12 @@ impl EncryptionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> {
|
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> {
|
||||||
// Generate a random 12-byte nonce
|
|
||||||
let mut nonce_bytes = [0u8; 12];
|
let mut nonce_bytes = [0u8; 12];
|
||||||
OsRng.fill_bytes(&mut nonce_bytes);
|
OsRng.fill_bytes(&mut nonce_bytes);
|
||||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||||
|
|
||||||
// Encrypt the data
|
|
||||||
let ciphertext = self.cipher
|
let ciphertext = self.cipher
|
||||||
.encrypt(nonce, data)
|
.encrypt(nonce, data)
|
||||||
.map_err(|e| AppError::EncryptionError(e.to_string()))?;
|
.map_err(|e| AppError::EncryptionError(e.to_string()))?;
|
||||||
|
|
||||||
// Combine nonce and ciphertext
|
|
||||||
let mut result = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
|
let mut result = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
|
||||||
result.extend_from_slice(&nonce_bytes);
|
result.extend_from_slice(&nonce_bytes);
|
||||||
result.extend_from_slice(&ciphertext);
|
result.extend_from_slice(&ciphertext);
|
||||||
|
@ -42,15 +34,12 @@ impl EncryptionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> {
|
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> {
|
||||||
// Extract nonce and ciphertext
|
|
||||||
if data.len() < 12 {
|
if data.len() < 12 {
|
||||||
return Err(AppError::EncryptionError("Invalid encrypted data format".to_string()));
|
return Err(AppError::EncryptionError("Invalid encrypted data format".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let nonce = Nonce::from_slice(&data[..12]);
|
let nonce = Nonce::from_slice(&data[..12]);
|
||||||
let ciphertext = &data[12..];
|
let ciphertext = &data[12..];
|
||||||
|
|
||||||
// Decrypt the data
|
|
||||||
let plaintext = self.cipher
|
let plaintext = self.cipher
|
||||||
.decrypt(nonce, ciphertext)
|
.decrypt(nonce, ciphertext)
|
||||||
.map_err(|e| AppError::EncryptionError(e.to_string()))?;
|
.map_err(|e| AppError::EncryptionError(e.to_string()))?;
|
||||||
|
@ -59,11 +48,9 @@ impl EncryptionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_master_key() -> String {
|
pub fn generate_master_key() -> String {
|
||||||
// Generate a random 32-byte key
|
|
||||||
let mut key = [0u8; 32];
|
let mut key = [0u8; 32];
|
||||||
OsRng.fill_bytes(&mut key);
|
OsRng.fill_bytes(&mut key);
|
||||||
|
|
||||||
// Encode as base64
|
|
||||||
general_purpose::STANDARD.encode(key)
|
general_purpose::STANDARD.encode(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,13 +18,12 @@ impl StorageService {
|
||||||
pub async fn save_file(&self, user_id: Uuid, file_id: Uuid, data: &[u8]) -> Result<(), AppError> {
|
pub async fn save_file(&self, user_id: Uuid, file_id: Uuid, data: &[u8]) -> Result<(), AppError> {
|
||||||
let file_path = self.get_file_path(user_id, file_id);
|
let file_path = self.get_file_path(user_id, file_id);
|
||||||
|
|
||||||
// Ensure the directory exists
|
|
||||||
if let Some(parent) = file_path.parent() {
|
if let Some(parent) = file_path.parent() {
|
||||||
fs::create_dir_all(parent).await
|
fs::create_dir_all(parent).await
|
||||||
.map_err(|e| AppError::IoError(e.to_string()))?;
|
.map_err(|e| AppError::IoError(e.to_string()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the file
|
|
||||||
fs::write(&file_path, data).await
|
fs::write(&file_path, data).await
|
||||||
.map_err(|e| AppError::IoError(e.to_string()))?;
|
.map_err(|e| AppError::IoError(e.to_string()))?;
|
||||||
|
|
||||||
|
@ -34,7 +33,6 @@ impl StorageService {
|
||||||
pub async fn read_file(&self, user_id: Uuid, file_id: Uuid) -> Result<Vec<u8>, AppError> {
|
pub async fn read_file(&self, user_id: Uuid, file_id: Uuid) -> Result<Vec<u8>, AppError> {
|
||||||
let file_path = self.get_file_path(user_id, file_id);
|
let file_path = self.get_file_path(user_id, file_id);
|
||||||
|
|
||||||
// Read the file
|
|
||||||
let data = fs::read(&file_path).await
|
let data = fs::read(&file_path).await
|
||||||
.map_err(|e| AppError::IoError(e.to_string()))?;
|
.map_err(|e| AppError::IoError(e.to_string()))?;
|
||||||
|
|
||||||
|
@ -44,12 +42,10 @@ impl StorageService {
|
||||||
pub async fn delete_file(&self, user_id: Uuid, file_id: Uuid) -> Result<(), AppError> {
|
pub async fn delete_file(&self, user_id: Uuid, file_id: Uuid) -> Result<(), AppError> {
|
||||||
let file_path = self.get_file_path(user_id, file_id);
|
let file_path = self.get_file_path(user_id, file_id);
|
||||||
|
|
||||||
// Check if file exists
|
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the file
|
|
||||||
fs::remove_file(&file_path).await
|
fs::remove_file(&file_path).await
|
||||||
.map_err(|e| AppError::IoError(e.to_string()))?;
|
.map_err(|e| AppError::IoError(e.to_string()))?;
|
||||||
|
|
||||||
|
@ -59,7 +55,6 @@ impl StorageService {
|
||||||
pub async fn create_user_directory(&self, user_id: Uuid) -> Result<(), AppError> {
|
pub async fn create_user_directory(&self, user_id: Uuid) -> Result<(), AppError> {
|
||||||
let dir_path = self.get_user_directory(user_id);
|
let dir_path = self.get_user_directory(user_id);
|
||||||
|
|
||||||
// Create the directory if it doesn't exist
|
|
||||||
if !dir_path.exists() {
|
if !dir_path.exists() {
|
||||||
fs::create_dir_all(&dir_path).await
|
fs::create_dir_all(&dir_path).await
|
||||||
.map_err(|e| AppError::IoError(e.to_string()))?;
|
.map_err(|e| AppError::IoError(e.to_string()))?;
|
||||||
|
|
Loading…
Reference in a new issue