Add subuser permission templates service and related tests

This commit is contained in:
Jeppe Bundgaard
2026-07-08 11:49:40 +02:00
parent f26a427510
commit 6b7592921d
8 changed files with 1937 additions and 0 deletions
+439
View File
@@ -1455,6 +1455,183 @@ paths:
$ref: '#/components/schemas/BirdFlashCallHangupResponse'
# Subusers (public registration + setup)
/superuser/users/{user_id}/subusers:
get:
tags:
- Subusers
summary: List subusers for a superuser customer account
description: Returns paginated driver access grants for the customer number resolved from the selected user.
operationId: listSuperuserUserSubusers
security:
- BearerAuth: []
parameters:
- name: user_id
in: path
required: true
schema:
type: integer
minimum: 1
- name: page
in: query
required: false
schema: { type: integer, minimum: 1 }
- name: limit
in: query
required: false
schema: { type: integer, minimum: 1, maximum: 1000 }
- name: search
in: query
required: false
schema: { type: string }
- name: include_non_enabled
in: query
required: false
schema: { type: boolean }
responses:
'200':
description: User-scoped subuser list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/SubuserManagementRow'
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'500': { $ref: '#/components/responses/InternalServerError' }
/superuser/users/{user_id}/subusers/summary:
get:
tags:
- Subusers
summary: Summarize subusers for a superuser customer account
operationId: summarizeSuperuserUserSubusers
security:
- BearerAuth: []
parameters:
- name: user_id
in: path
required: true
schema:
type: integer
minimum: 1
responses:
'200':
description: User-scoped subuser summary
content:
application/json:
schema:
$ref: '#/components/schemas/SubuserManagementSummary'
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'500': { $ref: '#/components/responses/InternalServerError' }
/superuser/users/{user_id}/subusers/invite:
post:
tags:
- Subusers
summary: Invite a subuser for a superuser customer account
operationId: inviteSuperuserUserSubuser
security:
- BearerAuth: []
parameters:
- name: user_id
in: path
required: true
schema:
type: integer
minimum: 1
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, phone_country_code, phone]
properties:
name: { type: string, minLength: 3, maxLength: 255 }
phone_country_code: { type: integer }
phone: { type: integer }
note: { type: string, nullable: true, maxLength: 65535 }
enabled: { type: boolean, default: true }
permission_template_key:
type: string
enum: [deactivated, driver, booking_coordinator, fleet_admin]
permissions:
type: array
items: { type: string }
responses:
'200':
description: Driver invited or linked
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'500': { $ref: '#/components/responses/InternalServerError' }
/superuser/users/{user_id}/subusers/{subuser_id}/invite/resend:
post:
tags:
- Subusers
summary: Resend a user-scoped subuser invite
operationId: resendSuperuserUserSubuserInvite
security:
- BearerAuth: []
parameters:
- name: user_id
in: path
required: true
schema: { type: integer, minimum: 1 }
- name: subuser_id
in: path
required: true
schema: { type: integer, minimum: 1 }
responses:
'200':
description: Invite resent
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'409': { $ref: '#/components/responses/Conflict' }
'500': { $ref: '#/components/responses/InternalServerError' }
/superuser/users/{user_id}/subusers/grants/{grant_id}:
patch:
tags:
- Subusers
summary: Update a user-scoped subuser grant
operationId: updateSuperuserUserSubuserGrant
security:
- BearerAuth: []
parameters:
- name: user_id
in: path
required: true
schema: { type: integer, minimum: 1 }
- name: grant_id
in: path
required: true
schema: { type: integer, minimum: 1 }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SubuserGrantUpdateRequest'
responses:
'200':
description: Grant updated
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'500': { $ref: '#/components/responses/InternalServerError' }
/subusers:
get:
tags:
@@ -2038,6 +2215,35 @@ paths:
'401': { $ref: '#/components/responses/Unauthorized' }
'500': { $ref: '#/components/responses/InternalServerError' }
/subusers/permission-templates:
get:
tags:
- Subusers
summary: List simplified subuser permission templates
description: Returns backend-owned driver access profiles and grouped capability metadata for subuser grants.
operationId: listSubuserPermissionTemplates
security:
- BearerAuth: []
responses:
'200':
description: Permission templates fetched
content:
application/json:
schema:
type: object
properties:
templates:
type: array
items:
$ref: '#/components/schemas/SubuserPermissionTemplate'
groups:
type: array
items:
$ref: '#/components/schemas/SubuserPermissionGroup'
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'500': { $ref: '#/components/responses/InternalServerError' }
# Authentication Endpoints
/auth/login:
post:
@@ -7438,6 +7644,139 @@ paths:
'403': { $ref: '#/components/responses/Forbidden' }
'500': { $ref: '#/components/responses/InternalServerError' }
/superuser/users/{user_id}/vehicles:
get:
tags: [Vehicles]
summary: List vehicles for a selected superuser user
operationId: listSuperuserUserVehicles
parameters:
- name: user_id
in: path
required: true
schema: { type: integer, minimum: 1 }
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/PerPageParam'
responses:
'200':
description: User-scoped vehicles retrieved
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Vehicle'
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
post:
tags: [Vehicles]
summary: Add a vehicle for a selected superuser user
operationId: addSuperuserUserVehicle
parameters:
- name: user_id
in: path
required: true
schema: { type: integer, minimum: 1 }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [reg, type]
properties:
reg: { type: string, minLength: 2, maxLength: 12 }
type: { type: integer }
wash_subscription: { type: boolean, default: false }
reference: { type: string, nullable: true, maxLength: 255 }
customer_id:
type: integer
description: Optional guard value; must match the selected user's customer number.
responses:
'200':
description: Vehicle created
content:
application/json:
schema:
$ref: '#/components/schemas/Vehicle'
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
put:
tags: [Vehicles]
summary: Edit a vehicle for a selected superuser user
operationId: editSuperuserUserVehicle
parameters:
- name: user_id
in: path
required: true
schema: { type: integer, minimum: 1 }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [id]
properties:
id: { type: integer, minimum: 1 }
reg: { type: string, minLength: 2, maxLength: 12 }
type: { type: integer }
wash_subscription: { type: boolean }
reference: { type: string, nullable: true, maxLength: 255 }
customer_id:
type: integer
description: Optional guard value; moving vehicles between customers is not allowed here.
responses:
'200':
description: Vehicle updated
content:
application/json:
schema:
$ref: '#/components/schemas/Vehicle'
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
tags: [Vehicles]
summary: Delete a vehicle for a selected superuser user
operationId: deleteSuperuserUserVehicle
parameters:
- name: user_id
in: path
required: true
schema: { type: integer, minimum: 1 }
- name: id
in: query
required: true
schema: { type: integer, minimum: 1 }
responses:
'200': { description: Vehicle deleted }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
/superuser/users/{user_id}/vehicles/summary:
get:
tags: [Vehicles]
summary: Summarize vehicles for a selected superuser user
operationId: summarizeSuperuserUserVehicles
parameters:
- name: user_id
in: path
required: true
schema: { type: integer, minimum: 1 }
responses:
'200':
description: User-scoped vehicle summary
content:
application/json:
schema:
$ref: '#/components/schemas/VehicleManagementSummary'
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
# Vehicles Endpoints
/vehicles:
get:
@@ -16532,6 +16871,55 @@ components:
format: date-time
nullable: true
SubuserManagementRow:
type: object
properties:
id: { type: integer }
username: { type: string, nullable: true }
name: { type: string, nullable: true }
email: { type: string, format: email, nullable: true }
phone_country_code: { type: integer, nullable: true }
phone: { type: integer, nullable: true }
created_at: { type: string, format: date-time, nullable: true }
updated_at: { type: string, format: date-time, nullable: true }
suspended_at: { type: string, format: date-time, nullable: true }
two_factor_enabled: { type: boolean }
setup_required: { type: boolean }
invite_accepted: { type: boolean }
can_resend_invite: { type: boolean }
profile_editable_by_manager: { type: boolean }
customer_number: { type: integer }
customer_name: { type: string, nullable: true }
grant_id: { type: integer, nullable: true }
grant_enabled: { type: boolean }
grant_note: { type: string, nullable: true }
grant_permissions:
type: array
items: { type: string }
permissions:
type: array
items: { type: string }
permission_template_key:
type: string
enum: [deactivated, driver, booking_coordinator, fleet_admin, custom]
permission_groups:
type: array
items:
$ref: '#/components/schemas/SubuserPermissionGroup'
grant_created_at: { type: string, format: date-time, nullable: true }
grant_updated_at: { type: string, format: date-time, nullable: true }
access_state:
type: string
enum: [active, pending_setup, disabled, inactive]
SubuserManagementSummary:
type: object
properties:
total: { type: integer }
active: { type: integer }
pending_setup: { type: integer }
disabled: { type: integer }
SubuserGrantCreateRequest:
type: object
required:
@@ -16556,6 +16944,9 @@ components:
description: Optional list of permission keys; defaults will be applied if omitted
items:
type: string
permission_template_key:
type: string
enum: [deactivated, driver, booking_coordinator, fleet_admin]
SubuserGrantUpdateRequest:
type: object
@@ -16570,6 +16961,40 @@ components:
type: array
items:
type: string
permission_template_key:
type: string
enum: [deactivated, driver, booking_coordinator, fleet_admin]
SubuserPermissionGroup:
type: object
properties:
key:
type: string
capabilities:
type: array
items:
type: string
SubuserPermissionTemplate:
type: object
properties:
key:
type: string
enum: [deactivated, driver, booking_coordinator, fleet_admin]
label:
type: string
description:
type: string
enabled:
type: boolean
permissions:
type: array
items:
type: string
permission_groups:
type: array
items:
$ref: '#/components/schemas/SubuserPermissionGroup'
SubuserGrantSummary:
type: object
@@ -20188,6 +20613,20 @@ components:
type: string
format: date-time
VehicleManagementSummary:
type: object
required: [total, wash_subscription, self_service]
properties:
total:
type: integer
minimum: 0
wash_subscription:
type: integer
minimum: 0
self_service:
type: integer
minimum: 0
Notification:
type: object
properties: