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