From 2fdac85d3171994317c6ea46260de00e72c06d7b Mon Sep 17 00:00:00 2001 From: Michael Panzlaff Date: Thu, 27 Feb 2025 14:04:54 +0100 Subject: [PATCH 1/3] fix: Do not allow to start a job with a state != running --- internal/api/rest.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/api/rest.go b/internal/api/rest.go index 2921ba5..704f91a 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -772,9 +772,8 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) { return } - if req.State == "" { - req.State = schema.JobStateRunning - } + req.State = schema.JobStateRunning + if err := importer.SanityChecks(&req.BaseJob); err != nil { handleError(err, http.StatusBadRequest, rw) return From f2428d3cb3c91d7380e9528588a75e085c07186f Mon Sep 17 00:00:00 2001 From: Michael Panzlaff Date: Thu, 27 Feb 2025 14:20:18 +0100 Subject: [PATCH 2/3] /jobs/stop_job/ change bad job_state to HTTP 422 --- internal/api/rest.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/api/rest.go b/internal/api/rest.go index 704f91a..ad92a7e 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -1003,8 +1003,13 @@ func (api *RestApi) deleteJobBefore(rw http.ResponseWriter, r *http.Request) { func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobApiRequest) { // Sanity checks - if job == nil || job.StartTime.Unix() >= req.StopTime || job.State != schema.JobStateRunning { - handleError(fmt.Errorf("jobId %d (id %d) on %s : stopTime %d must be larger than startTime %d and only running jobs can be stopped (state is: %s)", job.JobID, job.ID, job.Cluster, req.StopTime, job.StartTime.Unix(), job.State), http.StatusBadRequest, rw) + if job.State != schema.JobStateRunning { + handleError(fmt.Errorf("jobId %d (id %d) on %s : job has already been stopped (state is: %s)", job.JobID, job.ID, job.Cluster, job.State), http.StatusUnprocessableEntity, rw) + return + } + + if job == nil || job.StartTime.Unix() >= req.StopTime { + handleError(fmt.Errorf("jobId %d (id %d) on %s : stopTime %d must be larger than startTime %d", job.JobID, job.ID, job.Cluster, req.StopTime, job.StartTime.Unix()), http.StatusBadRequest, rw) return } From e376f97547bfcb4bd76a88ddab9add33921910cc Mon Sep 17 00:00:00 2001 From: Michael Panzlaff Date: Thu, 27 Feb 2025 14:42:18 +0100 Subject: [PATCH 3/3] make swagger --- api/swagger.json | 100 ++++++++++++++++++++++++++++++++++--------- api/swagger.yaml | 71 +++++++++++++++++++++++------- internal/api/docs.go | 100 ++++++++++++++++++++++++++++++++++--------- internal/api/rest.go | 10 ++--- 4 files changed, 218 insertions(+), 63 deletions(-) diff --git a/api/swagger.json b/api/swagger.json index 3b59b5e..51b22c8 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -202,7 +202,7 @@ "200": { "description": "Success message", "schema": { - "$ref": "#/definitions/api.DeleteJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -272,7 +272,7 @@ "200": { "description": "Success message", "schema": { - "$ref": "#/definitions/api.DeleteJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -342,7 +342,7 @@ "200": { "description": "Success message", "schema": { - "$ref": "#/definitions/api.DeleteJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -487,7 +487,7 @@ "201": { "description": "Job added successfully", "schema": { - "$ref": "#/definitions/api.StartJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -581,7 +581,7 @@ } }, "422": { - "description": "Unprocessable Entity: finding job failed: sql: no rows in result set", + "description": "Unprocessable Entity: job has already been stopped", "schema": { "$ref": "#/definitions/api.ErrorResponse" } @@ -827,6 +827,72 @@ } } }, + "/notice/": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Modifies the content of notice.txt, shown as notice box on the homepage.\nIf more than one formValue is set then only the highest priority field is used.\nOnly accessible from IPs registered with apiAllowedIPs configuration option.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "User" + ], + "summary": "Updates or empties the notice box content", + "parameters": [ + { + "type": "string", + "description": "Priority 1: New content to display", + "name": "new-content", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "Success Response Message", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "type": "string" + } + }, + "422": { + "description": "Unprocessable Entity: The user could not be updated", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/user/{id}": { "post": { "security": [ @@ -1207,6 +1273,14 @@ } } }, + "api.DefaultJobApiResponse": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + }, "api.DeleteJobApiRequest": { "type": "object", "required": [ @@ -1230,14 +1304,6 @@ } } }, - "api.DeleteJobApiResponse": { - "type": "object", - "properties": { - "msg": { - "type": "string" - } - } - }, "api.EditMetaRequest": { "type": "object", "properties": { @@ -1324,14 +1390,6 @@ } } }, - "api.StartJobApiResponse": { - "type": "object", - "properties": { - "msg": { - "type": "string" - } - } - }, "api.StopJobApiRequest": { "type": "object", "required": [ diff --git a/api/swagger.yaml b/api/swagger.yaml index 4e3c47e..f5f0081 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -32,6 +32,11 @@ definitions: example: Debug type: string type: object + api.DefaultJobApiResponse: + properties: + msg: + type: string + type: object api.DeleteJobApiRequest: properties: cluster: @@ -49,11 +54,6 @@ definitions: required: - jobId type: object - api.DeleteJobApiResponse: - properties: - msg: - type: string - type: object api.EditMetaRequest: properties: key: @@ -112,11 +112,6 @@ definitions: scope: $ref: '#/definitions/schema.MetricScope' type: object - api.StartJobApiResponse: - properties: - msg: - type: string - type: object api.StopJobApiRequest: properties: cluster: @@ -906,7 +901,7 @@ paths: "200": description: Success message schema: - $ref: '#/definitions/api.DeleteJobApiResponse' + $ref: '#/definitions/api.DefaultJobApiResponse' "400": description: Bad Request schema: @@ -953,7 +948,7 @@ paths: "200": description: Success message schema: - $ref: '#/definitions/api.DeleteJobApiResponse' + $ref: '#/definitions/api.DefaultJobApiResponse' "400": description: Bad Request schema: @@ -1000,7 +995,7 @@ paths: "200": description: Success message schema: - $ref: '#/definitions/api.DeleteJobApiResponse' + $ref: '#/definitions/api.DefaultJobApiResponse' "400": description: Bad Request schema: @@ -1098,7 +1093,7 @@ paths: "201": description: Job added successfully schema: - $ref: '#/definitions/api.StartJobApiResponse' + $ref: '#/definitions/api.DefaultJobApiResponse' "400": description: Bad Request schema: @@ -1161,8 +1156,7 @@ paths: schema: $ref: '#/definitions/api.ErrorResponse' "422": - description: 'Unprocessable Entity: finding job failed: sql: no rows in - result set' + description: 'Unprocessable Entity: job has already been stopped' schema: $ref: '#/definitions/api.ErrorResponse' "500": @@ -1224,6 +1218,51 @@ paths: summary: Adds one or more tags to a job tags: - Job add and modify + /notice/: + post: + consumes: + - multipart/form-data + description: |- + Modifies the content of notice.txt, shown as notice box on the homepage. + If more than one formValue is set then only the highest priority field is used. + Only accessible from IPs registered with apiAllowedIPs configuration option. + parameters: + - description: 'Priority 1: New content to display' + in: formData + name: new-content + type: string + produces: + - text/plain + responses: + "200": + description: Success Response Message + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "403": + description: Forbidden + schema: + type: string + "422": + description: 'Unprocessable Entity: The user could not be updated' + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Updates or empties the notice box content + tags: + - User /user/{id}: post: consumes: diff --git a/internal/api/docs.go b/internal/api/docs.go index 7c1daac..642003f 100644 --- a/internal/api/docs.go +++ b/internal/api/docs.go @@ -208,7 +208,7 @@ const docTemplate = `{ "200": { "description": "Success message", "schema": { - "$ref": "#/definitions/api.DeleteJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -278,7 +278,7 @@ const docTemplate = `{ "200": { "description": "Success message", "schema": { - "$ref": "#/definitions/api.DeleteJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -348,7 +348,7 @@ const docTemplate = `{ "200": { "description": "Success message", "schema": { - "$ref": "#/definitions/api.DeleteJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -493,7 +493,7 @@ const docTemplate = `{ "201": { "description": "Job added successfully", "schema": { - "$ref": "#/definitions/api.StartJobApiResponse" + "$ref": "#/definitions/api.DefaultJobApiResponse" } }, "400": { @@ -587,7 +587,7 @@ const docTemplate = `{ } }, "422": { - "description": "Unprocessable Entity: finding job failed: sql: no rows in result set", + "description": "Unprocessable Entity: job has already been stopped", "schema": { "$ref": "#/definitions/api.ErrorResponse" } @@ -833,6 +833,72 @@ const docTemplate = `{ } } }, + "/notice/": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Modifies the content of notice.txt, shown as notice box on the homepage.\nIf more than one formValue is set then only the highest priority field is used.\nOnly accessible from IPs registered with apiAllowedIPs configuration option.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "text/plain" + ], + "tags": [ + "User" + ], + "summary": "Updates or empties the notice box content", + "parameters": [ + { + "type": "string", + "description": "Priority 1: New content to display", + "name": "new-content", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "Success Response Message", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "type": "string" + } + }, + "422": { + "description": "Unprocessable Entity: The user could not be updated", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, "/user/{id}": { "post": { "security": [ @@ -1213,6 +1279,14 @@ const docTemplate = `{ } } }, + "api.DefaultJobApiResponse": { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + }, "api.DeleteJobApiRequest": { "type": "object", "required": [ @@ -1236,14 +1310,6 @@ const docTemplate = `{ } } }, - "api.DeleteJobApiResponse": { - "type": "object", - "properties": { - "msg": { - "type": "string" - } - } - }, "api.EditMetaRequest": { "type": "object", "properties": { @@ -1330,14 +1396,6 @@ const docTemplate = `{ } } }, - "api.StartJobApiResponse": { - "type": "object", - "properties": { - "msg": { - "type": "string" - } - } - }, "api.StopJobApiRequest": { "type": "object", "required": [ diff --git a/internal/api/rest.go b/internal/api/rest.go index ad92a7e..b76da0b 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -757,7 +757,7 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) { // @accept json // @produce json // @param request body schema.JobMeta true "Job to add" -// @success 201 {object} api.StartJobApiResponse "Job added successfully" +// @success 201 {object} api.DefaultJobApiResponse "Job added successfully" // @failure 400 {object} api.ErrorResponse "Bad Request" // @failure 401 {object} api.ErrorResponse "Unauthorized" // @failure 403 {object} api.ErrorResponse "Forbidden" @@ -834,7 +834,7 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) { // @failure 401 {object} api.ErrorResponse "Unauthorized" // @failure 403 {object} api.ErrorResponse "Forbidden" // @failure 404 {object} api.ErrorResponse "Resource not found" -// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: finding job failed: sql: no rows in result set" +// @failure 422 {object} api.ErrorResponse "Unprocessable Entity: job has already been stopped" // @failure 500 {object} api.ErrorResponse "Internal Server Error" // @security ApiKeyAuth // @router /jobs/stop_job/ [post] @@ -870,7 +870,7 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) { // @description Job to remove is specified by database ID. This will not remove the job from the job archive. // @produce json // @param id path int true "Database ID of Job" -// @success 200 {object} api.DeleteJobApiResponse "Success message" +// @success 200 {object} api.DefaultJobApiResponse "Success message" // @failure 400 {object} api.ErrorResponse "Bad Request" // @failure 401 {object} api.ErrorResponse "Unauthorized" // @failure 403 {object} api.ErrorResponse "Forbidden" @@ -913,7 +913,7 @@ func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) { // @accept json // @produce json // @param request body api.DeleteJobApiRequest true "All fields required" -// @success 200 {object} api.DeleteJobApiResponse "Success message" +// @success 200 {object} api.DefaultJobApiResponse "Success message" // @failure 400 {object} api.ErrorResponse "Bad Request" // @failure 401 {object} api.ErrorResponse "Unauthorized" // @failure 403 {object} api.ErrorResponse "Forbidden" @@ -963,7 +963,7 @@ func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request) // @description Remove all jobs with start time before timestamp. The jobs will not be removed from the job archive. // @produce json // @param ts path int true "Unix epoch timestamp" -// @success 200 {object} api.DeleteJobApiResponse "Success message" +// @success 200 {object} api.DefaultJobApiResponse "Success message" // @failure 400 {object} api.ErrorResponse "Bad Request" // @failure 401 {object} api.ErrorResponse "Unauthorized" // @failure 403 {object} api.ErrorResponse "Forbidden"