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:
39
backend/src/modules/users/users.controller.ts
Normal file
39
backend/src/modules/users/users.controller.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Controller, Get, Patch, Param, Body, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { UsersService } from './users.service';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { UserRole } from '../../common/enums';
|
||||
|
||||
@ApiTags('Users')
|
||||
@Controller('users')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@ApiBearerAuth()
|
||||
export class UsersController {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
@Get()
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Get all users (Admin only)' })
|
||||
@ApiResponse({ status: 200, description: 'List of all users' })
|
||||
async findAll() {
|
||||
return this.usersService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get user by ID' })
|
||||
@ApiResponse({ status: 200, description: 'User details' })
|
||||
@ApiResponse({ status: 404, description: 'User not found' })
|
||||
async findOne(@Param('id') id: string) {
|
||||
return this.usersService.findById(id);
|
||||
}
|
||||
|
||||
@Patch(':id/status')
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiOperation({ summary: 'Update user status (Admin only)' })
|
||||
@ApiResponse({ status: 200, description: 'User status updated' })
|
||||
async updateStatus(@Param('id') id: string, @Body('isActive') isActive: boolean) {
|
||||
return this.usersService.updateUserStatus(id, isActive);
|
||||
}
|
||||
}
|
||||
11
backend/src/modules/users/users.module.ts
Normal file
11
backend/src/modules/users/users.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
exports: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
58
backend/src/modules/users/users.service.ts
Normal file
58
backend/src/modules/users/users.service.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Injectable, NotFoundException, Inject } from '@nestjs/common';
|
||||
import { User } from '../../database/models/user.model';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
@Inject(User)
|
||||
private readonly userModel: typeof User,
|
||||
) {}
|
||||
|
||||
async findByEmail(email: string): Promise<User | undefined> {
|
||||
return this.userModel.query().findOne({ email });
|
||||
}
|
||||
|
||||
async findByEmailWithDepartment(email: string): Promise<User | undefined> {
|
||||
return this.userModel.query().findOne({ email }).withGraphFetched('department');
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<User | undefined> {
|
||||
return this.userModel.query().findById(id).withGraphFetched('department');
|
||||
}
|
||||
|
||||
async findAll(): Promise<User[]> {
|
||||
return this.userModel.query().withGraphFetched('department').orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
async updateLastLogin(userId: string): Promise<void> {
|
||||
await this.userModel.query().patchAndFetchById(userId, {
|
||||
lastLoginAt: new Date().toISOString() as any,
|
||||
});
|
||||
}
|
||||
|
||||
async updateUserStatus(userId: string, isActive: boolean): Promise<User> {
|
||||
const user = await this.userModel.query().patchAndFetchById(userId, {
|
||||
isActive,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException(`User with ID ${userId} not found`);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async create(data: Partial<User>): Promise<User> {
|
||||
return this.userModel.query().insert(data);
|
||||
}
|
||||
|
||||
async update(userId: string, data: Partial<User>): Promise<User> {
|
||||
const user = await this.userModel.query().patchAndFetchById(userId, data);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException(`User with ID ${userId} not found`);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user