SmartfolioBackend / src /modules /verification /verification-admin.controller.ts
ibrahimgulbutt's picture
deploy: appeals on connection checks
614ff63
Raw History Blame Contribute Delete
2.93 kB
import {
Body,
Controller,
Get,
NotFoundException,
Param,
ParseUUIDPipe,
Patch,
Query,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { VerificationService } from './verification.service';
import { ResolveAppealDto } from './dto/appeal.dto';
import { DeviceSignatureService } from './device-signature.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
@ApiTags('Verification (admin)')
@ApiBearerAuth()
@Controller('admin/verification')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class VerificationAdminController {
constructor(
private readonly verification: VerificationService,
private readonly deviceSignatures: DeviceSignatureService,
) {}
/**
* Devices seen under more than one account.
*
* A lead list for a human, never an enforcement queue. Internet cafes, family
* computers and shared office machines all land here legitimately, so a
* cluster is a reason to look, not a reason to act.
*/
@Get('clusters')
@ApiOperation({ summary: 'Devices seen under more than one account' })
async clusters(@Query('limit') limit?: string) {
const clusters = await this.deviceSignatures.sharedClusters(
Math.min(Number(limit) || 100, 200),
);
return { count: clusters.length, clusters };
}
@Get('sessions')
@ApiOperation({ summary: 'Recent checks, newest first' })
async sessions(@Query('verdict') verdict?: string, @Query('limit') limit?: string) {
const rows = await this.verification.recent({ verdict, limit: Number(limit) || 50 });
return { count: rows.length, sessions: rows, lists: this.verification.listStatus() };
}
/*
* Before @Get(':id') on purpose. Nest matches in declaration order, and a
* literal path registered after a parameterised one is unreachable —
* 'appeals' would be read as an id and fail UUID validation.
*/
@Get('appeals')
@ApiOperation({ summary: 'Checks the candidate says got it wrong' })
async appeals(@Query('status') status?: string) {
const wanted =
status === 'accepted' || status === 'declined' || status === 'all' ? status : 'open';
const rows = await this.verification.appeals(wanted);
return { count: rows.length, appeals: rows };
}
@Patch(':id/appeal')
@ApiOperation({ summary: 'Decide one dispute' })
async resolveAppeal(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: ResolveAppealDto,
) {
return this.verification.resolveAppeal(id, dto);
}
@Get(':id')
@ApiOperation({ summary: 'One check in full' })
async detail(@Param('id', ParseUUIDPipe) id: string) {
const session = await this.verification.findOne(id);
if (!session) throw new NotFoundException('Verification session not found.');
return session;
}
}