Files
HermesMessages/backend/app/modules/admin/service.py
Hanzo_dev 798bd14312 feat: initial commit — HermesMessages SaaS platform
Backend (FastAPI + Python 3.12):
- Multi-tenant auth with JWT: login, register, refresh, Meta OAuth
- Business & BusinessConfig management
- WhatsApp webhook with HMAC signature verification
- Bot engine powered by Claude AI
- Calendar availability with Redis caching
- Reservations CRUD with status management
- Dashboard analytics (stats, agenda, peak hours)
- Billing & plan management
- Admin panel with platform-wide stats
- Async bcrypt via asyncio.to_thread
- IntegrityError handling for concurrent registration race conditions

Frontend (React 18 + Vite + Tailwind CSS):
- Multi-step guided registration form with helper text on every field
- Login page with show/hide password toggle
- Protected routes with AuthContext
- Dashboard with stats cards, bar chart, and daily agenda
- Reservations list with search, filters, and inline status actions
- Calendar with weekly view, slot availability, and date blocking
- Config page: business info, schedules, bot personality
- Billing page with plan comparison and usage bar

Design system:
- Bricolage Grotesque + DM Sans typography
- Emerald primary palette with semantic color tokens
- scale(0.97) button press feedback, ease-out animations
- Skeleton loaders, stagger animations, prefers-reduced-motion support
- Accessible: aria-labels, visible focus rings, 4.5:1 contrast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 09:49:41 -05:00

52 lines
1.8 KiB
Python

from fastapi import HTTPException, status
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.modules.admin.schemas import BusinessSummary, PlatformStats
from app.modules.business.models import Business
from app.modules.reservations.models import Reservation
VALID_STATUSES = {"trial", "active", "suspended"}
async def list_businesses(db: AsyncSession) -> list[Business]:
result = await db.execute(select(Business).order_by(Business.created_at.desc()))
return result.scalars().all()
async def get_business(db: AsyncSession, business_id: int) -> Business:
result = await db.execute(select(Business).where(Business.id == business_id))
business = result.scalar_one_or_none()
if not business:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Negocio no encontrado")
return business
async def update_business_status(db: AsyncSession, business_id: int, new_status: str) -> Business:
if new_status not in VALID_STATUSES:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Estado inválido. Opciones: {', '.join(VALID_STATUSES)}",
)
business = await get_business(db, business_id)
business.status = new_status
await db.commit()
await db.refresh(business)
return business
async def get_platform_stats(db: AsyncSession) -> PlatformStats:
total = (await db.execute(select(func.count(Business.id)))).scalar_one()
active = (
await db.execute(
select(func.count(Business.id)).where(Business.status == "active")
)
).scalar_one()
reservations = (await db.execute(select(func.count(Reservation.id)))).scalar_one()
return PlatformStats(
total_businesses=total,
active_businesses=active,
total_reservations=reservations,
)