From 8c6c2703cc9ea961e1b0bdd43d2ddc1fb4430527 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Mon, 16 Feb 2026 22:26:58 +0300 Subject: [PATCH 1/7] feat: expose scheduled tasks to API --- .../Api/ScheduledTasksController.php | 362 ++++++++++++++++++ app/Models/ScheduledTask.php | 17 + routes/api.php | 7 + 3 files changed, 386 insertions(+) create mode 100644 app/Http/Controllers/Api/ScheduledTasksController.php diff --git a/app/Http/Controllers/Api/ScheduledTasksController.php b/app/Http/Controllers/Api/ScheduledTasksController.php new file mode 100644 index 000000000..d9972a364 --- /dev/null +++ b/app/Http/Controllers/Api/ScheduledTasksController.php @@ -0,0 +1,362 @@ +makeHidden([ + 'id', + 'team_id', + 'application_id', + 'service_id', + 'standalone_postgresql_id', + ]); + + return serializeApiResponse($task); + } + + public function create_scheduled_task(Request $request, Application|Service $resource) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $return = validateIncomingRequest($request); + if ($return instanceof \Illuminate\Http\JsonResponse) { + return $return; + } + + $validator = Validator::make($request->all(), [ + 'name' => 'required|string|max:255', + 'command' => 'required|string', + 'frequency' => 'required|string', + 'container' => 'string|nullable', + 'timeout' => 'integer|min:1', + 'enabled' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + if (! validate_cron_expression($request->frequency)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['frequency' => ['Invalid cron expression or frequency format.']], + ], 422); + } + + $task = new ScheduledTask(); + $data = $request->all(); + $task->fill($data); + $task->team_id = $teamId; + + if ($resource instanceof Application) { + $task->application_id = $resource->id; + } elseif ($resource instanceof Service) { + $task->service_id = $resource->id; + } + + $task->save(); + + return response()->json($this->removeSensitiveData($task), 201); + } + + #[OA\Get( + summary: 'List (Application)', + description: 'List all scheduled tasks for an application.', + path: '/applications/{uuid}/scheduled-tasks', + operationId: 'list-scheduled-tasks-by-application-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Get all scheduled tasks for an application.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/ScheduledTask') + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function scheduled_tasks_by_application_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $application) { + return response()->json(['message' => 'Application not found.'], 404); + } + + $tasks = $application->scheduled_tasks->map(function ($task) { + return $this->removeSensitiveData($task); + }); + + return response()->json($tasks); + } + + #[OA\Post( + summary: 'Create (Application)', + description: 'Create a new scheduled task for an application.', + path: '/applications/{uuid}/scheduled-tasks', + operationId: 'create-scheduled-task-by-application-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Scheduled task data', + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['name', 'command', 'frequency'], + properties: [ + 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], + 'command' => ['type' => 'string', 'description' => 'The command to execute.'], + 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], + 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], + 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], + ], + ), + ) + ), + responses: [ + new OA\Response( + response: 201, + description: 'Scheduled task created.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function create_scheduled_task_by_application_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $application) { + return response()->json(['message' => 'Application not found.'], 404); + } + + return $this->create_scheduled_task($request, $application); + } + + #[OA\Get( + summary: 'List (Service)', + description: 'List all scheduled tasks for a service.', + path: '/services/{uuid}/scheduled-tasks', + operationId: 'list-scheduled-tasks-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Get all scheduled tasks for a service.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/ScheduledTask') + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function scheduled_tasks_by_service_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $tasks = $service->scheduled_tasks->map(function ($task) { + return $this->removeSensitiveData($task); + }); + + return response()->json($tasks); + } + + #[OA\Post( + summary: 'Create (Service)', + description: 'Create a new scheduled task for a service.', + path: '/services/{uuid}/scheduled-tasks', + operationId: 'create-scheduled-task-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Scheduled task data', + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['name', 'command', 'frequency'], + properties: [ + 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], + 'command' => ['type' => 'string', 'description' => 'The command to execute.'], + 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], + 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], + 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], + ], + ), + ) + ), + responses: [ + new OA\Response( + response: 201, + description: 'Scheduled task created.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function create_scheduled_task_by_service_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + return $this->create_scheduled_task($request, $service); + } +} diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index bada0b7a5..f4b021f27 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -5,7 +5,24 @@ use App\Traits\HasSafeStringAttribute; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; +use OpenApi\Attributes as OA; +#[OA\Schema( + description: 'Scheduled Task model', + type: 'object', + properties: [ + 'id' => ['type' => 'integer', 'description' => 'The unique identifier of the scheduled task in the database.'], + 'uuid' => ['type' => 'string', 'description' => 'The unique identifier of the scheduled task.'], + 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.'], + 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], + 'command' => ['type' => 'string', 'description' => 'The command to execute.'], + 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], + 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.'], + 'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the scheduled task was created.'], + 'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the scheduled task was last updated.'], + ], +)] class ScheduledTask extends BaseModel { use HasSafeStringAttribute; diff --git a/routes/api.php b/routes/api.php index 8ff1fd1cc..fb91baa84 100644 --- a/routes/api.php +++ b/routes/api.php @@ -9,6 +9,7 @@ use App\Http\Controllers\Api\OtherController; use App\Http\Controllers\Api\ProjectController; use App\Http\Controllers\Api\ResourcesController; +use App\Http\Controllers\Api\ScheduledTasksController; use App\Http\Controllers\Api\SecurityController; use App\Http\Controllers\Api\ServersController; use App\Http\Controllers\Api\ServicesController; @@ -171,6 +172,12 @@ Route::match(['get', 'post'], '/services/{uuid}/start', [ServicesController::class, 'action_deploy'])->middleware(['api.ability:write']); Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware(['api.ability:write']); Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware(['api.ability:write']); + + Route::get('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_application_uuid'])->middleware(['api.ability:read']); + Route::post('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); + + Route::get('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_service_uuid'])->middleware(['api.ability:read']); + Route::post('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); }); Route::group([ From edc92d7edcca25030575241ca55cf62c18ef0d1f Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 17 Feb 2026 00:56:40 +0300 Subject: [PATCH 2/7] feat(api): add OpenAPI for managing scheduled tasks for applications and services --- openapi.json | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++ openapi.yaml | 225 +++++++++++++++++++++++++++++++++++ 2 files changed, 548 insertions(+) diff --git a/openapi.json b/openapi.json index f5da0883f..0c4a3240e 100644 --- a/openapi.json +++ b/openapi.json @@ -3433,6 +3433,143 @@ ] } }, + "\/applications\/{uuid}\/scheduled-tasks": { + "get": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "List Tasks", + "description": "List all scheduled tasks for an application.", + "operationId": "list-scheduled-tasks-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get all scheduled tasks for an application.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Create Tasks", + "description": "Create a new scheduled task for an application.", + "operationId": "create-scheduled-task-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "name", + "command", + "frequency" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 3600 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Scheduled task created.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/cloud-tokens": { "get": { "tags": [ @@ -9967,6 +10104,143 @@ ] } }, + "\/services\/{uuid}\/scheduled-tasks": { + "get": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "List (Service)", + "description": "List all scheduled tasks for a service.", + "operationId": "list-scheduled-tasks-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get all scheduled tasks for a service.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Create (Service)", + "description": "Create a new scheduled task for a service.", + "operationId": "create-scheduled-task-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "name", + "command", + "frequency" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 3600 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Scheduled task created.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/teams": { "get": { "tags": [ @@ -10967,6 +11241,55 @@ }, "type": "object" }, + "ScheduledTask": { + "description": "Scheduled Task model", + "properties": { + "id": { + "type": "integer", + "description": "The unique identifier of the scheduled task in the database." + }, + "uuid": { + "type": "string", + "description": "The unique identifier of the scheduled task." + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled." + }, + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time when the scheduled task was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The date and time when the scheduled task was last updated." + } + }, + "type": "object" + }, "Service": { "description": "Service model", "properties": { diff --git a/openapi.yaml b/openapi.yaml index 172607117..d329ec644 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2163,6 +2163,100 @@ paths: security: - bearerAuth: [] + '/applications/{uuid}/scheduled-tasks': + get: + tags: + - 'Scheduled Tasks' + summary: 'List Tasks' + description: 'List all scheduled tasks for an application.' + operationId: list-scheduled-tasks-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + responses: + '200': + description: 'Get all scheduled tasks for an application.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + post: + tags: + - 'Scheduled Tasks' + summary: 'Create Tasks' + description: 'Create a new scheduled task for an application.' + operationId: create-scheduled-task-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true, + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + required: + - name + - command + - frequency + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 3600 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '201': + description: 'Scheduled task created.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] /cloud-tokens: get: tags: @@ -6231,6 +6325,100 @@ paths: security: - bearerAuth: [] + '/services/{uuid}/scheduled-tasks': + get: + tags: + - 'Scheduled Tasks' + summary: 'List (Service)' + description: 'List all scheduled tasks for a service.' + operationId: list-scheduled-tasks-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + responses: + '200': + description: 'Get all scheduled tasks for a service.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + post: + tags: + - 'Scheduled Tasks' + summary: 'Create (Service)' + description: 'Create a new scheduled task for a service.' + operationId: create-scheduled-task-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + required: + - name + - command + - frequency + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 3600 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '201': + description: 'Scheduled task created.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] /teams: get: tags: @@ -6944,6 +7132,43 @@ components: type: boolean description: 'The flag to indicate if the unused networks should be deleted.' type: object + ScheduledTask: + description: 'Scheduled Task model' + properties: + id: + type: integer + description: 'The unique identifier of the scheduled task in the database.' + uuid: + type: string + description: 'The unique identifier of the scheduled task.' + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + created_at: + type: string + format: date-time + description: 'The date and time when the scheduled task was created.' + updated_at: + type: string + format: date-time + description: 'The date and time when the scheduled task was last updated.' + type: object Service: description: 'Service model' properties: From a5d48c54da5320fab892b53e8877c739d1f23d71 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 17 Feb 2026 01:33:46 +0300 Subject: [PATCH 3/7] feat(api): add delete endpoints for scheduled tasks in applications and services --- .../Api/ScheduledTasksController.php | 162 +++++++++++++++++- openapi.json | 118 +++++++++++++ openapi.yaml | 80 +++++++++ routes/api.php | 2 + 4 files changed, 358 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/ScheduledTasksController.php b/app/Http/Controllers/Api/ScheduledTasksController.php index d9972a364..4778dec2a 100644 --- a/app/Http/Controllers/Api/ScheduledTasksController.php +++ b/app/Http/Controllers/Api/ScheduledTasksController.php @@ -77,7 +77,7 @@ public function create_scheduled_task(Request $request, Application|Service $res } #[OA\Get( - summary: 'List (Application)', + summary: 'List Task', description: 'List all scheduled tasks for an application.', path: '/applications/{uuid}/scheduled-tasks', operationId: 'list-scheduled-tasks-by-application-uuid', @@ -140,7 +140,7 @@ public function scheduled_tasks_by_application_uuid(Request $request) } #[OA\Post( - summary: 'Create (Application)', + summary: 'Create Task', description: 'Create a new scheduled task for an application.', path: '/applications/{uuid}/scheduled-tasks', operationId: 'create-scheduled-task-by-application-uuid', @@ -218,8 +218,85 @@ public function create_scheduled_task_by_application_uuid(Request $request) return $this->create_scheduled_task($request, $application); } + #[OA\Delete( + summary: 'Delete Task', + description: 'Delete a scheduled task for an application.', + path: '/applications/{uuid}/scheduled-tasks/{task_uuid}', + operationId: 'delete-scheduled-task-by-application-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + new OA\Parameter( + name: 'task_uuid', + in: 'path', + description: 'UUID of the scheduled task.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Scheduled task deleted.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Scheduled task deleted.'], + ] + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function delete_scheduled_task_by_application_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $application) { + return response()->json(['message' => 'Application not found.'], 404); + } + + $task = $application->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); + if (! $task) { + return response()->json(['message' => 'Scheduled task not found.'], 404); + } + + $task->delete(); + + return response()->json(['message' => 'Scheduled task deleted.']); + } + #[OA\Get( - summary: 'List (Service)', + summary: 'List Tasks', description: 'List all scheduled tasks for a service.', path: '/services/{uuid}/scheduled-tasks', operationId: 'list-scheduled-tasks-by-service-uuid', @@ -282,7 +359,7 @@ public function scheduled_tasks_by_service_uuid(Request $request) } #[OA\Post( - summary: 'Create (Service)', + summary: 'Create Task', description: 'Create a new scheduled task for a service.', path: '/services/{uuid}/scheduled-tasks', operationId: 'create-scheduled-task-by-service-uuid', @@ -359,4 +436,81 @@ public function create_scheduled_task_by_service_uuid(Request $request) return $this->create_scheduled_task($request, $service); } + + #[OA\Delete( + summary: 'Delete Task', + description: 'Delete a scheduled task for a service.', + path: '/services/{uuid}/scheduled-tasks/{task_uuid}', + operationId: 'delete-scheduled-task-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + new OA\Parameter( + name: 'task_uuid', + in: 'path', + description: 'UUID of the scheduled task.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Scheduled task deleted.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Scheduled task deleted.'], + ] + ) + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function delete_scheduled_task_by_service_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $task = $service->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); + if (! $task) { + return response()->json(['message' => 'Scheduled task not found.'], 404); + } + + $task->delete(); + + return response()->json(['message' => 'Scheduled task deleted.']); + } } diff --git a/openapi.json b/openapi.json index 0c4a3240e..7df13e4ae 100644 --- a/openapi.json +++ b/openapi.json @@ -3570,6 +3570,65 @@ ] } }, + "\/applications\/{uuid}\/scheduled-tasks\/{task_uuid}": { + "delete": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Delete Task", + "description": "Delete a scheduled task for an application.", + "operationId": "delete-scheduled-task-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Scheduled task deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Scheduled task deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/cloud-tokens": { "get": { "tags": [ @@ -10241,6 +10300,65 @@ ] } }, + "\/services\/{uuid}\/scheduled-tasks\/{task_uuid}": { + "delete": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Delete Task", + "description": "Delete a scheduled task for a service.", + "operationId": "delete-scheduled-task-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Scheduled task deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Scheduled task deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/teams": { "get": { "tags": [ diff --git a/openapi.yaml b/openapi.yaml index d329ec644..ac684c98a 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2257,6 +2257,46 @@ paths: security: - bearerAuth: [] + '/applications/{uuid}/scheduled-tasks/{task_uuid}': + delete: + tags: + - 'Scheduled Tasks' + summary: 'Delete Task' + description: 'Delete a scheduled task for an application.' + operationId: delete-scheduled-task-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + responses: + '200': + description: 'Scheduled task deleted.' + content: + application/json: + schema: + properties: + message: + type: string + example: 'Scheduled task deleted.' + type: object + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] /cloud-tokens: get: tags: @@ -6419,6 +6459,46 @@ paths: security: - bearerAuth: [] + '/services/{uuid}/scheduled-tasks/{task_uuid}': + delete: + tags: + - 'Scheduled Tasks' + summary: 'Delete Task' + description: 'Delete a scheduled task for a service.' + operationId: delete-scheduled-task-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + responses: + '200': + description: 'Scheduled task deleted.' + content: + application/json: + schema: + properties: + message: + type: string + example: 'Scheduled task deleted.' + type: object + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] /teams: get: tags: diff --git a/routes/api.php b/routes/api.php index fb91baa84..7f9e0d3be 100644 --- a/routes/api.php +++ b/routes/api.php @@ -175,9 +175,11 @@ Route::get('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_application_uuid'])->middleware(['api.ability:read']); Route::post('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); + Route::delete('/applications/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'delete_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); Route::get('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_service_uuid'])->middleware(['api.ability:read']); Route::post('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); + Route::delete('/services/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'delete_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); }); Route::group([ From 2b913a1c35f62cc6c1d53d018999911267f420d1 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 17 Feb 2026 02:18:08 +0300 Subject: [PATCH 4/7] feat(api): add update endpoints for scheduled tasks in applications and services --- .../Api/ScheduledTasksController.php | 220 ++++++++++++++++++ openapi.json | 188 +++++++++++++++ openapi.yaml | 132 +++++++++++ routes/api.php | 2 + 4 files changed, 542 insertions(+) diff --git a/app/Http/Controllers/Api/ScheduledTasksController.php b/app/Http/Controllers/Api/ScheduledTasksController.php index 4778dec2a..13fe21a4a 100644 --- a/app/Http/Controllers/Api/ScheduledTasksController.php +++ b/app/Http/Controllers/Api/ScheduledTasksController.php @@ -76,6 +76,52 @@ public function create_scheduled_task(Request $request, Application|Service $res return response()->json($this->removeSensitiveData($task), 201); } + public function update_scheduled_task(Request $request, Application|Service $resource) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $return = validateIncomingRequest($request); + if ($return instanceof \Illuminate\Http\JsonResponse) { + return $return; + } + + $validator = Validator::make($request->all(), [ + 'name' => 'string|max:255', + 'command' => 'string', + 'frequency' => 'string', + 'container' => 'string|nullable', + 'timeout' => 'integer|min:1', + 'enabled' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + if ($request->has('frequency') && ! validate_cron_expression($request->frequency)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['frequency' => ['Invalid cron expression or frequency format.']], + ], 422); + } + + $task = $resource->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); + if (! $task) { + return response()->json(['message' => 'Scheduled task not found.'], 404); + } + + $data = $request->all(); + $task->update($data); + + return response()->json($this->removeSensitiveData($task), 200); + } + #[OA\Get( summary: 'List Task', description: 'List all scheduled tasks for an application.', @@ -295,6 +341,93 @@ public function delete_scheduled_task_by_application_uuid(Request $request) return response()->json(['message' => 'Scheduled task deleted.']); } + #[OA\Patch( + summary: 'Update Task', + description: 'Update a scheduled task for an application.', + path: '/applications/{uuid}/scheduled-tasks/{task_uuid}', + operationId: 'update-scheduled-task-by-application-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + new OA\Parameter( + name: 'task_uuid', + in: 'path', + description: 'UUID of the scheduled task.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Scheduled task data', + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], + 'command' => ['type' => 'string', 'description' => 'The command to execute.'], + 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], + 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], + 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], + ], + ), + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Scheduled task updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function update_scheduled_task_by_application_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $application) { + return response()->json(['message' => 'Application not found.'], 404); + } + + return $this->update_scheduled_task($request, $application); + } + #[OA\Get( summary: 'List Tasks', description: 'List all scheduled tasks for a service.', @@ -513,4 +646,91 @@ public function delete_scheduled_task_by_service_uuid(Request $request) return response()->json(['message' => 'Scheduled task deleted.']); } + + #[OA\Patch( + summary: 'Update Task', + description: 'Update a scheduled task for a service.', + path: '/services/{uuid}/scheduled-tasks/{task_uuid}', + operationId: 'update-scheduled-task-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + new OA\Parameter( + name: 'task_uuid', + in: 'path', + description: 'UUID of the scheduled task.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Scheduled task data', + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], + 'command' => ['type' => 'string', 'description' => 'The command to execute.'], + 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], + 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], + 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], + ], + ), + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Scheduled task updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function update_scheduled_task_by_service_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + return $this->update_scheduled_task($request, $service); + } } diff --git a/openapi.json b/openapi.json index 7df13e4ae..ef71fb5da 100644 --- a/openapi.json +++ b/openapi.json @@ -3571,6 +3571,100 @@ } }, "\/applications\/{uuid}\/scheduled-tasks\/{task_uuid}": { + "patch": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Update Task", + "description": "Update a scheduled task for an application.", + "operationId": "update-scheduled-task-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 3600 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled task updated.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, "delete": { "tags": [ "Scheduled Tasks" @@ -10301,6 +10395,100 @@ } }, "\/services\/{uuid}\/scheduled-tasks\/{task_uuid}": { + "patch": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Update Task", + "description": "Update a scheduled task for a service.", + "operationId": "update-scheduled-task-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 3600 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled task updated.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, "delete": { "tags": [ "Scheduled Tasks" diff --git a/openapi.yaml b/openapi.yaml index ac684c98a..99cc84dcb 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2258,6 +2258,72 @@ paths: - bearerAuth: [] '/applications/{uuid}/scheduled-tasks/{task_uuid}': + patch: + tags: + - 'Scheduled Tasks' + summary: 'Update Task' + description: 'Update a scheduled task for an application.' + operationId: update-scheduled-task-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 3600 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '200': + description: 'Scheduled task updated.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] delete: tags: - 'Scheduled Tasks' @@ -6460,6 +6526,72 @@ paths: - bearerAuth: [] '/services/{uuid}/scheduled-tasks/{task_uuid}': + patch: + tags: + - 'Scheduled Tasks' + summary: 'Update Task' + description: 'Update a scheduled task for a service.' + operationId: update-scheduled-task-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 3600 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '200': + description: 'Scheduled task updated.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] delete: tags: - 'Scheduled Tasks' diff --git a/routes/api.php b/routes/api.php index 7f9e0d3be..9c4d703a9 100644 --- a/routes/api.php +++ b/routes/api.php @@ -175,10 +175,12 @@ Route::get('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_application_uuid'])->middleware(['api.ability:read']); Route::post('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); + Route::patch('/applications/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'update_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); Route::delete('/applications/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'delete_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); Route::get('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_service_uuid'])->middleware(['api.ability:read']); Route::post('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); + Route::patch('/services/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'update_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); Route::delete('/services/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'delete_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); }); From ab79a51e298e7d5d339b04f3b5f8864c2459e3da Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:53:58 +0100 Subject: [PATCH 5/7] fix(api): improve scheduled tasks API with auth, validation, and execution endpoints - Add authorization checks ($this->authorize) for all read/write operations - Use customApiValidator() instead of Validator::make() to match codebase patterns - Add extra field rejection to prevent mass assignment - Use Application::ownedByCurrentTeamAPI() for consistent query patterns - Remove non-existent standalone_postgresql_id from hidden fields - Add execution listing endpoints for both applications and services - Add ScheduledTaskExecution OpenAPI schema - Use $request->only() instead of $request->all() for safe updates - Add ScheduledTaskFactory and feature tests Co-Authored-By: Claude Opus 4.6 --- .../Api/ScheduledTasksController.php | 444 +++-- app/Models/ScheduledTask.php | 5 + app/Models/ScheduledTaskExecution.php | 16 + database/factories/ScheduledTaskFactory.php | 20 + openapi.json | 1429 +++++++++-------- openapi.yaml | 992 +++++++----- routes/api.php | 4 +- tests/Feature/ScheduledTaskApiTest.php | 365 +++++ 8 files changed, 2078 insertions(+), 1197 deletions(-) create mode 100644 database/factories/ScheduledTaskFactory.php create mode 100644 tests/Feature/ScheduledTaskApiTest.php diff --git a/app/Http/Controllers/Api/ScheduledTasksController.php b/app/Http/Controllers/Api/ScheduledTasksController.php index 13fe21a4a..cf3574f1c 100644 --- a/app/Http/Controllers/Api/ScheduledTasksController.php +++ b/app/Http/Controllers/Api/ScheduledTasksController.php @@ -7,7 +7,6 @@ use App\Models\ScheduledTask; use App\Models\Service; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Validator; use OpenApi\Attributes as OA; class ScheduledTasksController extends Controller @@ -19,25 +18,44 @@ private function removeSensitiveData($task) 'team_id', 'application_id', 'service_id', - 'standalone_postgresql_id', ]); return serializeApiResponse($task); } - public function create_scheduled_task(Request $request, Application|Service $resource) + private function resolveApplication(Request $request, int $teamId): ?Application { - $teamId = getTeamIdFromToken(); - if (is_null($teamId)) { - return invalidTokenResponse(); - } + return Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + } + + private function resolveService(Request $request, int $teamId): ?Service + { + return Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + } + + private function listTasks(Application|Service $resource): \Illuminate\Http\JsonResponse + { + $this->authorize('view', $resource); + + $tasks = $resource->scheduled_tasks->map(function ($task) { + return $this->removeSensitiveData($task); + }); + + return response()->json($tasks); + } + + private function createTask(Request $request, Application|Service $resource): \Illuminate\Http\JsonResponse + { + $this->authorize('update', $resource); $return = validateIncomingRequest($request); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $validator = Validator::make($request->all(), [ + $allowedFields = ['name', 'command', 'frequency', 'container', 'timeout', 'enabled']; + + $validator = customApiValidator($request->all(), [ 'name' => 'required|string|max:255', 'command' => 'required|string', 'frequency' => 'required|string', @@ -46,10 +64,18 @@ public function create_scheduled_task(Request $request, Application|Service $res 'enabled' => 'boolean', ]); - if ($validator->fails()) { + $extraFields = array_diff(array_keys($request->all()), $allowedFields); + if ($validator->fails() || ! empty($extraFields)) { + $errors = $validator->errors(); + if (! empty($extraFields)) { + foreach ($extraFields as $field) { + $errors->add($field, 'This field is not allowed.'); + } + } + return response()->json([ 'message' => 'Validation failed.', - 'errors' => $validator->errors(), + 'errors' => $errors, ], 422); } @@ -60,9 +86,15 @@ public function create_scheduled_task(Request $request, Application|Service $res ], 422); } - $task = new ScheduledTask(); - $data = $request->all(); - $task->fill($data); + $teamId = getTeamIdFromToken(); + + $task = new ScheduledTask; + $task->name = $request->name; + $task->command = $request->command; + $task->frequency = $request->frequency; + $task->container = $request->container; + $task->timeout = $request->timeout ?? 300; + $task->enabled = $request->enabled ?? true; $task->team_id = $teamId; if ($resource instanceof Application) { @@ -76,19 +108,18 @@ public function create_scheduled_task(Request $request, Application|Service $res return response()->json($this->removeSensitiveData($task), 201); } - public function update_scheduled_task(Request $request, Application|Service $resource) + private function updateTask(Request $request, Application|Service $resource): \Illuminate\Http\JsonResponse { - $teamId = getTeamIdFromToken(); - if (is_null($teamId)) { - return invalidTokenResponse(); - } + $this->authorize('update', $resource); $return = validateIncomingRequest($request); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $validator = Validator::make($request->all(), [ + $allowedFields = ['name', 'command', 'frequency', 'container', 'timeout', 'enabled']; + + $validator = customApiValidator($request->all(), [ 'name' => 'string|max:255', 'command' => 'string', 'frequency' => 'string', @@ -97,10 +128,18 @@ public function update_scheduled_task(Request $request, Application|Service $res 'enabled' => 'boolean', ]); - if ($validator->fails()) { + $extraFields = array_diff(array_keys($request->all()), $allowedFields); + if ($validator->fails() || ! empty($extraFields)) { + $errors = $validator->errors(); + if (! empty($extraFields)) { + foreach ($extraFields as $field) { + $errors->add($field, 'This field is not allowed.'); + } + } + return response()->json([ 'message' => 'Validation failed.', - 'errors' => $validator->errors(), + 'errors' => $errors, ], 422); } @@ -116,14 +155,45 @@ public function update_scheduled_task(Request $request, Application|Service $res return response()->json(['message' => 'Scheduled task not found.'], 404); } - $data = $request->all(); - $task->update($data); + $task->update($request->only($allowedFields)); return response()->json($this->removeSensitiveData($task), 200); } + private function deleteTask(Request $request, Application|Service $resource): \Illuminate\Http\JsonResponse + { + $this->authorize('update', $resource); + + $task = $resource->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); + if (! $task) { + return response()->json(['message' => 'Scheduled task not found.'], 404); + } + + $task->delete(); + + return response()->json(['message' => 'Scheduled task deleted.']); + } + + private function getExecutions(Request $request, Application|Service $resource): \Illuminate\Http\JsonResponse + { + $this->authorize('view', $resource); + + $task = $resource->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); + if (! $task) { + return response()->json(['message' => 'Scheduled task not found.'], 404); + } + + $executions = $task->executions()->get()->map(function ($execution) { + $execution->makeHidden(['id', 'scheduled_task_id']); + + return serializeApiResponse($execution); + }); + + return response()->json($executions); + } + #[OA\Get( - summary: 'List Task', + summary: 'List Tasks', description: 'List all scheduled tasks for an application.', path: '/applications/{uuid}/scheduled-tasks', operationId: 'list-scheduled-tasks-by-application-uuid', @@ -166,23 +236,19 @@ public function update_scheduled_task(Request $request, Application|Service $res ), ] )] - public function scheduled_tasks_by_application_uuid(Request $request) + public function scheduled_tasks_by_application_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $application = $this->resolveApplication($request, $teamId); if (! $application) { return response()->json(['message' => 'Application not found.'], 404); } - $tasks = $application->scheduled_tasks->map(function ($task) { - return $this->removeSensitiveData($task); - }); - - return response()->json($tasks); + return $this->listTasks($application); } #[OA\Post( @@ -218,7 +284,7 @@ public function scheduled_tasks_by_application_uuid(Request $request) 'command' => ['type' => 'string', 'description' => 'The command to execute.'], 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], - 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 300], 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], ], ), @@ -249,19 +315,106 @@ public function scheduled_tasks_by_application_uuid(Request $request) ), ] )] - public function create_scheduled_task_by_application_uuid(Request $request) + public function create_scheduled_task_by_application_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $application = $this->resolveApplication($request, $teamId); if (! $application) { return response()->json(['message' => 'Application not found.'], 404); } - return $this->create_scheduled_task($request, $application); + return $this->createTask($request, $application); + } + + #[OA\Patch( + summary: 'Update Task', + description: 'Update a scheduled task for an application.', + path: '/applications/{uuid}/scheduled-tasks/{task_uuid}', + operationId: 'update-scheduled-task-by-application-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the application.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + new OA\Parameter( + name: 'task_uuid', + in: 'path', + description: 'UUID of the scheduled task.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Scheduled task data', + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], + 'command' => ['type' => 'string', 'description' => 'The command to execute.'], + 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], + 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 300], + 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], + ], + ), + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Scheduled task updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function update_scheduled_task_by_application_uuid(Request $request): \Illuminate\Http\JsonResponse + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $application = $this->resolveApplication($request, $teamId); + if (! $application) { + return response()->json(['message' => 'Application not found.'], 404); + } + + return $this->updateTask($request, $application); } #[OA\Delete( @@ -319,33 +472,26 @@ public function create_scheduled_task_by_application_uuid(Request $request) ), ] )] - public function delete_scheduled_task_by_application_uuid(Request $request) + public function delete_scheduled_task_by_application_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $application = $this->resolveApplication($request, $teamId); if (! $application) { return response()->json(['message' => 'Application not found.'], 404); } - $task = $application->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); - if (! $task) { - return response()->json(['message' => 'Scheduled task not found.'], 404); - } - - $task->delete(); - - return response()->json(['message' => 'Scheduled task deleted.']); + return $this->deleteTask($request, $application); } - #[OA\Patch( - summary: 'Update Task', - description: 'Update a scheduled task for an application.', - path: '/applications/{uuid}/scheduled-tasks/{task_uuid}', - operationId: 'update-scheduled-task-by-application-uuid', + #[OA\Get( + summary: 'List Executions', + description: 'List all executions for a scheduled task on an application.', + path: '/applications/{uuid}/scheduled-tasks/{task_uuid}/executions', + operationId: 'list-scheduled-task-executions-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -370,32 +516,17 @@ public function delete_scheduled_task_by_application_uuid(Request $request) ) ), ], - requestBody: new OA\RequestBody( - description: 'Scheduled task data', - required: true, - content: new OA\MediaType( - mediaType: 'application/json', - schema: new OA\Schema( - type: 'object', - properties: [ - 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], - 'command' => ['type' => 'string', 'description' => 'The command to execute.'], - 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], - 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], - 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], - 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], - ], - ), - ) - ), responses: [ new OA\Response( response: 200, - description: 'Scheduled task updated.', + description: 'Get all executions for a scheduled task.', content: [ new OA\MediaType( mediaType: 'application/json', - schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/ScheduledTaskExecution') + ) ), ] ), @@ -407,25 +538,21 @@ public function delete_scheduled_task_by_application_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), - new OA\Response( - response: 422, - ref: '#/components/responses/422', - ), ] )] - public function update_scheduled_task_by_application_uuid(Request $request) + public function executions_by_application_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $application = Application::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $application = $this->resolveApplication($request, $teamId); if (! $application) { return response()->json(['message' => 'Application not found.'], 404); } - return $this->update_scheduled_task($request, $application); + return $this->getExecutions($request, $application); } #[OA\Get( @@ -472,23 +599,19 @@ public function update_scheduled_task_by_application_uuid(Request $request) ), ] )] - public function scheduled_tasks_by_service_uuid(Request $request) + public function scheduled_tasks_by_service_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $service = $this->resolveService($request, $teamId); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - $tasks = $service->scheduled_tasks->map(function ($task) { - return $this->removeSensitiveData($task); - }); - - return response()->json($tasks); + return $this->listTasks($service); } #[OA\Post( @@ -524,7 +647,7 @@ public function scheduled_tasks_by_service_uuid(Request $request) 'command' => ['type' => 'string', 'description' => 'The command to execute.'], 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], - 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 300], 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], ], ), @@ -555,19 +678,106 @@ public function scheduled_tasks_by_service_uuid(Request $request) ), ] )] - public function create_scheduled_task_by_service_uuid(Request $request) + public function create_scheduled_task_by_service_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $service = $this->resolveService($request, $teamId); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - return $this->create_scheduled_task($request, $service); + return $this->createTask($request, $service); + } + + #[OA\Patch( + summary: 'Update Task', + description: 'Update a scheduled task for a service.', + path: '/services/{uuid}/scheduled-tasks/{task_uuid}', + operationId: 'update-scheduled-task-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Scheduled Tasks'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + new OA\Parameter( + name: 'task_uuid', + in: 'path', + description: 'UUID of the scheduled task.', + required: true, + schema: new OA\Schema( + type: 'string', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Scheduled task data', + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], + 'command' => ['type' => 'string', 'description' => 'The command to execute.'], + 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], + 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], + 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 300], + 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], + ], + ), + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Scheduled task updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + ), + ] + ), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + new OA\Response( + response: 422, + ref: '#/components/responses/422', + ), + ] + )] + public function update_scheduled_task_by_service_uuid(Request $request): \Illuminate\Http\JsonResponse + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = $this->resolveService($request, $teamId); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + return $this->updateTask($request, $service); } #[OA\Delete( @@ -625,33 +835,26 @@ public function create_scheduled_task_by_service_uuid(Request $request) ), ] )] - public function delete_scheduled_task_by_service_uuid(Request $request) + public function delete_scheduled_task_by_service_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $service = $this->resolveService($request, $teamId); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - $task = $service->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); - if (! $task) { - return response()->json(['message' => 'Scheduled task not found.'], 404); - } - - $task->delete(); - - return response()->json(['message' => 'Scheduled task deleted.']); + return $this->deleteTask($request, $service); } - #[OA\Patch( - summary: 'Update Task', - description: 'Update a scheduled task for a service.', - path: '/services/{uuid}/scheduled-tasks/{task_uuid}', - operationId: 'update-scheduled-task-by-service-uuid', + #[OA\Get( + summary: 'List Executions', + description: 'List all executions for a scheduled task on a service.', + path: '/services/{uuid}/scheduled-tasks/{task_uuid}/executions', + operationId: 'list-scheduled-task-executions-by-service-uuid', security: [ ['bearerAuth' => []], ], @@ -676,32 +879,17 @@ public function delete_scheduled_task_by_service_uuid(Request $request) ) ), ], - requestBody: new OA\RequestBody( - description: 'Scheduled task data', - required: true, - content: new OA\MediaType( - mediaType: 'application/json', - schema: new OA\Schema( - type: 'object', - properties: [ - 'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'], - 'command' => ['type' => 'string', 'description' => 'The command to execute.'], - 'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'], - 'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'], - 'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.', 'default' => 3600], - 'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.', 'default' => true], - ], - ), - ) - ), responses: [ new OA\Response( response: 200, - description: 'Scheduled task updated.', + description: 'Get all executions for a scheduled task.', content: [ new OA\MediaType( mediaType: 'application/json', - schema: new OA\Schema(ref: '#/components/schemas/ScheduledTask') + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/ScheduledTaskExecution') + ) ), ] ), @@ -713,24 +901,20 @@ public function delete_scheduled_task_by_service_uuid(Request $request) response: 404, ref: '#/components/responses/404', ), - new OA\Response( - response: 422, - ref: '#/components/responses/422', - ), ] )] - public function update_scheduled_task_by_service_uuid(Request $request) + public function executions_by_service_uuid(Request $request): \Illuminate\Http\JsonResponse { $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); } - $service = Service::whereRelation('environment.project.team', 'id', $teamId)->where('uuid', $request->uuid)->first(); + $service = $this->resolveService($request, $teamId); if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - return $this->update_scheduled_task($request, $service); + return $this->getExecutions($request, $service); } } diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index f4b021f27..272638a81 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -29,6 +29,11 @@ class ScheduledTask extends BaseModel protected $guarded = []; + public static function ownedByCurrentTeamAPI(int $teamId) + { + return static::where('team_id', $teamId)->orderBy('created_at', 'desc'); + } + protected function casts(): array { return [ diff --git a/app/Models/ScheduledTaskExecution.php b/app/Models/ScheduledTaskExecution.php index 02fd6917a..c0601a4c9 100644 --- a/app/Models/ScheduledTaskExecution.php +++ b/app/Models/ScheduledTaskExecution.php @@ -3,7 +3,23 @@ namespace App\Models; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use OpenApi\Attributes as OA; +#[OA\Schema( + description: 'Scheduled Task Execution model', + type: 'object', + properties: [ + 'uuid' => ['type' => 'string', 'description' => 'The unique identifier of the execution.'], + 'status' => ['type' => 'string', 'enum' => ['success', 'failed', 'running'], 'description' => 'The status of the execution.'], + 'message' => ['type' => 'string', 'nullable' => true, 'description' => 'The output message of the execution.'], + 'retry_count' => ['type' => 'integer', 'description' => 'The number of retries.'], + 'duration' => ['type' => 'number', 'nullable' => true, 'description' => 'Duration in seconds.'], + 'started_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'When the execution started.'], + 'finished_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'When the execution finished.'], + 'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'When the record was created.'], + 'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'When the record was last updated.'], + ], +)] class ScheduledTaskExecution extends BaseModel { protected $guarded = []; diff --git a/database/factories/ScheduledTaskFactory.php b/database/factories/ScheduledTaskFactory.php new file mode 100644 index 000000000..5f6519288 --- /dev/null +++ b/database/factories/ScheduledTaskFactory.php @@ -0,0 +1,20 @@ + fake()->word(), + 'command' => 'echo hello', + 'frequency' => '* * * * *', + 'timeout' => 300, + 'enabled' => true, + 'team_id' => 1, + ]; + } +} diff --git a/openapi.json b/openapi.json index ef71fb5da..d3bf08e30 100644 --- a/openapi.json +++ b/openapi.json @@ -3433,296 +3433,6 @@ ] } }, - "\/applications\/{uuid}\/scheduled-tasks": { - "get": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "List Tasks", - "description": "List all scheduled tasks for an application.", - "operationId": "list-scheduled-tasks-by-application-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the application.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Get all scheduled tasks for an application.", - "content": { - "application\/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#\/components\/schemas\/ScheduledTask" - } - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "Create Tasks", - "description": "Create a new scheduled task for an application.", - "operationId": "create-scheduled-task-by-application-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the application.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Scheduled task data", - "required": true, - "content": { - "application\/json": { - "schema": { - "required": [ - "name", - "command", - "frequency" - ], - "properties": { - "name": { - "type": "string", - "description": "The name of the scheduled task." - }, - "command": { - "type": "string", - "description": "The command to execute." - }, - "frequency": { - "type": "string", - "description": "The frequency of the scheduled task." - }, - "container": { - "type": "string", - "nullable": true, - "description": "The container where the command should be executed." - }, - "timeout": { - "type": "integer", - "description": "The timeout of the scheduled task in seconds.", - "default": 3600 - }, - "enabled": { - "type": "boolean", - "description": "The flag to indicate if the scheduled task is enabled.", - "default": true - } - }, - "type": "object" - } - } - } - }, - "responses": { - "201": { - "description": "Scheduled task created.", - "content": { - "application\/json": { - "schema": { - "$ref": "#\/components\/schemas\/ScheduledTask" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - }, - "422": { - "$ref": "#\/components\/responses\/422" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "\/applications\/{uuid}\/scheduled-tasks\/{task_uuid}": { - "patch": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "Update Task", - "description": "Update a scheduled task for an application.", - "operationId": "update-scheduled-task-by-application-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the application.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "task_uuid", - "in": "path", - "description": "UUID of the scheduled task.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Scheduled task data", - "required": true, - "content": { - "application\/json": { - "schema": { - "properties": { - "name": { - "type": "string", - "description": "The name of the scheduled task." - }, - "command": { - "type": "string", - "description": "The command to execute." - }, - "frequency": { - "type": "string", - "description": "The frequency of the scheduled task." - }, - "container": { - "type": "string", - "nullable": true, - "description": "The container where the command should be executed." - }, - "timeout": { - "type": "integer", - "description": "The timeout of the scheduled task in seconds.", - "default": 3600 - }, - "enabled": { - "type": "boolean", - "description": "The flag to indicate if the scheduled task is enabled.", - "default": true - } - }, - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "Scheduled task updated.", - "content": { - "application\/json": { - "schema": { - "$ref": "#\/components\/schemas\/ScheduledTask" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - }, - "422": { - "$ref": "#\/components\/responses\/422" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "Delete Task", - "description": "Delete a scheduled task for an application.", - "operationId": "delete-scheduled-task-by-application-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the application.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "task_uuid", - "in": "path", - "description": "UUID of the scheduled task.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Scheduled task deleted.", - "content": { - "application\/json": { - "schema": { - "properties": { - "message": { - "type": "string", - "example": "Scheduled task deleted." - } - }, - "type": "object" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, "\/cloud-tokens": { "get": { "tags": [ @@ -8339,6 +8049,698 @@ ] } }, + "\/applications\/{uuid}\/scheduled-tasks": { + "get": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "List Tasks", + "description": "List all scheduled tasks for an application.", + "operationId": "list-scheduled-tasks-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get all scheduled tasks for an application.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Create Task", + "description": "Create a new scheduled task for an application.", + "operationId": "create-scheduled-task-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "name", + "command", + "frequency" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 300 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Scheduled task created.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/scheduled-tasks\/{task_uuid}": { + "delete": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Delete Task", + "description": "Delete a scheduled task for an application.", + "operationId": "delete-scheduled-task-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Scheduled task deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Scheduled task deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Update Task", + "description": "Update a scheduled task for an application.", + "operationId": "update-scheduled-task-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 300 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled task updated.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/scheduled-tasks\/{task_uuid}\/executions": { + "get": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "List Executions", + "description": "List all executions for a scheduled task on an application.", + "operationId": "list-scheduled-task-executions-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get all executions for a scheduled task.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/ScheduledTaskExecution" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/scheduled-tasks": { + "get": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "List Tasks", + "description": "List all scheduled tasks for a service.", + "operationId": "list-scheduled-tasks-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get all scheduled tasks for a service.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Create Task", + "description": "Create a new scheduled task for a service.", + "operationId": "create-scheduled-task-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "name", + "command", + "frequency" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 300 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Scheduled task created.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/scheduled-tasks\/{task_uuid}": { + "delete": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Delete Task", + "description": "Delete a scheduled task for a service.", + "operationId": "delete-scheduled-task-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Scheduled task deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Scheduled task deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "Update Task", + "description": "Update a scheduled task for a service.", + "operationId": "update-scheduled-task-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Scheduled task data", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds.", + "default": 300 + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled.", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled task updated.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ScheduledTask" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + }, + "422": { + "$ref": "#\/components\/responses\/422" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/scheduled-tasks\/{task_uuid}\/executions": { + "get": { + "tags": [ + "Scheduled Tasks" + ], + "summary": "List Executions", + "description": "List all executions for a scheduled task on a service.", + "operationId": "list-scheduled-task-executions-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_uuid", + "in": "path", + "description": "UUID of the scheduled task.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get all executions for a scheduled task.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/ScheduledTaskExecution" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/security\/keys": { "get": { "tags": [ @@ -10257,296 +10659,6 @@ ] } }, - "\/services\/{uuid}\/scheduled-tasks": { - "get": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "List (Service)", - "description": "List all scheduled tasks for a service.", - "operationId": "list-scheduled-tasks-by-service-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the service.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Get all scheduled tasks for a service.", - "content": { - "application\/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#\/components\/schemas\/ScheduledTask" - } - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "Create (Service)", - "description": "Create a new scheduled task for a service.", - "operationId": "create-scheduled-task-by-service-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the service.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Scheduled task data", - "required": true, - "content": { - "application\/json": { - "schema": { - "required": [ - "name", - "command", - "frequency" - ], - "properties": { - "name": { - "type": "string", - "description": "The name of the scheduled task." - }, - "command": { - "type": "string", - "description": "The command to execute." - }, - "frequency": { - "type": "string", - "description": "The frequency of the scheduled task." - }, - "container": { - "type": "string", - "nullable": true, - "description": "The container where the command should be executed." - }, - "timeout": { - "type": "integer", - "description": "The timeout of the scheduled task in seconds.", - "default": 3600 - }, - "enabled": { - "type": "boolean", - "description": "The flag to indicate if the scheduled task is enabled.", - "default": true - } - }, - "type": "object" - } - } - } - }, - "responses": { - "201": { - "description": "Scheduled task created.", - "content": { - "application\/json": { - "schema": { - "$ref": "#\/components\/schemas\/ScheduledTask" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - }, - "422": { - "$ref": "#\/components\/responses\/422" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "\/services\/{uuid}\/scheduled-tasks\/{task_uuid}": { - "patch": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "Update Task", - "description": "Update a scheduled task for a service.", - "operationId": "update-scheduled-task-by-service-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the service.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "task_uuid", - "in": "path", - "description": "UUID of the scheduled task.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Scheduled task data", - "required": true, - "content": { - "application\/json": { - "schema": { - "properties": { - "name": { - "type": "string", - "description": "The name of the scheduled task." - }, - "command": { - "type": "string", - "description": "The command to execute." - }, - "frequency": { - "type": "string", - "description": "The frequency of the scheduled task." - }, - "container": { - "type": "string", - "nullable": true, - "description": "The container where the command should be executed." - }, - "timeout": { - "type": "integer", - "description": "The timeout of the scheduled task in seconds.", - "default": 3600 - }, - "enabled": { - "type": "boolean", - "description": "The flag to indicate if the scheduled task is enabled.", - "default": true - } - }, - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "Scheduled task updated.", - "content": { - "application\/json": { - "schema": { - "$ref": "#\/components\/schemas\/ScheduledTask" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - }, - "422": { - "$ref": "#\/components\/responses\/422" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": [ - "Scheduled Tasks" - ], - "summary": "Delete Task", - "description": "Delete a scheduled task for a service.", - "operationId": "delete-scheduled-task-by-service-uuid", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the service.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "task_uuid", - "in": "path", - "description": "UUID of the scheduled task.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Scheduled task deleted.", - "content": { - "application\/json": { - "schema": { - "properties": { - "message": { - "type": "string", - "example": "Scheduled task deleted." - } - }, - "type": "object" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "404": { - "$ref": "#\/components\/responses\/404" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, "\/teams": { "get": { "tags": [ @@ -11351,6 +11463,110 @@ }, "type": "object" }, + "ScheduledTask": { + "description": "Scheduled Task model", + "properties": { + "id": { + "type": "integer", + "description": "The unique identifier of the scheduled task in the database." + }, + "uuid": { + "type": "string", + "description": "The unique identifier of the scheduled task." + }, + "enabled": { + "type": "boolean", + "description": "The flag to indicate if the scheduled task is enabled." + }, + "name": { + "type": "string", + "description": "The name of the scheduled task." + }, + "command": { + "type": "string", + "description": "The command to execute." + }, + "frequency": { + "type": "string", + "description": "The frequency of the scheduled task." + }, + "container": { + "type": "string", + "nullable": true, + "description": "The container where the command should be executed." + }, + "timeout": { + "type": "integer", + "description": "The timeout of the scheduled task in seconds." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time when the scheduled task was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The date and time when the scheduled task was last updated." + } + }, + "type": "object" + }, + "ScheduledTaskExecution": { + "description": "Scheduled Task Execution model", + "properties": { + "uuid": { + "type": "string", + "description": "The unique identifier of the execution." + }, + "status": { + "type": "string", + "enum": [ + "success", + "failed", + "running" + ], + "description": "The status of the execution." + }, + "message": { + "type": "string", + "nullable": true, + "description": "The output message of the execution." + }, + "retry_count": { + "type": "integer", + "description": "The number of retries." + }, + "duration": { + "type": "number", + "nullable": true, + "description": "Duration in seconds." + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the execution started." + }, + "finished_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the execution finished." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the record was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the record was last updated." + } + }, + "type": "object" + }, "Server": { "description": "Server model", "properties": { @@ -11547,55 +11763,6 @@ }, "type": "object" }, - "ScheduledTask": { - "description": "Scheduled Task model", - "properties": { - "id": { - "type": "integer", - "description": "The unique identifier of the scheduled task in the database." - }, - "uuid": { - "type": "string", - "description": "The unique identifier of the scheduled task." - }, - "enabled": { - "type": "boolean", - "description": "The flag to indicate if the scheduled task is enabled." - }, - "name": { - "type": "string", - "description": "The name of the scheduled task." - }, - "command": { - "type": "string", - "description": "The command to execute." - }, - "frequency": { - "type": "string", - "description": "The frequency of the scheduled task." - }, - "container": { - "type": "string", - "nullable": true, - "description": "The container where the command should be executed." - }, - "timeout": { - "type": "integer", - "description": "The timeout of the scheduled task in seconds." - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time when the scheduled task was created." - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "The date and time when the scheduled task was last updated." - } - }, - "type": "object" - }, "Service": { "description": "Service model", "properties": { @@ -11912,6 +12079,10 @@ "name": "Resources", "description": "Resources" }, + { + "name": "Scheduled Tasks", + "description": "Scheduled Tasks" + }, { "name": "Private Keys", "description": "Private Keys" diff --git a/openapi.yaml b/openapi.yaml index 99cc84dcb..6fb548f9f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2163,206 +2163,6 @@ paths: security: - bearerAuth: [] - '/applications/{uuid}/scheduled-tasks': - get: - tags: - - 'Scheduled Tasks' - summary: 'List Tasks' - description: 'List all scheduled tasks for an application.' - operationId: list-scheduled-tasks-by-application-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the application.' - required: true - schema: - type: string - responses: - '200': - description: 'Get all scheduled tasks for an application.' - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/ScheduledTask' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - security: - - - bearerAuth: [] - post: - tags: - - 'Scheduled Tasks' - summary: 'Create Tasks' - description: 'Create a new scheduled task for an application.' - operationId: create-scheduled-task-by-application-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the application.' - required: true, - schema: - type: string - requestBody: - description: 'Scheduled task data' - required: true - content: - application/json: - schema: - required: - - name - - command - - frequency - properties: - name: - type: string - description: 'The name of the scheduled task.' - command: - type: string - description: 'The command to execute.' - frequency: - type: string - description: 'The frequency of the scheduled task.' - container: - type: string - nullable: true - description: 'The container where the command should be executed.' - timeout: - type: integer - description: 'The timeout of the scheduled task in seconds.' - default: 3600 - enabled: - type: boolean - description: 'The flag to indicate if the scheduled task is enabled.' - default: true - type: object - responses: - '201': - description: 'Scheduled task created.' - content: - application/json: - schema: - $ref: '#/components/schemas/ScheduledTask' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '422': - $ref: '#/components/responses/422' - security: - - - bearerAuth: [] - '/applications/{uuid}/scheduled-tasks/{task_uuid}': - patch: - tags: - - 'Scheduled Tasks' - summary: 'Update Task' - description: 'Update a scheduled task for an application.' - operationId: update-scheduled-task-by-application-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the application.' - required: true - schema: - type: string - - - name: task_uuid - in: path - description: 'UUID of the scheduled task.' - required: true - schema: - type: string - requestBody: - description: 'Scheduled task data' - required: true - content: - application/json: - schema: - properties: - name: - type: string - description: 'The name of the scheduled task.' - command: - type: string - description: 'The command to execute.' - frequency: - type: string - description: 'The frequency of the scheduled task.' - container: - type: string - nullable: true - description: 'The container where the command should be executed.' - timeout: - type: integer - description: 'The timeout of the scheduled task in seconds.' - default: 3600 - enabled: - type: boolean - description: 'The flag to indicate if the scheduled task is enabled.' - default: true - type: object - responses: - '200': - description: 'Scheduled task updated.' - content: - application/json: - schema: - $ref: '#/components/schemas/ScheduledTask' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '422': - $ref: '#/components/responses/422' - security: - - - bearerAuth: [] - delete: - tags: - - 'Scheduled Tasks' - summary: 'Delete Task' - description: 'Delete a scheduled task for an application.' - operationId: delete-scheduled-task-by-application-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the application.' - required: true - schema: - type: string - - - name: task_uuid - in: path - description: 'UUID of the scheduled task.' - required: true - schema: - type: string - responses: - '200': - description: 'Scheduled task deleted.' - content: - application/json: - schema: - properties: - message: - type: string - example: 'Scheduled task deleted.' - type: object - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - security: - - - bearerAuth: [] /cloud-tokens: get: tags: @@ -5285,6 +5085,478 @@ paths: security: - bearerAuth: [] + '/applications/{uuid}/scheduled-tasks': + get: + tags: + - 'Scheduled Tasks' + summary: 'List Tasks' + description: 'List all scheduled tasks for an application.' + operationId: list-scheduled-tasks-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + responses: + '200': + description: 'Get all scheduled tasks for an application.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + post: + tags: + - 'Scheduled Tasks' + summary: 'Create Task' + description: 'Create a new scheduled task for an application.' + operationId: create-scheduled-task-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + required: + - name + - command + - frequency + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 300 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '201': + description: 'Scheduled task created.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] + '/applications/{uuid}/scheduled-tasks/{task_uuid}': + delete: + tags: + - 'Scheduled Tasks' + summary: 'Delete Task' + description: 'Delete a scheduled task for an application.' + operationId: delete-scheduled-task-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + responses: + '200': + description: 'Scheduled task deleted.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Scheduled task deleted.' } + type: object + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + patch: + tags: + - 'Scheduled Tasks' + summary: 'Update Task' + description: 'Update a scheduled task for an application.' + operationId: update-scheduled-task-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 300 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '200': + description: 'Scheduled task updated.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] + '/applications/{uuid}/scheduled-tasks/{task_uuid}/executions': + get: + tags: + - 'Scheduled Tasks' + summary: 'List Executions' + description: 'List all executions for a scheduled task on an application.' + operationId: list-scheduled-task-executions-by-application-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + responses: + '200': + description: 'Get all executions for a scheduled task.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduledTaskExecution' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + '/services/{uuid}/scheduled-tasks': + get: + tags: + - 'Scheduled Tasks' + summary: 'List Tasks' + description: 'List all scheduled tasks for a service.' + operationId: list-scheduled-tasks-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + responses: + '200': + description: 'Get all scheduled tasks for a service.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + post: + tags: + - 'Scheduled Tasks' + summary: 'Create Task' + description: 'Create a new scheduled task for a service.' + operationId: create-scheduled-task-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + required: + - name + - command + - frequency + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 300 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '201': + description: 'Scheduled task created.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] + '/services/{uuid}/scheduled-tasks/{task_uuid}': + delete: + tags: + - 'Scheduled Tasks' + summary: 'Delete Task' + description: 'Delete a scheduled task for a service.' + operationId: delete-scheduled-task-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + responses: + '200': + description: 'Scheduled task deleted.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Scheduled task deleted.' } + type: object + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + patch: + tags: + - 'Scheduled Tasks' + summary: 'Update Task' + description: 'Update a scheduled task for a service.' + operationId: update-scheduled-task-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + requestBody: + description: 'Scheduled task data' + required: true + content: + application/json: + schema: + properties: + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + default: 300 + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + default: true + type: object + responses: + '200': + description: 'Scheduled task updated.' + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduledTask' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + '422': + $ref: '#/components/responses/422' + security: + - + bearerAuth: [] + '/services/{uuid}/scheduled-tasks/{task_uuid}/executions': + get: + tags: + - 'Scheduled Tasks' + summary: 'List Executions' + description: 'List all executions for a scheduled task on a service.' + operationId: list-scheduled-task-executions-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + - + name: task_uuid + in: path + description: 'UUID of the scheduled task.' + required: true + schema: + type: string + responses: + '200': + description: 'Get all executions for a scheduled task.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduledTaskExecution' + '401': + $ref: '#/components/responses/401' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] /security/keys: get: tags: @@ -6431,206 +6703,6 @@ paths: security: - bearerAuth: [] - '/services/{uuid}/scheduled-tasks': - get: - tags: - - 'Scheduled Tasks' - summary: 'List (Service)' - description: 'List all scheduled tasks for a service.' - operationId: list-scheduled-tasks-by-service-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the service.' - required: true - schema: - type: string - responses: - '200': - description: 'Get all scheduled tasks for a service.' - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/ScheduledTask' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - security: - - - bearerAuth: [] - post: - tags: - - 'Scheduled Tasks' - summary: 'Create (Service)' - description: 'Create a new scheduled task for a service.' - operationId: create-scheduled-task-by-service-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the service.' - required: true - schema: - type: string - requestBody: - description: 'Scheduled task data' - required: true - content: - application/json: - schema: - required: - - name - - command - - frequency - properties: - name: - type: string - description: 'The name of the scheduled task.' - command: - type: string - description: 'The command to execute.' - frequency: - type: string - description: 'The frequency of the scheduled task.' - container: - type: string - nullable: true - description: 'The container where the command should be executed.' - timeout: - type: integer - description: 'The timeout of the scheduled task in seconds.' - default: 3600 - enabled: - type: boolean - description: 'The flag to indicate if the scheduled task is enabled.' - default: true - type: object - responses: - '201': - description: 'Scheduled task created.' - content: - application/json: - schema: - $ref: '#/components/schemas/ScheduledTask' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '422': - $ref: '#/components/responses/422' - security: - - - bearerAuth: [] - '/services/{uuid}/scheduled-tasks/{task_uuid}': - patch: - tags: - - 'Scheduled Tasks' - summary: 'Update Task' - description: 'Update a scheduled task for a service.' - operationId: update-scheduled-task-by-service-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the service.' - required: true - schema: - type: string - - - name: task_uuid - in: path - description: 'UUID of the scheduled task.' - required: true - schema: - type: string - requestBody: - description: 'Scheduled task data' - required: true - content: - application/json: - schema: - properties: - name: - type: string - description: 'The name of the scheduled task.' - command: - type: string - description: 'The command to execute.' - frequency: - type: string - description: 'The frequency of the scheduled task.' - container: - type: string - nullable: true - description: 'The container where the command should be executed.' - timeout: - type: integer - description: 'The timeout of the scheduled task in seconds.' - default: 3600 - enabled: - type: boolean - description: 'The flag to indicate if the scheduled task is enabled.' - default: true - type: object - responses: - '200': - description: 'Scheduled task updated.' - content: - application/json: - schema: - $ref: '#/components/schemas/ScheduledTask' - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - '422': - $ref: '#/components/responses/422' - security: - - - bearerAuth: [] - delete: - tags: - - 'Scheduled Tasks' - summary: 'Delete Task' - description: 'Delete a scheduled task for a service.' - operationId: delete-scheduled-task-by-service-uuid - parameters: - - - name: uuid - in: path - description: 'UUID of the service.' - required: true - schema: - type: string - - - name: task_uuid - in: path - description: 'UUID of the scheduled task.' - required: true - schema: - type: string - responses: - '200': - description: 'Scheduled task deleted.' - content: - application/json: - schema: - properties: - message: - type: string - example: 'Scheduled task deleted.' - type: object - '401': - $ref: '#/components/responses/401' - '404': - $ref: '#/components/responses/404' - security: - - - bearerAuth: [] /teams: get: tags: @@ -7207,6 +7279,86 @@ components: description: type: string type: object + ScheduledTask: + description: 'Scheduled Task model' + properties: + id: + type: integer + description: 'The unique identifier of the scheduled task in the database.' + uuid: + type: string + description: 'The unique identifier of the scheduled task.' + enabled: + type: boolean + description: 'The flag to indicate if the scheduled task is enabled.' + name: + type: string + description: 'The name of the scheduled task.' + command: + type: string + description: 'The command to execute.' + frequency: + type: string + description: 'The frequency of the scheduled task.' + container: + type: string + nullable: true + description: 'The container where the command should be executed.' + timeout: + type: integer + description: 'The timeout of the scheduled task in seconds.' + created_at: + type: string + format: date-time + description: 'The date and time when the scheduled task was created.' + updated_at: + type: string + format: date-time + description: 'The date and time when the scheduled task was last updated.' + type: object + ScheduledTaskExecution: + description: 'Scheduled Task Execution model' + properties: + uuid: + type: string + description: 'The unique identifier of the execution.' + status: + type: string + enum: + - success + - failed + - running + description: 'The status of the execution.' + message: + type: string + nullable: true + description: 'The output message of the execution.' + retry_count: + type: integer + description: 'The number of retries.' + duration: + type: number + nullable: true + description: 'Duration in seconds.' + started_at: + type: string + format: date-time + nullable: true + description: 'When the execution started.' + finished_at: + type: string + format: date-time + nullable: true + description: 'When the execution finished.' + created_at: + type: string + format: date-time + description: 'When the record was created.' + updated_at: + type: string + format: date-time + description: 'When the record was last updated.' + type: object Server: description: 'Server model' properties: @@ -7344,43 +7496,6 @@ components: type: boolean description: 'The flag to indicate if the unused networks should be deleted.' type: object - ScheduledTask: - description: 'Scheduled Task model' - properties: - id: - type: integer - description: 'The unique identifier of the scheduled task in the database.' - uuid: - type: string - description: 'The unique identifier of the scheduled task.' - enabled: - type: boolean - description: 'The flag to indicate if the scheduled task is enabled.' - name: - type: string - description: 'The name of the scheduled task.' - command: - type: string - description: 'The command to execute.' - frequency: - type: string - description: 'The frequency of the scheduled task.' - container: - type: string - nullable: true - description: 'The container where the command should be executed.' - timeout: - type: integer - description: 'The timeout of the scheduled task in seconds.' - created_at: - type: string - format: date-time - description: 'The date and time when the scheduled task was created.' - updated_at: - type: string - format: date-time - description: 'The date and time when the scheduled task was last updated.' - type: object Service: description: 'Service model' properties: @@ -7598,6 +7713,9 @@ tags: - name: Resources description: Resources + - + name: 'Scheduled Tasks' + description: 'Scheduled Tasks' - name: 'Private Keys' description: 'Private Keys' diff --git a/routes/api.php b/routes/api.php index 9c4d703a9..c39f22c02 100644 --- a/routes/api.php +++ b/routes/api.php @@ -107,7 +107,7 @@ /** * @deprecated Use POST /api/v1/services instead. This endpoint creates a Service, not an Application and is a unstable duplicate of POST /api/v1/services. - */ + */ Route::post('/applications/dockercompose', [ApplicationsController::class, 'create_dockercompose_application'])->middleware(['api.ability:write']); Route::get('/applications/{uuid}', [ApplicationsController::class, 'application_by_uuid'])->middleware(['api.ability:read']); @@ -177,11 +177,13 @@ Route::post('/applications/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); Route::patch('/applications/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'update_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); Route::delete('/applications/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'delete_scheduled_task_by_application_uuid'])->middleware(['api.ability:write']); + Route::get('/applications/{uuid}/scheduled-tasks/{task_uuid}/executions', [ScheduledTasksController::class, 'executions_by_application_uuid'])->middleware(['api.ability:read']); Route::get('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'scheduled_tasks_by_service_uuid'])->middleware(['api.ability:read']); Route::post('/services/{uuid}/scheduled-tasks', [ScheduledTasksController::class, 'create_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); Route::patch('/services/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'update_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); Route::delete('/services/{uuid}/scheduled-tasks/{task_uuid}', [ScheduledTasksController::class, 'delete_scheduled_task_by_service_uuid'])->middleware(['api.ability:write']); + Route::get('/services/{uuid}/scheduled-tasks/{task_uuid}/executions', [ScheduledTasksController::class, 'executions_by_service_uuid'])->middleware(['api.ability:read']); }); Route::group([ diff --git a/tests/Feature/ScheduledTaskApiTest.php b/tests/Feature/ScheduledTaskApiTest.php new file mode 100644 index 000000000..fbd6e383e --- /dev/null +++ b/tests/Feature/ScheduledTaskApiTest.php @@ -0,0 +1,365 @@ +team = Team::factory()->create(); + $this->user = User::factory()->create(); + $this->team->members()->attach($this->user->id, ['role' => 'owner']); + + session(['currentTeam' => $this->team]); + + $this->token = $this->user->createToken('test-token', ['*']); + $this->bearerToken = $this->token->plainTextToken; + + $this->server = Server::factory()->create(['team_id' => $this->team->id]); + $this->destination = StandaloneDocker::factory()->create(['server_id' => $this->server->id]); + $this->project = Project::factory()->create(['team_id' => $this->team->id]); + $this->environment = Environment::factory()->create(['project_id' => $this->project->id]); +}); + +function authHeaders($bearerToken): array +{ + return [ + 'Authorization' => 'Bearer '.$bearerToken, + 'Content-Type' => 'application/json', + ]; +} + +describe('GET /api/v1/applications/{uuid}/scheduled-tasks', function () { + test('returns empty array when no tasks exist', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks"); + + $response->assertStatus(200); + $response->assertJson([]); + }); + + test('returns tasks when they exist', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + ScheduledTask::factory()->create([ + 'application_id' => $application->id, + 'team_id' => $this->team->id, + 'name' => 'Test Task', + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks"); + + $response->assertStatus(200); + $response->assertJsonCount(1); + $response->assertJsonFragment(['name' => 'Test Task']); + }); + + test('returns 404 for unknown application uuid', function () { + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->getJson('/api/v1/applications/nonexistent-uuid/scheduled-tasks'); + + $response->assertStatus(404); + }); +}); + +describe('POST /api/v1/applications/{uuid}/scheduled-tasks', function () { + test('creates a task with valid data', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [ + 'name' => 'Backup', + 'command' => 'php artisan backup', + 'frequency' => '0 0 * * *', + ]); + + $response->assertStatus(201); + $response->assertJsonFragment(['name' => 'Backup']); + + $this->assertDatabaseHas('scheduled_tasks', [ + 'name' => 'Backup', + 'command' => 'php artisan backup', + 'frequency' => '0 0 * * *', + 'application_id' => $application->id, + 'team_id' => $this->team->id, + ]); + }); + + test('returns 422 when name is missing', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [ + 'command' => 'echo test', + 'frequency' => '* * * * *', + ]); + + $response->assertStatus(422); + }); + + test('returns 422 for invalid cron expression', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [ + 'name' => 'Test', + 'command' => 'echo test', + 'frequency' => 'not-a-cron', + ]); + + $response->assertStatus(422); + $response->assertJsonPath('errors.frequency.0', 'Invalid cron expression or frequency format.'); + }); + + test('returns 422 when extra fields are present', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [ + 'name' => 'Test', + 'command' => 'echo test', + 'frequency' => '* * * * *', + 'unknown_field' => 'value', + ]); + + $response->assertStatus(422); + }); + + test('defaults timeout and enabled when not provided', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->postJson("/api/v1/applications/{$application->uuid}/scheduled-tasks", [ + 'name' => 'Test', + 'command' => 'echo test', + 'frequency' => '* * * * *', + ]); + + $response->assertStatus(201); + + $this->assertDatabaseHas('scheduled_tasks', [ + 'name' => 'Test', + 'timeout' => 300, + 'enabled' => true, + ]); + }); +}); + +describe('PATCH /api/v1/applications/{uuid}/scheduled-tasks/{task_uuid}', function () { + test('updates task with partial data', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $task = ScheduledTask::factory()->create([ + 'application_id' => $application->id, + 'team_id' => $this->team->id, + 'name' => 'Old Name', + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->patchJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}", [ + 'name' => 'New Name', + ]); + + $response->assertStatus(200); + $response->assertJsonFragment(['name' => 'New Name']); + }); + + test('returns 404 when task not found', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->patchJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent", [ + 'name' => 'Test', + ]); + + $response->assertStatus(404); + }); +}); + +describe('DELETE /api/v1/applications/{uuid}/scheduled-tasks/{task_uuid}', function () { + test('deletes task successfully', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $task = ScheduledTask::factory()->create([ + 'application_id' => $application->id, + 'team_id' => $this->team->id, + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->deleteJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}"); + + $response->assertStatus(200); + $response->assertJson(['message' => 'Scheduled task deleted.']); + + $this->assertDatabaseMissing('scheduled_tasks', ['uuid' => $task->uuid]); + }); + + test('returns 404 when task not found', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->deleteJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent"); + + $response->assertStatus(404); + }); +}); + +describe('GET /api/v1/applications/{uuid}/scheduled-tasks/{task_uuid}/executions', function () { + test('returns executions for a task', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $task = ScheduledTask::factory()->create([ + 'application_id' => $application->id, + 'team_id' => $this->team->id, + ]); + + ScheduledTaskExecution::create([ + 'scheduled_task_id' => $task->id, + 'status' => 'success', + 'message' => 'OK', + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/{$task->uuid}/executions"); + + $response->assertStatus(200); + $response->assertJsonCount(1); + $response->assertJsonFragment(['status' => 'success']); + }); + + test('returns 404 when task not found', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->getJson("/api/v1/applications/{$application->uuid}/scheduled-tasks/nonexistent/executions"); + + $response->assertStatus(404); + }); +}); + +describe('Service scheduled tasks API', function () { + test('can list tasks for a service', function () { + $service = Service::factory()->create([ + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'environment_id' => $this->environment->id, + ]); + + ScheduledTask::factory()->create([ + 'service_id' => $service->id, + 'team_id' => $this->team->id, + 'name' => 'Service Task', + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->getJson("/api/v1/services/{$service->uuid}/scheduled-tasks"); + + $response->assertStatus(200); + $response->assertJsonCount(1); + $response->assertJsonFragment(['name' => 'Service Task']); + }); + + test('can create a task for a service', function () { + $service = Service::factory()->create([ + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'environment_id' => $this->environment->id, + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->postJson("/api/v1/services/{$service->uuid}/scheduled-tasks", [ + 'name' => 'Service Backup', + 'command' => 'pg_dump', + 'frequency' => '0 2 * * *', + ]); + + $response->assertStatus(201); + $response->assertJsonFragment(['name' => 'Service Backup']); + }); + + test('can delete a task for a service', function () { + $service = Service::factory()->create([ + 'server_id' => $this->server->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + 'environment_id' => $this->environment->id, + ]); + + $task = ScheduledTask::factory()->create([ + 'service_id' => $service->id, + 'team_id' => $this->team->id, + ]); + + $response = $this->withHeaders(authHeaders($this->bearerToken)) + ->deleteJson("/api/v1/services/{$service->uuid}/scheduled-tasks/{$task->uuid}"); + + $response->assertStatus(200); + $response->assertJson(['message' => 'Scheduled task deleted.']); + }); +}); From f0e93eadde55bcfdea4b476e76eb6dfaabe9600d Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:22:35 +0100 Subject: [PATCH 6/7] chore: prepare for PR From 4d36265017fa47e546ee4dc12471c13f29981874 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:30:44 +0100 Subject: [PATCH 7/7] fix(api): improve scheduled tasks validation and delete logic - Use explicit has() checks for timeout and enabled fields to properly handle falsy values - Add validation to prevent empty update requests - Optimize delete endpoint to use direct query deletion instead of fetch-then-delete - Update factory to use Team::factory() for proper test isolation --- .../Controllers/Api/ScheduledTasksController.php | 14 ++++++++------ database/factories/ScheduledTaskFactory.php | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/ScheduledTasksController.php b/app/Http/Controllers/Api/ScheduledTasksController.php index cf3574f1c..6245dc2ec 100644 --- a/app/Http/Controllers/Api/ScheduledTasksController.php +++ b/app/Http/Controllers/Api/ScheduledTasksController.php @@ -93,8 +93,8 @@ private function createTask(Request $request, Application|Service $resource): \I $task->command = $request->command; $task->frequency = $request->frequency; $task->container = $request->container; - $task->timeout = $request->timeout ?? 300; - $task->enabled = $request->enabled ?? true; + $task->timeout = $request->has('timeout') ? $request->timeout : 300; + $task->enabled = $request->has('enabled') ? $request->enabled : true; $task->team_id = $teamId; if ($resource instanceof Application) { @@ -117,6 +117,10 @@ private function updateTask(Request $request, Application|Service $resource): \I return $return; } + if ($request->all() === []) { + return response()->json(['message' => 'At least one field must be provided.'], 422); + } + $allowedFields = ['name', 'command', 'frequency', 'container', 'timeout', 'enabled']; $validator = customApiValidator($request->all(), [ @@ -164,13 +168,11 @@ private function deleteTask(Request $request, Application|Service $resource): \I { $this->authorize('update', $resource); - $task = $resource->scheduled_tasks()->where('uuid', $request->task_uuid)->first(); - if (! $task) { + $deleted = $resource->scheduled_tasks()->where('uuid', $request->task_uuid)->delete(); + if (! $deleted) { return response()->json(['message' => 'Scheduled task not found.'], 404); } - $task->delete(); - return response()->json(['message' => 'Scheduled task deleted.']); } diff --git a/database/factories/ScheduledTaskFactory.php b/database/factories/ScheduledTaskFactory.php index 5f6519288..6e4d6d740 100644 --- a/database/factories/ScheduledTaskFactory.php +++ b/database/factories/ScheduledTaskFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Models\Team; use Illuminate\Database\Eloquent\Factories\Factory; class ScheduledTaskFactory extends Factory @@ -14,7 +15,7 @@ public function definition(): array 'frequency' => '* * * * *', 'timeout' => 300, 'enabled' => true, - 'team_id' => 1, + 'team_id' => Team::factory(), ]; } }