fix: auto-apply DB schema on fresh install and fix admin registration (#81)
- Auto-apply schema.sql when database tables don't exist (embedded in binary) - Handle missing tables gracefully during admin registration (treat as fresh install) - Fix docker-compose depends_on to wait for postgres healthcheck - Rename personal folder from 'Mi Carpeta' to 'My Folder' with backward compat - Translate remaining Spanish messages to English
This commit is contained in:
Generated
+1
-1
@@ -1686,7 +1686,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "oxicloud"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@ services:
|
||||
networks:
|
||||
- oxicloud
|
||||
depends_on:
|
||||
- postgres
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- "OXICLOUD_DB_CONNECTION_STRING=postgres://postgres:postgres@postgres/oxicloud"
|
||||
- "DATABASE_URL=postgres://postgres:postgres@postgres/oxicloud"
|
||||
|
||||
@@ -179,6 +179,12 @@ impl AuthApplicationService {
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let err_msg = e.to_string();
|
||||
// If the table doesn't exist, this is a fresh install - allow admin creation
|
||||
if err_msg.contains("does not exist") || err_msg.contains("relation") {
|
||||
tracing::info!("Database tables not yet ready, treating as fresh install - allowing admin creation");
|
||||
// Continue with registration - this is a fresh install
|
||||
} else {
|
||||
tracing::error!("Error counting admin users: {}", e);
|
||||
// For security, if we cannot verify, we reject admin creation
|
||||
return Err(DomainError::new(
|
||||
@@ -189,6 +195,7 @@ impl AuthApplicationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine role and quota based on user type
|
||||
// If an explicit "admin" role is provided, use the administrator role
|
||||
@@ -244,7 +251,7 @@ impl AuthApplicationService {
|
||||
|
||||
// Create personal folder for the user
|
||||
if let Some(folder_service) = &self.folder_service {
|
||||
let folder_name = format!("Mi Carpeta - {}", dto.username);
|
||||
let folder_name = format!("My Folder - {}", dto.username);
|
||||
|
||||
match folder_service.create_folder(CreateFolderDto {
|
||||
name: folder_name,
|
||||
@@ -572,7 +579,7 @@ impl AuthApplicationService {
|
||||
|
||||
// 5. Create personal folder for the new admin if folder service is available
|
||||
if let Some(folder_service) = &self.folder_service {
|
||||
let folder_name = format!("Mi Carpeta - {}", dto.username);
|
||||
let folder_name = format!("My Folder - {}", dto.username);
|
||||
|
||||
match folder_service.create_folder(CreateFolderDto {
|
||||
name: folder_name,
|
||||
@@ -951,7 +958,7 @@ impl AuthApplicationService {
|
||||
/// Helper to create a personal folder for a new user
|
||||
async fn create_personal_folder(&self, username: &str, user_id: &str) {
|
||||
if let Some(folder_service) = &self.folder_service {
|
||||
let folder_name = format!("Mi Carpeta - {}", username);
|
||||
let folder_name = format!("My Folder - {}", username);
|
||||
match folder_service.create_folder(CreateFolderDto {
|
||||
name: folder_name.clone(),
|
||||
parent_id: None,
|
||||
|
||||
@@ -19,10 +19,15 @@ const WRITE_BEHIND_THRESHOLD: usize = 256 * 1024;
|
||||
|
||||
/// Helper function to extract username from folder path string
|
||||
fn extract_username_from_path(path: &str) -> Option<String> {
|
||||
if !path.contains("Mi Carpeta - ") {
|
||||
// Support both new ("My Folder - ") and legacy ("Mi Carpeta - ") prefixes
|
||||
let prefix = if path.contains("My Folder - ") {
|
||||
"My Folder - "
|
||||
} else if path.contains("Mi Carpeta - ") {
|
||||
"Mi Carpeta - "
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
let parts: Vec<&str> = path.split("Mi Carpeta - ").collect();
|
||||
};
|
||||
let parts: Vec<&str> = path.split(prefix).collect();
|
||||
if parts.len() <= 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ impl RecentItemsUseCase for RecentService {
|
||||
info!("Removing {} '{}' from recent for user {}", item_type, item_id, user_id);
|
||||
let removed = self.repo.remove_item(user_id, item_id, item_type).await?;
|
||||
info!(
|
||||
"{} {} '{}' de recientes para usuario {}",
|
||||
"{} {} '{}' from recent items for user {}",
|
||||
if removed { "Successfully removed" } else { "Not found" },
|
||||
item_type, item_id, user_id
|
||||
);
|
||||
|
||||
@@ -57,16 +57,17 @@ impl StorageUsageService {
|
||||
let all_folders = self.file_repository.list_files(None).await
|
||||
.map_err(|e| DomainError::internal_error("File repository", e.to_string()))?;
|
||||
|
||||
// Find the user's home folder (usually named "Mi Carpeta - {username}")
|
||||
let home_folder_name = format!("Mi Carpeta - {}", username);
|
||||
debug!("Looking for home folder: {}", home_folder_name);
|
||||
// Find the user's home folder (named "My Folder - {username}" or legacy "Mi Carpeta - {username}")
|
||||
let home_folder_name = format!("My Folder - {}", username);
|
||||
let legacy_folder_name = format!("Mi Carpeta - {}", username);
|
||||
debug!("Looking for home folder: {} or {}", home_folder_name, legacy_folder_name);
|
||||
|
||||
let mut total_usage: i64 = 0;
|
||||
let mut home_folder_id = None;
|
||||
|
||||
// Find the home folder ID
|
||||
// Find the home folder ID (check both new and legacy names)
|
||||
for folder in &all_folders {
|
||||
if folder.name() == home_folder_name {
|
||||
if folder.name() == home_folder_name || folder.name() == legacy_folder_name {
|
||||
home_folder_id = Some(folder.id().to_string());
|
||||
debug!("Found home folder for user {}: ID={}", username, folder.id());
|
||||
break;
|
||||
|
||||
@@ -39,11 +39,29 @@ pub async fn create_database_pool(config: &AppConfig) -> Result<PgPool> {
|
||||
Ok(row) => {
|
||||
let tables_exist: bool = row.get(0);
|
||||
if !tables_exist {
|
||||
tracing::warn!("Database tables do not exist. Please run migrations with: cargo run --bin migrate --features migrations");
|
||||
tracing::warn!("Database tables do not exist. Auto-applying schema...");
|
||||
let schema_sql = include_str!("../../db/schema.sql");
|
||||
match sqlx::raw_sql(schema_sql).execute(&pool).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Database schema applied successfully");
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to auto-apply database schema: {}. You may need to run: psql -f db/schema.sql", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
tracing::warn!("Could not verify migration status. Please run migrations with: cargo run --bin migrate --features migrations");
|
||||
tracing::warn!("Could not verify migration status. Attempting to auto-apply schema...");
|
||||
let schema_sql = include_str!("../../db/schema.sql");
|
||||
match sqlx::raw_sql(schema_sql).execute(&pool).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Database schema applied successfully");
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to auto-apply database schema: {}. You may need to run: psql -f db/schema.sql", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ impl UserPgRepository {
|
||||
impl UserRepository for UserPgRepository {
|
||||
/// Creates a new user using a transaction
|
||||
async fn create_user(&self, user: User) -> UserRepositoryResult<User> {
|
||||
// Creamos una copia del usuario para el closure
|
||||
// Create a copy of the user for the closure
|
||||
let user_clone = user.clone();
|
||||
|
||||
with_transaction(
|
||||
|
||||
@@ -43,7 +43,7 @@ impl PasswordHasherPort for Argon2PasswordHasher {
|
||||
.map_err(|e| DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"PasswordHasher",
|
||||
format!("Error al generar hash de password: {}", e)
|
||||
format!("Error generating password hash: {}", e)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ impl PasswordHasherPort for Argon2PasswordHasher {
|
||||
.map_err(|e| DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"PasswordHasher",
|
||||
format!("Error al procesar hash: {}", e)
|
||||
format!("Error processing password hash: {}", e)
|
||||
))?;
|
||||
|
||||
Ok(Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok())
|
||||
|
||||
Reference in New Issue
Block a user