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,41 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-jwt';
import { Request } from 'express';
import { AuthService } from '../auth.service';
import { API_KEY_HEADER, DEPARTMENT_CODE_HEADER } from '../../../common/constants';
@Injectable()
export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: (req: Request) => {
const apiKey = req.headers[API_KEY_HEADER] as string;
const departmentCode = req.headers[DEPARTMENT_CODE_HEADER] as string;
if (!apiKey || !departmentCode) {
return null;
}
// Return a dummy token - actual validation happens in validate()
return `${apiKey}:${departmentCode}`;
},
secretOrKey: 'api-key-strategy',
});
}
async validate(token: string): Promise<{ departmentId: string; departmentCode: string }> {
const [apiKey, departmentCode] = token.split(':');
if (!apiKey || !departmentCode) {
throw new UnauthorizedException('API key and department code are required');
}
const result = await this.authService.validateDepartmentApiKey(apiKey, departmentCode);
return {
departmentId: result.department.id,
departmentCode: result.department.code,
};
}
}

View File

@@ -0,0 +1,28 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { AuthService } from '../auth.service';
import { JwtPayload } from '../../../common/interfaces/request-context.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
private readonly configService: ConfigService,
private readonly authService: AuthService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}
async validate(payload: JwtPayload): Promise<JwtPayload> {
try {
return await this.authService.validateJwtPayload(payload);
} catch {
throw new UnauthorizedException('Invalid token');
}
}
}