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