Fix: Update permissions and add missing /api/exchange/slots endpoint

- Updated auth-context.tsx to use new exchange permissions
  (CREATE_EXCHANGE, VIEW_OWN_EXCHANGES, etc.) instead of old
  appointment permissions (BOOK_APPOINTMENT, etc.)

- Updated exchange/page.tsx, trades/page.tsx, admin/trades/page.tsx
  to use correct permission constants

- Updated profile/page.test.tsx mock permissions

- Updated admin/availability/page.tsx to use constants.exchange
  instead of constants.booking

- Added /api/exchange/slots endpoint to return available slots
  for a date, filtering out already booked slots

- Fixed E2E tests:
  - exchange.spec.ts: Wait for button to be enabled before clicking
  - permissions.spec.ts: Use more specific heading selector
  - price-history.spec.ts: Expect /exchange redirect for regular users
This commit is contained in:
counterweight 2025-12-22 21:42:42 +01:00
parent 65ba4dc42a
commit 1008eea2d9
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
11 changed files with 184 additions and 379 deletions

View file

@ -316,126 +316,6 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/booking/slots": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Available Slots
* @description Get available booking slots for a specific date.
*/
get: operations["get_available_slots_api_booking_slots_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/booking": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Create Booking
* @description Book an appointment slot.
*/
post: operations["create_booking_api_booking_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/appointments": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get My Appointments
* @description Get the current user's appointments, sorted by date (upcoming first).
*/
get: operations["get_my_appointments_api_appointments_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/appointments/{appointment_id}/cancel": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Cancel My Appointment
* @description Cancel one of the current user's appointments.
*/
post: operations["cancel_my_appointment_api_appointments__appointment_id__cancel_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/appointments": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get All Appointments
* @description Get all appointments (admin only), sorted by date descending with pagination.
*/
get: operations["get_all_appointments_api_admin_appointments_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/appointments/{appointment_id}/cancel": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Admin Cancel Appointment
* @description Cancel any appointment (admin only).
*/
post: operations["admin_cancel_appointment_api_admin_appointments__appointment_id__cancel_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/exchange/price": {
parameters: {
query?: never;
@ -465,6 +345,30 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/exchange/slots": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Available Slots
* @description Get available booking slots for a specific date.
*
* Returns all slots that:
* - Fall within admin-defined availability windows
* - Are not already booked by another user
*/
get: operations["get_available_slots_api_exchange_slots_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/exchange": {
parameters: {
query?: never;
@ -697,39 +601,6 @@ export interface components {
/** Email */
email: string;
};
/**
* AppointmentResponse
* @description Response model for an appointment.
*/
AppointmentResponse: {
/** Id */
id: number;
/** User Id */
user_id: number;
/** User Email */
user_email: string;
/**
* Slot Start
* Format: date-time
*/
slot_start: string;
/**
* Slot End
* Format: date-time
*/
slot_end: string;
/** Note */
note: string | null;
/** Status */
status: string;
/**
* Created At
* Format: date-time
*/
created_at: string;
/** Cancelled At */
cancelled_at: string | null;
};
/**
* AvailabilityDay
* @description Availability for a single day.
@ -753,7 +624,7 @@ export interface components {
};
/**
* AvailableSlotsResponse
* @description Response for available slots on a given date.
* @description Response containing available slots for a date.
*/
AvailableSlotsResponse: {
/**
@ -766,7 +637,7 @@ export interface components {
};
/**
* BookableSlot
* @description A bookable 15-minute slot.
* @description A single bookable time slot.
*/
BookableSlot: {
/**
@ -780,19 +651,6 @@ export interface components {
*/
end_time: string;
};
/**
* BookingRequest
* @description Request to book an appointment.
*/
BookingRequest: {
/**
* Slot Start
* Format: date-time
*/
slot_start: string;
/** Note */
note?: string | null;
};
/**
* ConstantsResponse
* @description Response model for shared constants.
@ -981,19 +839,6 @@ export interface components {
* @enum {string}
*/
InviteStatus: "ready" | "spent" | "revoked";
/** PaginatedResponse[AppointmentResponse] */
PaginatedResponse_AppointmentResponse_: {
/** Records */
records: components["schemas"]["AppointmentResponse"][];
/** Total */
total: number;
/** Page */
page: number;
/** Per Page */
per_page: number;
/** Total Pages */
total_pages: number;
};
/** PaginatedResponse[InviteResponse] */
PaginatedResponse_InviteResponse_: {
/** Records */
@ -1012,7 +857,7 @@ export interface components {
* @description All available permissions in the system.
* @enum {string}
*/
Permission: "view_audit" | "fetch_price" | "manage_own_profile" | "manage_invites" | "view_own_invites" | "book_appointment" | "view_own_appointments" | "cancel_own_appointment" | "manage_availability" | "view_all_appointments" | "cancel_any_appointment";
Permission: "view_audit" | "fetch_price" | "manage_own_profile" | "manage_invites" | "view_own_invites" | "create_exchange" | "view_own_exchanges" | "cancel_own_exchange" | "manage_availability" | "view_all_exchanges" | "cancel_any_exchange" | "complete_exchange";
/**
* PriceHistoryResponse
* @description Response model for a price history record.
@ -1688,10 +1533,29 @@ export interface operations {
};
};
};
get_available_slots_api_booking_slots_get: {
get_exchange_price_api_exchange_price_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExchangePriceResponse"];
};
};
};
};
get_available_slots_api_exchange_slots_get: {
parameters: {
query: {
/** @description Date to get slots for */
date: string;
};
header?: never;
@ -1720,173 +1584,6 @@ export interface operations {
};
};
};
create_booking_api_booking_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["BookingRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AppointmentResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_my_appointments_api_appointments_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AppointmentResponse"][];
};
};
};
};
cancel_my_appointment_api_appointments__appointment_id__cancel_post: {
parameters: {
query?: never;
header?: never;
path: {
appointment_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AppointmentResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_all_appointments_api_admin_appointments_get: {
parameters: {
query?: {
page?: number;
per_page?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PaginatedResponse_AppointmentResponse_"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
admin_cancel_appointment_api_admin_appointments__appointment_id__cancel_post: {
parameters: {
query?: never;
header?: never;
path: {
appointment_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AppointmentResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_exchange_price_api_exchange_price_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExchangePriceResponse"];
};
};
};
};
create_exchange_api_exchange_post: {
parameters: {
query?: never;