diff --git a/backend/models.py b/backend/models.py index e0ad83c..dcdb537 100644 --- a/backend/models.py +++ b/backend/models.py @@ -27,6 +27,16 @@ class Permission(str, PyEnum): # Invite permissions MANAGE_INVITES = "manage_invites" VIEW_OWN_INVITES = "view_own_invites" + + # Booking permissions (regular users) + BOOK_APPOINTMENT = "book_appointment" + VIEW_OWN_APPOINTMENTS = "view_own_appointments" + CANCEL_OWN_APPOINTMENT = "cancel_own_appointment" + + # Availability/Appointments permissions (admin) + MANAGE_AVAILABILITY = "manage_availability" + VIEW_ALL_APPOINTMENTS = "view_all_appointments" + CANCEL_ANY_APPOINTMENT = "cancel_any_appointment" class InviteStatus(str, PyEnum): @@ -36,6 +46,13 @@ class InviteStatus(str, PyEnum): REVOKED = "revoked" +class AppointmentStatus(str, PyEnum): + """Status of an appointment.""" + BOOKED = "booked" + CANCELLED_BY_USER = "cancelled_by_user" + CANCELLED_BY_ADMIN = "cancelled_by_admin" + + # Role name constants ROLE_ADMIN = "admin" ROLE_REGULAR = "regular" @@ -43,19 +60,25 @@ ROLE_REGULAR = "regular" # Role definitions with their permissions ROLE_DEFINITIONS: dict[str, RoleConfig] = { ROLE_ADMIN: { - "description": "Administrator with audit and invite management access", + "description": "Administrator with audit, invite, and appointment management access", "permissions": [ Permission.VIEW_AUDIT, Permission.MANAGE_INVITES, + Permission.MANAGE_AVAILABILITY, + Permission.VIEW_ALL_APPOINTMENTS, + Permission.CANCEL_ANY_APPOINTMENT, ], }, ROLE_REGULAR: { - "description": "Regular user with counter, sum, and invite access", + "description": "Regular user with counter, sum, invite, and booking access", "permissions": [ Permission.VIEW_COUNTER, Permission.INCREMENT_COUNTER, Permission.USE_SUM, Permission.VIEW_OWN_INVITES, + Permission.BOOK_APPOINTMENT, + Permission.VIEW_OWN_APPOINTMENTS, + Permission.CANCEL_OWN_APPOINTMENT, ], }, } diff --git a/backend/validate_constants.py b/backend/validate_constants.py index b645114..e485399 100644 --- a/backend/validate_constants.py +++ b/backend/validate_constants.py @@ -2,7 +2,7 @@ import json from pathlib import Path -from models import ROLE_ADMIN, ROLE_REGULAR, InviteStatus +from models import ROLE_ADMIN, ROLE_REGULAR, InviteStatus, AppointmentStatus def validate_shared_constants() -> None: @@ -27,13 +27,28 @@ def validate_shared_constants() -> None: ) # Validate invite statuses - expected_statuses = {s.name: s.value for s in InviteStatus} - if constants.get("inviteStatuses") != expected_statuses: + expected_invite_statuses = {s.name: s.value for s in InviteStatus} + if constants.get("inviteStatuses") != expected_invite_statuses: raise ValueError( f"Invite status mismatch in shared/constants.json. " - f"Expected: {expected_statuses}, Got: {constants.get('inviteStatuses')}" + f"Expected: {expected_invite_statuses}, Got: {constants.get('inviteStatuses')}" ) + # Validate appointment statuses + expected_appointment_statuses = {s.name: s.value for s in AppointmentStatus} + if constants.get("appointmentStatuses") != expected_appointment_statuses: + raise ValueError( + f"Appointment status mismatch in shared/constants.json. " + f"Expected: {expected_appointment_statuses}, Got: {constants.get('appointmentStatuses')}" + ) + + # Validate booking constants exist with required fields + booking = constants.get("booking", {}) + required_booking_fields = ["slotDurationMinutes", "maxAdvanceDays", "minAdvanceDays", "noteMaxLength"] + for field in required_booking_fields: + if field not in booking: + raise ValueError(f"Missing booking constant '{field}' in shared/constants.json") + # Validate validation rules exist (structure check only) validation = constants.get("validation", {}) required_fields = ["telegram", "signal", "nostrNpub"] diff --git a/shared/constants.json b/shared/constants.json index 0e278cc..54f1086 100644 --- a/shared/constants.json +++ b/shared/constants.json @@ -8,6 +8,17 @@ "SPENT": "spent", "REVOKED": "revoked" }, + "appointmentStatuses": { + "BOOKED": "booked", + "CANCELLED_BY_USER": "cancelled_by_user", + "CANCELLED_BY_ADMIN": "cancelled_by_admin" + }, + "booking": { + "slotDurationMinutes": 15, + "maxAdvanceDays": 30, + "minAdvanceDays": 1, + "noteMaxLength": 144 + }, "validation": { "telegram": { "maxLengthAfterAt": 32,