feat(init): Alembic migration fase3 + init_system.py script
Add fase3_user_isolation migration that assigns orphan projects to first admin, and init_system.py script to bootstrap DB, admin user and default SystemConfig on first deploy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
424c37e050
commit
fa39d6b55e
|
|
@ -0,0 +1,43 @@
|
|||
"""fase3_user_isolation
|
||||
|
||||
Revision ID: c9c1383b04bd
|
||||
Revises: fef526537305
|
||||
Create Date: 2026-05-16 09:22:43.640108
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'c9c1383b04bd'
|
||||
down_revision: Union[str, Sequence[str], None] = 'fef526537305'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# No DDL changes needed — schema already includes all fase3 tables (users, system_config, etc.)
|
||||
# from initial_schema migration.
|
||||
# ### end Alembic commands ###
|
||||
|
||||
# Assignar projectes orfes al primer admin
|
||||
op.execute("""
|
||||
UPDATE projects
|
||||
SET user_id = (
|
||||
SELECT id FROM users WHERE role = 'admin' ORDER BY created_at LIMIT 1
|
||||
)
|
||||
WHERE user_id IS NULL
|
||||
AND EXISTS (SELECT 1 FROM users WHERE role = 'admin')
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Inicialitzar el sistema MiroFish per al primer ús.
|
||||
Ús: ADMIN_EMAIL=admin@dev.local ADMIN_PASSWORD=adminpass123 uv run python backend/scripts/init_system.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
|
||||
def main():
|
||||
from app.config import Config
|
||||
from app.db import init_db, get_session
|
||||
from app.models.db_models import UserModel, SystemConfigModel
|
||||
from app.services.auth_service import hash_password
|
||||
from sqlalchemy import select
|
||||
|
||||
db_url = Config.DATABASE_URL
|
||||
short_url = db_url.split('@')[-1] if '@' in db_url else db_url
|
||||
print(f"[init_system] Connecting to: {short_url}")
|
||||
init_db(db_url)
|
||||
|
||||
# Executar migracions Alembic
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
['uv', 'run', 'alembic', 'upgrade', 'head'],
|
||||
cwd=os.path.dirname(__file__) + '/..',
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"[init_system] Alembic warning: {result.stderr}")
|
||||
else:
|
||||
print("[init_system] Alembic migrations: OK")
|
||||
except Exception as e:
|
||||
print(f"[init_system] Alembic skipped: {e}")
|
||||
|
||||
with get_session() as db:
|
||||
# Crear admin si no existeix cap usuari
|
||||
any_user = db.execute(select(UserModel).limit(1)).scalar_one_or_none()
|
||||
if any_user is None:
|
||||
admin_email = Config.ADMIN_EMAIL or input("Admin email: ").strip()
|
||||
admin_password = Config.ADMIN_PASSWORD or input("Admin password: ").strip()
|
||||
if not admin_email or not admin_password:
|
||||
print("[init_system] ERROR: ADMIN_EMAIL i ADMIN_PASSWORD requerits")
|
||||
sys.exit(1)
|
||||
admin = UserModel(
|
||||
email=admin_email.lower(),
|
||||
name="Admin",
|
||||
role="admin",
|
||||
status="active",
|
||||
password_hash=hash_password(admin_password)
|
||||
)
|
||||
db.add(admin)
|
||||
db.commit()
|
||||
print(f"[init_system] Admin creat: {admin_email}")
|
||||
else:
|
||||
print(f"[init_system] Usuaris existents, saltant creació admin")
|
||||
|
||||
# Inserir SystemConfig per defecte si no existeix
|
||||
defaults = [
|
||||
('llm.model_name', Config.LLM_MODEL_NAME, 'string', 'llm', 'Model LLM', '', False),
|
||||
('llm.base_url', Config.LLM_BASE_URL, 'string', 'llm', 'URL base LLM', '', False),
|
||||
('llm.api_key', Config.LLM_API_KEY or '', 'string', 'llm', 'API Key LLM', '', True),
|
||||
('limits.max_projects_per_user', '20', 'int', 'limits', 'Màx. projectes', '', False),
|
||||
('limits.max_simulations', '10', 'int', 'limits', 'Màx. simulacions', '', False),
|
||||
]
|
||||
for key, value, vtype, group, label, desc, is_secret in defaults:
|
||||
existing = db.get(SystemConfigModel, key)
|
||||
if not existing:
|
||||
db.add(SystemConfigModel(
|
||||
key=key, value=value, value_type=vtype,
|
||||
group=group, label=label, description=desc, is_secret=is_secret
|
||||
))
|
||||
db.commit()
|
||||
print("[init_system] SystemConfig per defecte: OK")
|
||||
|
||||
print("[init_system] Inicialització completada.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in New Issue