From 9fad40c8f867e171e8a5aa4081be2d3062903f8a Mon Sep 17 00:00:00 2001 From: Kavya Shukla Date: Thu, 24 Aug 2023 22:27:48 +0530 Subject: [PATCH] feat: added obligation endpoints. - POST Method "/api/obligations" -create an obligation and its Map - DELETE Method "/api/obligations/:topic" -Delete an obligation by its topic and its Map - PATCH Method "/api/obligations/:topic" -Update an obligation by its topic - GET Method "/api/obligations" -Get all the obligations - GET Method "/api/obligations/:topic" -Get an obligation by its topic Signed-off-by: Kavya Shukla --- cmd/laas/main.go | 9 ++ pkg/api/api.go | 350 +++++++++++++++++++++++++++++++++++++++++++- pkg/api/api_test.go | 2 - pkg/auth/auth.go | 4 +- pkg/models/types.go | 72 +++++++-- 5 files changed, 420 insertions(+), 17 deletions(-) diff --git a/cmd/laas/main.go b/cmd/laas/main.go index 235c97d..48fd058 100644 --- a/cmd/laas/main.go +++ b/cmd/laas/main.go @@ -50,6 +50,15 @@ func main() { if err := db.DB.AutoMigrate(&models.ChangeLog{}); err != nil { log.Fatalf("Failed to automigrate database: %v", err) } + + if err := db.DB.AutoMigrate(&models.Obligation{}); err != nil { + log.Fatalf("Failed to automigrate database: %v", err) + } + + if err := db.DB.AutoMigrate(&models.ObligationMap{}); err != nil { + log.Fatalf("Failed to automigrate database: %v", err) + } + db.Populatedb(*populatedb, *datafile) r := api.Router() diff --git a/pkg/api/api.go b/pkg/api/api.go index 5f7d01b..99e0988 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -4,6 +4,8 @@ package api import ( + "crypto/md5" + "encoding/hex" "fmt" "net/http" "strconv" @@ -42,6 +44,12 @@ func Router() *gin.Engine { authorized.GET("/api/audit/:audit_id/changes", GetChangeLog) authorized.GET("/api/audit/:audit_id/changes/:id", GetChangeLogbyId) + authorized.POST("/api/obligations", CreateObligation) + authorized.DELETE("/api/obligations/:topic", DeleteObligation) + authorized.PATCH("/api/obligations/:topic", UpdateObligation) + r.GET("/api/obligations", GetAllObligation) + r.GET("/api/obligations/:topic", GetObligation) + return r } @@ -134,7 +142,28 @@ func CreateLicense(c *gin.Context) { if input.Active == "" { input.Active = "t" } - license := models.LicenseDB(input) + license := models.LicenseDB{ + Shortname: input.Shortname, + Fullname: input.Fullname, + Text: input.Text, + Url: input.Url, + AddDate: input.AddDate, + Copyleft: input.Copyleft, + Active: input.Active, + FSFfree: input.FSFfree, + GPLv2compatible: input.GPLv2compatible, + GPLv3compatible: input.GPLv3compatible, + OSIapproved: input.OSIapproved, + TextUpdatable: input.TextUpdatable, + DetectorType: input.DetectorType, + Marydone: input.Marydone, + Notes: input.Notes, + Fedora: input.Fedora, + Flag: input.Flag, + Source: input.Source, + SpdxId: input.SpdxId, + Risk: input.Risk, + } result := db.DB.FirstOrCreate(&license) if result.RowsAffected == 0 { @@ -221,10 +250,14 @@ func UpdateLicense(c *gin.Context) { ResourceCount: 1, }, } + + var user models.User + db.DB.Where("username = ?", username).First(&user) audit := models.Audit{ - Username: username, - Shortname: shortname, + UserId: user.Id, + TypeId: license.Id, Timestamp: time.Now().Format(time.RFC3339), + Type: "license", } db.DB.Create(&audit) @@ -606,7 +639,7 @@ func GetChangeLog(c *gin.Context) { Data: changelog, Status: http.StatusOK, Meta: models.PaginationMeta{ - ResourceCount: 1, + ResourceCount: len(changelog), }, } @@ -648,3 +681,312 @@ func GetChangeLogbyId(c *gin.Context) { } c.JSON(http.StatusOK, res) } + +func CreateObligation(c *gin.Context) { + var input models.ObligationInput + + if err := c.ShouldBindJSON(&input); err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "invalid json body", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + s := input.Text + hash := md5.Sum([]byte(s)) + md5hash := hex.EncodeToString(hash[:]) + fmt.Printf(md5hash) + input.Active = true + + input.TextUpdatable = false + + obligation := models.Obligation{ + Md5: md5hash, + Type: input.Type, + Topic: input.Topic, + Text: input.Text, + Classification: input.Classification, + Comment: input.Comment, + Modifications: input.Modifications, + TextUpdatable: input.TextUpdatable, + Active: input.Active, + } + fmt.Print(obligation) + + result := db.DB.Debug().FirstOrCreate(&obligation) + + fmt.Print(obligation) + if result.RowsAffected == 0 { + + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "can not create obligation with same Md5", + Error: fmt.Sprintf("Error: Obligation with Md5 '%s' already exists", obligation.Md5), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + if result.Error != nil { + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to create obligation", + Error: result.Error.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + for _, i := range input.Shortnames { + var license models.LicenseDB + db.DB.Where("shortname = ?", i).Find(&license) + obmap := models.ObligationMap{ + ObligationPk: obligation.Id, + RfPk: license.Id, + } + db.DB.Create(&obmap) + } + + res := models.ObligationResponse{ + Data: []models.Obligation{obligation}, + Status: http.StatusCreated, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusCreated, res) +} + +func GetAllObligation(c *gin.Context) { + var obligations []models.Obligation + query := db.DB.Model(&obligations) + query = query.Where("active=?", true) + err := query.Find(&obligations).Error + if err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Obligations not found", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + res := models.ObligationResponse{ + Data: obligations, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: len(obligations), + }, + } + + c.JSON(http.StatusOK, res) +} + +func UpdateObligation(c *gin.Context) { + var update models.UpdateObligation + var oldobligation models.Obligation + var obligation models.Obligation + + username := c.GetString("username") + query := db.DB.Model(&obligation) + query = query.Where("active=?", true) + tp := c.Param("topic") + if err := query.Where("topic = ?", tp).First(&obligation).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("obligation with topic '%s' not found", tp), + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + oldobligation = obligation + if err := c.ShouldBindJSON(&update); err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "invalid json body", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + if err := db.DB.Model(&obligation).Updates(update).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 + } + + var user models.User + db.DB.Where("username = ?", username).First(&user) + audit := models.Audit{ + UserId: user.Id, + TypeId: obligation.Id, + Timestamp: time.Now().Format(time.RFC3339), + Type: "Obligation", + } + db.DB.Create(&audit) + + if oldobligation.Topic != obligation.Topic { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Topic", + OldValue: oldobligation.Topic, + UpdatedValue: obligation.Topic, + } + db.DB.Create(&change) + } + if oldobligation.Type != obligation.Type { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Type", + OldValue: oldobligation.Type, + UpdatedValue: obligation.Type, + } + db.DB.Create(&change) + } + if oldobligation.Text != obligation.Text { + if oldobligation.TextUpdatable == true { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Text", + OldValue: oldobligation.Text, + UpdatedValue: obligation.Text, + } + db.DB.Create(&change) + } else { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Can not update obligation text", + Error: "Invalid Request", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + } + if oldobligation.Classification == obligation.Classification { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Classification", + OldValue: oldobligation.Classification, + UpdatedValue: obligation.Classification, + } + db.DB.Create(&change) + } + if oldobligation.Modifications == obligation.Modifications { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Modification", + OldValue: oldobligation.Modifications, + UpdatedValue: obligation.Modifications, + } + db.DB.Create(&change) + } + if oldobligation.Comment == obligation.Comment { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Comment", + OldValue: oldobligation.Comment, + UpdatedValue: obligation.Comment, + } + db.DB.Create(&change) + } + if oldobligation.Active == obligation.Active { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Active", + OldValue: strconv.FormatBool(oldobligation.Active), + UpdatedValue: strconv.FormatBool(obligation.Active), + } + db.DB.Create(&change) + } + if oldobligation.TextUpdatable == obligation.TextUpdatable { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "TextUpdatable", + OldValue: strconv.FormatBool(oldobligation.TextUpdatable), + UpdatedValue: strconv.FormatBool(obligation.TextUpdatable), + } + db.DB.Create(&change) + } + if oldobligation.Md5 == obligation.Md5 { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Md5", + OldValue: oldobligation.Md5, + UpdatedValue: obligation.Md5, + } + db.DB.Create(&change) + } + res := models.ObligationResponse{ + Data: []models.Obligation{obligation}, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + c.JSON(http.StatusOK, res) +} + +func DeleteObligation(c *gin.Context) { + var obligation models.Obligation + tp := c.Param("topic") + if err := db.DB.Where("topic= ?", tp).First(&obligation).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("obligation with topic '%s' not found", tp), + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + obligation.Active = false +} + +func GetObligation(c *gin.Context) { + var obligation models.Obligation + query := db.DB.Model(&obligation) + query = query.Where("active=?", true) + tp := c.Param("topic") + if err := query.Where("topic= ?", tp).First(&obligation).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("obligation with topic '%s' not found", tp), + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + res := models.ObligationResponse{ + Data: []models.Obligation{obligation}, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + c.JSON(http.StatusOK, res) +} diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index edf232d..e5c6f06 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -189,7 +189,6 @@ func TestSearchInLicense2(t *testing.T) { func TestGetUser(t *testing.T) { expectUser := models.User{ - UserId: "1", Username: "fossy", Userpassword: "fossy", Userlevel: "admin", @@ -206,7 +205,6 @@ func TestGetUser(t *testing.T) { func TestCreateUser(t *testing.T) { user := models.User{ - UserId: "2", Username: "general_user", Userpassword: "abc123", Userlevel: "participant", diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 494273d..5a20367 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -33,7 +33,7 @@ func CreateUser(c *gin.Context) { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "can not create user with same userid", - Error: fmt.Sprintf("Error: License with userid '%s' already exists", user.UserId), + Error: fmt.Sprintf("Error: License with userid '%d' already exists", user.Id), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } @@ -80,7 +80,7 @@ func GetUser(c *gin.Context) { var user models.User id := c.Param("id") - if err := db.DB.Where("userid = ?", id).First(&user).Error; err != nil { + if err := db.DB.Where("id = ?", id).First(&user).Error; err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "no user with such user id exists", diff --git a/pkg/models/types.go b/pkg/models/types.go index 4d5eedc..1cf2026 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -7,7 +7,8 @@ package models // properties associated with it. // It provides structured storage for license-related information. type LicenseDB struct { - Shortname string `json:"rf_shortname" gorm:"primary_key"` + Id int64 `json:"rf_id" gorm:"primary_key"` + Shortname string `json:"rf_shortname"` Fullname string `json:"rf_fullname"` Text string `json:"rf_text"` Url string `json:"rf_url"` @@ -110,7 +111,7 @@ type LicenseInput struct { // User struct is representation of user information. type User struct { - UserId string `json:"userid" gorm:"primary_key" binding:"required"` + Id int64 `json:"id" gorm:"primary_key"` Username string `json:"username" gorm:"unique" binding:"required"` Userlevel string `json:"userlevel" gorm:"unique" binding:"required"` Userpassword string `json:"userpassword" gorm:"unique" binding:"required"` @@ -123,19 +124,21 @@ type UserResponse struct { Meta PaginationMeta `json:"paginationmeta"` } -type Audit struct { - Id int `json:"id" gorm:"primary_key"` - Username string `json:"username"` - Shortname string `json:"shortname"` - Timestamp string `json:"timestamp"` -} - type SearchLicense struct { Field string `json:"field" binding:"required"` SearchTerm string `json:"search_term" binding:"required"` Search string `json:"search"` } +type Audit struct { + Id int `json:"id" gorm:"primary_key"` + UserId int64 `json:"user_id"` + User User `gorm:"foreignKey:UserId;references:Id" json:"-"` + TypeId int64 `json:"type_id"` + Timestamp string `json:"timestamp"` + Type string `json:"type"` +} + type ChangeLog struct { Id int `json:"id" gorm:"primary_key"` Field string `json:"field"` @@ -156,3 +159,54 @@ type AuditResponse struct { Data []Audit `json:"data"` Meta PaginationMeta `json:"paginationmeta"` } + +type Obligation struct { + Id int64 `json:"id" gorm:"primary_key"` + Topic string `json:"topic"` + Type string `json:"type"` + Text string `json:"text"` + Classification string `json:"classification"` + Modifications string `json:"modifications"` + Comment string `json:"comment"` + Active bool `json:"active"` + TextUpdatable bool `json:"text_updatable"` + Md5 string `json:"md5" gorm:"unique"` +} + +type ObligationInput struct { + Topic string `json:"topic" binding:"required"` + Type string `json:"type" binding:"required"` + Text string `json:"text" binding:"required"` + Classification string `json:"classification"` + Modifications string `json:"modifications"` + Comment string `json:"comment"` + Active bool `json:"active"` + TextUpdatable bool `json:"text_updatable"` + Shortnames []string `json:"shortnames"` +} + +type UpdateObligation struct { + Topic string `json:"topic"` + Type string `json:"type"` + Text string `json:"text"` + Classification string `json:"classification"` + Modifications string `json:"modifications"` + Comment string `json:"comment"` + Active bool `json:"active"` + TextUpdatable bool `json:"text_updatable"` + Md5 string `json:"md5"` +} + +type ObligationMap struct { + ObligationPk int64 `json:"obligation_pk"` + Obligation Obligation `gorm:"foreignKey:ObligationPk;references:Id" json:"-"` + OmPk int64 `json:"om_pk" gorm:"primary_key"` + RfPk int64 `json:"rf_pk"` + LicenseDB LicenseDB `gorm:"foreignKey:RfPk;references:Id" json:"-"` +} + +type ObligationResponse struct { + Status int `json:"status"` + Data []Obligation `json:"data"` + Meta PaginationMeta `json:"paginationmeta"` +}