diff --git a/internal/api/rest.go b/internal/api/rest.go index db9a860..89bdd5e 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -750,6 +750,122 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) { json.NewEncoder(rw).Encode(job) } +// removeTagJob godoc +// @summary Removes one or more tags from a job +// @tags Job add and modify +// @description Removes tag(s) from a job specified by DB ID. Name and Type of Tag(s) must match. +// @description Tag Scope is required for matching, options: "global", "admin". Private tags can not be deleted via API. +// @description If tagged job is already finished: Tag will be removed from respective archive files. +// @accept json +// @produce json +// @param id path int true "Job Database ID" +// @param request body api.TagJobApiRequest true "Array of tag-objects to remove" +// @success 200 {object} schema.Job "Updated job resource" +// @failure 400 {object} api.ErrorResponse "Bad Request" +// @failure 401 {object} api.ErrorResponse "Unauthorized" +// @failure 404 {object} api.ErrorResponse "Job or tag does not exist" +// @failure 500 {object} api.ErrorResponse "Internal Server Error" +// @security ApiKeyAuth +// @router /jobs/tag_job/{id} [delete] +func (api *RestApi) removeTagJob(rw http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + job, err := api.JobRepository.FindById(r.Context(), id) + if err != nil { + http.Error(rw, err.Error(), http.StatusNotFound) + return + } + + job.Tags, err = api.JobRepository.GetTags(repository.GetUserFromContext(r.Context()), &job.ID) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + var req TagJobApiRequest + if err := decode(r.Body, &req); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + for _, rtag := range req { + // Only Global and Admin Tags + if rtag.Scope != "global" && rtag.Scope != "admin" { + log.Warnf("Cannot delete private tag for job %d: Skip", job.JobID) + continue + } + + remainingTags, err := api.JobRepository.RemoveJobTagByRequest(repository.GetUserFromContext(r.Context()), job.ID, rtag.Type, rtag.Name, rtag.Scope) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + // remainingTags := job.Tags[:0] + // for _, tag := range job.Tags { + // if tag.Type != rtag.Type && + // tag.Name != rtag.Name && + // tag.Scope != rtag.Scope { + // remainingTags = append(remainingTags, tag) + // } + // } + job.Tags = remainingTags + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + json.NewEncoder(rw).Encode(job) +} + +// removeTags godoc +// @summary Removes all tags and job-relations for type:name tuple +// @tags Tag remove +// @description Removes tags by type and name. Name and Type of Tag(s) must match. +// @description Tag Scope is required for matching, options: "global", "admin". Private tags can not be deleted via API. +// @description Tag wills be removed from respective archive files. +// @accept json +// @produce plain +// @param request body api.TagJobApiRequest true "Array of tag-objects to remove" +// @success 200 {string} string "Success Response" +// @failure 400 {object} api.ErrorResponse "Bad Request" +// @failure 401 {object} api.ErrorResponse "Unauthorized" +// @failure 404 {object} api.ErrorResponse "Job or tag does not exist" +// @failure 500 {object} api.ErrorResponse "Internal Server Error" +// @security ApiKeyAuth +// @router /jobs/tag_job/ [delete] +func (api *RestApi) removeTags(rw http.ResponseWriter, r *http.Request) { + var req TagJobApiRequest + if err := decode(r.Body, &req); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + targetCount := len(req) + currentCount := 0 + for _, rtag := range req { + // Only Global and Admin Tags + if rtag.Scope != "global" && rtag.Scope != "admin" { + log.Warn("Cannot delete private tag: Skip") + continue + } + + err := api.JobRepository.RemoveTagByRequest(rtag.Type, rtag.Name, rtag.Scope) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } else { + currentCount++ + } + } + + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(fmt.Sprintf("Deleted Tags from DB: %d of %d", currentCount, targetCount))) +} + // startJob godoc // @summary Adds a new job as "running" // @tags Job add and modify diff --git a/internal/repository/tags.go b/internal/repository/tags.go index 8120364..3a35b34 100644 --- a/internal/repository/tags.go +++ b/internal/repository/tags.go @@ -45,7 +45,7 @@ func (r *JobRepository) AddTag(user *schema.User, job int64, tag int64) ([]*sche return tags, archive.UpdateTags(j, archiveTags) } -// Removes a tag from a job +// Removes a tag from a job by its ID func (r *JobRepository) RemoveTag(user *schema.User, job, tag int64) ([]*schema.Tag, error) { j, err := r.FindByIdWithUser(user, job) if err != nil { @@ -76,6 +76,76 @@ func (r *JobRepository) RemoveTag(user *schema.User, job, tag int64) ([]*schema. return tags, archive.UpdateTags(j, archiveTags) } +// Removes a tag from a job by tag info +func (r *JobRepository) RemoveJobTagByRequest(user *schema.User, job int64, tagType string, tagName string, tagScope string) ([]*schema.Tag, error) { + // Get Tag ID to delete + tagID, err := r.loadTagIDByInfo(tagName, tagType, tagScope) + if err != nil { + log.Warn("Error while finding tagId with: %s, %s, %s", tagName, tagType, tagScope) + return nil, err + } + + // Get Job + j, err := r.FindByIdWithUser(user, job) + if err != nil { + log.Warn("Error while finding job by id") + return nil, err + } + + // Handle Delete + q := sq.Delete("jobtag").Where("jobtag.job_id = ?", job).Where("jobtag.tag_id = ?", tagID) + + if _, err := q.RunWith(r.stmtCache).Exec(); err != nil { + s, _, _ := q.ToSql() + log.Errorf("Error removing tag from table 'jobTag' with %s: %v", s, err) + return nil, err + } + + tags, err := r.GetTags(user, &job) + if err != nil { + log.Warn("Error while getting tags for job") + return nil, err + } + + archiveTags, err := r.getArchiveTags(&job) + if err != nil { + log.Warn("Error while getting tags for job") + return nil, err + } + + return tags, archive.UpdateTags(j, archiveTags) +} + +// Removes a tag from db by tag info +func (r *JobRepository) RemoveTagByRequest(tagType string, tagName string, tagScope string) error { + // Get Tag ID to delete + tagID, err := r.loadTagIDByInfo(tagName, tagType, tagScope) + if err != nil { + log.Warn("Error while finding tagId with: %s, %s, %s", tagName, tagType, tagScope) + return err + } + + // Handle Delete JobTagTable + qJobTag := sq.Delete("jobtag").Where("jobtag.tag_id = ?", tagID) + + if _, err := qJobTag.RunWith(r.stmtCache).Exec(); err != nil { + s, _, _ := qJobTag.ToSql() + log.Errorf("Error removing tag from table 'jobTag' with %s: %v", s, err) + return err + } + + // Handle Delete TagTable + qTag := sq.Delete("tag").Where("tag.id = ?", tagID) + + if _, err := qTag.RunWith(r.stmtCache).Exec(); err != nil { + s, _, _ := qTag.ToSql() + log.Errorf("Error removing tag from table 'tag' with %s: %v", s, err) + return err + } + + return nil +} + // CreateTag creates a new tag with the specified type and name and returns its database id. func (r *JobRepository) CreateTag(tagType string, tagName string, tagScope string) (tagId int64, err error) { // Default to "Global" scope if none defined @@ -325,3 +395,29 @@ func (r *JobRepository) checkScopeAuth(user *schema.User, operation string, scop return false, fmt.Errorf("error while checking tag operation auth: no user in context") } } + +func (r *JobRepository) loadTagIDByInfo(tagType string, tagName string, tagScope string) (tagID int64, err error) { + // Get Tag ID to delete + getq := sq.Select("id").From("tag"). + Where("tag_type = ?", tagType). + Where("tag_name = ?", tagName). + Where("tag_scope = ?", tagScope) + + rows, err := getq.RunWith(r.stmtCache).Query() + if err != nil { + s, _, _ := getq.ToSql() + log.Errorf("Error get tags for delete with %s: %v", s, err) + return 0, err + } + + dbTags := make([]*schema.Tag, 0) + for rows.Next() { + dbTag := &schema.Tag{} + if err := rows.Scan(&dbTag.ID); err != nil { + log.Warn("Error while scanning rows") + return 0, err + } + } + + return dbTags[0].ID, nil +}