From bec1cdaeda9cca0da0d07e0ce24ae63c86cddbec Mon Sep 17 00:00:00 2001 From: Kavya Shukla Date: Mon, 26 Jun 2023 22:07:29 +0530 Subject: [PATCH] feat: added authentication in API and search api Get license with specific search term: field: represents the name of field to be searched search_term: represents the term to be searched search: represent the algorithm to search with authenticate the API: added the struct for user created a user table in the database added basic user endpoints: get user using id get all user create user added basic authentication to all the API endpoints i.e, group of endpoints Signed-off-by: Kavya Shukla --- cmd/laas/main.go | 49 ++-------- go.mod | 3 + pkg/api/api.go | 106 ++++++++++++++++++---- pkg/api/api_test.go | 211 ++++++++++++++++++++++++++++++++++++++++++++ pkg/auth/auth.go | 32 +++---- pkg/db/db.go | 47 ++++++++++ pkg/models/types.go | 16 ++-- pkg/utils/util.go | 2 +- 8 files changed, 377 insertions(+), 89 deletions(-) create mode 100644 pkg/api/api_test.go create mode 100644 pkg/db/db.go diff --git a/cmd/laas/main.go b/cmd/laas/main.go index a979df3..c17ff99 100644 --- a/cmd/laas/main.go +++ b/cmd/laas/main.go @@ -4,19 +4,12 @@ package main import ( - "encoding/json" "flag" - "fmt" - "io/ioutil" "log" "github.com/fossology/LicenseDb/pkg/api" - "github.com/fossology/LicenseDb/pkg/auth" + "github.com/fossology/LicenseDb/pkg/db" "github.com/fossology/LicenseDb/pkg/models" - "github.com/fossology/LicenseDb/pkg/utils" - "github.com/gin-gonic/gin" - "gorm.io/driver/postgres" - "gorm.io/gorm" ) // declare flags to input the basic requirement of database connection and the path of the data file @@ -40,46 +33,18 @@ var ( func main() { flag.Parse() - dburi := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s", *dbhost, *port, *user, *dbname, *password) - gormConfig := &gorm.Config{} - database, err := gorm.Open(postgres.Open(dburi), gormConfig) - if err != nil { - log.Fatalf("Failed to connect to database: %v", err) - } + db.Connect(dbhost, port, user, dbname, password) - if err := database.AutoMigrate(&models.LicenseDB{}); err != nil { + if err := db.DB.AutoMigrate(&models.LicenseDB{}); err != nil { log.Fatalf("Failed to automigrate database: %v", err) } - if err := database.AutoMigrate(&models.User{}); err != nil { + if err := db.DB.AutoMigrate(&models.User{}); err != nil { log.Fatalf("Failed to automigrate database: %v", err) } - if *populatedb { - var licenses []models.LicenseJson - // read the file of data - byteResult, _ := ioutil.ReadFile(*datafile) - // unmarshal the json file and it into the struct format - if err := json.Unmarshal(byteResult, &licenses); err != nil { - log.Fatalf("error reading from json file: %v", err) - } - for _, license := range licenses { - // populate the data in the database table - result := utils.Converter(license) - database.Create(&result) - } - } - api.DB = database - r := gin.Default() - r.NoRoute(api.HandleInvalidUrl) - authorized := r.Group("/") - authorized.Use(auth.AuthenticationMiddleware()) - authorized.GET("/api/license/:shortname", api.GetLicense) - authorized.POST("/api/license", api.CreateLicense) - authorized.PATCH("/api/license/update/:shortname", api.UpdateLicense) - authorized.GET("/api/licenses", api.SearchInLicense) - r.POST("/api/user", auth.CreateUser) - authorized.GET("/api/users", auth.GetAllUser) - authorized.GET("/api/user/:id", auth.GetUser) + db.Populatedb(*populatedb, *datafile) + + r := api.Router() r.Run() } diff --git a/go.mod b/go.mod index 03f5281..6f32d2f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/gin-gonic/gin v1.9.1 + github.com/stretchr/testify v1.8.3 gorm.io/driver/postgres v1.5.2 gorm.io/gorm v1.25.1 ) @@ -11,6 +12,7 @@ require ( require ( github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -30,6 +32,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect diff --git a/pkg/api/api.go b/pkg/api/api.go index 9ee1638..2e2178a 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -8,12 +8,35 @@ import ( "net/http" "time" + "github.com/fossology/LicenseDb/pkg/auth" + "github.com/fossology/LicenseDb/pkg/db" "github.com/fossology/LicenseDb/pkg/models" "github.com/gin-gonic/gin" - "gorm.io/gorm" ) -var DB *gorm.DB +func Router() *gin.Engine { + // r is a default instance of gin engine + r := gin.Default() + + // return error for invalid routes + r.NoRoute(HandleInvalidUrl) + + // authorization not required for these routes + r.GET("/api/license/:shortname", GetLicense) + r.GET("/api/licenses", SearchInLicense) + r.GET("/api/users", auth.GetAllUser) + r.GET("/api/user/:id", auth.GetUser) + + // set up authentication + authorized := r.Group("/") + authorized.Use(auth.AuthenticationMiddleware()) + + authorized.POST("/api/license", CreateLicense) + authorized.PATCH("/api/license/update/:shortname", UpdateLicense) + authorized.POST("/api/user", auth.CreateUser) + + return r +} func HandleInvalidUrl(c *gin.Context) { @@ -27,9 +50,10 @@ func HandleInvalidUrl(c *gin.Context) { c.JSON(http.StatusNotFound, er) } func GetAllLicense(c *gin.Context) { + var licenses []models.LicenseDB - err := DB.Find(&licenses).Error + err := db.DB.Find(&licenses).Error if err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, @@ -44,7 +68,7 @@ func GetAllLicense(c *gin.Context) { res := models.LicenseResponse{ Data: licenses, Status: http.StatusOK, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: len(licenses), }, } @@ -60,7 +84,7 @@ func GetLicense(c *gin.Context) { return } - err := DB.Where("shortname = ?", queryParam).First(&license).Error + err := db.DB.Where("shortname = ?", queryParam).First(&license).Error if err != nil { er := models.LicenseError{ @@ -77,7 +101,7 @@ func GetLicense(c *gin.Context) { res := models.LicenseResponse{ Data: []models.LicenseDB{license}, Status: http.StatusOK, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: 1, }, } @@ -105,7 +129,7 @@ func CreateLicense(c *gin.Context) { } license := models.LicenseDB(input) - result := DB.FirstOrCreate(&license) + result := db.DB.FirstOrCreate(&license) if result.RowsAffected == 0 { er := models.LicenseError{ @@ -132,7 +156,7 @@ func CreateLicense(c *gin.Context) { res := models.LicenseResponse{ Data: []models.LicenseDB{license}, Status: http.StatusCreated, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: 1, }, } @@ -144,7 +168,7 @@ func UpdateLicense(c *gin.Context) { var update models.LicenseDB var license models.LicenseDB shortname := c.Param("shortname") - if err := DB.Where("shortname = ?", shortname).First(&license).Error; err != nil { + if err := db.DB.Where("shortname = ?", shortname).First(&license).Error; err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, Message: fmt.Sprintf("license with shortname '%s' not found", shortname), @@ -166,7 +190,7 @@ func UpdateLicense(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } - if err := DB.Model(&license).Updates(update).Error; err != nil { + if err := db.DB.Model(&license).Updates(update).Error; err != nil { er := models.LicenseError{ Status: http.StatusInternalServerError, Message: "Failed to update license", @@ -180,7 +204,7 @@ func UpdateLicense(c *gin.Context) { res := models.LicenseResponse{ Data: []models.LicenseDB{license}, Status: http.StatusOK, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: 1, }, } @@ -193,16 +217,62 @@ func SearchInLicense(c *gin.Context) { field := c.Query("field") search_term := c.Query("search_term") search := c.Query("search") - if field == "" && search_term == "" { + SpdxId := c.Query("spdxid") + DetectorType := c.Query("detector_type") + GPLv2compatible := c.Query("gplv2compatible") + GPLv3compatible := c.Query("gplv3compatible") + marydone := c.Query("marydone") + active := c.Query("active") + OSIapproved := c.Query("osiapproved") + fsffree := c.Query("fsffree") + copyleft := c.Query("copyleft") + var license []models.LicenseDB + query := db.DB.Model(&license) + + if field == "" && search_term == "" && SpdxId == "" && GPLv2compatible == "" && GPLv3compatible == "" && DetectorType == "" && marydone == "" && active == "" && fsffree == "" && OSIapproved == "" && copyleft == "" { GetAllLicense(c) return } - var query *gorm.DB - var license []models.LicenseDB + if active != "" { + query = query.Where("active=?", active) + } + + if fsffree != "" { + query = query.Where("fs_ffree=?", fsffree) + } + + if OSIapproved != "" { + query = query.Where("os_iapproved=?", OSIapproved) + } + + if copyleft != "" { + query = query.Where("copyleft=?", copyleft) + } + + if SpdxId != "" { + query = query.Where("spdx_id=?", SpdxId) + } + + if DetectorType != "" { + query = query.Where("detector_type=?", DetectorType) + } + + if GPLv2compatible != "" { + query = query.Where("gp_lv2compatible=?", GPLv2compatible) + } + + if GPLv3compatible != "" { + query = query.Where("gp_lv3compatible=?", GPLv3compatible) + } + + if marydone != "" { + query = query.Where("marydone=?", marydone) + } + if search == "fuzzy" { - query = DB.Where(fmt.Sprintf("%s ILIKE ?", field), fmt.Sprintf("%%%s%%", search_term)).Find(&license) + query = query.Where(fmt.Sprintf("%s ILIKE ?", field), fmt.Sprintf("%%%s%%", search_term)) } else if search == "" || search == "full_text_search" { - query = DB.Where(field+" @@ plainto_tsquery(?)", search_term).Find(&license) + query = query.Where(field+" @@ plainto_tsquery(?)", search_term) } else { er := models.LicenseError{ Status: http.StatusBadRequest, @@ -226,10 +296,12 @@ func SearchInLicense(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } + query.Find(&license) + res := models.LicenseResponse{ Data: license, Status: http.StatusOK, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: len(license), }, } diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go new file mode 100644 index 0000000..f9eb8c5 --- /dev/null +++ b/pkg/api/api_test.go @@ -0,0 +1,211 @@ +// SPDX-FileCopyrightText: 2023 Kavya Shukla +// SPDX-License-Identifier: GPL-2.0-only + +package api + +import ( + "bytes" + "encoding/base64" + "encoding/json" + + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/fossology/LicenseDb/pkg/db" + "github.com/fossology/LicenseDb/pkg/models" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + gin.SetMode(gin.TestMode) + dbname := "fossology" + user := "fossy" + password := "fossy" + port := "5432" + dbhost := "localhost" + db.Connect(&dbhost, &port, &user, &dbname, &password) + + exitcode := m.Run() + os.Exit(exitcode) +} + +func makeRequest(method, path string, body interface{}, isAuthanticated bool) *httptest.ResponseRecorder { + reqBody, _ := json.Marshal(body) + req := httptest.NewRequest(method, path, bytes.NewBuffer(reqBody)) + req.Header.Set("Content-Type", "application/json") + if isAuthanticated { + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("fossy:fossy"))) + } + w := httptest.NewRecorder() + Router().ServeHTTP(w, req) + return w +} +func TestGetLicense(t *testing.T) { + expectLicense := models.LicenseDB{ + Shortname: "MIT", + Fullname: "MIT License", + Text: "MIT License\n\nCopyright (c) \n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", + Url: "https://opensource.org/licenses/MIT", + TextUpdatable: "f", + DetectorType: "1", + Active: "t", + Flag: "1", + Marydone: "f", + SpdxId: "MIT", + } + w := makeRequest("GET", "/api/license/MIT", nil, false) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + json.Unmarshal(w.Body.Bytes(), &res) + + assert.Equal(t, expectLicense, res.Data[0]) + +} + +func TestCreateLicense(t *testing.T) { + License := models.LicenseDB{ + Shortname: "ABCD", + Fullname: "ABCD License", + Text: "just a license", + Url: "https://abcdlicense/ABCD", + SpdxId: "1", + TextUpdatable: "f", + DetectorType: "1", + Active: "t", + } + w := makeRequest("POST", "/api/license", License, true) + assert.Equal(t, http.StatusCreated, w.Code) + type response struct { + Data models.LicenseDB `json:"data"` + } + + var res models.LicenseResponse + json.Unmarshal(w.Body.Bytes(), &res) + + assert.Equal(t, License, res.Data[0]) + +} + +func TestUpdateLicense(t *testing.T) { + License := models.LicenseDB{ + Marydone: "t", + } + expectedLicense := models.LicenseDB{ + Shortname: "MIT", + Fullname: "MIT License", + Text: "MIT License\n\nCopyright (c) \n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", + Url: "https://opensource.org/licenses/MIT", + TextUpdatable: "f", + DetectorType: "1", + Active: "t", + Flag: "1", + Marydone: "t", + SpdxId: "MIT", + } + w := makeRequest("PATCH", "/api/license/update/MIT", License, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + json.Unmarshal(w.Body.Bytes(), &res) + + assert.Equal(t, expectedLicense, res.Data[0]) + +} + +func TestSearchInLicense(t *testing.T) { + expectLicense := models.LicenseDB{ + Shortname: "PostgreSQL", + Fullname: "PostgreSQL License", + Text: "PostgreSQL Database Management System\n(formerly known as Postgres, then as Postgres95)\n\nPortions Copyright (c) 1996-2010, The PostgreSQL Global Development Group\n\nPortions Copyright (c) 1994, The Regents of the University of California\n\nPermission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.\n\nIN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nTHE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.\n", + Url: "http://www.postgresql.org/about/licence", + TextUpdatable: "f", + DetectorType: "1", + Active: "t", + Flag: "1", + Marydone: "f", + SpdxId: "PostgreSQL", + } + w := makeRequest("GET", "/api/licenses?field=fullname&search_term=Postgresql", nil, false) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + json.Unmarshal(w.Body.Bytes(), &res) + + assert.Equal(t, expectLicense, res.Data[0]) + +} + +func TestSearchInLicense2(t *testing.T) { + expectLicense := []models.LicenseDB{ + {Shortname: "GPL-2.0-with-autoconf-exception", + Fullname: "GNU General Public License v2.0 w/Autoconf exception", + Text: "insert GPL v2 license text here\n\nAutoconf Exception\n\nAs a special exception, the Free Software Foundation gives unlimited permission to copy, distribute and modify the configure scripts that are the output of Autoconf. You need not follow the terms of the GNU General Public License when using or distributing such scripts, even though portions of the text of Autoconf appear in them. The GNU General Public License (GPL) does govern all other use of the material that constitutes the Autoconf program.\n\nCertain portions of the Autoconf source text are designed to be copied (in certain cases, depending on the input) into the output of Autoconf. We call these the \"data\" portions. The rest of the Autoconf source text consists of comments plus executable code that decides which of the data portions to output in any given case. We call these comments and executable code the \"non-data\" portions. Autoconf never copies any of the non-data portions into its output.\n\nThis special exception to the GPL applies to versions of Autoconf released by the Free Software Foundation. When you make and distribute a modified version of Autoconf, you may extend this special exception to the GPL to apply to your modified version as well, *unless* your modified version has the potential to copy into its output some of the text that was the non-data portion of the version that you started with. (In other words, unless your change moves or copies text from the non-data portions to the data portions.) If your modification has such potential, you must delete any notice of this special exception to the GPL from your modified version.\n\n", + Url: "http://ac-archive.sourceforge.net/doc/copyright.html", + Notes: "DEPRECATED: Use license expression including main license, \"WITH\" operator, and identifier: Autoconf-exception-2.0", + TextUpdatable: "f", + DetectorType: "1", + Active: "t", + Flag: "1", + Marydone: "f", + SpdxId: "LicenseRef-fossology-GPL-2.0-with-autoconf-exception", + }, + { + Shortname: "Autoconf-exception-2.0", + Fullname: "Autoconf exception 2.0", + Text: "As a special exception, the Free Software Foundation gives unlimited permission to copy, distribute and modify the configure scripts that are the output of Autoconf. You need not follow the terms of the GNU General Public License when using or distributing such scripts, even though portions of the text of Autoconf appear in them. The GNU General Public License (GPL) does govern all other use of the material that constitutes the Autoconf program.\n\nCertain portions of the Autoconf source text are designed to be copied (in certain cases, depending on the input) into the output of Autoconf. We call these the \"data\" portions. The rest of the Autoconf source text consists of comments plus executable code that decides which of the data portions to output in any given case. We call these comments and executable code the \"non-data\" portions. Autoconf never copies any of the non-data portions into its output.\n\nThis special exception to the GPL applies to versions of Autoconf released by the Free Software Foundation. When you make and distribute a modified version of Autoconf, you may extend this special exception to the GPL to apply to your modified version as well, *unless* your modified version has the potential to copy into its output some of the text that was the non-data portion of the version that you started with. (In other words, unless your change moves or copies text from the non-data portions to the data portions.) If your modification has such potential, you must delete any notice of this special exception to the GPL from your modified version.\n", + Url: "http://ac-archive.sourceforge.net/doc/copyright.html", + Notes: "Typically used with GPL-2.0", + TextUpdatable: "f", + DetectorType: "1", + Active: "t", + Flag: "1", + Marydone: "f", + SpdxId: "Autoconf-exception-2.0", + }, + } + + w := makeRequest("GET", "/api/licenses?field=url&search_term=http://ac-archive.sourceforge.net/doc/copyright.html", nil, false) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + json.Unmarshal(w.Body.Bytes(), &res) + + assert.Equal(t, expectLicense, res.Data) +} + +func TestGetUser(t *testing.T) { + expectUser := models.User{ + Userid: "1", + Username: "fossy", + Userpassword: "fossy", + Userlevel: "admin", + } + w := makeRequest("GET", "/api/user/1", nil, false) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + json.Unmarshal(w.Body.Bytes(), &res) + + assert.Equal(t, expectUser, res.Data[0]) + +} + +func TestCreateUser(t *testing.T) { + user := models.User{ + Userid: "2", + Username: "general_user", + Userpassword: "abc123", + Userlevel: "participant", + } + w := makeRequest("POST", "/api/user", user, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + json.Unmarshal(w.Body.Bytes(), &res) + + assert.Equal(t, user, res.Data[0]) +} diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 7891e80..79843d4 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/fossology/LicenseDb/pkg/api" + "github.com/fossology/LicenseDb/pkg/db" "github.com/fossology/LicenseDb/pkg/models" "github.com/gin-gonic/gin" ) @@ -28,7 +28,7 @@ func CreateUser(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } - result := api.DB.FirstOrCreate(&user) + result := db.DB.FirstOrCreate(&user) if result.RowsAffected == 0 { er := models.LicenseError{ Status: http.StatusBadRequest, @@ -40,21 +40,11 @@ func CreateUser(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } - if result.Error != nil { - er := models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Failed to create user", - Error: result.Error.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusInternalServerError, er) - return - } + res := models.UserResponse{ Data: []models.User{user}, Status: http.StatusCreated, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: 1, }, } @@ -64,7 +54,8 @@ func CreateUser(c *gin.Context) { func GetAllUser(c *gin.Context) { var users []models.User - if err := api.DB.Find(&users).Error; err != nil { + + if err := db.DB.Find(&users).Error; err != nil { er := models.LicenseError{ Status: http.StatusInternalServerError, Message: "can not create user", @@ -77,7 +68,7 @@ func GetAllUser(c *gin.Context) { res := models.UserResponse{ Data: users, Status: http.StatusOK, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: len(users), }, } @@ -89,7 +80,7 @@ func GetUser(c *gin.Context) { var user models.User id := c.Param("id") - if err := api.DB.Where("userid = ?", id).First(&user).Error; err != nil { + if err := db.DB.Where("userid = ?", id).First(&user).Error; err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "no user with such user id exists", @@ -102,7 +93,7 @@ func GetUser(c *gin.Context) { res := models.UserResponse{ Data: []models.User{user}, Status: http.StatusOK, - Meta: models.Meta{ + Meta: models.PaginationMeta{ ResourceCount: 1, }, } @@ -152,9 +143,8 @@ func AuthenticationMiddleware() gin.HandlerFunc { password := auth[1] var user models.User - - err = api.DB.Where("username = ?", username).First(&user).Error - if err != nil { + result := db.DB.Where("username = ?", username).First(&user) + if result.Error != nil { er := models.LicenseError{ Status: http.StatusUnauthorized, Message: "User name not found", diff --git a/pkg/db/db.go b/pkg/db/db.go new file mode 100644 index 0000000..4ec7f72 --- /dev/null +++ b/pkg/db/db.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Kavya Shukla +// SPDX-License-Identifier: GPL-2.0-only + +package db + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + + "github.com/fossology/LicenseDb/pkg/models" + "github.com/fossology/LicenseDb/pkg/utils" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var DB *gorm.DB + +func Connect(dbhost, port, user, dbname, password *string) { + + dburi := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s", *dbhost, *port, *user, *dbname, *password) + gormConfig := &gorm.Config{} + database, err := gorm.Open(postgres.Open(dburi), gormConfig) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + + DB = database +} + +func Populatedb(populatedb bool, datafile string) { + if populatedb { + var licenses []models.LicenseJson + // read the file of data + byteResult, _ := ioutil.ReadFile(datafile) + // unmarshal the json file and it into the struct format + if err := json.Unmarshal(byteResult, &licenses); err != nil { + log.Fatalf("error reading from json file: %v", err) + } + for _, license := range licenses { + // populate the data in the database table + result := utils.Converter(license) + DB.Create(&result) + } + } +} diff --git a/pkg/models/types.go b/pkg/models/types.go index 8780b60..fc75ece 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -56,7 +56,7 @@ type LicenseJson struct { // license retrieval operation. // It contains information that provides context and supplementary details // about the retrieved license data. -type Meta struct { +type PaginationMeta struct { ResourceCount int `json:"resource_count"` Page int `json:"page,omitempty"` PerPage int `json:"per_page,omitempty"` @@ -67,9 +67,9 @@ type Meta struct { // retrieving license information. // It is used to encapsulate license-related data in an organized manner. type LicenseResponse struct { - Status int `json:"status"` - Data []LicenseDB `json:"data"` - Meta Meta `json:"meta"` + Status int `json:"status"` + Data []LicenseDB `json:"data"` + Meta PaginationMeta `json:"paginationmeta"` } // The LicenseError struct represents an error response related to license operations. @@ -89,7 +89,7 @@ type LicenseInput struct { Shortname string `json:"rf_shortname" binding:"required"` Fullname string `json:"rf_fullname" binding:"required"` Text string `json:"rf_text" binding:"required"` - Url string `json:"rf_url" binding:"required"` + Url string `json:"rf_url" ` AddDate string `json:"rf_add_date"` Copyleft string `json:"rf_copyleft"` FSFfree string `json:"rf_FSFfree"` @@ -118,7 +118,7 @@ type User struct { // UserResponse struct is representation of design API response of user. type UserResponse struct { - Status int `json:"status"` - Data []User `json:"data"` - Meta Meta `json:"meta"` + Status int `json:"status"` + Data []User `json:"data"` + Meta PaginationMeta `json:"paginationmeta"` } diff --git a/pkg/utils/util.go b/pkg/utils/util.go index 08b12bf..3f1fadc 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -15,7 +15,7 @@ func Converter(input models.LicenseJson) models.LicenseDB { Shortname: input.Shortname, Fullname: input.Fullname, Text: input.Text, - Url: input.Fullname, + Url: input.Url, Copyleft: input.Copyleft, AddDate: input.AddDate, FSFfree: input.FSFfree,