feat: driver documents (sovereignty storage adapter) + load-test script

- documents: upload (multipart) + review (admin/CS) + auto-approve driver when all docs approved
- StorageService: per-tenant local volume now, in-country storage swap via STORAGE_BASE_URL
- doc.number encrypted (AES-256-GCM); migration InitDocuments; tripz-storage volume
- scripts/loadtest.mjs: concurrent full trip-cycle benchmark (throughput + latency percentiles)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 19:36:27 +03:00
co-authored by Claude Opus 4.8
parent f3383c3cf8
commit 7a58a01417
11 changed files with 424 additions and 0 deletions
@@ -0,0 +1,63 @@
import {
Body,
Controller,
Get,
Param,
Patch,
Post,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { DocumentsService } from './documents.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { CurrentUser, AuthUser } from '../auth/decorators/current-user.decorator';
@ApiTags('documents')
@ApiBearerAuth()
@Controller('drivers/documents')
export class DocumentsController {
constructor(private readonly documents: DocumentsService) {}
// السائق يرفع وثيقة (multipart: file + type + number + expiry_date)
@UseGuards(JwtAuthGuard)
@Post()
@UseInterceptors(FileInterceptor('file'))
upload(
@CurrentUser() user: AuthUser,
@UploadedFile() file: any,
@Body() body: any,
) {
return this.documents.upload(user.tenantId, user.userId, file, body);
}
@UseGuards(JwtAuthGuard)
@Get('mine')
mine(@CurrentUser() user: AuthUser) {
return this.documents.listMine(user.tenantId, user.userId);
}
// مراجعة: خدمة العملاء/الأدمن
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin', 'dispatcher')
@Get('pending')
pending(@CurrentUser() user: AuthUser) {
return this.documents.pending(user.tenantId);
}
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin', 'dispatcher')
@Patch(':id/review')
review(
@CurrentUser() user: AuthUser,
@Param('id') id: string,
@Body('status') status: 'approved' | 'rejected',
@Body('note') note: string,
) {
return this.documents.review(user.tenantId, id, user.userId, status, note);
}
}