From de097eef91e9e94682fc41adb9f2fcc7ee09ff67 Mon Sep 17 00:00:00 2001 From: deo002 Date: Tue, 2 Apr 2024 11:31:11 +0530 Subject: [PATCH] fix(fix): Fix --- cmd/laas/docs/docs.go | 48 ++++++- cmd/laas/docs/swagger.json | 48 ++++++- cmd/laas/docs/swagger.yaml | 32 ++++- pkg/api/obligations.go | 265 ++++++++++++++++++------------------- pkg/models/types.go | 12 +- 5 files changed, 254 insertions(+), 151 deletions(-) diff --git a/cmd/laas/docs/docs.go b/cmd/laas/docs/docs.go index eafcefd..ce39e36 100644 --- a/cmd/laas/docs/docs.go +++ b/cmd/laas/docs/docs.go @@ -909,10 +909,25 @@ const docTemplate = `{ } ], "responses": { - "207": { - "description": "Multi-Status", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.ImportObligationsResponse" + "allOf": [ + { + "$ref": "#/definitions/models.ImportObligationsResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ObligationImportStatus" + } + } + } + } + ] } }, "400": { @@ -1416,7 +1431,7 @@ const docTemplate = `{ }, "status": { "type": "integer", - "example": 207 + "example": 200 } } }, @@ -1784,6 +1799,31 @@ const docTemplate = `{ } } }, + "models.ObligationId": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 31 + }, + "topic": { + "type": "string", + "example": "copyleft" + } + } + }, + "models.ObligationImportStatus": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.ObligationId" + }, + "status": { + "type": "integer", + "example": 200 + } + } + }, "models.ObligationMapResponse": { "type": "object", "properties": { diff --git a/cmd/laas/docs/swagger.json b/cmd/laas/docs/swagger.json index 7bce839..f0bff29 100644 --- a/cmd/laas/docs/swagger.json +++ b/cmd/laas/docs/swagger.json @@ -902,10 +902,25 @@ } ], "responses": { - "207": { - "description": "Multi-Status", + "200": { + "description": "OK", "schema": { - "$ref": "#/definitions/models.ImportObligationsResponse" + "allOf": [ + { + "$ref": "#/definitions/models.ImportObligationsResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ObligationImportStatus" + } + } + } + } + ] } }, "400": { @@ -1409,7 +1424,7 @@ }, "status": { "type": "integer", - "example": 207 + "example": 200 } } }, @@ -1777,6 +1792,31 @@ } } }, + "models.ObligationId": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 31 + }, + "topic": { + "type": "string", + "example": "copyleft" + } + } + }, + "models.ObligationImportStatus": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.ObligationId" + }, + "status": { + "type": "integer", + "example": 200 + } + } + }, "models.ObligationMapResponse": { "type": "object", "properties": { diff --git a/cmd/laas/docs/swagger.yaml b/cmd/laas/docs/swagger.yaml index 22cc815..c2b7a33 100644 --- a/cmd/laas/docs/swagger.yaml +++ b/cmd/laas/docs/swagger.yaml @@ -69,7 +69,7 @@ definitions: items: {} type: array status: - example: 207 + example: 200 type: integer type: object models.LicenseDB: @@ -328,6 +328,23 @@ definitions: example: risk type: string type: object + models.ObligationId: + properties: + id: + example: 31 + type: integer + topic: + example: copyleft + type: string + type: object + models.ObligationImportStatus: + properties: + data: + $ref: '#/definitions/models.ObligationId' + status: + example: 200 + type: integer + type: object models.ObligationMapResponse: properties: data: @@ -1256,10 +1273,17 @@ paths: produces: - application/json responses: - "207": - description: Multi-Status + "200": + description: OK schema: - $ref: '#/definitions/models.ImportObligationsResponse' + allOf: + - $ref: '#/definitions/models.ImportObligationsResponse' + - properties: + data: + items: + $ref: '#/definitions/models.ObligationImportStatus' + type: array + type: object "400": description: input file must be present schema: diff --git a/pkg/api/obligations.go b/pkg/api/obligations.go index f405ff3..08d92eb 100644 --- a/pkg/api/obligations.go +++ b/pkg/api/obligations.go @@ -12,7 +12,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "net/http" "path/filepath" "strconv" @@ -357,8 +356,7 @@ func UpdateObligation(c *gin.Context) { return err } - var user models.User - if err := tx.Where(models.User{Username: username}).First(&user).Error; err != nil { + if err := addChangelogsForObligationUpdate(tx, username, &newObligation, &oldObligation); err != nil { er := models.LicenseError{ Status: http.StatusInternalServerError, Message: "Failed to update license", @@ -370,95 +368,6 @@ func UpdateObligation(c *gin.Context) { return err } - var changes []models.ChangeLog - - if oldObligation.Topic != newObligation.Topic { - changes = append(changes, models.ChangeLog{ - Field: "Topic", - OldValue: &oldObligation.Topic, - UpdatedValue: &newObligation.Topic, - }) - } - if oldObligation.Type != newObligation.Type { - changes = append(changes, models.ChangeLog{ - Field: "Type", - OldValue: &oldObligation.Type, - UpdatedValue: &newObligation.Type, - }) - } - if oldObligation.Md5 != newObligation.Md5 { - changes = append(changes, models.ChangeLog{ - Field: "Text", - OldValue: &oldObligation.Text, - UpdatedValue: &newObligation.Text, - }) - } - if oldObligation.Classification != newObligation.Classification { - oldVal := strconv.FormatBool(oldObligation.Modifications) - newVal := strconv.FormatBool(newObligation.Modifications) - changes = append(changes, models.ChangeLog{ - Field: "Classification", - OldValue: &oldVal, - UpdatedValue: &newVal, - }) - } - if oldObligation.Modifications != newObligation.Modifications { - oldVal := strconv.FormatBool(oldObligation.Modifications) - newVal := strconv.FormatBool(newObligation.Modifications) - changes = append(changes, models.ChangeLog{ - Field: "Modifications", - OldValue: &oldVal, - UpdatedValue: &newVal, - }) - } - if oldObligation.Comment != newObligation.Comment { - changes = append(changes, models.ChangeLog{ - Field: "Comment", - OldValue: &oldObligation.Comment, - UpdatedValue: &newObligation.Comment, - }) - } - if oldObligation.Active != newObligation.Active { - oldVal := strconv.FormatBool(oldObligation.Active) - newVal := strconv.FormatBool(newObligation.Active) - changes = append(changes, models.ChangeLog{ - Field: "Active", - OldValue: &oldVal, - UpdatedValue: &newVal, - }) - } - if oldObligation.TextUpdatable != newObligation.TextUpdatable { - oldVal := strconv.FormatBool(oldObligation.TextUpdatable) - newVal := strconv.FormatBool(newObligation.TextUpdatable) - changes = append(changes, models.ChangeLog{ - Field: "TextUpdatable", - OldValue: &oldVal, - UpdatedValue: &newVal, - }) - } - - if len(changes) != 0 { - audit := models.Audit{ - UserId: user.Id, - TypeId: newObligation.Id, - Timestamp: time.Now(), - Type: "Obligation", - ChangeLogs: changes, - } - - if err := tx.Create(&audit).Error; err != nil { - er := models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Failed to update license", - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusInternalServerError, er) - return err - } - } - res := models.ObligationResponse{ Data: []models.Obligation{newObligation}, Status: http.StatusOK, @@ -575,12 +484,13 @@ func GetObligationAudits(c *gin.Context) { // @Accept multipart/form-data // @Produce json // @Param file formData file true "obligations json file list" -// @Success 207 {object} models.ImportObligationsResponse +// @Success 200 {object} models.ImportObligationsResponse{data=[]models.ObligationImportStatus} // @Failure 400 {object} models.LicenseError "input file must be present" // @Failure 500 {object} models.LicenseError "Internal server error" // @Security ApiKeyAuth // @Router /obligations/import [post] func ImportObligations(c *gin.Context) { + username := c.GetString("username") file, header, err := c.Request.FormFile("file") if err != nil { er := models.LicenseError{ @@ -622,7 +532,7 @@ func ImportObligations(c *gin.Context) { } res := models.ImportObligationsResponse{ - Status: http.StatusMultiStatus, + Status: http.StatusOK, } for _, obligation := range obligations { @@ -642,22 +552,11 @@ func ImportObligations(c *gin.Context) { md5hash := hex.EncodeToString(hash[:]) ob.Md5 = md5hash + var oldObligation models.Obligation result := tx. Where(&models.Obligation{Topic: ob.Topic}). Or(&models.Obligation{Md5: ob.Md5}). - FirstOrCreate(&ob) - - if result.RowsAffected == 0 { - res.Data = append(res.Data, models.LicenseError{ - Status: http.StatusConflict, - Message: fmt.Sprintf("Obligation with topic '%s' or MD5 '%s' already exists", - ob.Topic, ob.Md5), - Error: ob.Topic, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - }) - return errors.New("obligation already exists") - } + FirstOrCreate(&oldObligation) if result.Error != nil { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusInternalServerError, @@ -667,47 +566,147 @@ func ImportObligations(c *gin.Context) { Timestamp: time.Now().Format(time.RFC3339), }) return err - } - - var maps []models.ObligationMap - for _, i := range obligation.Shortnames { - var license models.LicenseDB - if err := tx.Debug().Where(models.LicenseDB{Shortname: i}).First(&license).Error; err != nil { + } else if result.RowsAffected == 0 { + // case when obligation exists in database and is updated + result := tx.Model(&ob).Clauses(clause.Returning{}).Where(&models.Obligation{Topic: ob.Topic}).Updates(&ob) + if result.Error != nil { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusInternalServerError, - Message: fmt.Sprintf("Error finding license with shortname: %s", i), + Message: fmt.Sprintf("Failed to update obligation: %s", result.Error.Error()), Error: ob.Topic, Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), }) return err } - log.Println(license.Shortname) - maps = append(maps, models.ObligationMap{ - ObligationPk: ob.Id, - RfPk: license.Id, + + if result.RowsAffected == 0 { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusConflict, + Message: "Another obligation with the same text exists", + Error: ob.Topic, + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } + + if err := addChangelogsForObligationUpdate(tx, username, &ob, &oldObligation); err != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to update license", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } + + res.Data = append(res.Data, models.ObligationImportStatus{ + Data: models.ObligationId{Id: ob.Id, Topic: ob.Topic}, + Status: http.StatusOK, }) - } - if err := tx.Create(&maps).Error; err != nil { - res.Data = append(res.Data, models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Error linking obligation with license", - Error: ob.Topic, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), + } else { + // case when obligation doesn't exist in database and is inserted + res.Data = append(res.Data, models.ObligationImportStatus{ + Data: models.ObligationId{Id: oldObligation.Id, Topic: oldObligation.Topic}, + Status: http.StatusCreated, }) - return err } - res.Data = append(res.Data, models.ObligationImportStatus{ - Data: models.ObligationId{Id: ob.Id, Topic: ob.Topic}, - Status: http.StatusCreated, - }) - return nil }) } - c.JSON(http.StatusMultiStatus, res) + c.JSON(http.StatusOK, res) +} + +// addChangelogsForObligationUpdate adds changelogs for the updated fields on obligation update +func addChangelogsForObligationUpdate(tx *gorm.DB, username string, + newObligation, oldObligation *models.Obligation) error { + var user models.User + if err := tx.Where(models.User{Username: username}).First(&user).Error; err != nil { + return err + } + var changes []models.ChangeLog + + if oldObligation.Topic != newObligation.Topic { + changes = append(changes, models.ChangeLog{ + Field: "Topic", + OldValue: &oldObligation.Topic, + UpdatedValue: &newObligation.Topic, + }) + } + if oldObligation.Type != newObligation.Type { + changes = append(changes, models.ChangeLog{ + Field: "Type", + OldValue: &oldObligation.Type, + UpdatedValue: &newObligation.Type, + }) + } + if oldObligation.Md5 != newObligation.Md5 { + changes = append(changes, models.ChangeLog{ + Field: "Text", + OldValue: &oldObligation.Text, + UpdatedValue: &newObligation.Text, + }) + } + if oldObligation.Classification != newObligation.Classification { + changes = append(changes, models.ChangeLog{ + Field: "Classification", + OldValue: &oldObligation.Classification, + UpdatedValue: &newObligation.Classification, + }) + } + if oldObligation.Modifications != newObligation.Modifications { + oldVal := strconv.FormatBool(oldObligation.Modifications) + newVal := strconv.FormatBool(newObligation.Modifications) + changes = append(changes, models.ChangeLog{ + Field: "Modifications", + OldValue: &oldVal, + UpdatedValue: &newVal, + }) + } + if oldObligation.Comment != newObligation.Comment { + changes = append(changes, models.ChangeLog{ + Field: "Comment", + OldValue: &oldObligation.Comment, + UpdatedValue: &newObligation.Comment, + }) + } + if oldObligation.Active != newObligation.Active { + oldVal := strconv.FormatBool(oldObligation.Active) + newVal := strconv.FormatBool(newObligation.Active) + changes = append(changes, models.ChangeLog{ + Field: "Active", + OldValue: &oldVal, + UpdatedValue: &newVal, + }) + } + if oldObligation.TextUpdatable != newObligation.TextUpdatable { + oldVal := strconv.FormatBool(oldObligation.TextUpdatable) + newVal := strconv.FormatBool(newObligation.TextUpdatable) + changes = append(changes, models.ChangeLog{ + Field: "TextUpdatable", + OldValue: &oldVal, + UpdatedValue: &newVal, + }) + } + + if len(changes) != 0 { + audit := models.Audit{ + UserId: user.Id, + TypeId: newObligation.Id, + Timestamp: time.Now(), + Type: "Obligation", + ChangeLogs: changes, + } + + if err := tx.Create(&audit).Error; err != nil { + return err + } + } + + return nil } diff --git a/pkg/models/types.go b/pkg/models/types.go index ad7850b..195d33a 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -332,7 +332,7 @@ type ObligationImportRequest struct { // Obligation represents an obligation record in the import json file. type ObligationImport struct { - Topic string `json:"topic" example:"copyleft" validate:"required"` + Topic string `json:"topic" example:"copyleft" validate:"required"` // binding:"required" tag cannot be used as is works only for request body Type string `json:"type" enums:"obligation,restriction,risk,right" validate:"required"` Text string `json:"text" example:"Source code be made available when distributing the software." validate:"required"` Classification string `json:"classification" enums:"green,white,yellow,red" validate:"required"` @@ -343,20 +343,20 @@ type ObligationImport struct { Shortnames []string `json:"shortnames" example:"GPL-2.0-only,GPL-2.0-or-later" validate:"required"` } -// Id of successfully imported obligation +// ObligationId is the id of successfully imported obligation type ObligationId struct { - Id int64 `json:"id"` + Id int64 `json:"id" example:"31"` Topic string `json:"topic" example:"copyleft"` } -// Status of obligation records successfully inserted in the database during import +// ObligationImportStatus is the status of obligation records successfully inserted in the database during import type ObligationImportStatus struct { Status int `json:"status" example:"200"` Data ObligationId `json:"data"` } -// Response structure for import obligation response +// ImportObligationsResponse is the response structure for import obligation response type ImportObligationsResponse struct { - Status int `json:"status" example:"207"` + Status int `json:"status" example:"200"` Data []interface{} `json:"data"` // can be of type models.LicenseError or models.ObligationImportStatus }