CoolFace
Apppublic

knai/school-system

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
main.py173 linesDownload Raw Back to root
1from fastapi import FastAPI2from fastapi.middleware.cors import CORSMiddleware3from sqlalchemy import text4from sqlalchemy.orm import Session5from routes import (6    auth_router, students_router, fees_router,7    payments_router, attendance_router, marks_router,8    messages_router, classes_router, teachers_router,9    users_router, admins_router, settings_router, homework_router,10    schools_router11)12from config import settings13import random14import string15 16 17def _random_link_code() -> str:18    """7-char uppercase alphanumeric code (no 0/O/1/I to avoid confusion)."""19    chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'20    return ''.join(random.choices(chars, k=3)) + '-' + ''.join(random.choices(chars, k=3))21 22app = FastAPI(23    title="School Management System API",24    description="Complete school management backend with FastAPI",25    version="1.0.0",26    docs_url="/docs",27    redoc_url="/redoc",28)29 30# CORS - allow Next.js frontend31_allowed_origins = [settings.FRONTEND_URL, "http://localhost:3000", "http://127.0.0.1:3000"]32if settings.EXTRA_ALLOWED_ORIGINS:33    _allowed_origins += [o.strip() for o in settings.EXTRA_ALLOWED_ORIGINS.split(",") if o.strip()]34 35app.add_middleware(36    CORSMiddleware,37    allow_origins=_allowed_origins,38    allow_credentials=True,39    allow_methods=["*"],40    allow_headers=["*"],41)42 43# Register all routers44app.include_router(auth_router)45app.include_router(students_router)46app.include_router(fees_router)47app.include_router(payments_router)48app.include_router(attendance_router)49app.include_router(marks_router)50app.include_router(messages_router)51app.include_router(classes_router)52app.include_router(teachers_router)53app.include_router(users_router)54app.include_router(admins_router)55app.include_router(settings_router)56app.include_router(homework_router)57app.include_router(schools_router)58 59 60def _safe_add_column(conn, table: str, col_name: str, col_type: str):61    """Add column if it doesn't exist, handling both PG (IF NOT EXISTS) and fallback."""62    try:63        conn.execute(text(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {col_name} {col_type}"))64        conn.commit()65    except Exception:66        try:67            conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {col_name} {col_type}"))68            conn.commit()69        except Exception:70            pass  # Column already exists71 72 73@app.on_event("startup")74async def startup_migrate():75    """Add missing columns and backfill existing rows."""76    from database import engine77    from models import Student78    from models.models import Base, Homework as _HW, School as _School, TokenBlacklist as _TB79 80    with engine.connect() as conn:81        # ── Create schools table (new) ─────────────────────────────────82        try:83            Base.metadata.create_all(engine, tables=[_School.__table__], checkfirst=True)84        except Exception:85            pass86 87        # homeworks table — use SQLAlchemy ORM to handle dialect differences88        try:89            Base.metadata.create_all(engine, tables=[_HW.__table__], checkfirst=True)90        except Exception:91            pass92 93        # token_blacklist table94        try:95            Base.metadata.create_all(engine, tables=[_TB.__table__], checkfirst=True)96        except Exception:97            pass98 99        # Cleanup expired tokens on every startup100        try:101            conn.execute(text("DELETE FROM token_blacklist WHERE expires_at < NOW()"))102            conn.commit()103        except Exception:104            pass105 106        # link_code on students107        _safe_add_column(conn, "students", "link_code", "VARCHAR(10)")108 109        # school_id on all affected tables (nullable INTEGER, no FK constraint)110        school_id_tables = [111            "users",112            "classes",113            "fee_structures",114            "fee_invoices",115            "homeworks",116            "attendance",117            "marks",118        ]119        for table in school_id_tables:120            _safe_add_column(conn, table, "school_id", "INTEGER")121 122        # school_id on school_settings (unique)123        _safe_add_column(conn, "school_settings", "school_id", "INTEGER")124 125        # admin_permissions on school_settings126        _safe_add_column(conn, "school_settings", "admin_permissions", "TEXT")127 128        # JazzCash payment gateway columns on school_settings129        jazzcash_cols = [130            ("jazzcash_merchant_id",   "VARCHAR(100)"),131            ("jazzcash_password",      "VARCHAR(100)"),132            ("jazzcash_integrity_salt","VARCHAR(100)"),133            ("jazzcash_is_sandbox",    "BOOLEAN DEFAULT TRUE"),134            ("jazzcash_enabled",       "BOOLEAN DEFAULT FALSE"),135            ("jazzcash_number",        "VARCHAR(20)"),136        ]137        for col_name, col_type in jazzcash_cols:138            _safe_add_column(conn, "school_settings", col_name, col_type)139    # Backfill any students missing a link_code140    db = Session(engine)141    try:142        students = db.query(Student).filter(Student.link_code == None).all()143        used = set(r[0] for r in db.query(Student.link_code).filter(Student.link_code != None).all())144        for s in students:145            code = _random_link_code()146            while code in used:147                code = _random_link_code()148            s.link_code = code149            used.add(code)150        if students:151            db.commit()152    finally:153        db.close()154 155 156@app.get("/")157async def root():158    return {159        "message": "School Management System API",160        "version": "1.0.0",161        "docs": "/docs",162        "status": "running"163    }164 165@app.get("/health")166async def health():167    return {"status": "healthy"}168 169 170if __name__ == "__main__":171    import uvicorn172    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)173