Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: added documention #17

Merged
merged 1 commit into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Binary file added docs/assets/licensedb_erd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/assets/licensedb_erd.png.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 Kavya Shukla <[email protected]>

SPDX-License-Identifier: GPL-2.0-only
42 changes: 42 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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))
Expand Down
4 changes: 4 additions & 0 deletions pkg/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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

Expand All @@ -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")
Expand All @@ -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")
Expand Down
7 changes: 5 additions & 2 deletions pkg/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/models/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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"`
Expand All @@ -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"`
Expand All @@ -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"`
Expand All @@ -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"`
Expand All @@ -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:"-"`
Expand All @@ -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"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/utils/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down