Add Self-Serve section to OpenAPI spec and implement related CRUD routes with department-level access control

This commit is contained in:
Jeppe Bundgaard
2026-01-08 11:15:58 +01:00
parent 1251791ba9
commit 338c244e53
2 changed files with 222 additions and 13 deletions
+202
View File
@@ -68,6 +68,8 @@ tags:
description: Branding options management
- name: Roles
description: Role and permission management
- name: Self-Serve
description: Self-serve lane operations and questions
paths:
# Authentication Endpoints
@@ -1262,6 +1264,171 @@ paths:
items:
$ref: '#/components/schemas/Department'
/department/selfserve/questions:
get:
tags:
- Self-Serve
summary: List self-serve questions
description: Retrieve a list of self-serve questions for a department, lane, or product.
operationId: listSelfserveQuestions
parameters:
- name: id
in: query
description: Filter by question ID
schema:
type: integer
- name: department
in: query
description: Filter by department ID
schema:
type: integer
- name: lane
in: query
description: Filter by lane ID
schema:
type: integer
- name: product
in: query
description: Filter by product ID
schema:
type: integer
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/PerPageParam'
- $ref: '#/components/parameters/SearchParam'
- $ref: '#/components/parameters/FiltersParam'
responses:
'200':
description: Successfully retrieved questions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/DepartmentSelfserveQuestion'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
post:
tags:
- Self-Serve
summary: Add self-serve question
description: Add a new self-serve question.
operationId: addSelfserveQuestion
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- department
- lane
- product
- question
- description
properties:
department:
type: integer
lane:
type: integer
product:
type: integer
question:
type: string
description:
type: string
image:
type: integer
nullable: true
order_priority:
type: integer
default: 0
responses:
'200':
description: Successfully added question
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentSelfserveQuestion'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
put:
tags:
- Self-Serve
summary: Update self-serve question
description: Update an existing self-serve question.
operationId: updateSelfserveQuestion
parameters:
- name: id
in: query
required: true
description: Question ID
schema:
type: integer
requestBody:
content:
application/json:
schema:
type: object
properties:
department:
type: integer
lane:
type: integer
product:
type: integer
question:
type: string
description:
type: string
image:
type: integer
nullable: true
order_priority:
type: integer
responses:
'200':
description: Successfully updated question
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentSelfserveQuestion'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags:
- Self-Serve
summary: Delete self-serve question
description: Delete a self-serve question by ID.
operationId: deleteSelfserveQuestion
parameters:
- name: id
in: query
required: true
description: Question ID
schema:
type: integer
responses:
'200':
description: Successfully deleted question
content:
application/json:
schema:
type: string
example: Question deleted
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
# Products Endpoints
/products:
get:
@@ -4178,6 +4345,41 @@ components:
type: string
format: date-time
DepartmentSelfserveQuestion:
type: object
properties:
id:
type: integer
description: Question ID
department:
type: integer
description: Department ID
lane:
type: integer
description: Lane ID
product:
type: integer
description: Product ID
image:
type: integer
description: Image ID
nullable: true
question:
type: string
description: Question text
description:
type: string
description: Question description
order_priority:
type: integer
description: Display order priority (lower numbers shown first)
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
OrderCreate:
type: object
required:
@@ -36,25 +36,39 @@ class departmentSelfserveQuestionsRoute
if ($questions_o->exists()) {
if (!in_array((int)$questions_o->department->value(), $authorized_department_ids)) {
$response->error('You do not have access to this department', 403);
return;
}
$response->success($questions_o->asArray());
return;
} else {
$response->error('Question not found', 404);
return;
}
}
$filters = [];
if (self::isParametersSet(['department'])) {
$requested_department = (int)self::getParameter('department');
if (!in_array($requested_department, $authorized_department_ids)) {
$response->error('You do not have access to this department', 403);
}
$filters['department'] = $requested_department;
} else {
$filters['department'] = $authorized_department_ids;
}
if (self::isParametersSet(['lane'])) {
$filters['lane'] = (int)self::getParameter('lane');
}
if (self::isParametersSet(['product'])) {
$filters['product'] = (int)self::getParameter('product');
}
$response->success(
$questions_o->setSearchableFields(['id', 'department', 'lane', 'product', 'question', 'description'])
->listObjectsWithPaginationIfSet(function ($question) {
$q = new department_selfserve_questions_o();
$q->select((int)$question['id']);
return $q->asArray();
}, $questions_o->forceRestrictFilters([
'department' => $authorized_department_ids
]))
}, $questions_o->forceRestrictFilters($filters))
);
} else {
$response->error('Invalid session', 400);
@@ -81,13 +95,11 @@ class departmentSelfserveQuestionsRoute
if (!$department || !$lane || !$product || !$question || !$description) {
$response->error('Missing required fields', 400);
return;
}
$authorized_department_ids = $user->getGroup()->getDepartments();
if (!in_array($department, $authorized_department_ids)) {
$response->error('You do not have access to this department', 403);
return;
}
try {
@@ -127,20 +139,17 @@ class departmentSelfserveQuestionsRoute
if (!$question_o->exists()) {
$response->error('Question not found', 404);
return;
}
$authorized_department_ids = $user->getGroup()->getDepartments();
if (!in_array((int)$question_o->department->value(), $authorized_department_ids)) {
$response->error('You do not have access to this department', 403);
return;
}
if (self::isParametersSet(['department'])) {
$new_department = (int)self::getParameter('department');
if (!in_array($new_department, $authorized_department_ids)) {
$response->error('You do not have access to the new department', 403);
return;
}
$question_o->department->set($new_department);
}
@@ -187,13 +196,11 @@ class departmentSelfserveQuestionsRoute
if (!$question_o->exists()) {
$response->error('Question not found', 404);
return;
}
$authorized_department_ids = $user->getGroup()->getDepartments();
if (!in_array((int)$question_o->department->value(), $authorized_department_ids)) {
$response->error('You do not have access to this department', 403);
return;
}
$question_o->delete();