feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation

Complete implementation of the Goa Government e-Licensing platform with:

Backend:
- NestJS API with JWT authentication
- PostgreSQL database with Knex ORM
- Redis caching and session management
- MinIO document storage
- Hyperledger Besu blockchain integration
- Multi-department workflow system
- Comprehensive API tests (266/282 passing)

Frontend:
- Angular 21 with standalone components
- Angular Material + TailwindCSS UI
- Visual workflow builder
- Document upload with progress tracking
- Blockchain explorer integration
- Role-based dashboards (Admin, Department, Citizen)
- E2E tests with Playwright (37 tests)

Infrastructure:
- Docker Compose orchestration
- Blockscout blockchain explorer
- Development and production configurations
This commit is contained in:
Mahi
2026-02-07 10:23:29 -04:00
commit 80566bf0a2
441 changed files with 102418 additions and 0 deletions

3448
api/openapi.json Normal file

File diff suppressed because it is too large Load Diff

2283
api/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

543
api/swagger-ui.html Normal file
View File

@@ -0,0 +1,543 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Goa GEL API Documentation</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css">
<style>
body {
margin: 0;
padding: 0;
}
.swagger-ui .topbar {
background-color: #1a365d;
}
.swagger-ui .info .title {
color: #1a365d;
}
.swagger-ui .scheme-container {
background: #f8f9fa;
padding: 20px;
}
/* Custom header */
.custom-header {
background: linear-gradient(135deg, #1a365d 0%, #2c5282 100%);
color: white;
padding: 30px 50px;
text-align: center;
}
.custom-header h1 {
margin: 0 0 10px 0;
font-size: 28px;
font-weight: 600;
}
.custom-header p {
margin: 0;
opacity: 0.9;
font-size: 16px;
}
.custom-header .badges {
margin-top: 15px;
}
.custom-header .badge {
display: inline-block;
background: rgba(255,255,255,0.2);
padding: 5px 12px;
border-radius: 20px;
margin: 0 5px;
font-size: 13px;
}
</style>
</head>
<body>
<div class="custom-header">
<h1>🏛️ Goa GEL - Blockchain Document Verification Platform</h1>
<p>Government of Goa | Hyperledger Besu | ERC-721 Soulbound NFTs</p>
<div class="badges">
<span class="badge">OpenAPI 3.0</span>
<span class="badge">REST API</span>
<span class="badge">Blockchain-backed</span>
</div>
</div>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
// OpenAPI spec embedded inline
const spec = {
"openapi": "3.0.3",
"info": {
"title": "Goa GEL - Blockchain Document Verification Platform API",
"description": "## Overview\nREST API for the Government of Goa's Blockchain-based Document Verification Platform (GEL).\nThis platform enables multi-department approval workflows for various licenses and permits,\nwith blockchain-backed verification using Hyperledger Besu and ERC-721 Soulbound NFTs.\n\n## Authentication\n- **Department APIs**: Use `X-API-Key` and `X-Department-Code` headers\n- **Applicant APIs**: Use Bearer token from DigiLocker authentication (mock for POC)\n- **Admin APIs**: Use Bearer token with admin role\n\n## Blockchain Integration\nAll critical operations (request creation, approvals, document updates) are recorded on-chain.\nResponse includes `transactionHash` for blockchain verification.\n\n## Webhooks\nDepartments can register webhooks to receive real-time notifications for:\n- `APPROVAL_REQUIRED` - New request pending approval\n- `DOCUMENT_UPDATED` - Applicant updated a document\n- `REQUEST_APPROVED` - Request fully approved\n- `REQUEST_REJECTED` - Request rejected",
"version": "1.0.0",
"contact": {
"name": "Goa GEL Platform Support",
"email": "support@goagel.gov.in"
}
},
"servers": [
{"url": "https://api.goagel.gov.in/api/v1", "description": "Production server"},
{"url": "https://staging-api.goagel.gov.in/api/v1", "description": "Staging server"},
{"url": "http://localhost:3001/api/v1", "description": "Local development"}
],
"tags": [
{"name": "Requests", "description": "License request operations"},
{"name": "Documents", "description": "Document upload and retrieval"},
{"name": "Approvals", "description": "Department approval actions"},
{"name": "Departments", "description": "Department management"},
{"name": "Workflows", "description": "Workflow configuration"},
{"name": "Webhooks", "description": "Webhook management"},
{"name": "Admin", "description": "Platform administration"},
{"name": "Verification", "description": "Public verification endpoints"}
],
"paths": {
"/requests": {
"post": {
"tags": ["Requests"],
"summary": "Create new license request",
"description": "Creates a new license request and mints a draft NFT on the blockchain.",
"operationId": "createRequest",
"security": [{"BearerAuth": []}],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/CreateRequestInput"},
"example": {
"applicantId": "DL-GOA-123456789",
"requestType": "RESORT_LICENSE",
"metadata": {
"resortName": "Paradise Beach Resort",
"location": "Calangute, North Goa",
"plotArea": 5000,
"builtUpArea": 3500,
"numberOfRooms": 50
}
}
}
}
},
"responses": {
"201": {"description": "Request created successfully", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RequestResponse"}}}},
"400": {"$ref": "#/components/responses/BadRequest"},
"401": {"$ref": "#/components/responses/Unauthorized"}
}
},
"get": {
"tags": ["Requests"],
"summary": "List requests",
"description": "Get list of requests with optional filters",
"operationId": "listRequests",
"security": [{"BearerAuth": []}, {"ApiKeyAuth": []}],
"parameters": [
{"name": "status", "in": "query", "schema": {"$ref": "#/components/schemas/RequestStatus"}},
{"name": "requestType", "in": "query", "schema": {"type": "string", "example": "RESORT_LICENSE"}},
{"name": "page", "in": "query", "schema": {"type": "integer", "default": 1}},
{"name": "limit", "in": "query", "schema": {"type": "integer", "default": 20}}
],
"responses": {"200": {"description": "List of requests", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RequestListResponse"}}}}}
}
},
"/requests/pending": {
"get": {
"tags": ["Requests"],
"summary": "Get requests pending for department",
"operationId": "getPendingRequests",
"security": [{"ApiKeyAuth": []}],
"parameters": [
{"name": "department", "in": "query", "required": true, "schema": {"type": "string"}, "example": "FIRE_DEPT"}
],
"responses": {"200": {"description": "List of pending requests"}}
}
},
"/requests/{requestId}": {
"get": {
"tags": ["Requests"],
"summary": "Get request details",
"operationId": "getRequest",
"security": [{"BearerAuth": []}, {"ApiKeyAuth": []}],
"parameters": [{"$ref": "#/components/parameters/RequestId"}],
"responses": {
"200": {"description": "Request details", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RequestDetailResponse"}}}},
"404": {"$ref": "#/components/responses/NotFound"}
}
}
},
"/requests/{requestId}/submit": {
"post": {
"tags": ["Requests"],
"summary": "Submit request for approval",
"description": "Submits the request for departmental approval workflow.",
"operationId": "submitRequest",
"security": [{"BearerAuth": []}],
"parameters": [{"$ref": "#/components/parameters/RequestId"}],
"responses": {"200": {"description": "Request submitted successfully"}}
}
},
"/requests/{requestId}/documents": {
"post": {
"tags": ["Documents"],
"summary": "Upload document",
"description": "Uploads a document. Hash is recorded on blockchain.",
"operationId": "uploadDocument",
"security": [{"BearerAuth": []}],
"parameters": [{"$ref": "#/components/parameters/RequestId"}],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": ["file", "docType"],
"properties": {
"file": {"type": "string", "format": "binary"},
"docType": {"type": "string", "example": "FIRE_SAFETY_CERTIFICATE"},
"description": {"type": "string"}
}
}
}
}
},
"responses": {"201": {"description": "Document uploaded"}}
}
},
"/requests/{requestId}/approve": {
"post": {
"tags": ["Approvals"],
"summary": "Approve request",
"description": "Records department approval on the blockchain.",
"operationId": "approveRequest",
"security": [{"ApiKeyAuth": []}],
"parameters": [{"$ref": "#/components/parameters/RequestId"}],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["remarks", "reviewedDocuments"],
"properties": {
"remarks": {"type": "string", "example": "All fire safety requirements met"},
"reviewedDocuments": {"type": "array", "items": {"type": "string", "format": "uuid"}}
}
}
}
}
},
"responses": {"200": {"description": "Approval recorded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApprovalResponse"}}}}}
}
},
"/requests/{requestId}/reject": {
"post": {
"tags": ["Approvals"],
"summary": "Reject request",
"operationId": "rejectRequest",
"security": [{"ApiKeyAuth": []}],
"parameters": [{"$ref": "#/components/parameters/RequestId"}],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["remarks", "reason"],
"properties": {
"remarks": {"type": "string"},
"reason": {"type": "string", "enum": ["SAFETY_VIOLATION", "INCOMPLETE_DOCUMENTS", "POLICY_VIOLATION", "FRAUDULENT_APPLICATION", "OTHER"]}
}
}
}
}
},
"responses": {"200": {"description": "Rejection recorded"}}
}
},
"/requests/{requestId}/request-changes": {
"post": {
"tags": ["Approvals"],
"summary": "Request changes",
"operationId": "requestChanges",
"security": [{"ApiKeyAuth": []}],
"parameters": [{"$ref": "#/components/parameters/RequestId"}],
"responses": {"200": {"description": "Changes requested"}}
}
},
"/departments": {
"get": {
"tags": ["Departments"],
"summary": "List all departments",
"operationId": "listDepartments",
"security": [{"BearerAuth": []}, {"ApiKeyAuth": []}],
"responses": {"200": {"description": "List of departments"}}
},
"post": {
"tags": ["Departments"],
"summary": "Register new department",
"operationId": "createDepartment",
"security": [{"AdminAuth": []}],
"requestBody": {
"required": true,
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateDepartmentInput"}}}
},
"responses": {"201": {"description": "Department created"}}
}
},
"/workflows": {
"get": {
"tags": ["Workflows"],
"summary": "List workflow definitions",
"operationId": "listWorkflows",
"security": [{"AdminAuth": []}],
"responses": {"200": {"description": "List of workflows"}}
},
"post": {
"tags": ["Workflows"],
"summary": "Create workflow definition",
"operationId": "createWorkflow",
"security": [{"AdminAuth": []}],
"responses": {"201": {"description": "Workflow created"}}
}
},
"/webhooks": {
"get": {
"tags": ["Webhooks"],
"summary": "List registered webhooks",
"operationId": "listWebhooks",
"security": [{"ApiKeyAuth": []}, {"AdminAuth": []}],
"responses": {"200": {"description": "List of webhooks"}}
},
"post": {
"tags": ["Webhooks"],
"summary": "Register webhook",
"operationId": "createWebhook",
"security": [{"ApiKeyAuth": []}],
"responses": {"201": {"description": "Webhook registered"}}
}
},
"/admin/stats": {
"get": {
"tags": ["Admin"],
"summary": "Get platform statistics",
"operationId": "getPlatformStats",
"security": [{"AdminAuth": []}],
"responses": {"200": {"description": "Platform statistics"}}
}
},
"/admin/blockchain/status": {
"get": {
"tags": ["Admin"],
"summary": "Get blockchain network status",
"operationId": "getBlockchainStatus",
"security": [{"AdminAuth": []}],
"responses": {"200": {"description": "Blockchain status"}}
}
},
"/verify/{tokenId}": {
"get": {
"tags": ["Verification"],
"summary": "Verify license by token ID",
"description": "Public endpoint for verifying a license using its NFT token ID. No authentication required.",
"operationId": "verifyByTokenId",
"parameters": [{"name": "tokenId", "in": "path", "required": true, "schema": {"type": "integer"}, "example": 12345}],
"responses": {"200": {"description": "Verification result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VerificationResponse"}}}}}
}
},
"/verify/document/{hash}": {
"get": {
"tags": ["Verification"],
"summary": "Verify document by hash",
"operationId": "verifyDocumentByHash",
"parameters": [{"name": "hash", "in": "path", "required": true, "schema": {"type": "string"}}],
"responses": {"200": {"description": "Document verification result"}}
}
}
},
"components": {
"securitySchemes": {
"BearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "JWT token from DigiLocker authentication"},
"ApiKeyAuth": {"type": "apiKey", "in": "header", "name": "X-API-Key", "description": "Department API key. Must be used with X-Department-Code header."},
"AdminAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "Admin JWT token"}
},
"parameters": {
"RequestId": {"name": "requestId", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "Unique request identifier"}
},
"responses": {
"BadRequest": {"description": "Bad request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"Unauthorized": {"description": "Unauthorized", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
"NotFound": {"description": "Resource not found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
},
"schemas": {
"CreateRequestInput": {
"type": "object",
"required": ["applicantId", "requestType"],
"properties": {
"applicantId": {"type": "string", "description": "DigiLocker ID", "example": "DL-GOA-123456789"},
"requestType": {"type": "string", "enum": ["RESORT_LICENSE", "TRADE_LICENSE", "BUILDING_PERMIT"], "example": "RESORT_LICENSE"},
"metadata": {"type": "object", "description": "Request-specific data"}
}
},
"CreateDepartmentInput": {
"type": "object",
"required": ["code", "name"],
"properties": {
"code": {"type": "string", "pattern": "^[A-Z_]+$", "example": "FIRE_DEPT"},
"name": {"type": "string", "example": "Fire & Emergency Services Department"},
"webhookUrl": {"type": "string", "format": "uri"}
}
},
"RequestResponse": {
"type": "object",
"properties": {
"requestId": {"type": "string", "format": "uuid"},
"requestNumber": {"type": "string", "example": "RL-2024-001234"},
"status": {"$ref": "#/components/schemas/RequestStatus"},
"transactionHash": {"type": "string"},
"createdAt": {"type": "string", "format": "date-time"}
}
},
"RequestListResponse": {
"type": "object",
"properties": {
"requests": {"type": "array", "items": {"$ref": "#/components/schemas/RequestSummary"}},
"pagination": {"$ref": "#/components/schemas/Pagination"}
}
},
"RequestSummary": {
"type": "object",
"properties": {
"requestId": {"type": "string", "format": "uuid"},
"requestNumber": {"type": "string"},
"requestType": {"type": "string"},
"status": {"$ref": "#/components/schemas/RequestStatus"},
"applicantName": {"type": "string"},
"currentStage": {"type": "string"},
"createdAt": {"type": "string", "format": "date-time"}
}
},
"RequestDetailResponse": {
"type": "object",
"properties": {
"requestId": {"type": "string", "format": "uuid"},
"requestNumber": {"type": "string"},
"tokenId": {"type": "integer"},
"requestType": {"type": "string"},
"status": {"$ref": "#/components/schemas/RequestStatus"},
"applicant": {"type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "email": {"type": "string"}}},
"documents": {"type": "array", "items": {"$ref": "#/components/schemas/Document"}},
"approvals": {"type": "array", "items": {"$ref": "#/components/schemas/Approval"}},
"blockchainData": {"type": "object", "properties": {"tokenId": {"type": "integer"}, "contractAddress": {"type": "string"}, "creationTxHash": {"type": "string"}}}
}
},
"ApprovalResponse": {
"type": "object",
"properties": {
"approvalId": {"type": "string", "format": "uuid"},
"status": {"$ref": "#/components/schemas/ApprovalStatus"},
"transactionHash": {"type": "string"},
"workflowStatus": {"type": "object", "properties": {"currentStage": {"type": "string"}, "isComplete": {"type": "boolean"}, "nextPendingDepartments": {"type": "array", "items": {"type": "string"}}}}
}
},
"VerificationResponse": {
"type": "object",
"properties": {
"isValid": {"type": "boolean"},
"tokenId": {"type": "integer"},
"requestNumber": {"type": "string"},
"requestType": {"type": "string"},
"status": {"$ref": "#/components/schemas/RequestStatus"},
"applicantName": {"type": "string"},
"issuedAt": {"type": "string", "format": "date-time"},
"approvals": {"type": "array", "items": {"type": "object", "properties": {"departmentName": {"type": "string"}, "approvedAt": {"type": "string", "format": "date-time"}}}},
"blockchainProof": {"type": "object", "properties": {"contractAddress": {"type": "string"}, "tokenId": {"type": "integer"}, "transactionHash": {"type": "string"}}}
}
},
"Document": {
"type": "object",
"properties": {
"documentId": {"type": "string", "format": "uuid"},
"docType": {"type": "string"},
"originalFilename": {"type": "string"},
"currentVersion": {"type": "integer"},
"currentHash": {"type": "string"},
"uploadedAt": {"type": "string", "format": "date-time"}
}
},
"Approval": {
"type": "object",
"properties": {
"approvalId": {"type": "string", "format": "uuid"},
"departmentCode": {"type": "string"},
"departmentName": {"type": "string"},
"status": {"$ref": "#/components/schemas/ApprovalStatus"},
"remarks": {"type": "string"},
"isActive": {"type": "boolean"},
"transactionHash": {"type": "string"},
"createdAt": {"type": "string", "format": "date-time"}
}
},
"RequestStatus": {
"type": "string",
"enum": ["DRAFT", "SUBMITTED", "IN_REVIEW", "PENDING_RESUBMISSION", "APPROVED", "REJECTED", "REVOKED", "CANCELLED"]
},
"ApprovalStatus": {
"type": "string",
"enum": ["PENDING", "APPROVED", "REJECTED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"]
},
"Pagination": {
"type": "object",
"properties": {
"page": {"type": "integer"},
"limit": {"type": "integer"},
"total": {"type": "integer"},
"totalPages": {"type": "integer"},
"hasNext": {"type": "boolean"},
"hasPrev": {"type": "boolean"}
}
},
"Error": {
"type": "object",
"properties": {
"code": {"type": "string"},
"message": {"type": "string"},
"details": {"type": "object"},
"timestamp": {"type": "string", "format": "date-time"},
"path": {"type": "string"}
}
}
}
}
};
const ui = SwaggerUIBundle({
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
validatorUrl: null,
defaultModelsExpandDepth: 1,
defaultModelExpandDepth: 1,
displayRequestDuration: true,
filter: true,
showExtensions: true,
showCommonExtensions: true,
syntaxHighlight: {
activate: true,
theme: "monokai"
}
});
window.ui = ui;
};
</script>
</body>
</html>