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

View File

@@ -0,0 +1,67 @@
import {
IsString,
Matches,
MinLength,
IsOptional,
IsUrl,
IsEmail,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateDepartmentDto {
@ApiProperty({
description: 'Department code (uppercase letters, numbers, and underscores only)',
example: 'DEPT_001',
pattern: '^[A-Z0-9_]+$',
})
@IsString()
@Matches(/^[A-Z0-9_]+$/, {
message: 'Department code must contain only uppercase letters, numbers, and underscores',
})
@MinLength(3, { message: 'Department code must be at least 3 characters long' })
code: string;
@ApiProperty({
description: 'Department name',
example: 'Finance Department',
})
@IsString()
@MinLength(3, { message: 'Department name must be at least 3 characters long' })
name: string;
@ApiProperty({
description: 'Department description',
example: 'Handles financial operations and compliance',
required: false,
})
@IsOptional()
@IsString()
description?: string;
@ApiProperty({
description: 'Contact email for the department',
example: 'contact@department.gov.in',
required: false,
})
@IsOptional()
@IsEmail()
contactEmail?: string;
@ApiProperty({
description: 'Contact phone number',
example: '+91-11-XXXXXXXX',
required: false,
})
@IsOptional()
@IsString()
contactPhone?: string;
@ApiProperty({
description: 'Webhook URL for credential issuance events',
example: 'https://api.department.gov.in/webhooks/credentials',
required: false,
})
@IsOptional()
@IsUrl()
webhookUrl?: string;
}

View File

@@ -0,0 +1,121 @@
import { ApiProperty } from '@nestjs/swagger';
import { Department } from '../../../database/models/department.model';
export class DepartmentResponseDto {
@ApiProperty({
description: 'Unique identifier for the department',
example: '550e8400-e29b-41d4-a716-446655440000',
})
id: string;
@ApiProperty({
description: 'Department code',
example: 'DEPT_001',
})
code: string;
@ApiProperty({
description: 'Department name',
example: 'Finance Department',
})
name: string;
@ApiProperty({
description: 'Blockchain wallet address',
example: '0x1234567890123456789012345678901234567890',
required: false,
})
walletAddress?: string;
@ApiProperty({
description: 'Department description',
example: 'Handles financial operations',
required: false,
})
description?: string;
@ApiProperty({
description: 'Contact email',
example: 'contact@department.gov.in',
required: false,
})
contactEmail?: string;
@ApiProperty({
description: 'Contact phone number',
example: '+91-11-XXXXXXXX',
required: false,
})
contactPhone?: string;
@ApiProperty({
description: 'Webhook URL',
example: 'https://api.department.gov.in/webhooks/credentials',
required: false,
})
webhookUrl?: string;
@ApiProperty({
description: 'Whether the department is active',
example: true,
})
isActive: boolean;
@ApiProperty({
description: 'Timestamp when the department was created',
example: '2024-01-15T10:30:00Z',
})
createdAt: Date;
@ApiProperty({
description: 'Timestamp when the department was last updated',
example: '2024-01-15T10:30:00Z',
})
updatedAt: Date;
@ApiProperty({
description: 'Total number of applicants in this department',
example: 150,
})
totalApplicants: number;
@ApiProperty({
description: 'Total number of issued credentials',
example: 145,
})
issuedCredentials: number;
@ApiProperty({
description: 'Timestamp of last webhook call',
example: '2024-01-15T10:30:00Z',
required: false,
})
lastWebhookAt?: Date;
@ApiProperty({
description: 'Hashed API key for the department',
example: 'hashed_api_key_value',
required: false,
})
apiKeyHash?: string;
static fromEntity(department: Department): DepartmentResponseDto {
const dto = new DepartmentResponseDto();
dto.id = department.id;
dto.code = department.code;
dto.name = department.name;
dto.walletAddress = (department as any).walletAddress;
dto.description = (department as any).description;
dto.contactEmail = (department as any).contactEmail;
dto.contactPhone = (department as any).contactPhone;
dto.webhookUrl = department.webhookUrl;
dto.isActive = department.isActive;
dto.createdAt = department.createdAt;
dto.updatedAt = department.updatedAt;
dto.totalApplicants = (department as any).totalApplicants;
dto.issuedCredentials = (department as any).issuedCredentials;
dto.lastWebhookAt = (department as any).lastWebhookAt;
dto.apiKeyHash = department.apiKeyHash;
return dto;
}
}

View File

@@ -0,0 +1,65 @@
import { ApiProperty } from '@nestjs/swagger';
export class DepartmentStatsDto {
@ApiProperty({
description: 'Department code',
example: 'DEPT_001',
})
departmentCode: string;
@ApiProperty({
description: 'Department name',
example: 'Finance Department',
})
departmentName: string;
@ApiProperty({
description: 'Total number of applicants',
example: 150,
})
totalApplicants: number;
@ApiProperty({
description: 'Total number of credentials issued',
example: 145,
})
totalCredentialsIssued: number;
@ApiProperty({
description: 'Whether the department is active',
example: true,
})
isActive: boolean;
@ApiProperty({
description: 'Department creation timestamp',
example: '2024-01-15T10:30:00Z',
})
createdAt: Date;
@ApiProperty({
description: 'Department last update timestamp',
example: '2024-01-15T10:30:00Z',
})
updatedAt: Date;
@ApiProperty({
description: 'Timestamp of last webhook event',
example: '2024-01-20T14:00:00Z',
required: false,
})
lastWebhookAt?: Date;
@ApiProperty({
description: 'Percentage of credentials issued vs applicants',
example: 96.67,
})
issueRate?: number;
constructor() {
if (this.totalApplicants > 0) {
this.issueRate =
(this.totalCredentialsIssued / this.totalApplicants) * 100;
}
}
}

View File

@@ -0,0 +1,42 @@
import { PartialType } from '@nestjs/swagger';
import { CreateDepartmentDto } from './create-department.dto';
import {
IsString,
Matches,
MinLength,
IsOptional,
IsUrl,
IsEmail,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class UpdateDepartmentDto extends PartialType(CreateDepartmentDto) {
@IsOptional()
@IsString()
@Matches(/^[A-Z_]+$/, {
message: 'Department code must contain only uppercase letters and underscores',
})
@MinLength(3)
code?: string;
@IsOptional()
@IsString()
@MinLength(3)
name?: string;
@IsOptional()
@IsString()
description?: string;
@IsOptional()
@IsEmail()
contactEmail?: string;
@IsOptional()
@IsString()
contactPhone?: string;
@IsOptional()
@IsUrl()
webhookUrl?: string;
}