diff --git a/cmd/laas/docs/docs.go b/cmd/laas/docs/docs.go index 7723a2e..423080f 100644 --- a/cmd/laas/docs/docs.go +++ b/cmd/laas/docs/docs.go @@ -456,7 +456,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LicensePOSTRequestJSONSchema" + "$ref": "#/definitions/models.LicenseDB" } } ], @@ -511,7 +511,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.ExportLicenseDB" + "$ref": "#/definitions/models.LicenseDB" } } }, @@ -716,7 +716,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LicensePATCHRequestJSONSchema" + "$ref": "#/definitions/models.LicenseUpdateJSONSchema" } } ], @@ -1806,83 +1806,6 @@ const docTemplate = `{ } } }, - "models.ExportLicenseDB": { - "type": "object", - "properties": { - "external_ref": { - "$ref": "#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension" - }, - "marydone": { - "type": "boolean" - }, - "rf_FSFfree": { - "type": "boolean" - }, - "rf_Fedora": { - "type": "string" - }, - "rf_GPLv2compatible": { - "type": "boolean" - }, - "rf_GPLv3compatible": { - "type": "boolean" - }, - "rf_OSIapproved": { - "type": "boolean" - }, - "rf_active": { - "type": "boolean" - }, - "rf_add_date": { - "type": "string", - "example": "2023-12-01T18:10:25.00+05:30" - }, - "rf_copyleft": { - "type": "boolean" - }, - "rf_detector_type": { - "type": "integer", - "example": 1 - }, - "rf_flag": { - "type": "integer", - "example": 1 - }, - "rf_fullname": { - "type": "string", - "example": "MIT License" - }, - "rf_notes": { - "type": "string", - "example": "This license has been superseded." - }, - "rf_risk": { - "type": "integer" - }, - "rf_shortname": { - "type": "string", - "example": "MIT" - }, - "rf_source": { - "type": "string" - }, - "rf_spdx_id": { - "type": "string", - "example": "MIT" - }, - "rf_text": { - "type": "string", - "example": "MIT License Text here" - }, - "rf_text_updatable": { - "type": "boolean" - }, - "rf_url": { - "type": "string", - "example": "https://opensource.org/licenses/MIT" - } - } - }, "models.ImportLicensesResponse": { "type": "object", "properties": { @@ -1913,6 +1836,12 @@ const docTemplate = `{ }, "models.LicenseDB": { "type": "object", + "required": [ + "rf_fullname", + "rf_shortname", + "rf_spdx_id", + "rf_text" + ], "properties": { "external_ref": { "$ref": "#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension" @@ -1947,26 +1876,28 @@ const docTemplate = `{ }, "rf_detector_type": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_flag": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_fullname": { "type": "string", "example": "MIT License" }, - "rf_id": { - "type": "integer", - "example": 123 - }, "rf_notes": { "type": "string", "example": "This license has been superseded." }, "rf_risk": { - "type": "integer" + "type": "integer", + "maximum": 5, + "minimum": 0 }, "rf_shortname": { "type": "string", @@ -2066,135 +1997,106 @@ const docTemplate = `{ } } }, - "models.LicensePATCHRequestJSONSchema": { + "models.LicensePreviewResponse": { "type": "object", "properties": { - "external_ref": { - "type": "object", - "additionalProperties": true - }, - "marydone": { - "type": "boolean" - }, - "rf_FSFfree": { - "type": "boolean" - }, - "rf_Fedora": { - "type": "string" - }, - "rf_GPLv2compatible": { - "type": "boolean" - }, - "rf_GPLv3compatible": { - "type": "boolean" - }, - "rf_OSIapproved": { - "type": "boolean" - }, - "rf_active": { - "type": "boolean" - }, - "rf_copyleft": { - "type": "boolean" - }, - "rf_detector_type": { - "type": "integer", - "example": 1 + "shortnames": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "GPL-2.0-only", + "GPL-2.0-or-later" + ] }, - "rf_flag": { + "status": { "type": "integer", - "example": 1 - }, - "rf_fullname": { - "type": "string", - "example": "MIT License" + "example": 200 + } + } + }, + "models.LicenseResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.LicenseDB" + } }, - "rf_notes": { - "type": "string", - "example": "This license has been superseded." + "paginationmeta": { + "$ref": "#/definitions/models.PaginationMeta" }, - "rf_risk": { + "status": { "type": "integer", - "example": 3 - }, - "rf_source": { - "type": "string" - }, - "rf_spdx_id": { - "type": "string", - "example": "MIT" - }, - "rf_text": { - "type": "string", - "example": "MIT License Text here" - }, - "rf_text_updatable": { - "type": "boolean" - }, - "rf_url": { - "type": "string", - "example": "https://opensource.org/licenses/MIT" + "example": 200 } } }, - "models.LicensePOSTRequestJSONSchema": { + "models.LicenseShortnamesInput": { + "type": "object", + "properties": { + "shortnames": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "GPL-2.0-only", + "GPL-2.0-or-later" + ] + } + } + }, + "models.LicenseUpdateJSONSchema": { "type": "object", - "required": [ - "external_ref", - "marydone", - "rf_FSFfree", - "rf_Fedora", - "rf_GPLv2compatible", - "rf_GPLv3compatible", - "rf_OSIapproved", - "rf_active", - "rf_copyleft", - "rf_detector_type", - "rf_flag", - "rf_fullname", - "rf_notes", - "rf_risk", - "rf_shortname", - "rf_source", - "rf_spdx_id", - "rf_text", - "rf_text_updatable", - "rf_url" - ], "properties": { "external_ref": { "$ref": "#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension" }, "marydone": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_FSFfree": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_Fedora": { - "type": "string" + "type": "string", + "example": "Fedora" }, "rf_GPLv2compatible": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_GPLv3compatible": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_OSIapproved": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_active": { - "type": "boolean" + "type": "boolean", + "example": true }, "rf_copyleft": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_detector_type": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_flag": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_fullname": { @@ -2206,14 +2108,14 @@ const docTemplate = `{ "example": "This license has been superseded." }, "rf_risk": { - "type": "integer" - }, - "rf_shortname": { - "type": "string", - "example": "MIT" + "type": "integer", + "maximum": 5, + "minimum": 0, + "example": 1 }, "rf_source": { - "type": "string" + "type": "string", + "example": "Source" }, "rf_spdx_id": { "type": "string", @@ -2224,7 +2126,8 @@ const docTemplate = `{ "example": "MIT License Text here" }, "rf_text_updatable": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_url": { "type": "string", @@ -2232,58 +2135,6 @@ const docTemplate = `{ } } }, - "models.LicensePreviewResponse": { - "type": "object", - "properties": { - "shortnames": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "GPL-2.0-only", - "GPL-2.0-or-later" - ] - }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.LicenseResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/models.LicenseDB" - } - }, - "paginationmeta": { - "$ref": "#/definitions/models.PaginationMeta" - }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.LicenseShortnamesInput": { - "type": "object", - "properties": { - "shortnames": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "GPL-2.0-only", - "GPL-2.0-or-later" - ] - } - } - }, "models.Obligation": { "type": "object", "properties": { diff --git a/cmd/laas/docs/swagger.json b/cmd/laas/docs/swagger.json index 37d588b..e4cd619 100644 --- a/cmd/laas/docs/swagger.json +++ b/cmd/laas/docs/swagger.json @@ -449,7 +449,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LicensePOSTRequestJSONSchema" + "$ref": "#/definitions/models.LicenseDB" } } ], @@ -504,7 +504,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.ExportLicenseDB" + "$ref": "#/definitions/models.LicenseDB" } } }, @@ -709,7 +709,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LicensePATCHRequestJSONSchema" + "$ref": "#/definitions/models.LicenseUpdateJSONSchema" } } ], @@ -1799,83 +1799,6 @@ } } }, - "models.ExportLicenseDB": { - "type": "object", - "properties": { - "external_ref": { - "$ref": "#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension" - }, - "marydone": { - "type": "boolean" - }, - "rf_FSFfree": { - "type": "boolean" - }, - "rf_Fedora": { - "type": "string" - }, - "rf_GPLv2compatible": { - "type": "boolean" - }, - "rf_GPLv3compatible": { - "type": "boolean" - }, - "rf_OSIapproved": { - "type": "boolean" - }, - "rf_active": { - "type": "boolean" - }, - "rf_add_date": { - "type": "string", - "example": "2023-12-01T18:10:25.00+05:30" - }, - "rf_copyleft": { - "type": "boolean" - }, - "rf_detector_type": { - "type": "integer", - "example": 1 - }, - "rf_flag": { - "type": "integer", - "example": 1 - }, - "rf_fullname": { - "type": "string", - "example": "MIT License" - }, - "rf_notes": { - "type": "string", - "example": "This license has been superseded." - }, - "rf_risk": { - "type": "integer" - }, - "rf_shortname": { - "type": "string", - "example": "MIT" - }, - "rf_source": { - "type": "string" - }, - "rf_spdx_id": { - "type": "string", - "example": "MIT" - }, - "rf_text": { - "type": "string", - "example": "MIT License Text here" - }, - "rf_text_updatable": { - "type": "boolean" - }, - "rf_url": { - "type": "string", - "example": "https://opensource.org/licenses/MIT" - } - } - }, "models.ImportLicensesResponse": { "type": "object", "properties": { @@ -1906,6 +1829,12 @@ }, "models.LicenseDB": { "type": "object", + "required": [ + "rf_fullname", + "rf_shortname", + "rf_spdx_id", + "rf_text" + ], "properties": { "external_ref": { "$ref": "#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension" @@ -1940,26 +1869,28 @@ }, "rf_detector_type": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_flag": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_fullname": { "type": "string", "example": "MIT License" }, - "rf_id": { - "type": "integer", - "example": 123 - }, "rf_notes": { "type": "string", "example": "This license has been superseded." }, "rf_risk": { - "type": "integer" + "type": "integer", + "maximum": 5, + "minimum": 0 }, "rf_shortname": { "type": "string", @@ -2059,135 +1990,106 @@ } } }, - "models.LicensePATCHRequestJSONSchema": { + "models.LicensePreviewResponse": { "type": "object", "properties": { - "external_ref": { - "type": "object", - "additionalProperties": true - }, - "marydone": { - "type": "boolean" - }, - "rf_FSFfree": { - "type": "boolean" - }, - "rf_Fedora": { - "type": "string" - }, - "rf_GPLv2compatible": { - "type": "boolean" - }, - "rf_GPLv3compatible": { - "type": "boolean" - }, - "rf_OSIapproved": { - "type": "boolean" - }, - "rf_active": { - "type": "boolean" - }, - "rf_copyleft": { - "type": "boolean" - }, - "rf_detector_type": { - "type": "integer", - "example": 1 + "shortnames": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "GPL-2.0-only", + "GPL-2.0-or-later" + ] }, - "rf_flag": { + "status": { "type": "integer", - "example": 1 - }, - "rf_fullname": { - "type": "string", - "example": "MIT License" + "example": 200 + } + } + }, + "models.LicenseResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.LicenseDB" + } }, - "rf_notes": { - "type": "string", - "example": "This license has been superseded." + "paginationmeta": { + "$ref": "#/definitions/models.PaginationMeta" }, - "rf_risk": { + "status": { "type": "integer", - "example": 3 - }, - "rf_source": { - "type": "string" - }, - "rf_spdx_id": { - "type": "string", - "example": "MIT" - }, - "rf_text": { - "type": "string", - "example": "MIT License Text here" - }, - "rf_text_updatable": { - "type": "boolean" - }, - "rf_url": { - "type": "string", - "example": "https://opensource.org/licenses/MIT" + "example": 200 } } }, - "models.LicensePOSTRequestJSONSchema": { + "models.LicenseShortnamesInput": { + "type": "object", + "properties": { + "shortnames": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "GPL-2.0-only", + "GPL-2.0-or-later" + ] + } + } + }, + "models.LicenseUpdateJSONSchema": { "type": "object", - "required": [ - "external_ref", - "marydone", - "rf_FSFfree", - "rf_Fedora", - "rf_GPLv2compatible", - "rf_GPLv3compatible", - "rf_OSIapproved", - "rf_active", - "rf_copyleft", - "rf_detector_type", - "rf_flag", - "rf_fullname", - "rf_notes", - "rf_risk", - "rf_shortname", - "rf_source", - "rf_spdx_id", - "rf_text", - "rf_text_updatable", - "rf_url" - ], "properties": { "external_ref": { "$ref": "#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension" }, "marydone": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_FSFfree": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_Fedora": { - "type": "string" + "type": "string", + "example": "Fedora" }, "rf_GPLv2compatible": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_GPLv3compatible": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_OSIapproved": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_active": { - "type": "boolean" + "type": "boolean", + "example": true }, "rf_copyleft": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_detector_type": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_flag": { "type": "integer", + "maximum": 2, + "minimum": 0, "example": 1 }, "rf_fullname": { @@ -2199,14 +2101,14 @@ "example": "This license has been superseded." }, "rf_risk": { - "type": "integer" - }, - "rf_shortname": { - "type": "string", - "example": "MIT" + "type": "integer", + "maximum": 5, + "minimum": 0, + "example": 1 }, "rf_source": { - "type": "string" + "type": "string", + "example": "Source" }, "rf_spdx_id": { "type": "string", @@ -2217,7 +2119,8 @@ "example": "MIT License Text here" }, "rf_text_updatable": { - "type": "boolean" + "type": "boolean", + "example": false }, "rf_url": { "type": "string", @@ -2225,58 +2128,6 @@ } } }, - "models.LicensePreviewResponse": { - "type": "object", - "properties": { - "shortnames": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "GPL-2.0-only", - "GPL-2.0-or-later" - ] - }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.LicenseResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/models.LicenseDB" - } - }, - "paginationmeta": { - "$ref": "#/definitions/models.PaginationMeta" - }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.LicenseShortnamesInput": { - "type": "object", - "properties": { - "shortnames": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "GPL-2.0-only", - "GPL-2.0-or-later" - ] - } - } - }, "models.Obligation": { "type": "object", "properties": { diff --git a/cmd/laas/docs/swagger.yaml b/cmd/laas/docs/swagger.yaml index 6c81df5..224be34 100644 --- a/cmd/laas/docs/swagger.yaml +++ b/cmd/laas/docs/swagger.yaml @@ -84,60 +84,6 @@ definitions: example: 200 type: integer type: object - models.ExportLicenseDB: - properties: - external_ref: - $ref: '#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension' - marydone: - type: boolean - rf_FSFfree: - type: boolean - rf_Fedora: - type: string - rf_GPLv2compatible: - type: boolean - rf_GPLv3compatible: - type: boolean - rf_OSIapproved: - type: boolean - rf_active: - type: boolean - rf_add_date: - example: "2023-12-01T18:10:25.00+05:30" - type: string - rf_copyleft: - type: boolean - rf_detector_type: - example: 1 - type: integer - rf_flag: - example: 1 - type: integer - rf_fullname: - example: MIT License - type: string - rf_notes: - example: This license has been superseded. - type: string - rf_risk: - type: integer - rf_shortname: - example: MIT - type: string - rf_source: - type: string - rf_spdx_id: - example: MIT - type: string - rf_text: - example: MIT License Text here - type: string - rf_text_updatable: - type: boolean - rf_url: - example: https://opensource.org/licenses/MIT - type: string - type: object models.ImportLicensesResponse: properties: data: @@ -183,20 +129,23 @@ definitions: type: boolean rf_detector_type: example: 1 + maximum: 2 + minimum: 0 type: integer rf_flag: example: 1 + maximum: 2 + minimum: 0 type: integer rf_fullname: example: MIT License type: string - rf_id: - example: 123 - type: integer rf_notes: example: This license has been superseded. type: string rf_risk: + maximum: 5 + minimum: 0 type: integer rf_shortname: example: MIT @@ -214,6 +163,11 @@ definitions: rf_url: example: https://opensource.org/licenses/MIT type: string + required: + - rf_fullname + - rf_shortname + - rf_spdx_id + - rf_text type: object models.LicenseError: properties: @@ -266,81 +220,78 @@ definitions: $ref: '#/definitions/models.LicenseMapShortnamesElement' type: array type: object - models.LicensePATCHRequestJSONSchema: + models.LicensePreviewResponse: properties: - external_ref: - additionalProperties: true - type: object - marydone: - type: boolean - rf_FSFfree: - type: boolean - rf_Fedora: - type: string - rf_GPLv2compatible: - type: boolean - rf_GPLv3compatible: - type: boolean - rf_OSIapproved: - type: boolean - rf_active: - type: boolean - rf_copyleft: - type: boolean - rf_detector_type: - example: 1 - type: integer - rf_flag: - example: 1 + shortnames: + example: + - GPL-2.0-only + - GPL-2.0-or-later + items: + type: string + type: array + status: + example: 200 type: integer - rf_fullname: - example: MIT License - type: string - rf_notes: - example: This license has been superseded. - type: string - rf_risk: - example: 3 + type: object + models.LicenseResponse: + properties: + data: + items: + $ref: '#/definitions/models.LicenseDB' + type: array + paginationmeta: + $ref: '#/definitions/models.PaginationMeta' + status: + example: 200 type: integer - rf_source: - type: string - rf_spdx_id: - example: MIT - type: string - rf_text: - example: MIT License Text here - type: string - rf_text_updatable: - type: boolean - rf_url: - example: https://opensource.org/licenses/MIT - type: string type: object - models.LicensePOSTRequestJSONSchema: + models.LicenseShortnamesInput: + properties: + shortnames: + example: + - GPL-2.0-only + - GPL-2.0-or-later + items: + type: string + type: array + type: object + models.LicenseUpdateJSONSchema: properties: external_ref: $ref: '#/definitions/datatypes.JSONType-models_LicenseDBSchemaExtension' marydone: + example: false type: boolean rf_FSFfree: + example: false type: boolean rf_Fedora: + example: Fedora type: string rf_GPLv2compatible: + example: false type: boolean rf_GPLv3compatible: + example: false type: boolean rf_OSIapproved: + example: false type: boolean rf_active: + example: true type: boolean rf_copyleft: + example: false type: boolean rf_detector_type: example: 1 + maximum: 2 + minimum: 0 type: integer rf_flag: example: 1 + maximum: 2 + minimum: 0 type: integer rf_fullname: example: MIT License @@ -349,11 +300,12 @@ definitions: example: This license has been superseded. type: string rf_risk: + example: 1 + maximum: 5 + minimum: 0 type: integer - rf_shortname: - example: MIT - type: string rf_source: + example: Source type: string rf_spdx_id: example: MIT @@ -362,66 +314,11 @@ definitions: example: MIT License Text here type: string rf_text_updatable: + example: false type: boolean rf_url: example: https://opensource.org/licenses/MIT type: string - required: - - external_ref - - marydone - - rf_FSFfree - - rf_Fedora - - rf_GPLv2compatible - - rf_GPLv3compatible - - rf_OSIapproved - - rf_active - - rf_copyleft - - rf_detector_type - - rf_flag - - rf_fullname - - rf_notes - - rf_risk - - rf_shortname - - rf_source - - rf_spdx_id - - rf_text - - rf_text_updatable - - rf_url - type: object - models.LicensePreviewResponse: - properties: - shortnames: - example: - - GPL-2.0-only - - GPL-2.0-or-later - items: - type: string - type: array - status: - example: 200 - type: integer - type: object - models.LicenseResponse: - properties: - data: - items: - $ref: '#/definitions/models.LicenseDB' - type: array - paginationmeta: - $ref: '#/definitions/models.PaginationMeta' - status: - example: 200 - type: integer - type: object - models.LicenseShortnamesInput: - properties: - shortnames: - example: - - GPL-2.0-only - - GPL-2.0-or-later - items: - type: string - type: array type: object models.Obligation: properties: @@ -1059,7 +956,7 @@ paths: name: license required: true schema: - $ref: '#/definitions/models.LicensePOSTRequestJSONSchema' + $ref: '#/definitions/models.LicenseDB' produces: - application/json responses: @@ -1129,7 +1026,7 @@ paths: name: license required: true schema: - $ref: '#/definitions/models.LicensePATCHRequestJSONSchema' + $ref: '#/definitions/models.LicenseUpdateJSONSchema' produces: - application/json responses: @@ -1169,7 +1066,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/models.ExportLicenseDB' + $ref: '#/definitions/models.LicenseDB' type: array "500": description: Failed to fetch Licenses diff --git a/go.mod b/go.mod index 88ca4d6..d8fd826 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/joho/godotenv v1.5.1 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.9.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.2 @@ -31,7 +31,7 @@ require ( github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/dave/jennifer v1.7.0 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect @@ -39,7 +39,7 @@ require ( github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-playground/validator/v10 v10.22.0 github.com/goccy/go-json v0.10.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect @@ -49,7 +49,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index 3243479..c7bc963 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -38,8 +38,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -78,8 +78,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= @@ -109,9 +109,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index dc89535..ce4766a 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -49,16 +49,16 @@ func makeRequest(method, path string, body interface{}, isAuthanticated bool) *h } 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: false, - DetectorType: 1, - Active: true, - Flag: 1, - Marydone: true, - SpdxId: "MIT", + Shortname: func(s string) *string { return &s }("MIT"), + Fullname: func(s string) *string { return &s }("MIT License"), + Text: func(s string) *string { return &s }("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: func(s string) *string { return &s }("https://opensource.org/licenses/MIT"), + TextUpdatable: func(b bool) *bool { return &b }(false), + DetectorType: func(i int64) *int64 { return &i }(1), + Active: func(b bool) *bool { return &b }(true), + Flag: func(i int64) *int64 { return &i }(1), + Marydone: func(b bool) *bool { return &b }(true), + SpdxId: func(s string) *string { return &s }("MIT"), } w := makeRequest("GET", "/api/licenses/MIT", nil, false) assert.Equal(t, http.StatusOK, w.Code) @@ -75,14 +75,14 @@ func TestGetLicense(t *testing.T) { func TestCreateLicense(t *testing.T) { License := models.LicenseDB{ - Shortname: "ABCD", - Fullname: "ABCD License", - Text: "just a license", - Url: "https://abcdlicense/ABCD", - SpdxId: "1", - TextUpdatable: false, - DetectorType: 1, - Active: true, + Shortname: func(s string) *string { return &s }("ABCD"), + Fullname: func(s string) *string { return &s }("ABCD License"), + Text: func(s string) *string { return &s }("just a license"), + Url: func(s string) *string { return &s }("https://abcdlicense/ABCD"), + SpdxId: func(s string) *string { return &s }("1"), + TextUpdatable: func(b bool) *bool { return &b }(false), + DetectorType: func(i int64) *int64 { return &i }(1), + Active: func(b bool) *bool { return &b }(true), } w := makeRequest("POST", "/api/licenses", License, true) assert.Equal(t, http.StatusCreated, w.Code) @@ -99,19 +99,19 @@ func TestCreateLicense(t *testing.T) { func TestUpdateLicense(t *testing.T) { License := models.LicenseDB{ - Marydone: true, + Marydone: func(b bool) *bool { return &b }(true), } 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: false, - DetectorType: 1, - Active: true, - Flag: 1, - Marydone: true, - SpdxId: "MIT", + Shortname: func(s string) *string { return &s }("MIT"), + Fullname: func(s string) *string { return &s }("MIT License"), + Text: func(s string) *string { return &s }("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: func(s string) *string { return &s }("https://opensource.org/licenses/MIT"), + TextUpdatable: func(b bool) *bool { return &b }(false), + DetectorType: func(i int64) *int64 { return &i }(1), + Active: func(b bool) *bool { return &b }(true), + Flag: func(i int64) *int64 { return &i }(1), + Marydone: func(b bool) *bool { return &b }(true), + SpdxId: func(s string) *string { return &s }("MIT"), } w := makeRequest("PATCH", "/api/licenses/MIT", License, true) assert.Equal(t, http.StatusOK, w.Code) @@ -128,16 +128,16 @@ func TestUpdateLicense(t *testing.T) { 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: false, - DetectorType: 1, - Active: true, - Flag: 1, - Marydone: false, - SpdxId: "PostgreSQL", + Shortname: func(s string) *string { return &s }("PostgreSQL"), + Fullname: func(s string) *string { return &s }("PostgreSQL License"), + Text: func(s string) *string { return &s }("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: func(s string) *string { return &s }("http://www.postgresql.org/about/licence"), + TextUpdatable: func(b bool) *bool { return &b }(false), + DetectorType: func(i int64) *int64 { return &i }(1), + Active: func(b bool) *bool { return &b }(true), + Flag: func(i int64) *int64 { return &i }(1), + Marydone: func(b bool) *bool { return &b }(false), + SpdxId: func(s string) *string { return &s }("PostgreSQL"), } search := models.SearchLicense{ Field: "fullname", @@ -160,30 +160,30 @@ func TestSearchInLicense(t *testing.T) { 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: false, - DetectorType: 1, - Active: true, - Flag: 1, - Marydone: false, - SpdxId: "LicenseRef-fossology-GPL-2.0-with-autoconf-exception", + Shortname: func(s string) *string { return &s }("GPL-2.0-with-autoconf-exception"), + Fullname: func(s string) *string { return &s }("GNU General Public License v2.0 w/Autoconf exception"), + Text: func(s string) *string { return &s }("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: func(s string) *string { return &s }("http://ac-archive.sourceforge.net/doc/copyright.html"), + Notes: func(s string) *string { return &s }("DEPRECATED: Use license expression including main license, \"WITH\" operator, and identifier: Autoconf-exception-2.0"), + TextUpdatable: func(b bool) *bool { return &b }(false), + DetectorType: func(i int64) *int64 { return &i }(1), + Active: func(b bool) *bool { return &b }(true), + Flag: func(i int64) *int64 { return &i }(1), + Marydone: func(b bool) *bool { return &b }(false), + SpdxId: func(s string) *string { return &s }("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: false, - DetectorType: 1, - Active: true, - Flag: 1, - Marydone: false, - SpdxId: "Autoconf-exception-2.0", + Shortname: func(s string) *string { return &s }("Autoconf-exception-2.0"), + Fullname: func(s string) *string { return &s }("Autoconf exception 2.0"), + Text: func(s string) *string { return &s }("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: func(s string) *string { return &s }("http://ac-archive.sourceforge.net/doc/copyright.html"), + Notes: func(s string) *string { return &s }("Typically used with GPL-2.0"), + TextUpdatable: func(b bool) *bool { return &b }(false), + DetectorType: func(i int64) *int64 { return &i }(1), + Active: func(b bool) *bool { return &b }(true), + Flag: func(i int64) *int64 { return &i }(1), + Marydone: func(b bool) *bool { return &b }(false), + SpdxId: func(s string) *string { return &s }("Autoconf-exception-2.0"), }, } search := models.SearchLicense{ diff --git a/pkg/api/licenses.go b/pkg/api/licenses.go index c988c1b..ba93978 100644 --- a/pkg/api/licenses.go +++ b/pkg/api/licenses.go @@ -10,6 +10,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "path/filepath" "reflect" @@ -21,6 +22,8 @@ import ( "github.com/fossology/LicenseDb/pkg/models" "github.com/fossology/LicenseDb/pkg/utils" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -89,7 +92,7 @@ func FilterLicense(c *gin.Context) { if err != nil { parsedActive = false } - query = query.Where(models.LicenseDB{Active: parsedActive}) + query = query.Where(models.LicenseDB{Active: &parsedActive}) } if fsffree != "" { @@ -97,7 +100,7 @@ func FilterLicense(c *gin.Context) { if err != nil { parsedFsffree = false } - query = query.Where(models.LicenseDB{FSFfree: parsedFsffree}) + query = query.Where(models.LicenseDB{FSFfree: &parsedFsffree}) } if OSIapproved != "" { @@ -105,7 +108,7 @@ func FilterLicense(c *gin.Context) { if err != nil { parsedOsiApproved = false } - query = query.Where(models.LicenseDB{OSIapproved: parsedOsiApproved}) + query = query.Where(models.LicenseDB{OSIapproved: &parsedOsiApproved}) } if copyleft != "" { @@ -113,11 +116,11 @@ func FilterLicense(c *gin.Context) { if err != nil { parsedCopyleft = false } - query = query.Where(models.LicenseDB{Copyleft: parsedCopyleft}) + query = query.Where(models.LicenseDB{Copyleft: &parsedCopyleft}) } if SpdxId != "" { - query = query.Where(models.LicenseDB{SpdxId: SpdxId}) + query = query.Where(models.LicenseDB{SpdxId: &SpdxId}) } if DetectorType != "" { @@ -133,7 +136,7 @@ func FilterLicense(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } - query = query.Where(models.LicenseDB{DetectorType: parsedDetectorType}) + query = query.Where(models.LicenseDB{DetectorType: &parsedDetectorType}) } if GPLv2compatible != "" { @@ -141,7 +144,7 @@ func FilterLicense(c *gin.Context) { if err != nil { parsedGPLv2compatible = false } - query = query.Where(models.LicenseDB{GPLv2compatible: parsedGPLv2compatible}) + query = query.Where(models.LicenseDB{GPLv2compatible: &parsedGPLv2compatible}) } if GPLv3compatible != "" { @@ -149,7 +152,7 @@ func FilterLicense(c *gin.Context) { if err != nil { parsedGPLv3compatible = false } - query = query.Where(models.LicenseDB{GPLv3compatible: parsedGPLv3compatible}) + query = query.Where(models.LicenseDB{GPLv3compatible: &parsedGPLv3compatible}) } if marydone != "" { @@ -157,7 +160,7 @@ func FilterLicense(c *gin.Context) { if err != nil { parsedMarydone = false } - query = query.Where(models.LicenseDB{Marydone: parsedMarydone}) + query = query.Where(models.LicenseDB{Marydone: &parsedMarydone}) } for externalRefKey, externalRefValue := range externalRefData { @@ -224,7 +227,7 @@ func GetLicense(c *gin.Context) { return } - err := db.DB.Where(models.LicenseDB{Shortname: queryParam}).First(&license).Error + err := db.DB.Where(models.LicenseDB{Shortname: &queryParam}).First(&license).Error if err != nil { er := models.LicenseError{ @@ -257,15 +260,15 @@ func GetLicense(c *gin.Context) { // @Tags Licenses // @Accept json // @Produce json -// @Param license body models.LicensePOSTRequestJSONSchema true "New license to be created" -// @Success 201 {object} models.LicenseResponse "New license created successfully" -// @Failure 400 {object} models.LicenseError "Invalid request body" -// @Failure 409 {object} models.LicenseError "License with same shortname already exists" -// @Failure 500 {object} models.LicenseError "Failed to create license" +// @Param license body models.LicenseDB true "New license to be created" +// @Success 201 {object} models.LicenseResponse "New license created successfully" +// @Failure 400 {object} models.LicenseError "Invalid request body" +// @Failure 409 {object} models.LicenseError "License with same shortname already exists" +// @Failure 500 {object} models.LicenseError "Failed to create license" // @Security ApiKeyAuth // @Router /licenses [post] func CreateLicense(c *gin.Context) { - var input models.LicensePOSTRequestJSONSchema + var input models.LicenseDB if err := c.ShouldBindJSON(&input); err != nil { er := models.LicenseError{ @@ -279,43 +282,22 @@ func CreateLicense(c *gin.Context) { return } - license := models.LicenseDB{ - Shortname: input.Shortname, - Fullname: input.Fullname, - Text: input.Text, - Url: input.Url, - 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, - ExternalRef: input.ExternalRef, - } - - result := db.DB. - Where(&models.LicenseDB{Shortname: license.Shortname}). - FirstOrCreate(&license) - if result.RowsAffected == 0 { + validate := validator.New(validator.WithRequiredStructEnabled()) + if err := validate.Struct(&input); err != nil { er := models.LicenseError{ - Status: http.StatusConflict, - Message: "can not create license with same shortname", - Error: fmt.Sprintf("Error: License with shortname '%s' already exists", input.Shortname), + Status: http.StatusBadRequest, + Message: "can not create license with these field values", + Error: fmt.Sprintf("field '%s' failed validation: %s\n", err.(validator.ValidationErrors)[0].Field(), err.(validator.ValidationErrors)[0].Tag()), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } - c.JSON(http.StatusConflict, er) + c.JSON(http.StatusBadRequest, er) return } + + result := db.DB. + Where(&models.LicenseDB{Shortname: input.Shortname}). + FirstOrCreate(&input) if result.Error != nil { er := models.LicenseError{ Status: http.StatusInternalServerError, @@ -327,8 +309,19 @@ func CreateLicense(c *gin.Context) { c.JSON(http.StatusInternalServerError, er) return } + if result.RowsAffected == 0 { + er := models.LicenseError{ + Status: http.StatusConflict, + Message: "can not create license with same shortname", + Error: fmt.Sprintf("Error: License with shortname '%s' already exists", *input.Shortname), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusConflict, er) + return + } res := models.LicenseResponse{ - Data: []models.LicenseDB{license}, + Data: []models.LicenseDB{input}, Status: http.StatusCreated, Meta: &models.PaginationMeta{ ResourceCount: 1, @@ -346,26 +339,25 @@ func CreateLicense(c *gin.Context) { // @Tags Licenses // @Accept json // @Produce json -// @Param shortname path string true "Shortname of the license to be updated" -// @Param license body models.LicensePATCHRequestJSONSchema true "Update license body (requires only the fields to be updated)" -// @Success 200 {object} models.LicenseResponse "License updated successfully" -// @Failure 400 {object} models.LicenseError "Invalid license body" -// @Failure 404 {object} models.LicenseError "License with shortname not found" -// @Failure 409 {object} models.LicenseError "License with same shortname already exists" -// @Failure 500 {object} models.LicenseError "Failed to update license" +// @Param shortname path string true "Shortname of the license to be updated" +// @Param license body models.LicenseUpdateJSONSchema true "Update license body (requires only the fields to be updated)" +// @Success 200 {object} models.LicenseResponse "License updated successfully" +// @Failure 400 {object} models.LicenseError "Invalid license body" +// @Failure 404 {object} models.LicenseError "License with shortname not found" +// @Failure 409 {object} models.LicenseError "License with same shortname already exists" +// @Failure 500 {object} models.LicenseError "Failed to update license" // @Security ApiKeyAuth // @Router /licenses/{shortname} [patch] func UpdateLicense(c *gin.Context) { _ = db.DB.Transaction(func(tx *gorm.DB) error { - var updates models.LicensePATCHRequestJSONSchema - var newLicense models.LicenseDB + var updates models.LicenseUpdateJSONSchema + var externalRefsPayload models.UpdateExternalRefsJSONPayload var oldLicense models.LicenseDB - newLicenseMap := make(map[string]interface{}) username := c.GetString("username") shortname := c.Param("shortname") - if err := tx.Where(models.LicenseDB{Shortname: shortname}).First(&oldLicense).Error; err != nil { + if err := tx.Where(models.LicenseDB{Shortname: &shortname}).First(&oldLicense).Error; err != nil { er := models.LicenseError{ Status: http.StatusNotFound, Message: fmt.Sprintf("license with shortname '%s' not found", shortname), @@ -377,7 +369,8 @@ func UpdateLicense(c *gin.Context) { return err } - if err := c.ShouldBindJSON(&updates); err != nil { + // https://github.com/gin-gonic/gin/pull/1341 + if err := c.ShouldBindBodyWith(&updates, binding.JSON); err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "invalid json body update", @@ -389,21 +382,33 @@ func UpdateLicense(c *gin.Context) { return err } - // Overwrite values of existing keys, add new key value pairs and remove keys with null values. - if err := tx.Model(&newLicense).Clauses(clause.Returning{}).Where(models.LicenseDB{Id: oldLicense.Id}).UpdateColumn("external_ref", gorm.Expr("jsonb_strip_nulls(COALESCE(external_ref, '{}'::jsonb) || ?)", updates.ExternalRef)).Error; err != nil { + validate := validator.New() + if err := validate.Struct(&updates); err != nil { er := models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Failed to update license", + Status: http.StatusBadRequest, + Message: "can not update license with these field values", + Error: fmt.Sprintf("field '%s' failed validation: %s\n", err.(validator.ValidationErrors)[0].Field(), err.(validator.ValidationErrors)[0].Tag()), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusBadRequest, er) + return err + } + + if err := c.ShouldBindBodyWith(&externalRefsPayload, binding.JSON); 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.StatusInternalServerError, er) + c.JSON(http.StatusBadRequest, er) return err } - if updates.Text.IsDefined && oldLicense.Text != updates.Text.Value { - if !oldLicense.TextUpdatable { + if updates.Text != nil && *oldLicense.Text != *updates.Text { + if !*oldLicense.TextUpdatable { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "Text is not updatable", @@ -415,115 +420,29 @@ func UpdateLicense(c *gin.Context) { return errors.New("field `rf_text_updatable` needs to be true to update the text") } - if updates.Text.Value == "" { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "`rf_text` field cannot be empty", - Error: "`rf_text` field cannot be empty", - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return errors.New("`rf_text` field cannot be empty") - } - // Update flag to indicate the license text was updated. - newLicenseMap["rf_flag"] = 2 - newLicenseMap["rf_text"] = updates.Text.Value + *updates.Flag = 2 } - if updates.Fullname.IsDefined { - if updates.Fullname.Value == "" { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "`rf_fullname` field cannot be empty", - Error: "`rf_fullname` field cannot be empty", - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return errors.New("`rf_fullname` field cannot be empty") - } - newLicenseMap["rf_fullname"] = updates.Fullname.Value - } - - if updates.SpdxId.IsDefined { - if updates.SpdxId.Value == "" { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "`rf_spdx_id` field cannot be empty", - Error: "`rf_spdx_id` field cannot be empty", - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return errors.New("`rf_spdx_id` field cannot be empty") + // Overwrite values of existing keys, add new key value pairs and remove keys with null values. + if err := tx.Model(&models.LicenseDB{}).Where(models.LicenseDB{Id: oldLicense.Id}).UpdateColumn("external_ref", gorm.Expr("jsonb_strip_nulls(COALESCE(external_ref, '{}'::jsonb) || ?)", externalRefsPayload.ExternalRef)).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), } - newLicenseMap["rf_spdx_id"] = updates.SpdxId.Value - } - - if updates.Url.IsDefined { - newLicenseMap["rf_url"] = updates.Url.Value - } - - if updates.Copyleft.IsDefined { - newLicenseMap["rf_copyleft"] = updates.Copyleft.Value - } - - if updates.FSFfree.IsDefined { - newLicenseMap["rf_FSFfree"] = updates.FSFfree.Value - } - - if updates.OSIapproved.IsDefined { - newLicenseMap["rf_OSIapproved"] = updates.OSIapproved.Value - } - - if updates.GPLv2compatible.IsDefined { - newLicenseMap["rf_GPLv2compatible"] = updates.GPLv2compatible.Value - } - - if updates.GPLv3compatible.IsDefined { - newLicenseMap["rf_GPLv3compatible"] = updates.GPLv3compatible.Value - } - - if updates.Notes.IsDefined { - newLicenseMap["rf_notes"] = updates.Notes.Value - } - - if updates.Fedora.IsDefined { - newLicenseMap["rf_Fedora"] = updates.Fedora.Value - } - - if updates.TextUpdatable.IsDefined { - newLicenseMap["rf_text_updatable"] = updates.TextUpdatable.Value - } - - if updates.DetectorType.IsDefined { - newLicenseMap["rf_detector_type"] = updates.DetectorType.Value - } - - if updates.Active.IsDefined { - newLicenseMap["rf_active"] = updates.Active.Value - } - - if updates.Source.IsDefined { - newLicenseMap["rf_source"] = updates.Source.Value - } - - if updates.Risk.IsDefined { - newLicenseMap["rf_risk"] = updates.Risk.Value - } - - if updates.Flag.IsDefined { - newLicenseMap["rf_flag"] = updates.Flag.Value + c.JSON(http.StatusInternalServerError, er) + return err } - if updates.Marydone.IsDefined { - newLicenseMap["marydone"] = updates.Marydone.Value - } + // https://github.com/go-gorm/gorm/issues/3938: BeforeSave hook is called on the struct passed in .Model() + // Cannot pass empty newLicense struct in .Model() as all fields will be empty and no validation will happen + newLicense := models.LicenseDB(updates) - // Update all other fields except external_ref - if err := tx.Model(&newLicense).Clauses(clause.Returning{}).Where(models.LicenseDB{Id: oldLicense.Id}).Updates(newLicenseMap).Error; err != nil { + // Update all other fields except external_ref and rf_shortname + if err := tx.Model(&newLicense).Omit("external_ref", "rf_shortname").Clauses(clause.Returning{}).Where(models.LicenseDB{Id: oldLicense.Id}).Updates(newLicense).Error; err != nil { er := models.LicenseError{ Status: http.StatusInternalServerError, Message: "Failed to update license", @@ -566,18 +485,18 @@ func addChangelogsForLicenseUpdate(tx *gorm.DB, username string, newLicense, oldLicense *models.LicenseDB) error { var changes []models.ChangeLog - if oldLicense.Fullname != newLicense.Fullname { + if *oldLicense.Fullname != *newLicense.Fullname { changes = append(changes, models.ChangeLog{ Field: "Fullname", - OldValue: &oldLicense.Fullname, - UpdatedValue: &newLicense.Fullname, + OldValue: oldLicense.Fullname, + UpdatedValue: newLicense.Fullname, }) } - if oldLicense.Url != newLicense.Url { + if *oldLicense.Url != *newLicense.Url { changes = append(changes, models.ChangeLog{ Field: "Url", - OldValue: &oldLicense.Url, - UpdatedValue: &newLicense.Url, + OldValue: oldLicense.Url, + UpdatedValue: newLicense.Url, }) } if oldLicense.AddDate != newLicense.AddDate { @@ -589,134 +508,134 @@ func addChangelogsForLicenseUpdate(tx *gorm.DB, username string, UpdatedValue: &newVal, }) } - if oldLicense.Active != newLicense.Active { - oldVal := strconv.FormatBool(oldLicense.Active) - newVal := strconv.FormatBool(newLicense.Active) + if *oldLicense.Active != *newLicense.Active { + oldVal := strconv.FormatBool(*oldLicense.Active) + newVal := strconv.FormatBool(*newLicense.Active) changes = append(changes, models.ChangeLog{ Field: "Active", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.Copyleft != newLicense.Copyleft { - oldVal := strconv.FormatBool(oldLicense.Copyleft) - newVal := strconv.FormatBool(newLicense.Copyleft) + if *oldLicense.Copyleft != *newLicense.Copyleft { + oldVal := strconv.FormatBool(*oldLicense.Copyleft) + newVal := strconv.FormatBool(*newLicense.Copyleft) changes = append(changes, models.ChangeLog{ Field: "Copyleft", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.FSFfree != newLicense.FSFfree { - oldVal := strconv.FormatBool(oldLicense.FSFfree) - newVal := strconv.FormatBool(newLicense.FSFfree) + if *oldLicense.FSFfree != *newLicense.FSFfree { + oldVal := strconv.FormatBool(*oldLicense.FSFfree) + newVal := strconv.FormatBool(*newLicense.FSFfree) changes = append(changes, models.ChangeLog{ Field: "FSFfree", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.GPLv2compatible != newLicense.GPLv2compatible { - oldVal := strconv.FormatBool(oldLicense.GPLv2compatible) - newVal := strconv.FormatBool(newLicense.GPLv2compatible) + if *oldLicense.GPLv2compatible != *newLicense.GPLv2compatible { + oldVal := strconv.FormatBool(*oldLicense.GPLv2compatible) + newVal := strconv.FormatBool(*newLicense.GPLv2compatible) changes = append(changes, models.ChangeLog{ Field: "GPLv2compatible", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.GPLv3compatible != newLicense.GPLv3compatible { - oldVal := strconv.FormatBool(oldLicense.GPLv3compatible) - newVal := strconv.FormatBool(newLicense.GPLv3compatible) + if *oldLicense.GPLv3compatible != *newLicense.GPLv3compatible { + oldVal := strconv.FormatBool(*oldLicense.GPLv3compatible) + newVal := strconv.FormatBool(*newLicense.GPLv3compatible) changes = append(changes, models.ChangeLog{ Field: "GPLv3compatible", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.OSIapproved != newLicense.OSIapproved { - oldVal := strconv.FormatBool(oldLicense.OSIapproved) - newVal := strconv.FormatBool(newLicense.OSIapproved) + if *oldLicense.OSIapproved != *newLicense.OSIapproved { + oldVal := strconv.FormatBool(*oldLicense.OSIapproved) + newVal := strconv.FormatBool(*newLicense.OSIapproved) changes = append(changes, models.ChangeLog{ Field: "OSIapproved", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.Text != newLicense.Text { + if *oldLicense.Text != *newLicense.Text { changes = append(changes, models.ChangeLog{ Field: "Text", - OldValue: &oldLicense.Text, - UpdatedValue: &newLicense.Text, + OldValue: oldLicense.Text, + UpdatedValue: newLicense.Text, }) } - if oldLicense.TextUpdatable != newLicense.TextUpdatable { - oldVal := strconv.FormatBool(oldLicense.TextUpdatable) - newVal := strconv.FormatBool(newLicense.TextUpdatable) + if *oldLicense.TextUpdatable != *newLicense.TextUpdatable { + oldVal := strconv.FormatBool(*oldLicense.TextUpdatable) + newVal := strconv.FormatBool(*newLicense.TextUpdatable) changes = append(changes, models.ChangeLog{ Field: "TextUpdatable", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.Fedora != newLicense.Fedora { + if *oldLicense.Fedora != *newLicense.Fedora { changes = append(changes, models.ChangeLog{ Field: "Fedora", - OldValue: &oldLicense.Fedora, - UpdatedValue: &newLicense.Fedora, + OldValue: oldLicense.Fedora, + UpdatedValue: newLicense.Fedora, }) } - if oldLicense.Flag != newLicense.Flag { - oldVal := strconv.FormatInt(oldLicense.Flag, 10) - newVal := strconv.FormatInt(newLicense.Flag, 10) + if *oldLicense.Flag != *newLicense.Flag { + oldVal := strconv.FormatInt(*oldLicense.Flag, 10) + newVal := strconv.FormatInt(*newLicense.Flag, 10) changes = append(changes, models.ChangeLog{ Field: "Flag", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.Notes != newLicense.Notes { + if *oldLicense.Notes != *newLicense.Notes { changes = append(changes, models.ChangeLog{ Field: "Notes", - OldValue: &oldLicense.Notes, - UpdatedValue: &newLicense.Notes, + OldValue: oldLicense.Notes, + UpdatedValue: newLicense.Notes, }) } - if oldLicense.DetectorType != newLicense.DetectorType { - oldVal := strconv.FormatInt(oldLicense.DetectorType, 10) - newVal := strconv.FormatInt(newLicense.DetectorType, 10) + if *oldLicense.DetectorType != *newLicense.DetectorType { + oldVal := strconv.FormatInt(*oldLicense.DetectorType, 10) + newVal := strconv.FormatInt(*newLicense.DetectorType, 10) changes = append(changes, models.ChangeLog{ Field: "DetectorType", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.Source != newLicense.Source { + if *oldLicense.Source != *newLicense.Source { changes = append(changes, models.ChangeLog{ Field: "Source", - OldValue: &oldLicense.Source, - UpdatedValue: &newLicense.Source, + OldValue: oldLicense.Source, + UpdatedValue: newLicense.Source, }) } - if oldLicense.SpdxId != newLicense.SpdxId { + if *oldLicense.SpdxId != *newLicense.SpdxId { changes = append(changes, models.ChangeLog{ Field: "SpdxId", - OldValue: &oldLicense.SpdxId, - UpdatedValue: &newLicense.SpdxId, + OldValue: oldLicense.SpdxId, + UpdatedValue: newLicense.SpdxId, }) } - if oldLicense.Risk != newLicense.Risk { - oldVal := strconv.FormatInt(oldLicense.Risk, 10) - newVal := strconv.FormatInt(newLicense.Risk, 10) + if *oldLicense.Risk != *newLicense.Risk { + oldVal := strconv.FormatInt(*oldLicense.Risk, 10) + newVal := strconv.FormatInt(*newLicense.Risk, 10) changes = append(changes, models.ChangeLog{ Field: "Risk", OldValue: &oldVal, UpdatedValue: &newVal, }) } - if oldLicense.Marydone != newLicense.Marydone { - oldVal := strconv.FormatBool(oldLicense.Marydone) - newVal := strconv.FormatBool(newLicense.Marydone) + if *oldLicense.Marydone != *newLicense.Marydone { + oldVal := strconv.FormatBool(*oldLicense.Marydone) + newVal := strconv.FormatBool(*newLicense.Marydone) changes = append(changes, models.ChangeLog{ Field: "Marydone", OldValue: &oldVal, @@ -944,8 +863,9 @@ func ImportLicenses(c *gin.Context) { return } - var licenses []models.LicenseImport decoder := json.NewDecoder(file) + + var licenses []models.LicenseDB if err := decoder.Decode(&licenses); err != nil { er := models.LicenseError{ Status: http.StatusInternalServerError, @@ -958,107 +878,73 @@ func ImportLicenses(c *gin.Context) { return } + if _, err = file.Seek(0, io.SeekStart); err != nil { + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "error parsing json", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + var externalRefs []models.UpdateExternalRefsJSONPayload + if err := decoder.Decode(&externalRefs); err != nil { + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "invalid json", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusInternalServerError, er) + return + } + res := models.ImportLicensesResponse{ Status: http.StatusOK, } - for _, license := range licenses { + for i := range licenses { _ = db.DB.Transaction(func(tx *gorm.DB) error { - newLicenseMap := make(map[string]interface{}) - if err := addStructFieldToMap(c, &license.Shortname, "rf_shortname", newLicenseMap, "", &res); err != nil { - return err - } - if err := addStructFieldToMap(c, &license.Fullname, "rf_fullname", newLicenseMap, license.Shortname.Value, &res); err != nil { - return err - } - if err := addStructFieldToMap(c, &license.Text, "rf_text", newLicenseMap, license.Shortname.Value, &res); err != nil { - return err - } - if err := addStructFieldToMap(c, &license.Url, "rf_url", newLicenseMap, license.Shortname.Value, &res); err != nil { - return err - } - if err := addStructFieldToMap(c, &license.Active, "rf_active", newLicenseMap, license.Shortname.Value, &res); err != nil { - return err - } - if err := addStructFieldToMap(c, &license.Source, "rf_source", newLicenseMap, license.Shortname.Value, &res); err != nil { - return err - } - if err := addStructFieldToMap(c, &license.SpdxId, "rf_spdx_id", newLicenseMap, license.Shortname.Value, &res); err != nil { - return err - } - if err := addStructFieldToMap(c, &license.Risk, "rf_risk", newLicenseMap, license.Shortname.Value, &res); err != nil { - return err - } + errMessage, importStatus, oldLicense, newLicense := utils.InsertOrUpdateLicenseOnImport(tx, &licenses[i], &externalRefs[i]) - if license.Copyleft.IsDefinedAndNotNull { - newLicenseMap["rf_copyleft"] = license.Copyleft.Value - } - if license.FSFfree.IsDefinedAndNotNull { - newLicenseMap["rf_FSFfree"] = license.FSFfree.Value - } - if license.OSIapproved.IsDefinedAndNotNull { - newLicenseMap["rf_OSIapproved"] = license.OSIapproved.Value - } - if license.GPLv2compatible.IsDefinedAndNotNull { - newLicenseMap["rf_GPLv2compatible"] = license.GPLv2compatible.Value - } - if license.GPLv3compatible.IsDefinedAndNotNull { - newLicenseMap["rf_GPLv3compatible"] = license.GPLv3compatible.Value - } - if license.Notes.IsDefinedAndNotNull { - newLicenseMap["rf_notes"] = license.Notes.Value - } - if license.Fedora.IsDefinedAndNotNull { - newLicenseMap["rf_Fedora"] = license.Fedora.Value - } - if license.DetectorType.IsDefinedAndNotNull { - newLicenseMap["rf_detector_type"] = license.DetectorType.Value - } - if license.Flag.IsDefinedAndNotNull { - newLicenseMap["rf_flag"] = license.Flag.Value - } - if license.Marydone.IsDefinedAndNotNull { - newLicenseMap["marydone"] = license.Marydone.Value - } - newLicenseMap["external_ref"] = license.ExternalRef - - errMessage, importStatus, newLicense, oldLicense := InsertOrUpdateLicenseOnImport(tx, newLicenseMap) - - if importStatus == models.IMPORT_FAILED { + if importStatus == utils.IMPORT_FAILED { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusInternalServerError, Message: errMessage, - Error: newLicense.Shortname, + Error: *licenses[i].Shortname, Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), }) return errors.New(errMessage) - } else if importStatus == models.IMPORT_LICENSE_CREATED { + } else if importStatus == utils.IMPORT_LICENSE_CREATED { res.Data = append(res.Data, models.LicenseImportStatus{ - Data: models.LicenseId{Id: oldLicense.Id, Shortname: oldLicense.Shortname}, + Data: models.LicenseId{Id: oldLicense.Id, Shortname: *oldLicense.Shortname}, Status: http.StatusCreated, }) - } else if importStatus == models.IMPORT_LICENSE_UPDATED { + } else if importStatus == utils.IMPORT_LICENSE_UPDATED { if err := addChangelogsForLicenseUpdate(tx, username, newLicense, oldLicense); err != nil { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusInternalServerError, Message: "Failed to update license", - Error: newLicense.Shortname, + Error: *newLicense.Shortname, Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), }) return err } res.Data = append(res.Data, models.LicenseImportStatus{ - Data: models.LicenseId{Id: newLicense.Id, Shortname: newLicense.Shortname}, + Data: models.LicenseId{Id: newLicense.Id, Shortname: *newLicense.Shortname}, Status: http.StatusOK, }) - } else if importStatus == models.IMPORT_LICENSE_UPDATED_EXCEPT_TEXT { + } else if importStatus == utils.IMPORT_LICENSE_UPDATED_EXCEPT_TEXT { if err := addChangelogsForLicenseUpdate(tx, username, newLicense, oldLicense); err != nil { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusInternalServerError, Message: "Failed to update license", - Error: newLicense.Shortname, + Error: *newLicense.Shortname, Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), }) @@ -1068,7 +954,7 @@ func ImportLicenses(c *gin.Context) { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusConflict, Message: errMessage, - Error: newLicense.Shortname, + Error: *newLicense.Shortname, Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), }) @@ -1082,77 +968,6 @@ func ImportLicenses(c *gin.Context) { c.JSON(http.StatusOK, res) } -// addStructFieldToMap checks whether a field is defined and then adds it to the license map for update -func addStructFieldToMap[T any](c *gin.Context, structField *models.NullableAndOptionalData[T], structFieldName string, - licenseMap map[string]interface{}, shortName string, res *models.ImportLicensesResponse) error { - if structField.IsDefinedAndNotNull { - licenseMap[structFieldName] = structField.Value - } else { - res.Data = append(res.Data, models.LicenseError{ - Status: http.StatusInternalServerError, - Message: fmt.Sprintf("field %s cannot be null", structFieldName), - Error: shortName, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - }) - return fmt.Errorf("field %s cannot be null", structFieldName) - } - return nil -} - -// Updates/Creates a license from a map containing license values -func InsertOrUpdateLicenseOnImport(tx *gorm.DB, newLicenseMap map[string]interface{}) (string, models.LicenseImportStatusCode, *models.LicenseDB, *models.LicenseDB) { - var message string - var importStatus models.LicenseImportStatusCode - var newLicense, oldLicense models.LicenseDB - - result := tx. - Where(&models.LicenseDB{Shortname: newLicenseMap["rf_shortname"].(string)}). - Attrs(newLicenseMap). - FirstOrCreate(&oldLicense) - if result.Error != nil { - message = fmt.Sprintf("failed to create license: %s", result.Error.Error()) - importStatus = models.IMPORT_FAILED - return message, importStatus, &newLicense, &oldLicense - } else if result.RowsAffected == 0 { - // case when license exists in database and is updated - - // Overwrite values of existing keys, add new key value pairs and remove keys with null values. - if err := tx.Model(&models.LicenseDB{}).Where(&models.LicenseDB{Shortname: newLicenseMap["rf_shortname"].(string)}).UpdateColumn("external_ref", gorm.Expr("jsonb_strip_nulls(external_ref || ?)", newLicenseMap["external_ref"])).Error; err != nil { - message = fmt.Sprintf("failed to update license: %s", err.Error()) - importStatus = models.IMPORT_FAILED - return message, importStatus, &newLicense, &oldLicense - } - - // Update all other fields except external_ref - query := tx.Model(&newLicense).Where(&models.LicenseDB{Shortname: newLicenseMap["rf_shortname"].(string)}).Omit("external_ref") - - // Do not update text in import if it was modified manually - if oldLicense.Flag == 2 { - query = query.Omit("rf_text") - } - - if err := query.Clauses(clause.Returning{}).Updates(newLicenseMap).Error; err != nil { - message = fmt.Sprintf("failed to update license: %s", err.Error()) - importStatus = models.IMPORT_FAILED - return message, importStatus, &newLicense, &oldLicense - } - - if oldLicense.Flag == 2 { - message = "all fields except rf_text were updated. rf_text was updated manually and cannot be overwritten in an import." - importStatus = models.IMPORT_LICENSE_UPDATED_EXCEPT_TEXT - // error is not returned here as it will rollback the transaction - } else { - importStatus = models.IMPORT_LICENSE_UPDATED - } - } else { - // case when license doesn't exist in database and is inserted - importStatus = models.IMPORT_LICENSE_CREATED - } - - return message, importStatus, &newLicense, &oldLicense -} - // ExportLicenses gives users all licenses as a json file. // // @Summary Export all licenses as a json file @@ -1160,13 +975,12 @@ func InsertOrUpdateLicenseOnImport(tx *gorm.DB, newLicenseMap map[string]interfa // @Id ExportLicenses // @Tags Licenses // @Produce json -// @Success 200 {array} models.ExportLicenseDB +// @Success 200 {array} models.LicenseDB // @Failure 500 {object} models.LicenseError "Failed to fetch Licenses" // @Security ApiKeyAuth || {} // @Router /licenses/export [get] func ExportLicenses(c *gin.Context) { - - var licenses []models.ExportLicenseDB + var licenses []models.LicenseDB query := db.DB.Model(&models.LicenseDB{}) err := query.Find(&licenses).Error if err != nil { @@ -1241,7 +1055,7 @@ func GetAllLicensePreviews(c *gin.Context) { var res models.LicensePreviewResponse for _, lic := range licenses { - res.Shortnames = append(res.Shortnames, lic.Shortname) + res.Shortnames = append(res.Shortnames, *lic.Shortname) } res.Status = http.StatusOK diff --git a/pkg/api/obligationmap.go b/pkg/api/obligationmap.go index ced78fb..6074ec1 100644 --- a/pkg/api/obligationmap.go +++ b/pkg/api/obligationmap.go @@ -80,7 +80,7 @@ func GetObligationMapByTopic(c *gin.Context) { c.JSON(http.StatusNotFound, er) return } - shortnameList = append(shortnameList, license.Shortname) + shortnameList = append(shortnameList, *license.Shortname) } resObMap = models.ObligationMapUser{ @@ -120,7 +120,7 @@ func GetObligationMapByLicense(c *gin.Context) { licenseShortName := c.Param("license") - if err := db.DB.Where(models.LicenseDB{Shortname: licenseShortName}).First(&license).Error; err != nil { + if err := db.DB.Where(models.LicenseDB{Shortname: &licenseShortName}).First(&license).Error; err != nil { er := models.LicenseError{ Status: http.StatusNotFound, Message: fmt.Sprintf("license with shortname '%s' not found", licenseShortName), @@ -225,7 +225,7 @@ func PatchObligationMap(c *gin.Context) { for i := 0; i < len(obMapInput.MapInput); i++ { var license models.LicenseDB var obligationMap models.ObligationMap - if err := db.DB.Where(&models.LicenseDB{Shortname: obMapInput.MapInput[i].Shortname}).First(&license).Error; err != nil { + if err := db.DB.Where(&models.LicenseDB{Shortname: &obMapInput.MapInput[i].Shortname}).First(&license).Error; err != nil { er := models.LicenseError{ Status: http.StatusNotFound, Message: fmt.Sprintf("license with shortname '%s' not found", obMapInput.MapInput[i].Shortname), @@ -376,7 +376,7 @@ func GenerateDiffOfLicenses(c *gin.Context, obligation *models.Obligation, input for i := 0; i < len(inputShortnames); i++ { var license models.LicenseDB var obligationMap models.ObligationMap - if err := db.DB.Where(&models.LicenseDB{Shortname: inputShortnames[i]}).First(&license).Error; err != nil { + if err := db.DB.Where(&models.LicenseDB{Shortname: &inputShortnames[i]}).First(&license).Error; err != nil { er := models.LicenseError{ Status: http.StatusNotFound, Message: fmt.Sprintf("license with shortname '%s' not found", inputShortnames[i]), @@ -538,7 +538,7 @@ func createObligationMapUser(obligation models.Obligation, obMaps []models.Oblig if err := db.DB.Where(models.LicenseDB{Id: obMaps[i].RfPk}).First(&license).Error; err != nil { return nil, err } - shortnameList = append(shortnameList, license.Shortname) + shortnameList = append(shortnameList, *license.Shortname) } return &models.ObligationMapUser{ Topic: obligation.Topic, diff --git a/pkg/api/obligations.go b/pkg/api/obligations.go index 7c37986..267b095 100644 --- a/pkg/api/obligations.go +++ b/pkg/api/obligations.go @@ -210,7 +210,7 @@ func CreateObligation(c *gin.Context) { } for _, i := range input.Shortnames { var license models.LicenseDB - db.DB.Where(models.LicenseDB{Shortname: i}).Find(&license) + db.DB.Where(models.LicenseDB{Shortname: &i}).Find(&license) obmap := models.ObligationMap{ ObligationPk: obligation.Id, RfPk: license.Id, @@ -680,7 +680,7 @@ func ExportObligations(c *gin.Context) { var shortnames []string for _, obMap := range obligationMaps { - shortnames = append(shortnames, obMap.LicenseDB.Shortname) + shortnames = append(shortnames, *obMap.LicenseDB.Shortname) } obJSONFileFormat := models.ObligationJSONFileFormat{ diff --git a/pkg/db/db.go b/pkg/db/db.go index c77a0c8..4f199a6 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -5,6 +5,7 @@ package db import ( "encoding/json" + "errors" "fmt" "log" "os" @@ -45,20 +46,18 @@ func Populatedb(datafile string) { log.Fatalf("error reading from json file: %v", err) } for _, license := range licenses { - var existingLicense models.LicenseDB - // Create the license if it does not already exist in the database. - // Otherwise, update the license if flag is 1. result := utils.Converter(license) - DB.Where(models.LicenseDB{Shortname: result.Shortname}).First(&existingLicense) - // check if existingLicense found - if existingLicense.Id != 0 && existingLicense.Flag == 2 { - // Do not update the license where text was updated manually. - continue - } - err := DB.Where(models.LicenseDB{Shortname: result.Shortname}).Assign(result). - FirstOrCreate(&result).Error - if err != nil { - log.Fatalf("error creating license: %v", err) - } + _ = DB.Transaction(func(tx *gorm.DB) error { + errMessage, importStatus, _, _ := utils.InsertOrUpdateLicenseOnImport(tx, &result, &models.UpdateExternalRefsJSONPayload{ExternalRef: make(map[string]interface{})}) + if importStatus == utils.IMPORT_FAILED { + // ANSI escape code for red text + red := "\033[31m" + reset := "\033[0m" + log.Printf("%s%s: %s%s", red, *result.Shortname, errMessage, reset) + return errors.New(errMessage) + } + return nil + }) + } } diff --git a/pkg/models/types.go b/pkg/models/types.go index d13f473..8312a64 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -7,88 +7,97 @@ package models import ( + "errors" "time" "gorm.io/datatypes" + "gorm.io/gorm" ) // The LicenseDB struct represents a license entity with various attributes and // properties associated with it. // It provides structured storage for license-related information. type LicenseDB struct { - Id int64 `json:"rf_id" gorm:"primary_key;column:rf_id" example:"123"` - Shortname string `json:"rf_shortname" gorm:"unique;not null;column:rf_shortname" example:"MIT"` - Fullname string `json:"rf_fullname" gorm:"column:rf_fullname" example:"MIT License"` - Text string `json:"rf_text" gorm:"column:rf_text" example:"MIT License Text here"` - Url string `json:"rf_url" gorm:"column:rf_url" example:"https://opensource.org/licenses/MIT"` + Id int64 `json:"-" gorm:"primary_key;column:rf_id" example:"123"` + Shortname *string `json:"rf_shortname" gorm:"unique;not null;column:rf_shortname" validate:"required" example:"MIT"` + Fullname *string `json:"rf_fullname" gorm:"column:rf_fullname;not null" validate:"required" example:"MIT License"` + Text *string `json:"rf_text" gorm:"column:rf_text;not null" validate:"required" example:"MIT License Text here"` + Url *string `json:"rf_url" gorm:"column:rf_url;default:'';not null" example:"https://opensource.org/licenses/MIT"` AddDate time.Time `json:"rf_add_date" gorm:"default:CURRENT_TIMESTAMP;column:rf_add_date" example:"2023-12-01T18:10:25.00+05:30"` - Copyleft bool `json:"rf_copyleft" gorm:"column:rf_copyleft"` - FSFfree bool `json:"rf_FSFfree" gorm:"column:rf_FSFfree"` - OSIapproved bool `json:"rf_OSIapproved" gorm:"column:rf_OSIapproved"` - GPLv2compatible bool `json:"rf_GPLv2compatible" gorm:"column:rf_GPLv2compatible"` - GPLv3compatible bool `json:"rf_GPLv3compatible" gorm:"column:rf_GPLv3compatible"` - Notes string `json:"rf_notes" gorm:"column:rf_notes" example:"This license has been superseded."` - Fedora string `json:"rf_Fedora" gorm:"column:rf_Fedora"` - TextUpdatable bool `json:"rf_text_updatable" gorm:"column:rf_text_updatable"` - DetectorType int64 `json:"rf_detector_type" gorm:"column:rf_detector_type" example:"1"` - Active bool `json:"rf_active" gorm:"column:rf_active"` - Source string `json:"rf_source" gorm:"column:rf_source"` - SpdxId string `json:"rf_spdx_id" gorm:"column:rf_spdx_id" example:"MIT"` - Risk int64 `json:"rf_risk" gorm:"column:rf_risk"` - Flag int64 `json:"rf_flag" gorm:"default:1;column:rf_flag" example:"1"` - Marydone bool `json:"marydone" gorm:"column:marydone"` + Copyleft *bool `json:"rf_copyleft" gorm:"column:rf_copyleft;not null;default:false"` + FSFfree *bool `json:"rf_FSFfree" gorm:"column:rf_FSFfree;not null;default:false"` + OSIapproved *bool `json:"rf_OSIapproved" gorm:"column:rf_OSIapproved;not null;default:false"` + GPLv2compatible *bool `json:"rf_GPLv2compatible" gorm:"column:rf_GPLv2compatible;not null;default:false"` + GPLv3compatible *bool `json:"rf_GPLv3compatible" gorm:"column:rf_GPLv3compatible;not null;default:false"` + Notes *string `json:"rf_notes" gorm:"column:rf_notes;not null;default:''" example:"This license has been superseded."` + Fedora *string `json:"rf_Fedora" gorm:"column:rf_Fedora;not null;default:''"` + TextUpdatable *bool `json:"rf_text_updatable" gorm:"column:rf_text_updatable;not null;default:false"` + DetectorType *int64 `json:"rf_detector_type" gorm:"column:rf_detector_type;not null;default:1" validate:"omitempty,min=0,max=2" example:"1"` + Active *bool `json:"rf_active" gorm:"column:rf_active;not null;default:true"` + Source *string `json:"rf_source" gorm:"column:rf_source;not null;default:''"` + SpdxId *string `json:"rf_spdx_id" gorm:"column:rf_spdx_id;not null" validate:"required" example:"MIT"` + Risk *int64 `json:"rf_risk" gorm:"column:rf_risk;not null;default:0" validate:"omitempty,min=0,max=5"` + Flag *int64 `json:"rf_flag" gorm:"default:1;column:rf_flag;not null;default:0" validate:"omitempty,min=0,max=2" example:"1"` + Marydone *bool `json:"marydone" gorm:"column:marydone;not null;default:false"` ExternalRef datatypes.JSONType[LicenseDBSchemaExtension] `json:"external_ref"` } -// LicensePOSTRequestJSONSchema struct represents the input or payload required for creating a license. -// It contains various fields that capture the necessary information for defining a license entity. -type LicensePOSTRequestJSONSchema struct { - Shortname string `json:"rf_shortname" binding:"required" example:"MIT"` - Fullname string `json:"rf_fullname" binding:"required" example:"MIT License"` - Text string `json:"rf_text" binding:"required" example:"MIT License Text here"` - Url string `json:"rf_url" binding:"required" example:"https://opensource.org/licenses/MIT"` - Copyleft bool `json:"rf_copyleft" binding:"required"` - FSFfree bool `json:"rf_FSFfree" binding:"required"` - OSIapproved bool `json:"rf_OSIapproved" binding:"required"` - GPLv2compatible bool `json:"rf_GPLv2compatible" binding:"required"` - GPLv3compatible bool `json:"rf_GPLv3compatible" binding:"required"` - Notes string `json:"rf_notes" example:"This license has been superseded." binding:"required"` - Fedora string `json:"rf_Fedora" binding:"required"` - TextUpdatable bool `json:"rf_text_updatable" binding:"required"` - DetectorType int64 `json:"rf_detector_type" example:"1" binding:"required"` - Active bool `json:"rf_active" binding:"required"` - Source string `json:"rf_source" binding:"required"` - SpdxId string `json:"rf_spdx_id" binding:"required" example:"MIT"` - Risk int64 `json:"rf_risk" binding:"required"` - Flag int64 `json:"rf_flag" binding:"required" example:"1"` - Marydone bool `json:"marydone" binding:"required"` - ExternalRef datatypes.JSONType[LicenseDBSchemaExtension] `json:"external_ref" binding:"required"` -} - -type ExportLicenseDB struct { - Shortname string `json:"rf_shortname" gorm:"unique;not null;column:rf_shortname" example:"MIT"` - Fullname string `json:"rf_fullname" gorm:"column:rf_fullname" example:"MIT License"` - Text string `json:"rf_text" gorm:"column:rf_text" example:"MIT License Text here"` - Url string `json:"rf_url" gorm:"column:rf_url" example:"https://opensource.org/licenses/MIT"` - AddDate time.Time `json:"rf_add_date" gorm:"default:CURRENT_TIMESTAMP;column:rf_add_date" example:"2023-12-01T18:10:25.00+05:30"` - Copyleft bool `json:"rf_copyleft" gorm:"column:rf_copyleft"` - FSFfree bool `json:"rf_FSFfree" gorm:"column:rf_FSFfree"` - OSIapproved bool `json:"rf_OSIapproved" gorm:"column:rf_OSIapproved"` - GPLv2compatible bool `json:"rf_GPLv2compatible" gorm:"column:rf_GPLv2compatible"` - GPLv3compatible bool `json:"rf_GPLv3compatible" gorm:"column:rf_GPLv3compatible"` - Notes string `json:"rf_notes" gorm:"column:rf_notes" example:"This license has been superseded."` - Fedora string `json:"rf_Fedora" gorm:"column:rf_Fedora"` - TextUpdatable bool `json:"rf_text_updatable" gorm:"column:rf_text_updatable"` - DetectorType int64 `json:"rf_detector_type" gorm:"column:rf_detector_type" example:"1"` - Active bool `json:"rf_active" gorm:"column:rf_active"` - Source string `json:"rf_source" gorm:"column:rf_source"` - SpdxId string `json:"rf_spdx_id" gorm:"column:rf_spdx_id" example:"MIT"` - Risk int64 `json:"rf_risk" gorm:"column:rf_risk"` - Flag int64 `json:"rf_flag" gorm:"default:1;column:rf_flag" example:"1"` - Marydone bool `json:"marydone" gorm:"column:marydone"` +func (l *LicenseDB) BeforeSave(tx *gorm.DB) (err error) { + if l.Shortname != nil && *l.Shortname == "" { + return errors.New("rf_shortname cannot be an empty string") + } + if l.Fullname != nil && *l.Fullname == "" { + return errors.New("rf_fullname cannot be an empty string") + } + if l.Text != nil && *l.Text == "" { + return errors.New("rf_text cannot be an empty string") + } + if l.SpdxId != nil && *l.SpdxId == "" { + return errors.New("rf_spdx_id cannot be an empty string") + } + if l.Risk != nil && (*l.Risk < 0 && *l.Risk > 5) { + return errors.New("rf_risk can have values from 0 to 5 only") + } + if l.Flag != nil && (*l.Flag < 0 || *l.Flag > 2) { + return errors.New("rf_flag can have values from 0 to 2 only") + } + if l.DetectorType != nil && (*l.DetectorType < 0 || *l.DetectorType > 2) { + return errors.New("rf_detector_type can have values from 0 to 2 only") + } + return +} + +// LicenseUpdateJSONSchema struct represents the input format for updating an existing license. +type LicenseUpdateJSONSchema struct { + Id int64 `json:"-" example:"123"` + Shortname *string `json:"-" example:"MIT"` + Fullname *string `json:"rf_fullname" example:"MIT License"` + Text *string `json:"rf_text" example:"MIT License Text here"` + Url *string `json:"rf_url" example:"https://opensource.org/licenses/MIT"` + AddDate time.Time `json:"-" example:"2023-12-01T18:10:25.00+05:30"` + Copyleft *bool `json:"rf_copyleft" example:"false"` + FSFfree *bool `json:"rf_FSFfree" example:"false"` + OSIapproved *bool `json:"rf_OSIapproved" example:"false"` + GPLv2compatible *bool `json:"rf_GPLv2compatible" example:"false"` + GPLv3compatible *bool `json:"rf_GPLv3compatible" example:"false"` + Notes *string `json:"rf_notes" example:"This license has been superseded."` + Fedora *string `json:"rf_Fedora" example:"Fedora"` + TextUpdatable *bool `json:"rf_text_updatable" example:"false"` + DetectorType *int64 `json:"rf_detector_type" validate:"omitempty,min=0,max=2" example:"1"` + Active *bool `json:"rf_active" example:"true"` + Source *string `json:"rf_source" example:"Source"` + SpdxId *string `json:"rf_spdx_id" example:"MIT"` + Risk *int64 `json:"rf_risk" validate:"omitempty,min=0,max=5" example:"1"` + Flag *int64 `json:"rf_flag" validate:"omitempty,min=0,max=2" example:"1"` + Marydone *bool `json:"marydone" example:"false"` ExternalRef datatypes.JSONType[LicenseDBSchemaExtension] `json:"external_ref"` } +// UpdateExternalRefsJSONPayload struct represents the external ref key value pairs for update +type UpdateExternalRefsJSONPayload struct { + ExternalRef map[string]interface{} `json:"external_ref"` +} + type LicenseJson struct { Shortname string `json:"rf_shortname"` Fullname string `json:"rf_fullname"` @@ -112,71 +121,12 @@ type LicenseJson struct { Marydone string `json:"marydone"` } -// LicensePATCHRequestJSONSchema struct represents the input format for updating an existing license. -// Note that the license ID and shortname cannot be updated. -type LicensePATCHRequestJSONSchema struct { - Fullname OptionalData[string] `json:"rf_fullname" swaggertype:"string" example:"MIT License"` - Text OptionalData[string] `json:"rf_text" swaggertype:"string" example:"MIT License Text here"` - Url OptionalData[string] `json:"rf_url" swaggertype:"string" example:"https://opensource.org/licenses/MIT"` - Copyleft OptionalData[bool] `json:"rf_copyleft" swaggertype:"boolean"` - FSFfree OptionalData[bool] `json:"rf_FSFfree" swaggertype:"boolean"` - OSIapproved OptionalData[bool] `json:"rf_OSIapproved" swaggertype:"boolean"` - GPLv2compatible OptionalData[bool] `json:"rf_GPLv2compatible" swaggertype:"boolean"` - GPLv3compatible OptionalData[bool] `json:"rf_GPLv3compatible" swaggertype:"boolean"` - Notes OptionalData[string] `json:"rf_notes" example:"This license has been superseded." swaggertype:"string"` - Fedora OptionalData[string] `json:"rf_Fedora" swaggertype:"string"` - TextUpdatable OptionalData[bool] `json:"rf_text_updatable" swaggertype:"boolean"` - DetectorType OptionalData[int64] `json:"rf_detector_type" example:"1" swaggertype:"integer"` - Active OptionalData[bool] `json:"rf_active" swaggertype:"boolean"` - Source OptionalData[string] `json:"rf_source" swaggertype:"string"` - SpdxId OptionalData[string] `json:"rf_spdx_id" example:"MIT" swaggertype:"string"` - Risk OptionalData[int64] `json:"rf_risk" swaggertype:"integer" example:"3"` - Flag OptionalData[int64] `json:"rf_flag" example:"1" swaggertype:"integer"` - Marydone OptionalData[bool] `json:"marydone" swaggertype:"boolean"` - ExternalRef map[string]interface{} `json:"external_ref"` -} - // LicensePreviewResponse gets us the list of all license shortnames type LicensePreviewResponse struct { Status int `json:"status" example:"200"` Shortnames []string `json:"shortnames" example:"GPL-2.0-only,GPL-2.0-or-later"` } -// LicenseImport represents an license record in the import json file. -type LicenseImport struct { - Shortname NullableAndOptionalData[string] `json:"rf_shortname" validate:"required" example:"MIT"` - Fullname NullableAndOptionalData[string] `json:"rf_fullname" validate:"required" example:"MIT License"` - Text NullableAndOptionalData[string] `json:"rf_text" validate:"required" example:"MIT License Text here"` - Url NullableAndOptionalData[string] `json:"rf_url" validate:"required" example:"https://opensource.org/licenses/MIT"` - Copyleft NullableAndOptionalData[bool] `json:"rf_copyleft"` - FSFfree NullableAndOptionalData[bool] `json:"rf_FSFfree"` - OSIapproved NullableAndOptionalData[bool] `json:"rf_OSIapproved"` - GPLv2compatible NullableAndOptionalData[bool] `json:"rf_GPLv2compatible"` - GPLv3compatible NullableAndOptionalData[bool] `json:"rf_GPLv3compatible"` - Notes NullableAndOptionalData[string] `json:"rf_notes" example:"This license has been superseded."` - Fedora NullableAndOptionalData[string] `json:"rf_Fedora"` - TextUpdatable NullableAndOptionalData[bool] `json:"rf_text_updatable" validate:"required"` - DetectorType NullableAndOptionalData[int64] `json:"rf_detector_type" example:"1"` - Active NullableAndOptionalData[bool] `json:"rf_active" validate:"required"` - Source NullableAndOptionalData[string] `json:"rf_source" validate:"required"` - SpdxId NullableAndOptionalData[string] `json:"rf_spdx_id" validate:"required" example:"MIT"` - Risk NullableAndOptionalData[int64] `json:"rf_risk" validate:"required"` - Flag NullableAndOptionalData[int64] `json:"rf_flag"` - Marydone NullableAndOptionalData[bool] `json:"marydone"` - ExternalRef map[string]interface{} `json:"external_ref"` -} - -// LicenseImportStatusCode is internally used for checking status of a license import -type LicenseImportStatusCode int - -// Status codes covering various scenarios that can occur on a license import -const ( - IMPORT_FAILED LicenseImportStatusCode = iota + 1 - IMPORT_LICENSE_CREATED - IMPORT_LICENSE_UPDATED - IMPORT_LICENSE_UPDATED_EXCEPT_TEXT -) - // LicenseId is the id of successfully imported license type LicenseId struct { Id int64 `json:"id" example:"31"` diff --git a/pkg/utils/util.go b/pkg/utils/util.go index 1ee2e8a..4afb3b2 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -15,10 +15,12 @@ import ( "time" "gorm.io/gorm" + "gorm.io/gorm/clause" "golang.org/x/crypto/bcrypt" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" "github.com/fossology/LicenseDb/pkg/models" ) @@ -93,26 +95,26 @@ func Converter(input models.LicenseJson) models.LicenseDB { } result := models.LicenseDB{ - Shortname: input.Shortname, - Fullname: input.Fullname, - Text: input.Text, - Url: input.Url, - Copyleft: copyleft, + Shortname: &input.Shortname, + Fullname: &input.Fullname, + Text: &input.Text, + Url: &input.Url, + Copyleft: ©left, AddDate: addDate, - FSFfree: fsfFree, - OSIapproved: osiApproved, - GPLv2compatible: gplv2Compatible, - GPLv3compatible: gplv3Compatible, - Notes: input.Notes, - Fedora: input.Fedora, - TextUpdatable: textUpdatable, - DetectorType: detectorType, - Active: active, - Source: input.Source, - SpdxId: input.SpdxCompatible, - Risk: risk, - Flag: flag, - Marydone: marydone, + FSFfree: &fsfFree, + OSIapproved: &osiApproved, + GPLv2compatible: &gplv2Compatible, + GPLv3compatible: &gplv3Compatible, + Notes: &input.Notes, + Fedora: &input.Fedora, + TextUpdatable: &textUpdatable, + DetectorType: &detectorType, + Active: &active, + Source: &input.Source, + SpdxId: &input.SpdxCompatible, + Risk: &risk, + Flag: &flag, + Marydone: &marydone, } return result } @@ -183,3 +185,78 @@ func PreparePaginateResponse(c *gin.Context, query *gorm.DB, c.Set("responseModel", responseModel) return pagination } + +// LicenseImportStatusCode is internally used for checking status of a license import +type LicenseImportStatusCode int + +// Status codes covering various scenarios that can occur on a license import +const ( + IMPORT_FAILED LicenseImportStatusCode = iota + 1 + IMPORT_LICENSE_CREATED + IMPORT_LICENSE_UPDATED + IMPORT_LICENSE_UPDATED_EXCEPT_TEXT +) + +func InsertOrUpdateLicenseOnImport(tx *gorm.DB, license *models.LicenseDB, externalRefs *models.UpdateExternalRefsJSONPayload) (string, LicenseImportStatusCode, *models.LicenseDB, *models.LicenseDB) { + var message string + var importStatus LicenseImportStatusCode + var newLicense, oldLicense models.LicenseDB + + validate := validator.New(validator.WithRequiredStructEnabled()) + + if err := validate.Struct(license); err != nil { + message = fmt.Sprintf("field '%s' failed validation: %s\n", err.(validator.ValidationErrors)[0].Field(), err.(validator.ValidationErrors)[0].Tag()) + importStatus = IMPORT_FAILED + return message, importStatus, &oldLicense, &newLicense + } + + result := tx. + Where(&models.LicenseDB{Shortname: license.Shortname}). + Attrs(license). + FirstOrCreate(&oldLicense) + if result.Error != nil { + message = fmt.Sprintf("failed to create license: %s", result.Error.Error()) + importStatus = IMPORT_FAILED + return message, importStatus, &oldLicense, &newLicense + } else if result.RowsAffected == 0 { + // case when license exists in database and is updated + + // Overwrite values of existing keys, add new key value pairs and remove keys with null values. + if err := tx.Model(&models.LicenseDB{}).Where(models.LicenseDB{Id: oldLicense.Id}).UpdateColumn("external_ref", gorm.Expr("jsonb_strip_nulls(COALESCE(external_ref, '{}'::jsonb) || ?)", &externalRefs.ExternalRef)).Error; err != nil { + message = fmt.Sprintf("failed to update license: %s", err.Error()) + importStatus = IMPORT_FAILED + return message, importStatus, &oldLicense, &newLicense + } + + // https://github.com/go-gorm/gorm/issues/3938: BeforeSave hook is called on the struct passed in .Model() + // Cannot pass empty newLicense struct in .Model() as all fields will be empty and no validation will happen + newLicense = *license + + // Update all other fields except external_ref and rf_shortname + query := tx.Model(&newLicense).Where(&models.LicenseDB{Id: oldLicense.Id}).Omit("external_ref", "rf_shortname") + + // Do not update text in import if it was modified manually + if *oldLicense.Flag == 2 { + query = query.Omit("rf_text") + } + + if err := query.Clauses(clause.Returning{}).Updates(&newLicense).Error; err != nil { + message = fmt.Sprintf("failed to update license: %s", err.Error()) + importStatus = IMPORT_FAILED + return message, importStatus, &oldLicense, &newLicense + } + + if *newLicense.Flag == 2 { + message = "all fields except rf_text were updated. rf_text was updated manually and cannot be overwritten in an import." + importStatus = IMPORT_LICENSE_UPDATED_EXCEPT_TEXT + // error is not returned here as it will rollback the transaction + } else { + importStatus = IMPORT_LICENSE_UPDATED + } + } else { + // case when license doesn't exist in database and is inserted + importStatus = IMPORT_LICENSE_CREATED + } + + return message, importStatus, &oldLicense, &newLicense +}