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:
188
blockchain/contracts/WorkflowRegistry.sol
Normal file
188
blockchain/contracts/WorkflowRegistry.sol
Normal file
@@ -0,0 +1,188 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
/**
|
||||
* @title WorkflowRegistry
|
||||
* @notice Registers and tracks workflow definitions on-chain
|
||||
* @dev Placeholder for future workflow verification capabilities
|
||||
*/
|
||||
contract WorkflowRegistry is Ownable {
|
||||
struct WorkflowDefinition {
|
||||
bytes32 id;
|
||||
string workflowType;
|
||||
string name;
|
||||
bytes32 definitionHash;
|
||||
uint256 version;
|
||||
uint256 timestamp;
|
||||
bool isActive;
|
||||
}
|
||||
|
||||
// Mapping from workflow ID to definition
|
||||
mapping(bytes32 => WorkflowDefinition) private _workflows;
|
||||
|
||||
// Mapping from workflow type to latest workflow ID
|
||||
mapping(string => bytes32) private _latestWorkflows;
|
||||
|
||||
// Array of all workflow IDs
|
||||
bytes32[] private _workflowIds;
|
||||
|
||||
// Counter for unique IDs
|
||||
uint256 private _workflowCounter;
|
||||
|
||||
// Events
|
||||
event WorkflowRegistered(
|
||||
bytes32 indexed workflowId,
|
||||
string indexed workflowType,
|
||||
string name,
|
||||
bytes32 definitionHash,
|
||||
uint256 version
|
||||
);
|
||||
|
||||
event WorkflowDeactivated(bytes32 indexed workflowId);
|
||||
|
||||
event WorkflowActivated(bytes32 indexed workflowId);
|
||||
|
||||
constructor() Ownable(msg.sender) {}
|
||||
|
||||
/**
|
||||
* @notice Register a new workflow definition
|
||||
* @param workflowType The type of workflow (e.g., "RESORT_LICENSE")
|
||||
* @param name Human-readable name
|
||||
* @param definitionHash Hash of the workflow definition JSON
|
||||
* @return workflowId The unique workflow ID
|
||||
*/
|
||||
function registerWorkflow(
|
||||
string calldata workflowType,
|
||||
string calldata name,
|
||||
bytes32 definitionHash
|
||||
) public onlyOwner returns (bytes32) {
|
||||
require(bytes(workflowType).length > 0, "Workflow type required");
|
||||
require(bytes(name).length > 0, "Name required");
|
||||
require(definitionHash != bytes32(0), "Definition hash required");
|
||||
|
||||
_workflowCounter++;
|
||||
bytes32 workflowId = keccak256(
|
||||
abi.encodePacked(workflowType, _workflowCounter, block.timestamp)
|
||||
);
|
||||
|
||||
// Determine version
|
||||
uint256 version = 1;
|
||||
bytes32 latestId = _latestWorkflows[workflowType];
|
||||
if (latestId != bytes32(0)) {
|
||||
version = _workflows[latestId].version + 1;
|
||||
// Deactivate previous version
|
||||
_workflows[latestId].isActive = false;
|
||||
}
|
||||
|
||||
WorkflowDefinition storage workflow = _workflows[workflowId];
|
||||
workflow.id = workflowId;
|
||||
workflow.workflowType = workflowType;
|
||||
workflow.name = name;
|
||||
workflow.definitionHash = definitionHash;
|
||||
workflow.version = version;
|
||||
workflow.timestamp = block.timestamp;
|
||||
workflow.isActive = true;
|
||||
|
||||
_latestWorkflows[workflowType] = workflowId;
|
||||
_workflowIds.push(workflowId);
|
||||
|
||||
emit WorkflowRegistered(
|
||||
workflowId,
|
||||
workflowType,
|
||||
name,
|
||||
definitionHash,
|
||||
version
|
||||
);
|
||||
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get workflow definition by ID
|
||||
* @param workflowId The workflow ID
|
||||
* @return The WorkflowDefinition struct
|
||||
*/
|
||||
function getWorkflow(bytes32 workflowId)
|
||||
public
|
||||
view
|
||||
returns (WorkflowDefinition memory)
|
||||
{
|
||||
require(_workflows[workflowId].timestamp > 0, "Workflow not found");
|
||||
return _workflows[workflowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the latest active workflow for a type
|
||||
* @param workflowType The workflow type
|
||||
* @return The WorkflowDefinition struct
|
||||
*/
|
||||
function getLatestWorkflow(string calldata workflowType)
|
||||
public
|
||||
view
|
||||
returns (WorkflowDefinition memory)
|
||||
{
|
||||
bytes32 workflowId = _latestWorkflows[workflowType];
|
||||
require(workflowId != bytes32(0), "No workflow found for type");
|
||||
return _workflows[workflowId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Verify a workflow definition hash
|
||||
* @param workflowId The workflow ID
|
||||
* @param definitionHash The hash to verify
|
||||
* @return True if the hash matches
|
||||
*/
|
||||
function verifyWorkflow(bytes32 workflowId, bytes32 definitionHash)
|
||||
public
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
WorkflowDefinition memory workflow = _workflows[workflowId];
|
||||
return workflow.isActive && workflow.definitionHash == definitionHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deactivate a workflow
|
||||
* @param workflowId The workflow ID
|
||||
*/
|
||||
function deactivateWorkflow(bytes32 workflowId) public onlyOwner {
|
||||
require(_workflows[workflowId].timestamp > 0, "Workflow not found");
|
||||
require(_workflows[workflowId].isActive, "Workflow already inactive");
|
||||
|
||||
_workflows[workflowId].isActive = false;
|
||||
|
||||
emit WorkflowDeactivated(workflowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Activate a workflow
|
||||
* @param workflowId The workflow ID
|
||||
*/
|
||||
function activateWorkflow(bytes32 workflowId) public onlyOwner {
|
||||
require(_workflows[workflowId].timestamp > 0, "Workflow not found");
|
||||
require(!_workflows[workflowId].isActive, "Workflow already active");
|
||||
|
||||
_workflows[workflowId].isActive = true;
|
||||
|
||||
emit WorkflowActivated(workflowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get total workflow count
|
||||
* @return The number of registered workflows
|
||||
*/
|
||||
function getWorkflowCount() public view returns (uint256) {
|
||||
return _workflowIds.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Check if a workflow is active
|
||||
* @param workflowId The workflow ID
|
||||
* @return True if active
|
||||
*/
|
||||
function isWorkflowActive(bytes32 workflowId) public view returns (bool) {
|
||||
return _workflows[workflowId].isActive;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user