gmal-scope-builder/backend/app/models/user.py
DJP c49f83a5a2 Role-based access control: Viewer / Editor / Admin
Backend:
- AppUser model with email, name, role (viewer/editor/admin), azure_oid
- Users API: GET /users/me (current user + role), GET /users (admin: list all),
  PUT /users/{id}/role (admin: change role)
- Auto-create user on first login: first user = admin, rest = editor
- get_or_create_user helper for role lookup
- require_role helper for permission checks

Frontend:
- UserRoleContext provides role to all components
- useUserRole() hook: isAdmin, isEditor, isViewer
- Nav items filtered by role: GMAL Editor + Users only for admin
- Dashboard: Ingest button admin-only, New Project editor-only
- User Management page: list all users, change roles via dropdown
- Role badges: admin (red), editor (gold), viewer (grey)

Roles:
- Viewer: view projects, download exports
- Editor: create/edit projects, upload, match, build ratecards
- Admin: all + GMAL Editor, data ingest, user management

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:29:04 -04:00

27 lines
1 KiB
Python

"""User model with role-based access control."""
import enum
from datetime import datetime
from sqlalchemy import String, DateTime, Enum
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class UserRole(str, enum.Enum):
VIEWER = "viewer" # View projects and download exports
EDITOR = "editor" # Create/edit projects, run matching, build ratecards
ADMIN = "admin" # All editor permissions + GMAL management, user management, data ingest
class AppUser(Base):
__tablename__ = "app_users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
name: Mapped[str | None] = mapped_column(String(255))
role: Mapped[UserRole] = mapped_column(Enum(UserRole), default=UserRole.EDITOR)
azure_oid: Mapped[str | None] = mapped_column(String(100))
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
last_login: Mapped[datetime | None] = mapped_column(DateTime)