melodix-api / app /main.py
GitHub Action
deploy from github actions
4f1c41c
Raw
History Blame Contribute Delete
7.54 kB
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles
from app.database import engine, Base
from app.routers import auth, songs, lyrics, iglesia, aprendizaje, midi, alabanza, workers, admin, migration
from app.models.user import User, AdminUser, AdminNotification, LandingGalleryItem
from app.models.process import Task, Worker
from app.models.lyrics import Lyric, Repertorio, Group, UserGroup, Invitation
from app.models.iglesia import Iglesia, IglesiaMember, IglesiaRol, Calendario, Servicio, Asistencia, Finanza, Momento, RelacionFamiliar, Inventario, Miembro, DismissedVisita
from app.models.sync import B2SyncQueue, MigrationBackup
import app.models.aprendizaje # Registrar modelos de aprendizaje
import app.models.process # Asegura que las tablas de tareas se registren en Base
import app.models.midi # Registrar modelos de partituras/MIDI
import os
# Crear tablas (en producción usar Alembic)
Base.metadata.create_all(bind=engine)
# Migración en caliente: añadir columnas que pueden no existir en BDs antiguas
_migrations = [
# users
"ALTER TABLE users ADD COLUMN IF NOT EXISTS avatar_url VARCHAR",
# tasks
"ALTER TABLE tasks ADD COLUMN IF NOT EXISTS chords TEXT",
# b2_sync_queue
"ALTER TABLE b2_sync_queue ADD COLUMN IF NOT EXISTS is_uploaded BOOLEAN DEFAULT TRUE",
"ALTER TABLE b2_sync_queue ADD COLUMN IF NOT EXISTS uploading_by TEXT DEFAULT NULL",
"ALTER TABLE b2_sync_queue ADD COLUMN IF NOT EXISTS uploading_claimed_at TIMESTAMP DEFAULT NULL",
"ALTER TABLE b2_sync_queue ADD COLUMN IF NOT EXISTS missing_stems JSONB DEFAULT NULL",
# lyrics
"ALTER TABLE lyrics ADD COLUMN IF NOT EXISTS transpose_offset INTEGER DEFAULT 0",
# dismissed_visitas (creada en caliente si no existe)
"""
CREATE TABLE IF NOT EXISTS dismissed_visitas (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
miembro_id INTEGER REFERENCES miembros(id),
dismissed_at TIMESTAMP DEFAULT NOW()
)
""",
# finanzas
"ALTER TABLE finanzas ADD COLUMN IF NOT EXISTS reference_uuid TEXT",
# workers
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS user_id INTEGER",
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS token VARCHAR",
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS command VARCHAR DEFAULT 'none'",
"ALTER TABLE workers ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT FALSE",
# torneos
"ALTER TABLE torneos ADD COLUMN IF NOT EXISTS primero_termino_en VARCHAR",
# tasks — bpm y beats
"ALTER TABLE tasks ADD COLUMN IF NOT EXISTS bpm FLOAT",
"ALTER TABLE tasks ADD COLUMN IF NOT EXISTS beats TEXT",
]
from sqlalchemy import text as _sql_text
for _mig_sql in _migrations:
try:
with engine.begin() as _conn:
_conn.execute(_sql_text(_mig_sql))
except Exception as _mig_err:
pass # Columna ya existe u otro error no crítico
# Sembrar usuario Administrador por defecto si no existe
from app.database import SessionLocal
from app.routers.auth import get_password_hash
db = SessionLocal()
try:
admin_exists = db.query(AdminUser).filter(AdminUser.username == "isaacsalgado105@gmail.com").first()
if not admin_exists:
admin_pass = os.getenv("DEFAULT_ADMIN_PASSWORD", "Dis147258369")
hashed = get_password_hash(admin_pass)
admin_user = AdminUser(
username="isaacsalgado105@gmail.com",
hashed_password=hashed,
perm_users=True,
perm_workers=True,
perm_churches=True,
perm_groups=True,
perm_notifications=True
)
db.add(admin_user)
db.commit()
# Generar una notificación inicial de bienvenida
initial_notif = AdminNotification(
title="Bienvenido a Melodix Admin",
message="El panel de control administrativo y sistema de monitoreo ha sido configurado.",
read=False
)
db.add(initial_notif)
db.commit()
print("Usuario Administrador por defecto sembrado exitosamente.")
else:
# Migración en caliente: si las columnas de permisos son NULL, asignar True
changed = False
for perm_col in ('perm_users', 'perm_workers', 'perm_churches', 'perm_groups', 'perm_notifications'):
if getattr(admin_exists, perm_col, None) is None:
setattr(admin_exists, perm_col, True)
changed = True
if changed:
db.commit()
print("Permisos del administrador actualizados a True (migración en caliente).")
except Exception as e:
print(f"Error sembrando usuario administrador: {e}")
finally:
db.close()
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from app.websocket_manager import manager
app = FastAPI()
@app.on_event("startup")
async def startup_event():
import asyncio
manager.loop = asyncio.get_running_loop()
print("[WebSocket] Event loop registered in connection manager.")
@app.websocket("/ws/{user_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: int):
await manager.connect(user_id, websocket)
try:
while True:
# Mantener la conexión abierta recibiendo datos dummy
await websocket.receive_text()
except WebSocketDisconnect:
manager.disconnect(user_id)
# Configuración CORS
allowed_origins_env = os.getenv("ALLOWED_ORIGINS", "*")
if allowed_origins_env and allowed_origins_env != "*":
origins = [origin.strip() for origin in allowed_origins_env.split(",") if origin.strip()]
else:
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(GZipMiddleware, minimum_size=500)
# Servir archivos estáticos (audios)
# Ajustar rutas relativas si es necesario
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROCESSED_DIR = os.path.join(BASE_DIR, "processed")
os.makedirs(PROCESSED_DIR, exist_ok=True)
app.mount("/audios", StaticFiles(directory=PROCESSED_DIR), name="audios")
# Incluir routers
app.include_router(auth.router)
app.include_router(songs.router)
app.include_router(lyrics.router)
app.include_router(iglesia.router)
app.include_router(aprendizaje.router)
app.include_router(midi.router)
app.include_router(alabanza.router)
app.include_router(workers.router)
app.include_router(admin.router)
app.include_router(migration.router)
@app.get("/")
def read_root():
return {"message": "Melodix API is running"}
@app.get("/health")
def health_check():
"""Diagnóstico de conectividad con la base de datos y estado de tablas."""
import os
from sqlalchemy import text, inspect
from app.database import engine
try:
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
inspector = inspect(engine)
tables = inspector.get_table_names()
return {
"status": "ok",
"database": os.getenv("DATABASE_URL", "SQLite (local)").split("@")[-1] if os.getenv("DATABASE_URL") else "SQLite (local)",
"tables_count": len(tables),
"b2_sync_queue_exists": "b2_sync_queue" in tables,
"users_exists": "users" in tables,
"devices_exists": "devices" in tables,
"tables": tables
}
except Exception as e:
return {"status": "error", "detail": str(e)}