initial wcag-ada
This commit is contained in:
parent
042b8cb83a
commit
d52cfe7de2
112 changed files with 9069 additions and 0 deletions
67
apps/wcag-ada/config/src/config-instance.ts
Normal file
67
apps/wcag-ada/config/src/config-instance.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { ConfigManager, createAppConfig } from '@stock-bot/core-config';
|
||||
import { wcagAppConfigSchema, type WcagAppConfig } from './schemas/wcag-app.schema';
|
||||
import * as path from 'path';
|
||||
|
||||
let configInstance: ConfigManager<WcagAppConfig> | null = null;
|
||||
|
||||
/**
|
||||
* Initialize the WCAG-ADA configuration
|
||||
* @param serviceName - Optional service name for service-specific overrides
|
||||
*/
|
||||
export function initializeWcagConfig(
|
||||
serviceName?: 'api' | 'dashboard' | 'worker'
|
||||
): WcagAppConfig {
|
||||
if (!configInstance) {
|
||||
configInstance = createAppConfig(wcagAppConfigSchema, {
|
||||
configPath: path.join(__dirname, '../config'),
|
||||
envPrefix: 'WCAG_',
|
||||
});
|
||||
}
|
||||
|
||||
const config = configInstance.initialize(wcagAppConfigSchema);
|
||||
|
||||
// Apply service-specific overrides if provided
|
||||
if (serviceName && config.services[serviceName]) {
|
||||
const serviceConfig = config.services[serviceName];
|
||||
|
||||
// Override port if specified for the service
|
||||
if (serviceConfig.port && process.env.PORT) {
|
||||
serviceConfig.port = parseInt(process.env.PORT, 10);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current WCAG configuration instance
|
||||
* @throws Error if configuration hasn't been initialized
|
||||
*/
|
||||
export function getWcagConfig(): WcagAppConfig {
|
||||
if (!configInstance) {
|
||||
throw new Error('WCAG configuration not initialized. Call initializeWcagConfig() first.');
|
||||
}
|
||||
return configInstance.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific configuration value by path
|
||||
* @param path - Dot-notation path to the configuration value
|
||||
*/
|
||||
export function getConfigValue<T = unknown>(path: string): T {
|
||||
if (!configInstance) {
|
||||
throw new Error('WCAG configuration not initialized. Call initializeWcagConfig() first.');
|
||||
}
|
||||
return configInstance.getValue<T>(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a configuration path exists
|
||||
* @param path - Dot-notation path to check
|
||||
*/
|
||||
export function hasConfigValue(path: string): boolean {
|
||||
if (!configInstance) {
|
||||
return false;
|
||||
}
|
||||
return configInstance.has(path);
|
||||
}
|
||||
106
apps/wcag-ada/config/src/index.ts
Normal file
106
apps/wcag-ada/config/src/index.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Main configuration exports
|
||||
export {
|
||||
initializeWcagConfig,
|
||||
getWcagConfig,
|
||||
getConfigValue,
|
||||
hasConfigValue,
|
||||
} from './config-instance';
|
||||
|
||||
// Schema exports
|
||||
export { wcagAppConfigSchema, type WcagAppConfig } from './schemas/wcag-app.schema';
|
||||
export { scannerConfigSchema, type ScannerConfig } from './schemas/scanner.schema';
|
||||
export { workerConfigSchema, type WorkerConfig } from './schemas/worker.schema';
|
||||
export { featuresConfigSchema, type FeaturesConfig } from './schemas/features.schema';
|
||||
export { providersConfigSchema, type ProvidersConfig } from './schemas/providers.schema';
|
||||
|
||||
// Helper functions
|
||||
import { getWcagConfig } from './config-instance';
|
||||
import type { ScannerConfig, WorkerConfig, FeaturesConfig, ProvidersConfig } from './schemas';
|
||||
|
||||
/**
|
||||
* Get scanner configuration
|
||||
*/
|
||||
export function getScannerConfig(): ScannerConfig {
|
||||
return getWcagConfig().scanner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get worker configuration
|
||||
*/
|
||||
export function getWorkerConfig(): WorkerConfig {
|
||||
return getWcagConfig().worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get features configuration
|
||||
*/
|
||||
export function getFeaturesConfig(): FeaturesConfig {
|
||||
return getWcagConfig().features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get providers configuration
|
||||
*/
|
||||
export function getProvidersConfig(): ProvidersConfig {
|
||||
return getWcagConfig().providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service configuration
|
||||
*/
|
||||
export function getServiceConfig(service: 'api' | 'dashboard' | 'worker') {
|
||||
return getWcagConfig().services[service];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a feature is enabled
|
||||
*/
|
||||
export function isFeatureEnabled(feature: string): boolean {
|
||||
const features = getFeaturesConfig();
|
||||
const parts = feature.split('.');
|
||||
|
||||
let current: any = features;
|
||||
for (const part of parts) {
|
||||
if (typeof current !== 'object' || !(part in current)) {
|
||||
return false;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
|
||||
return current === true || (typeof current === 'object' && current.enabled === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database configuration
|
||||
*/
|
||||
export function getDatabaseConfig() {
|
||||
return getWcagConfig().database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redis configuration for queues
|
||||
*/
|
||||
export function getRedisConfig() {
|
||||
return getWcagConfig().worker.redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage configuration
|
||||
*/
|
||||
export function getStorageConfig() {
|
||||
return getWcagConfig().providers.storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get compliance configuration
|
||||
*/
|
||||
export function getComplianceConfig() {
|
||||
return getWcagConfig().compliance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription tier configuration
|
||||
*/
|
||||
export function getSubscriptionConfig(tier: 'starter' | 'professional' | 'enterprise') {
|
||||
return getWcagConfig().subscriptions.tiers[tier];
|
||||
}
|
||||
89
apps/wcag-ada/config/src/schemas/features.schema.ts
Normal file
89
apps/wcag-ada/config/src/schemas/features.schema.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const featuresConfigSchema = z.object({
|
||||
// Scanner features
|
||||
screenshots: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
quality: z.number().min(0).max(100).default(80),
|
||||
fullPage: z.boolean().default(false),
|
||||
}).default({}),
|
||||
|
||||
customRules: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
maxRules: z.number().default(50),
|
||||
}).default({}),
|
||||
|
||||
multiPage: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
maxPages: z.number().default(10),
|
||||
maxDepth: z.number().default(3),
|
||||
}).default({}),
|
||||
|
||||
// API features
|
||||
apiKeys: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
maxPerUser: z.number().default(5),
|
||||
}).default({}),
|
||||
|
||||
webhooks: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
maxPerWebsite: z.number().default(3),
|
||||
retryAttempts: z.number().default(3),
|
||||
}).default({}),
|
||||
|
||||
// Report features
|
||||
reports: z.object({
|
||||
pdf: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
watermark: z.boolean().default(true),
|
||||
}).default({}),
|
||||
excel: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
}).default({}),
|
||||
scheduling: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
|
||||
// Compliance features
|
||||
compliance: z.object({
|
||||
wcag20: z.boolean().default(true),
|
||||
wcag21: z.boolean().default(true),
|
||||
wcag22: z.boolean().default(true),
|
||||
section508: z.boolean().default(false),
|
||||
ada: z.boolean().default(true),
|
||||
}).default({}),
|
||||
|
||||
// Integration features
|
||||
integrations: z.object({
|
||||
github: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
}).default({}),
|
||||
slack: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
}).default({}),
|
||||
teams: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
}).default({}),
|
||||
jira: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
|
||||
// Enterprise features
|
||||
enterprise: z.object({
|
||||
sso: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
providers: z.array(z.enum(['saml', 'oauth', 'ldap'])).default([]),
|
||||
}).default({}),
|
||||
whiteLabel: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
}).default({}),
|
||||
audit: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
retention: z.number().default(365), // days
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
});
|
||||
|
||||
export type FeaturesConfig = z.infer<typeof featuresConfigSchema>;
|
||||
84
apps/wcag-ada/config/src/schemas/providers.schema.ts
Normal file
84
apps/wcag-ada/config/src/schemas/providers.schema.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const providersConfigSchema = z.object({
|
||||
// Storage providers
|
||||
storage: z.object({
|
||||
type: z.enum(['local', 's3', 'gcs', 'azure']).default('local'),
|
||||
local: z.object({
|
||||
basePath: z.string().default('/tmp/wcag-ada'),
|
||||
reports: z.string().default('reports'),
|
||||
screenshots: z.string().default('screenshots'),
|
||||
exports: z.string().default('exports'),
|
||||
}).default({}),
|
||||
s3: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
bucket: z.string().optional(),
|
||||
region: z.string().default('us-east-1'),
|
||||
accessKeyId: z.string().optional(),
|
||||
secretAccessKey: z.string().optional(),
|
||||
endpoint: z.string().optional(),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
|
||||
// Email providers
|
||||
email: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
provider: z.enum(['smtp', 'sendgrid', 'ses', 'postmark']).default('smtp'),
|
||||
from: z.object({
|
||||
name: z.string().default('WCAG-ADA Compliance'),
|
||||
email: z.string().email().default('noreply@wcag-ada.com'),
|
||||
}).default({}),
|
||||
smtp: z.object({
|
||||
host: z.string().default('localhost'),
|
||||
port: z.number().default(587),
|
||||
secure: z.boolean().default(false),
|
||||
auth: z.object({
|
||||
user: z.string().optional(),
|
||||
pass: z.string().optional(),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
sendgrid: z.object({
|
||||
apiKey: z.string().optional(),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
|
||||
// Authentication providers
|
||||
auth: z.object({
|
||||
jwt: z.object({
|
||||
secret: z.string().default('change-me-in-production'),
|
||||
expiresIn: z.string().default('7d'),
|
||||
refreshExpiresIn: z.string().default('30d'),
|
||||
}).default({}),
|
||||
oauth: z.object({
|
||||
google: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
clientId: z.string().optional(),
|
||||
clientSecret: z.string().optional(),
|
||||
}).default({}),
|
||||
github: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
clientId: z.string().optional(),
|
||||
clientSecret: z.string().optional(),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
|
||||
// Analytics providers
|
||||
analytics: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
provider: z.enum(['posthog', 'mixpanel', 'amplitude', 'custom']).default('posthog'),
|
||||
posthog: z.object({
|
||||
apiKey: z.string().optional(),
|
||||
host: z.string().default('https://app.posthog.com'),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
|
||||
// CDN providers
|
||||
cdn: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
provider: z.enum(['cloudflare', 'cloudfront', 'fastly']).default('cloudflare'),
|
||||
baseUrl: z.string().optional(),
|
||||
}).default({}),
|
||||
});
|
||||
|
||||
export type ProvidersConfig = z.infer<typeof providersConfigSchema>;
|
||||
34
apps/wcag-ada/config/src/schemas/scanner.schema.ts
Normal file
34
apps/wcag-ada/config/src/schemas/scanner.schema.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const scannerConfigSchema = z.object({
|
||||
concurrency: z.number().min(1).max(10).default(2),
|
||||
timeout: z.number().min(30000).default(120000), // 2 minutes default
|
||||
pageLoadTimeout: z.number().min(10000).default(30000),
|
||||
headless: z.boolean().default(true),
|
||||
blockResources: z.boolean().default(true),
|
||||
viewport: z.object({
|
||||
width: z.number().default(1280),
|
||||
height: z.number().default(720),
|
||||
deviceScaleFactor: z.number().default(1),
|
||||
}).default({}),
|
||||
browsers: z.object({
|
||||
chromium: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
args: z.array(z.string()).default(['--no-sandbox', '--disable-setuid-sandbox']),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
axe: z.object({
|
||||
tags: z.array(z.string()).default(['wcag2aa', 'wcag21aa']),
|
||||
resultTypes: z.array(z.enum(['violations', 'passes', 'incomplete', 'inapplicable']))
|
||||
.default(['violations', 'passes', 'incomplete', 'inapplicable']),
|
||||
}).default({}),
|
||||
retries: z.object({
|
||||
maxAttempts: z.number().default(3),
|
||||
backoff: z.object({
|
||||
type: z.enum(['exponential', 'fixed']).default('exponential'),
|
||||
delay: z.number().default(2000),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
});
|
||||
|
||||
export type ScannerConfig = z.infer<typeof scannerConfigSchema>;
|
||||
111
apps/wcag-ada/config/src/schemas/wcag-app.schema.ts
Normal file
111
apps/wcag-ada/config/src/schemas/wcag-app.schema.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { z } from 'zod';
|
||||
import {
|
||||
baseAppConfigSchema,
|
||||
logConfigSchema,
|
||||
databaseConfigSchema,
|
||||
serviceConfigSchema,
|
||||
} from '@stock-bot/core-config';
|
||||
import { scannerConfigSchema } from './scanner.schema';
|
||||
import { workerConfigSchema } from './worker.schema';
|
||||
import { featuresConfigSchema } from './features.schema';
|
||||
import { providersConfigSchema } from './providers.schema';
|
||||
|
||||
// Service-specific configurations
|
||||
const wcagServicesSchema = z.object({
|
||||
api: serviceConfigSchema.extend({
|
||||
port: z.number().default(3001),
|
||||
cors: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
origin: z.string().default('*'),
|
||||
credentials: z.boolean().default(true),
|
||||
}).default({}),
|
||||
rateLimit: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
windowMs: z.number().default(900000), // 15 minutes
|
||||
max: z.number().default(100),
|
||||
keyGenerator: z.enum(['ip', 'userId', 'apiKey']).default('userId'),
|
||||
}).default({}),
|
||||
pagination: z.object({
|
||||
defaultLimit: z.number().default(20),
|
||||
maxLimit: z.number().default(100),
|
||||
}).default({}),
|
||||
}),
|
||||
|
||||
dashboard: serviceConfigSchema.extend({
|
||||
port: z.number().default(3000),
|
||||
apiUrl: z.string().default('http://localhost:3001'),
|
||||
publicUrl: z.string().default('http://localhost:3000'),
|
||||
}),
|
||||
|
||||
worker: serviceConfigSchema.extend({
|
||||
port: z.number().default(3002), // For health checks
|
||||
}),
|
||||
});
|
||||
|
||||
// Main WCAG application configuration schema
|
||||
export const wcagAppConfigSchema = baseAppConfigSchema.extend({
|
||||
appName: z.literal('wcag-ada').default('wcag-ada'),
|
||||
|
||||
// Core configurations
|
||||
log: logConfigSchema,
|
||||
database: databaseConfigSchema,
|
||||
|
||||
// WCAG-specific configurations
|
||||
scanner: scannerConfigSchema,
|
||||
worker: workerConfigSchema,
|
||||
features: featuresConfigSchema,
|
||||
providers: providersConfigSchema,
|
||||
|
||||
// Service configurations
|
||||
services: wcagServicesSchema.default({}),
|
||||
|
||||
// Business logic configurations
|
||||
compliance: z.object({
|
||||
defaultLevel: z.object({
|
||||
standard: z.enum(['WCAG20', 'WCAG21', 'WCAG22']).default('WCAG21'),
|
||||
level: z.enum(['A', 'AA', 'AAA']).default('AA'),
|
||||
}).default({}),
|
||||
passingScore: z.object({
|
||||
A: z.number().min(0).max(100).default(95),
|
||||
AA: z.number().min(0).max(100).default(98),
|
||||
AAA: z.number().min(0).max(100).default(100),
|
||||
}).default({}),
|
||||
criticalCriteria: z.array(z.string()).default([
|
||||
'1.1.1', // Non-text Content
|
||||
'1.3.1', // Info and Relationships
|
||||
'1.4.3', // Contrast (Minimum)
|
||||
'2.1.1', // Keyboard
|
||||
'2.1.2', // No Keyboard Trap
|
||||
'2.4.1', // Bypass Blocks
|
||||
'2.4.2', // Page Titled
|
||||
'4.1.2', // Name, Role, Value
|
||||
]),
|
||||
}).default({}),
|
||||
|
||||
// Subscription/pricing tiers
|
||||
subscriptions: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
tiers: z.object({
|
||||
starter: z.object({
|
||||
websites: z.number().default(5),
|
||||
scansPerMonth: z.number().default(500),
|
||||
users: z.number().default(1),
|
||||
price: z.number().default(49),
|
||||
}).default({}),
|
||||
professional: z.object({
|
||||
websites: z.number().default(25),
|
||||
scansPerMonth: z.number().default(5000),
|
||||
users: z.number().default(5),
|
||||
price: z.number().default(149),
|
||||
}).default({}),
|
||||
enterprise: z.object({
|
||||
websites: z.number().default(-1), // Unlimited
|
||||
scansPerMonth: z.number().default(-1),
|
||||
users: z.number().default(-1),
|
||||
price: z.number().default(499),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
});
|
||||
|
||||
export type WcagAppConfig = z.infer<typeof wcagAppConfigSchema>;
|
||||
49
apps/wcag-ada/config/src/schemas/worker.schema.ts
Normal file
49
apps/wcag-ada/config/src/schemas/worker.schema.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const workerConfigSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
concurrency: z.number().min(1).max(10).default(2),
|
||||
queueName: z.string().default('accessibility-scans'),
|
||||
redis: z.object({
|
||||
host: z.string().default('localhost'),
|
||||
port: z.number().default(6379),
|
||||
password: z.string().optional(),
|
||||
db: z.number().default(2), // Different DB for WCAG
|
||||
maxRetriesPerRequest: z.number().nullable().default(null),
|
||||
}).default({}),
|
||||
jobs: z.object({
|
||||
scan: z.object({
|
||||
priority: z.number().default(0),
|
||||
attempts: z.number().default(3),
|
||||
backoff: z.object({
|
||||
type: z.enum(['exponential', 'fixed']).default('exponential'),
|
||||
delay: z.number().default(2000),
|
||||
}).default({}),
|
||||
timeout: z.number().default(300000), // 5 minutes
|
||||
removeOnComplete: z.object({
|
||||
age: z.number().default(86400), // 24 hours
|
||||
count: z.number().default(100),
|
||||
}).default({}),
|
||||
removeOnFail: z.object({
|
||||
age: z.number().default(604800), // 7 days
|
||||
count: z.number().default(500),
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
report: z.object({
|
||||
priority: z.number().default(1),
|
||||
attempts: z.number().default(2),
|
||||
backoff: z.object({
|
||||
type: z.enum(['exponential', 'fixed']).default('exponential'),
|
||||
delay: z.number().default(5000),
|
||||
}).default({}),
|
||||
timeout: z.number().default(600000), // 10 minutes
|
||||
}).default({}),
|
||||
}).default({}),
|
||||
scheduler: z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
interval: z.number().default(60000), // Check every minute
|
||||
timezone: z.string().default('UTC'),
|
||||
}).default({}),
|
||||
});
|
||||
|
||||
export type WorkerConfig = z.infer<typeof workerConfigSchema>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue