feat: technical role can view all applications with search, separate /all route

- Add GET /applications/all endpoint for admin/technical with search by user name/email/ID
- GET /applications now always returns only the current user's apps (fix for technical seeing all apps)
- Technical role can view/edit/delete any application (same as admin)
- Add 'All Applications' page with search bar, user info columns, status badges
- Add 'All Applications' link to admin and technical sidebar navigation
- Add user relation to Application TypeScript interface
This commit is contained in:
keyhan
2026-04-06 14:43:46 +03:30
parent a64f8407b9
commit 8243ac7df2
5 changed files with 281 additions and 15 deletions
@@ -6,6 +6,7 @@ import {
Delete,
Body,
Param,
Query,
UseGuards,
Request,
UseInterceptors,
@@ -62,16 +63,20 @@ export class ApplicationsController {
@Get()
@ApiOperation({ summary: 'List my applications' })
async findAll(@Request() req: any) {
if (req.user.role === UserRole.ADMIN) {
return this.applicationsService.findAll();
}
return this.applicationsService.findAllByUser(req.user.id);
}
@Get('all')
@Roles(UserRole.ADMIN, UserRole.TECHNICAL)
@ApiOperation({ summary: 'List all applications (admin/technical)' })
async findAllAdmin(@Request() req: any, @Query('search') search?: string) {
return this.applicationsService.findAll(search);
}
@Get(':id')
@ApiOperation({ summary: 'Get application details' })
async findOne(@Param('id') id: string, @Request() req: any) {
if (req.user.role === UserRole.ADMIN) {
if (req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL) {
return this.applicationsService.findOne(id);
}
return this.applicationsService.findOne(id, req.user.id);
@@ -84,6 +89,11 @@ export class ApplicationsController {
@Request() req: any,
@Body() dto: UpdateApplicationDto,
) {
const isStaff = req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL;
if (isStaff) {
const app = await this.applicationsService.findOne(id);
return this.applicationsService.update(id, app.userId, dto);
}
return this.applicationsService.update(id, req.user.id, dto);
}
@@ -92,7 +102,7 @@ export class ApplicationsController {
async getResources(@Param('id') id: string, @Request() req: any) {
const app = await this.applicationsService.findOne(
id,
req.user.role === UserRole.ADMIN ? undefined : req.user.id,
(req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL) ? undefined : req.user.id,
);
return this.kubernetesService.getResourceUsage(app);
}
@@ -104,7 +114,8 @@ export class ApplicationsController {
@Request() req: any,
@Body() dto: ScaleResourcesDto,
) {
const app = await this.applicationsService.findOne(id, req.user.id);
const isStaff = req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL;
const app = await this.applicationsService.findOne(id, isStaff ? undefined : req.user.id);
// Update in K8s (live)
await this.kubernetesService.updateResources(app, dto);
@@ -117,7 +128,7 @@ export class ApplicationsController {
if (dto.memoryLimit) updateFields.memoryLimit = dto.memoryLimit;
if (dto.replicas !== undefined) updateFields.replicas = dto.replicas;
const updated = await this.applicationsService.update(id, req.user.id, updateFields);
const updated = await this.applicationsService.update(id, app.userId, updateFields);
this.logger.log(`Updated resources for ${app.name}: ${JSON.stringify(dto)}`);
return updated;
}
@@ -127,7 +138,7 @@ export class ApplicationsController {
async getPreview(@Param('id') id: string, @Request() req: any) {
const app = await this.applicationsService.findOne(
id,
req.user.role === UserRole.ADMIN ? undefined : req.user.id,
(req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL) ? undefined : req.user.id,
);
return this.kubernetesService.getPreviewInfo(app);
}
@@ -135,8 +146,9 @@ export class ApplicationsController {
@Delete(':id')
@ApiOperation({ summary: 'Delete an application and all its resources' })
async delete(@Param('id') id: string, @Request() req: any) {
const isStaff = req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL;
// 1. Get the app first
const app = await this.applicationsService.findOne(id, req.user.id);
const app = await this.applicationsService.findOne(id, isStaff ? undefined : req.user.id);
// 2. Delete K8s resources (deployment, service, ingress, db, secrets)
try {
@@ -156,7 +168,7 @@ export class ApplicationsController {
}
// 4. Delete app (also deletes uploaded files)
await this.applicationsService.delete(id, req.user.id);
await this.applicationsService.delete(id, isStaff ? app.userId : req.user.id);
return { message: `Application "${app.name}" and all resources deleted` };
}
@@ -84,11 +84,22 @@ export class ApplicationsService {
});
}
async findAll(): Promise<Application[]> {
return this.appsRepository.find({
relations: ['user', 'deployments'],
order: { createdAt: 'DESC' },
});
async findAll(search?: string): Promise<Application[]> {
const qb = this.appsRepository
.createQueryBuilder('app')
.leftJoinAndSelect('app.user', 'user')
.leftJoinAndSelect('app.deployments', 'deployments')
.orderBy('app.createdAt', 'DESC');
if (search && search.trim()) {
const s = `%${search.trim()}%`;
qb.where(
'(user.firstName ILIKE :s OR user.lastName ILIKE :s OR user.email ILIKE :s OR CAST(app.userId AS TEXT) ILIKE :s OR app.name ILIKE :s)',
{ s },
);
}
return qb.getMany();
}
async findOne(id: string, userId?: string): Promise<Application> {