diff --git a/cmd/laas/main.go b/cmd/laas/main.go index c17ff99..235c97d 100644 --- a/cmd/laas/main.go +++ b/cmd/laas/main.go @@ -43,8 +43,16 @@ func main() { log.Fatalf("Failed to automigrate database: %v", err) } + if err := db.DB.AutoMigrate(&models.Audit{}); err != nil { + log.Fatalf("Failed to automigrate database: %v", err) + } + + if err := db.DB.AutoMigrate(&models.ChangeLog{}); err != nil { + log.Fatalf("Failed to automigrate database: %v", err) + } db.Populatedb(*populatedb, *datafile) r := api.Router() + r.Run() } diff --git a/pkg/api/api.go b/pkg/api/api.go index 2e2178a..aff9461 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -6,6 +6,7 @@ package api import ( "fmt" "net/http" + "strconv" "time" "github.com/fossology/LicenseDb/pkg/auth" @@ -22,19 +23,25 @@ func Router() *gin.Engine { r.NoRoute(HandleInvalidUrl) // authorization not required for these routes - r.GET("/api/license/:shortname", GetLicense) - r.GET("/api/licenses", SearchInLicense) + r.GET("/api/licenses/:shortname", GetLicense) + r.GET("/api/licenses", FilterLicense) r.GET("/api/users", auth.GetAllUser) r.GET("/api/user/:id", auth.GetUser) + r.POST("/api/licenses", SearchInLicense) //api/licenses/search // set up authentication authorized := r.Group("/") authorized.Use(auth.AuthenticationMiddleware()) authorized.POST("/api/license", CreateLicense) - authorized.PATCH("/api/license/update/:shortname", UpdateLicense) + authorized.PATCH("/api/licenses/update/:shortname", UpdateLicense) authorized.POST("/api/user", auth.CreateUser) + authorized.GET("/api/audit", GetAllAudit) + authorized.GET("/api/audit/:audit_id", GetAudit) + authorized.GET("/api/audit/:audit_id/changes", GetChangeLog) + authorized.GET("/api/audit/:audit_id/changes/:id", GetChangeLogbyId) + return r } @@ -167,6 +174,10 @@ func CreateLicense(c *gin.Context) { func UpdateLicense(c *gin.Context) { var update models.LicenseDB var license models.LicenseDB + var oldlicense models.LicenseDB + + username := c.GetString("username") + shortname := c.Param("shortname") if err := db.DB.Where("shortname = ?", shortname).First(&license).Error; err != nil { er := models.LicenseError{ @@ -179,6 +190,7 @@ func UpdateLicense(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } + oldlicense = license if err := c.ShouldBindJSON(&update); err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, @@ -201,6 +213,7 @@ func UpdateLicense(c *gin.Context) { c.JSON(http.StatusInternalServerError, er) return } + res := models.LicenseResponse{ Data: []models.LicenseDB{license}, Status: http.StatusOK, @@ -208,15 +221,199 @@ func UpdateLicense(c *gin.Context) { ResourceCount: 1, }, } + audit := models.Audit{ + Username: username, + Shortname: shortname, + Timestamp: time.Now().Format(time.RFC3339), + } + db.DB.Create(&audit) + + if oldlicense.Shortname != license.Shortname { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "shortname", + OldValue: oldlicense.Shortname, + UpdatedValue: license.Shortname, + } + db.DB.Create(&change) + } + if oldlicense.Fullname != license.Fullname { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "fullname", + OldValue: oldlicense.Fullname, + UpdatedValue: license.Fullname, + } + db.DB.Create(&change) + } + if oldlicense.Url != license.Url { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Url", + OldValue: oldlicense.Url, + UpdatedValue: license.Url, + } + db.DB.Create(&change) + } + if oldlicense.AddDate != license.AddDate { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Adddate", + OldValue: oldlicense.AddDate, + UpdatedValue: license.AddDate, + } + db.DB.Create(&change) + } + if oldlicense.Active != license.Active { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Active", + OldValue: oldlicense.Active, + UpdatedValue: license.Active, + } + db.DB.Create(&change) + } + if oldlicense.Copyleft != license.Copyleft { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Copyleft", + OldValue: oldlicense.Copyleft, + UpdatedValue: license.Copyleft, + } + db.DB.Create(&change) + } + if oldlicense.FSFfree != license.FSFfree { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "FSFfree", + OldValue: oldlicense.FSFfree, + UpdatedValue: license.FSFfree, + } + db.DB.Create(&change) + } + if oldlicense.GPLv2compatible != license.GPLv2compatible { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "GPLv2compatible", + OldValue: oldlicense.GPLv2compatible, + UpdatedValue: license.GPLv2compatible, + } + db.DB.Create(&change) + } + if oldlicense.GPLv3compatible != license.GPLv3compatible { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "GPLv3compatible", + OldValue: oldlicense.GPLv3compatible, + UpdatedValue: license.GPLv3compatible, + } + db.DB.Create(&change) + } + if oldlicense.OSIapproved != license.OSIapproved { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "OSIapproved", + OldValue: oldlicense.Shortname, + UpdatedValue: license.Shortname, + } + db.DB.Create(&change) + } + if oldlicense.Text != license.Text { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Text", + OldValue: oldlicense.Text, + UpdatedValue: license.Text, + } + db.DB.Create(&change) + } + if oldlicense.TextUpdatable != license.TextUpdatable { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "TextUpdatable", + OldValue: oldlicense.TextUpdatable, + UpdatedValue: license.TextUpdatable, + } + db.DB.Create(&change) + } + if oldlicense.Fedora != license.Fedora { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Fedora", + OldValue: oldlicense.Fedora, + UpdatedValue: license.Fedora, + } + db.DB.Create(&change) + } + if oldlicense.Flag != license.Flag { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Flag", + OldValue: oldlicense.Shortname, + UpdatedValue: license.Shortname, + } + db.DB.Create(&change) + } + if oldlicense.Notes != license.Notes { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Notes", + OldValue: oldlicense.Notes, + UpdatedValue: license.Notes, + } + db.DB.Create(&change) + } + if oldlicense.DetectorType != license.DetectorType { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "DetectorType", + OldValue: oldlicense.DetectorType, + UpdatedValue: license.DetectorType, + } + db.DB.Create(&change) + } + if oldlicense.Source != license.Source { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Source", + OldValue: oldlicense.Source, + UpdatedValue: license.Source, + } + db.DB.Create(&change) + } + if oldlicense.SpdxId != license.SpdxId { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "SpdxId", + OldValue: oldlicense.SpdxId, + UpdatedValue: license.SpdxId, + } + db.DB.Create(&change) + } + if oldlicense.Risk != license.Risk { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Risk", + OldValue: oldlicense.Risk, + UpdatedValue: license.Risk, + } + db.DB.Create(&change) + } + if oldlicense.Marydone != license.Marydone { + change := models.ChangeLog{ + AuditId: audit.Id, + Field: "Marydone", + OldValue: oldlicense.Marydone, + UpdatedValue: license.Marydone, + } + db.DB.Create(&change) + } c.JSON(http.StatusOK, res) } -func SearchInLicense(c *gin.Context) { - field := c.Query("field") - search_term := c.Query("search_term") - search := c.Query("search") +func FilterLicense(c *gin.Context) { SpdxId := c.Query("spdxid") DetectorType := c.Query("detector_type") GPLv2compatible := c.Query("gplv2compatible") @@ -229,7 +426,7 @@ func SearchInLicense(c *gin.Context) { var license []models.LicenseDB query := db.DB.Model(&license) - if field == "" && search_term == "" && SpdxId == "" && GPLv2compatible == "" && GPLv3compatible == "" && DetectorType == "" && marydone == "" && active == "" && fsffree == "" && OSIapproved == "" && copyleft == "" { + if SpdxId == "" && GPLv2compatible == "" && GPLv3compatible == "" && DetectorType == "" && marydone == "" && active == "" && fsffree == "" && OSIapproved == "" && copyleft == "" { GetAllLicense(c) return } @@ -269,26 +466,37 @@ func SearchInLicense(c *gin.Context) { query = query.Where("marydone=?", marydone) } - if search == "fuzzy" { - query = query.Where(fmt.Sprintf("%s ILIKE ?", field), fmt.Sprintf("%%%s%%", search_term)) - } else if search == "" || search == "full_text_search" { - query = query.Where(field+" @@ plainto_tsquery(?)", search_term) - } else { + if err := query.Error; err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, - Message: "search algorithm doesn't exist", - Error: "search algorithm with such name doesn't exists", + Message: "incorrect query to search in the database", + Error: err.Error(), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } c.JSON(http.StatusBadRequest, er) return } + query.Find(&license) - if err := query.Error; err != nil { + res := models.LicenseResponse{ + Data: license, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: len(license), + }, + } + c.JSON(http.StatusOK, res) + +} + +func SearchInLicense(c *gin.Context) { + var input models.SearchLicense + + if err := c.ShouldBindJSON(&input); err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, - Message: "incorrect query to search in the database", + Message: "invalid json body", Error: err.Error(), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), @@ -296,6 +504,26 @@ func SearchInLicense(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } + + var license []models.LicenseDB + query := db.DB.Model(&license) + + if input.Search == "fuzzy" { + query = query.Where(fmt.Sprintf("%s ILIKE ?", input.Field), fmt.Sprintf("%%%s%%", input.SearchTerm)) + } else if input.Search == "" || input.Search == "full_text_search" { + query = query.Where(input.Field+" @@ plainto_tsquery(?)", input.SearchTerm) + + } else { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "search algorithm doesn't exist", + Error: "search algorithm with such name doesn't exists", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } query.Find(&license) res := models.LicenseResponse{ @@ -308,3 +536,115 @@ func SearchInLicense(c *gin.Context) { c.JSON(http.StatusOK, res) } + +func GetAllAudit(c *gin.Context) { + var audit []models.Audit + + if err := db.DB.Find(&audit).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Change log not found", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return + } + res := models.AuditResponse{ + Data: audit, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: len(audit), + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetAudit(c *gin.Context) { + var chngelog models.Audit + id := c.Param("audit_id") + + if err := db.DB.Where("id = ?", id).First(&chngelog).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change log with such id exists", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + res := models.AuditResponse{ + Data: []models.Audit{chngelog}, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetChangeLog(c *gin.Context) { + var changelog []models.ChangeLog + id := c.Param("audit_id") + + if err := db.DB.Where("audit_id = ?", id).First(&changelog).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change log with such id exists", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + + res := models.ChangeLogResponse{ + Data: changelog, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusOK, res) +} + +func GetChangeLogbyId(c *gin.Context) { + var changelog models.ChangeLog + auditid := c.Param("audit_id") + id := c.Param("id") + + if err := db.DB.Where("id = ?", id).Find(&changelog).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change history with such id exists", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + audit_id, _ := strconv.Atoi(auditid) + if changelog.AuditId != audit_id { + er := models.LicenseError{ + Status: http.StatusBadRequest, + Message: "no change history with such id and audit id exists", + Error: "Invalid change history for the requested audit id", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + } + res := models.ChangeLogResponse{ + Data: []models.ChangeLog{changelog}, + 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 f9eb8c5..65454e7 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -179,7 +179,7 @@ func TestSearchInLicense2(t *testing.T) { func TestGetUser(t *testing.T) { expectUser := models.User{ - Userid: "1", + UserId: "1", Username: "fossy", Userpassword: "fossy", Userlevel: "admin", @@ -196,7 +196,7 @@ func TestGetUser(t *testing.T) { func TestCreateUser(t *testing.T) { user := models.User{ - Userid: "2", + UserId: "2", Username: "general_user", Userpassword: "abc123", Userlevel: "participant", diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 79843d4..494273d 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 '%s' already exists", user.UserId), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } @@ -172,7 +172,7 @@ func AuthenticationMiddleware() gin.HandlerFunc { c.Abort() return } - + c.Set("username", username) c.Next() } } diff --git a/pkg/models/types.go b/pkg/models/types.go index fc75ece..c014d51 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -110,7 +110,7 @@ type LicenseInput struct { // User struct is representation of user information. type User struct { - Userid string `json:"userid" gorm:"primary_key" binding:"required"` + UserId string `json:"userid" gorm:"primary_key" binding:"required"` Username string `json:"username" gorm:"unique" binding:"required"` Userlevel string `json:"userlevel" gorm:"unique" binding:"required"` Userpassword string `json:"userpassword" gorm:"unique" binding:"required"` @@ -122,3 +122,37 @@ type UserResponse struct { Data []User `json:"data"` 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 ChangeLog struct { + Id int `json:"id" gorm:"primary_key"` + Field string `json:"field"` + UpdatedValue string `json:"updated_value"` + OldValue string `json:"old_value"` + AuditId int `json:"-"` + Audit Audit `gorm:"foreignKey:AuditId;references:Id"` +} + +type ChangeLogResponse struct { + Status int `json:"status"` + Data []ChangeLog `json:"data"` + Meta PaginationMeta `json:"paginationmeta"` +} + +type AuditResponse struct { + Status int `json:"status"` + Data []Audit `json:"data"` + Meta PaginationMeta `json:"paginationmeta"` +}