Feature: Export study data - API Endpoints & Authorization
- Gap 1: No REST endpoints for creating and monitoring export tasks
- Gap 2: No authorization logic to verify publisher/admin access to study exports
- Gap 3: No presigned URL generation for secure download access
- Gap 4: No API endpoints for export task status polling
Create secure REST API endpoints for export task management with proper authorization checks, following existing controller patterns and security standards.
Key decisions:
- Authorization: Only study publisher (
publishedBy) or admin role can export - API Design: Follow existing REST patterns with proper HTTP status codes
- Security: Use existing
@ResearcherAuth()and@Role()decorators - Download: Generate presigned URLs for secure temporary access
describe('Study Export API HTTP routes', () => {
it('POST /admin/studies/:studyId/export - creates export task for study publisher or admin');
it('POST /admin/studies/:studyId/export - rejects export request from non-publisher researcher');
it('POST /admin/studies/:studyId/export - returns 404 for non-existent study');
it('GET /admin/studies/:studyId/export/:taskId - polls export status');
it('GET /admin/studies/:studyId/export/:taskId/download - generates valid presigned download URL');
});backend/src/modules/studies/export/
├── study-export.controller.ts # Export API endpoints
├── study-export.service.ts # Export API endpoints
└── tests/
└── study-export.controller.spec-e2e.ts
Export Controller:
@Controller('admin/studies/:studyId/export')
@ResearcherAuth()
export class StudyExportController {
@Post()
@HttpCode(HttpStatus.CREATED)
@Audit({ action: 'CREATE_EXPORT_TASK' })
async createExport(
@Param('studyId') studyId: string,
@Researcher() researcher: ResearcherDB
): Promise<ExportTaskDto>
@Get(':taskId')
async getExportStatus(
@Param('studyId') studyId: string,
@Param('taskId') taskId: string,
@Researcher() researcher: ResearcherDB
): Promise<ExportTaskDto>
}Study Access Validation:
private async validateExportAccess(
studyId: string,
researcher: ResearcherDB
): Promise<StudyWithQuestionnaires> {
// Allow admin role or study publisher
}Create Export Task:
async createExport(studyId: string, researcher: ResearcherDB): Promise<ExportTaskDto> {
// Check for existing pending export
// Create new export task and queue job
}Get Export Status with Fresh URL:
async getExportStatus(studyId: string, taskId: string, researcher: ResearcherDB): Promise<ExportTaskDto> {
// Generate fresh presigned URL for completed exports
}Export Task Response:
export const ExportTaskResponseSchema = z.object({
id: z.string(),
studyId: z.string(),
status: z.enum(['pending', 'processing', 'completed', 'failed']),
progress: z.number().min(0).max(100),
downloadUrl: z.string().url().optional(),
fileName: z.string().optional(),
fileSizeBytes: z.number().int().nonnegative().optional(),
expiresAt: z.string().datetime().optional(),
errorMessage: z.string().optional(),
createdAt: z.string().datetime(),
completedAt: z.string().datetime().optional(),
});- Studies Service: Study access validation
- Export Task Service: Task lifecycle management
- Job Queue Service: Background job processing
- Storage Service: Presigned URL generation
- Audit Service: Export operation logging
@docs/standards/general.md @docs/standards/backend.md @docs/standards/e2e-tests.md Use existing @ResearcherAuth() and authorization patterns Handle user expectable errors in controller with proper NestJS HTTP built-in exception Implement proper HTTP status codes and error responses Validate study publisher or admin role access before export Generate fresh presigned URLs on each status check for security Use existing audit patterns for export operation logging Follow REST API conventions with proper resource nesting Handle concurrent export requests with appropriate conflict responses
- Bulk export endpoints for multiple studies
- Export scheduling API endpoints
- Export sharing between researchers
- Export template customization endpoints