initial wcag-ada

This commit is contained in:
Boki 2025-06-28 11:11:34 -04:00
parent 042b8cb83a
commit d52cfe7de2
112 changed files with 9069 additions and 0 deletions

View file

@ -0,0 +1,152 @@
import { Worker, Job } from 'bullmq';
import { AccessibilityScanner } from '@wcag-ada/scanner';
import type { AccessibilityScanOptions } from '@wcag-ada/shared';
import { getWorkerConfig } from '@wcag-ada/config';
import { logger } from '../utils/logger';
import { prisma } from '../utils/prisma';
import { createRedisConnection } from '../utils/redis';
interface ScanJobData {
jobId: string;
websiteId: string;
userId: string;
options: AccessibilityScanOptions;
}
export class ScanWorker {
private worker: Worker<ScanJobData> | null = null;
private scanner: AccessibilityScanner | null = null;
private config = getWorkerConfig();
async start(): Promise<void> {
// Initialize scanner
this.scanner = new AccessibilityScanner(logger);
await this.scanner.initialize();
// Create worker
this.worker = new Worker<ScanJobData>(
this.config.queueName,
async (job) => this.processJob(job),
{
connection: createRedisConnection(),
concurrency: this.config.concurrency,
}
);
// Setup event handlers
this.worker.on('completed', (job) => {
logger.info('Job completed', { jobId: job.id, data: job.data });
});
this.worker.on('failed', (job, err) => {
logger.error('Job failed', {
jobId: job?.id,
error: err.message,
stack: err.stack,
});
});
this.worker.on('error', (err) => {
logger.error('Worker error', err);
});
}
private async processJob(job: Job<ScanJobData>): Promise<void> {
const { jobId, websiteId, userId, options } = job.data;
logger.info('Processing scan job', { jobId, websiteId });
try {
// Update job status
await prisma.scanJob.update({
where: { id: jobId },
data: {
status: 'RUNNING',
startedAt: new Date(),
},
});
// Perform scan
if (!this.scanner) {
throw new Error('Scanner not initialized');
}
const result = await this.scanner.scan(options);
// Save result
const scanResult = await prisma.scanResult.create({
data: {
websiteId,
jobId,
url: result.url,
scanDuration: result.scanDuration,
summary: result.summary as any,
violations: result.violations as any,
passes: result.passes as any,
incomplete: result.incomplete as any,
inapplicable: result.inapplicable as any,
pageMetadata: result.pageMetadata as any,
wcagCompliance: result.wcagCompliance as any,
},
});
// Update job status
await prisma.scanJob.update({
where: { id: jobId },
data: {
status: 'COMPLETED',
completedAt: new Date(),
},
});
// Update website compliance score
await prisma.website.update({
where: { id: websiteId },
data: {
lastScanAt: new Date(),
complianceScore: result.summary.score,
},
});
logger.info('Scan job completed', {
jobId,
resultId: scanResult.id,
score: result.summary.score,
});
} catch (error: any) {
logger.error('Scan job failed', {
jobId,
error: error.message,
stack: error.stack,
});
// Update job status
await prisma.scanJob.update({
where: { id: jobId },
data: {
status: 'FAILED',
error: error.message,
completedAt: new Date(),
},
});
throw error;
}
}
async stop(): Promise<void> {
logger.info('Stopping scan worker...');
if (this.worker) {
await this.worker.close();
this.worker = null;
}
if (this.scanner) {
await this.scanner.close();
this.scanner = null;
}
logger.info('Scan worker stopped');
}
}