Full project scaffold for TapTrack Hub — cloud SaaS control plane for managing on-prem TapTrack school deployments. ## Infrastructure - Docker Compose: backend (gunicorn+uvicorn), Celery worker + beat, frontend (Vite build + nginx), PostgreSQL 15, Redis 7, nginx proxy - Dockerfile for backend and frontend, nginx reverse proxy config ## Backend (FastAPI + SQLAlchemy async + Celery) Database schema (10 tables): hub_users, schools, licenses, sms_jobs, sms_credit_ledger, invoices, invoice_line_items, school_subscriptions, support_tickets, ticket_replies, audit_logs, announcements Auth: JWT (python-jose) + bcrypt + role-based FastAPI dependencies (get_current_user, require_super_admin, require_school_admin) Routers (11): auth, schools, licenses, sms, billing, tickets, users, dashboard, school_portal, announcements, sync Celery tasks (6): sms.process_queue, billing.generate_monthly_invoices, billing.send_invoice_email, billing.check_overdue, license.check_expiry, reports.send_monthly_reports Services: SMTP email helper (smtplib + Jinja2) Seed script: creates super admin admin@taptrack.io ## Frontend (Vue 3 + Vite + Pinia + Tailwind CSS) Router: 14 routes across super admin + school portal layouts Stores: Pinia auth store with localStorage persistence API client: full axios client for all backend endpoints Layouts: AppLayout (super admin), PortalLayout (school), AuthLayout Components: AppSidebar, PortalSidebar, SidebarItem, KpiCard, StatusBadge, ToastStack Pages: Login, Dashboard, Schools, SchoolDetail, Licenses, SMS, Billing, Tickets, TicketDetail, Users, Announcements, 404 Portal pages: Overview, Billing, SMS Reports, Tickets, Profile ## PAUL Planning Files - .paul/ROADMAP.md: full 15-phase roadmap with detailed scope - .paul/STATE.md: current position, tech stack, architecture notes - .paul/phases/01-setup/01-PLAN.md: complete Phase 1 plan (done) - .paul/phases/02 through 15: README stubs for all future phases
54 lines
2.3 KiB
Python
54 lines
2.3 KiB
Python
"""Super admin dashboard summary."""
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func, and_
|
|
from datetime import date, timedelta
|
|
|
|
from app.auth.dependencies import require_super_admin
|
|
from app.database import get_db
|
|
from app.models.user import HubUser
|
|
from app.models.school import School, SchoolStatus
|
|
from app.models.license import License, LicenseStatus
|
|
from app.models.sms import SmsJob, SmsJobStatus
|
|
from app.models.billing import Invoice, InvoiceStatus
|
|
from app.models.ticket import SupportTicket, TicketStatus
|
|
|
|
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
|
|
|
|
@router.get("/summary")
|
|
async def get_summary(
|
|
_admin: HubUser = Depends(require_super_admin),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
total_schools = (await db.execute(select(func.count()).select_from(School))).scalar_one()
|
|
active_schools = (await db.execute(select(func.count()).where(School.status == SchoolStatus.active))).scalar_one()
|
|
suspended_schools = (await db.execute(select(func.count()).where(School.status == SchoolStatus.suspended))).scalar_one()
|
|
expiring_soon = (await db.execute(
|
|
select(func.count()).where(
|
|
and_(License.expires_at != None, License.expires_at <= date.today() + timedelta(days=30),
|
|
License.status == LicenseStatus.active)
|
|
)
|
|
)).scalar_one()
|
|
open_tickets = (await db.execute(
|
|
select(func.count()).where(SupportTicket.status.in_([TicketStatus.open, TicketStatus.in_progress]))
|
|
)).scalar_one()
|
|
pending_invoices = (await db.execute(
|
|
select(func.count()).where(Invoice.status.in_([InvoiceStatus.sent, InvoiceStatus.overdue]))
|
|
)).scalar_one()
|
|
sms_today = (await db.execute(
|
|
select(func.count()).where(
|
|
and_(func.date(SmsJob.created_at) == date.today(), SmsJob.status == SmsJobStatus.sent)
|
|
)
|
|
)).scalar_one()
|
|
sms_pending = (await db.execute(
|
|
select(func.count()).where(SmsJob.status == SmsJobStatus.pending)
|
|
)).scalar_one()
|
|
|
|
return {
|
|
"schools": {"total": total_schools, "active": active_schools, "suspended": suspended_schools},
|
|
"licenses": {"expiring_soon": expiring_soon},
|
|
"tickets": {"open": open_tickets},
|
|
"invoices": {"pending": pending_invoices},
|
|
"sms": {"sent_today": sms_today, "pending": sms_pending},
|
|
}
|