diff --git a/README.md b/README.md index 508ac3f..3ebb37b 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,56 @@ --> # LicenseDb -This project aims to create a centralized OSS license database, to manage opensource -licenses used by an organization. And different compliance tools like fossology, -sw360 etc. can sync with licenseDB application to update its own license data. +License as a service provides a convenient and effective way for organizations to +manage their use of open-source licenses. With the growing popularity of open-source +software, organizations are finding it more difficult to keep track of the various +licenses and terms under which they are permitted to use open-source components. +Open-source licenses can be complicated, making it difficult to understand how they +apply to a specific piece of software or interact with other licenses. It can be +used for various purposes by organizations and tools like [FOSSology](https://fossology.org) +and [SW360](https://eclipse.org/sw360) like license identification, filtering, and +managing licenses. There are benefits of this service such as increasing flexibility, +a faster time-to-access, and managing the database. + +## Database + +Licensedb database has licenses, obligations, obligation map, users, their audits +and changes. + +- **license_dbs** table has list of licenses and all the data related to the licenses. +- **obligations** table has the list of obligations that are related to the licenses. +- **obligation_maps** table that maps obligations to their respective licenses. +- **users** table has the user that are associated with the licenses. +- **audits** table has the data of audits that are done in obligations or licenses +- **change_logs** table has all the change history of a particular audit. + +![alt text](./docs/assets/licensedb_erd.png) + +### APIs + +There are multiple API endpoints for licenses, obligations, user and audit +endpoints. + +### API endpoints + +| # | Method | API Endpoints | Examples | Descriptions | +| --- | --------- | ---------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------- | +| 1 | **GET** | `/api/licenses/:shortname` | /api/licenses/MIT | Gets all data related to licenses by their shortname | +| 2 | **GET** | `/api/licenses/` | /api/licenses/copyleft="t"&active="t" | Get filter the licenses as per the filters | +| 3 | **POST** | `/api/licenses` | /api/licenses | Create a license with unique shortname | +| 4 | **POST** | `/api/licenses/search` | /api/licenses/search | Get the licenses with the post request filtered by field, search term and type | +| 5 | **PATCH** | `/api/licenses/:shortname` | /api/licenses/MIT | It updates the particular fields as requested of the license with shortname | +| 6 | **GET** | `/api/users` | /api/users | Get all the users and their data | +| 7 | **GET** | `/api/users/:id` | /api/users/1 | Get data relate to user by its id | +| 8 | **POST** | `/api/users` | /api/users | Create a user with unique data | +| 9 | **GET** | `/api/obligations` | /api/obligations | Get all the obligations | +| 10 | **GET** | `/api/obligation/:topic` | /api/obligation/topic | Gets all data related to obligations by their topic | +| 11 | **POST** | `/api/obligations` | /api/obligations | Create an obligation as well as add it to obligation map | +| 12 | **PATCH** | `/api/obligations/:topic` | /api/obligations | It updates the particular fields as requested of the obligation with topic | +| 13 | **GET** | `/api/audit` | /api/audit | Get the audit history of all the licenses and obligations | +| 14 | **GET** | `/api/audit/:audit_id` | /api/audit/1 | Get the data of a particular audit by its id | +| 15 | **GET** | `/api/audit/:audit_id/changes` | /api/audit/1/changes | Get the change logs of the particular audit id | +| 16 | **GET** | `/api/audit/:audit_id/changes/:id` | /api/audit/1/changes/2 | Get a particular change log of the particular audit id | ## Prerequisite diff --git a/docs/assets/licensedb_erd.png b/docs/assets/licensedb_erd.png new file mode 100644 index 0000000..49135f0 Binary files /dev/null and b/docs/assets/licensedb_erd.png differ diff --git a/docs/assets/licensedb_erd.png.license b/docs/assets/licensedb_erd.png.license new file mode 100644 index 0000000..c5370a1 --- /dev/null +++ b/docs/assets/licensedb_erd.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Kavya Shukla + +SPDX-License-Identifier: GPL-2.0-only diff --git a/pkg/api/api.go b/pkg/api/api.go index 99e0988..9a0329c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -53,6 +53,7 @@ func Router() *gin.Engine { return r } +// The HandleInvalidUrl function returns the error when an invalid url is entered func HandleInvalidUrl(c *gin.Context) { er := models.LicenseError{ @@ -64,6 +65,8 @@ func HandleInvalidUrl(c *gin.Context) { } c.JSON(http.StatusNotFound, er) } + +// The get all License function returns all the license data present in the database func GetAllLicense(c *gin.Context) { var licenses []models.LicenseDB @@ -91,6 +94,9 @@ func GetAllLicense(c *gin.Context) { c.JSON(http.StatusOK, res) } +// Get license functions return data of the particular license by its shortname. +// It inputs the shortname as query parameter +// It returns error ehen no such license exists func GetLicense(c *gin.Context) { var license models.LicenseDB @@ -124,6 +130,8 @@ func GetLicense(c *gin.Context) { c.JSON(http.StatusOK, res) } +// The Create License function creates license in the database and add the required data +// It return the license if it already exists in the database func CreateLicense(c *gin.Context) { var input models.LicenseInput @@ -200,6 +208,9 @@ func CreateLicense(c *gin.Context) { c.JSON(http.StatusCreated, res) } +// The Update license functions updates the particular license with a particular shortname. +// It also creates the audit and change logs of the updates +// It returns the updated license func UpdateLicense(c *gin.Context) { var update models.LicenseDB var license models.LicenseDB @@ -446,6 +457,8 @@ func UpdateLicense(c *gin.Context) { } +// The filter licenses returns the licenses after passing through certain filters. +// It takes the filters as query parameters and filters accordingly. func FilterLicense(c *gin.Context) { SpdxId := c.Query("spdxid") DetectorType := c.Query("detector_type") @@ -523,6 +536,10 @@ func FilterLicense(c *gin.Context) { } +// SearchInLicense searches for license data based on user-provided search criteria. +// It accepts a JSON request body containing search parameters and responds with JSON +// containing the matching license data or error messages if the search request is +// invalid or if the search algorithm is not supported. func SearchInLicense(c *gin.Context) { var input models.SearchLicense @@ -570,6 +587,8 @@ func SearchInLicense(c *gin.Context) { } +// GetAllAudit retrieves a list of all audit records from the database and responds with +// JSON containing the audit data or an error message if the records are not found. func GetAllAudit(c *gin.Context) { var audit []models.Audit @@ -595,6 +614,8 @@ func GetAllAudit(c *gin.Context) { c.JSON(http.StatusOK, res) } +// GetAudit retrieves a specific audit record by its ID from the database and responds +// with JSON containing the audit data or an error message if the record is not found. func GetAudit(c *gin.Context) { var chngelog models.Audit id := c.Param("audit_id") @@ -620,6 +641,9 @@ func GetAudit(c *gin.Context) { c.JSON(http.StatusOK, res) } +// GetChangeLog retrieves a list of change history records associated with a specific +// audit by its audit ID from the database and responds with JSON containing the change +// history data or an error message if no records are found. func GetChangeLog(c *gin.Context) { var changelog []models.ChangeLog id := c.Param("audit_id") @@ -646,6 +670,9 @@ func GetChangeLog(c *gin.Context) { c.JSON(http.StatusOK, res) } +// GetChangeLogbyId retrieves a specific change history record by its ID for a given audit. +// It responds with JSON containing the change history data or error messages if the record +// is not found or if it does not belong to the specified audit. func GetChangeLogbyId(c *gin.Context) { var changelog models.ChangeLog auditid := c.Param("audit_id") @@ -682,6 +709,10 @@ func GetChangeLogbyId(c *gin.Context) { c.JSON(http.StatusOK, res) } +// CreateObligation creates a new obligation record based on the provided input JSON data. +// It performs validation, generates an MD5 hash of the obligation text, and associates +// the obligation with relevant licenses. The function responds with JSON containing the +// newly created obligation data or error messages in case of validation or database errors. func CreateObligation(c *gin.Context) { var input models.ObligationInput @@ -764,6 +795,8 @@ func CreateObligation(c *gin.Context) { c.JSON(http.StatusCreated, res) } +// GetAllObligation retrieves a list of all active obligation records from the database and +// responds with JSON containing the obligation data or an error message if no records are found. func GetAllObligation(c *gin.Context) { var obligations []models.Obligation query := db.DB.Model(&obligations) @@ -791,6 +824,10 @@ func GetAllObligation(c *gin.Context) { c.JSON(http.StatusOK, res) } +// UpdateObligation updates an existing active obligation record based on the provided input JSON data. +// It performs validation, updates the specified fields, and records changes in the audit log. +// The function responds with JSON containing the updated obligation data or error messages in case +// of validation or database errors. func UpdateObligation(c *gin.Context) { var update models.UpdateObligation var oldobligation models.Obligation @@ -948,6 +985,8 @@ func UpdateObligation(c *gin.Context) { c.JSON(http.StatusOK, res) } +// DeleteObligation marks an existing obligation record as inactive based on the provided topic parameter. +// It responds with an error message if the obligation is not found or if the deactivation operation fails. func DeleteObligation(c *gin.Context) { var obligation models.Obligation tp := c.Param("topic") @@ -965,6 +1004,9 @@ func DeleteObligation(c *gin.Context) { obligation.Active = false } +// GetObligation retrieves an active obligation record based on the provided topic parameter. +// It responds with JSON containing the obligation data or an error message if the obligation +// is not found or if there is an error during retrieval. func GetObligation(c *gin.Context) { var obligation models.Obligation query := db.DB.Model(&obligation) diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index e5c6f06..2d2a6a4 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -19,6 +19,9 @@ import ( "github.com/stretchr/testify/assert" ) +// TestMain is the main testing function for the application. It sets up the testing environment, +// including configuring the Gin web framework for testing, connecting to a database, +// running the tests, and exiting with the appropriate exit code. func TestMain(m *testing.M) { gin.SetMode(gin.TestMode) dbname := "fossology" @@ -32,6 +35,7 @@ func TestMain(m *testing.M) { os.Exit(exitcode) } +// makeRequest is a utility function for creating and sending HTTP requests during testing. func makeRequest(method, path string, body interface{}, isAuthanticated bool) *httptest.ResponseRecorder { reqBody, _ := json.Marshal(body) req := httptest.NewRequest(method, path, bytes.NewBuffer(reqBody)) diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 5a20367..563f4f5 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -15,6 +15,7 @@ import ( "github.com/gin-gonic/gin" ) +// CreateUser creates a new user based on the provided JSON request data. func CreateUser(c *gin.Context) { var user models.User if err := c.ShouldBindJSON(&user); err != nil { @@ -52,6 +53,7 @@ func CreateUser(c *gin.Context) { c.JSON(http.StatusCreated, res) } +// GetAllUser retrieves a list of all users from the database. func GetAllUser(c *gin.Context) { var users []models.User @@ -76,6 +78,7 @@ func GetAllUser(c *gin.Context) { c.JSON(http.StatusOK, res) } +// GetUser retrieves a user by their user ID from the database. func GetUser(c *gin.Context) { var user models.User id := c.Param("id") @@ -101,6 +104,7 @@ func GetUser(c *gin.Context) { c.JSON(http.StatusOK, res) } +// AuthenticationMiddleware is a middleware function for user authentication. func AuthenticationMiddleware() gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") diff --git a/pkg/db/db.go b/pkg/db/db.go index 4ec7f72..7b8713b 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -15,8 +15,10 @@ import ( "gorm.io/gorm" ) +// DB is a global variable to store the GORM database connection. var DB *gorm.DB +// Connect establishes a connection to the database using the provided parameters. 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) @@ -29,12 +31,13 @@ func Connect(dbhost, port, user, dbname, password *string) { DB = database } +// Populatedb populates the database with license data from a JSON file if 'populatedb' is true. func Populatedb(populatedb bool, datafile string) { if populatedb { var licenses []models.LicenseJson - // read the file of data + // Read the content of the data file. byteResult, _ := ioutil.ReadFile(datafile) - // unmarshal the json file and it into the struct format + // Unmarshal the JSON file data into a slice of LicenseJson structs. if err := json.Unmarshal(byteResult, &licenses); err != nil { log.Fatalf("error reading from json file: %v", err) } diff --git a/pkg/models/types.go b/pkg/models/types.go index 1cf2026..4667b1a 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -124,12 +124,15 @@ type UserResponse struct { Meta PaginationMeta `json:"paginationmeta"` } +// SearchLicense struct represents the input needed to search in a license. type SearchLicense struct { Field string `json:"field" binding:"required"` SearchTerm string `json:"search_term" binding:"required"` Search string `json:"search"` } +// Audit struct represents an audit entity with certain attributes and properties +// It has user id as a foreign key type Audit struct { Id int `json:"id" gorm:"primary_key"` UserId int64 `json:"user_id"` @@ -139,6 +142,7 @@ type Audit struct { Type string `json:"type"` } +// ChangeLog struct represents a change entity with certain attributes and properties type ChangeLog struct { Id int `json:"id" gorm:"primary_key"` Field string `json:"field"` @@ -148,18 +152,21 @@ type ChangeLog struct { Audit Audit `gorm:"foreignKey:AuditId;references:Id" json:"-"` } +// ChangeLogResponse represents the design of API response of change log type ChangeLogResponse struct { Status int `json:"status"` Data []ChangeLog `json:"data"` Meta PaginationMeta `json:"paginationmeta"` } +// AuditResponse represents the response format for audit data. type AuditResponse struct { Status int `json:"status"` Data []Audit `json:"data"` Meta PaginationMeta `json:"paginationmeta"` } +// Obligation represents an obligation record in the database. type Obligation struct { Id int64 `json:"id" gorm:"primary_key"` Topic string `json:"topic"` @@ -173,6 +180,7 @@ type Obligation struct { Md5 string `json:"md5" gorm:"unique"` } +// ObligationInput represents the input format for creating a new obligation. type ObligationInput struct { Topic string `json:"topic" binding:"required"` Type string `json:"type" binding:"required"` @@ -185,6 +193,7 @@ type ObligationInput struct { Shortnames []string `json:"shortnames"` } +// UpdateObligation represents the input format for updating an existing obligation. type UpdateObligation struct { Topic string `json:"topic"` Type string `json:"type"` @@ -197,6 +206,7 @@ type UpdateObligation struct { Md5 string `json:"md5"` } +// ObligationMap represents the mapping between an obligation and a license. type ObligationMap struct { ObligationPk int64 `json:"obligation_pk"` Obligation Obligation `gorm:"foreignKey:ObligationPk;references:Id" json:"-"` @@ -205,6 +215,7 @@ type ObligationMap struct { LicenseDB LicenseDB `gorm:"foreignKey:RfPk;references:Id" json:"-"` } +// ObligationResponse represents the response format for obligation data. type ObligationResponse struct { Status int `json:"status"` Data []Obligation `json:"data"` diff --git a/pkg/utils/util.go b/pkg/utils/util.go index 3f1fadc..9bcff75 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -5,6 +5,11 @@ package utils import "github.com/fossology/LicenseDb/pkg/models" +// The Converter function takes an input of type models.LicenseJson and converts it into a +// corresponding models.LicenseDB object. +// It performs several field assignments and transformations to create the LicenseDB object, +// including generating the SpdxId based on the SpdxCompatible field. +// The resulting LicenseDB object is returned as the output of this function. func Converter(input models.LicenseJson) models.LicenseDB { if input.SpdxCompatible == "t" { input.SpdxCompatible = input.Shortname