feat: Complete implementation of Tasks 1-5 - CIM Document Processor

Backend Infrastructure:
- Complete Express server setup with security middleware (helmet, CORS, rate limiting)
- Comprehensive error handling and logging with Winston
- Authentication system with JWT tokens and session management
- Database models and migrations for Users, Documents, Feedback, and Processing Jobs
- API routes structure for authentication and document management
- Integration tests for all server components (86 tests passing)

Frontend Infrastructure:
- React application with TypeScript and Vite
- Authentication UI with login form, protected routes, and logout functionality
- Authentication context with proper async state management
- Component tests with proper async handling (25 tests passing)
- Tailwind CSS styling and responsive design

Key Features:
- User registration, login, and authentication
- Protected routes with role-based access control
- Comprehensive error handling and user feedback
- Database schema with proper relationships
- Security middleware and validation
- Production-ready build configuration

Test Coverage: 111/111 tests passing
Tasks Completed: 1-5 (Project setup, Database, Auth system, Frontend UI, Backend infrastructure)

Ready for Task 6: File upload backend infrastructure
This commit is contained in:
Jon
2025-07-27 13:29:26 -04:00
commit 5a3c961bfc
72 changed files with 24326 additions and 0 deletions

160
backend/src/config/env.ts Normal file
View File

@@ -0,0 +1,160 @@
import dotenv from 'dotenv';
import Joi from 'joi';
// Load environment variables
dotenv.config();
// Environment validation schema
const envSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
PORT: Joi.number().default(5000),
// Database
DATABASE_URL: Joi.string().required(),
DB_HOST: Joi.string().default('localhost'),
DB_PORT: Joi.number().default(5432),
DB_NAME: Joi.string().required(),
DB_USER: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
// Redis
REDIS_URL: Joi.string().default('redis://localhost:6379'),
REDIS_HOST: Joi.string().default('localhost'),
REDIS_PORT: Joi.number().default(6379),
// JWT
JWT_SECRET: Joi.string().required(),
JWT_EXPIRES_IN: Joi.string().default('1h'),
JWT_REFRESH_SECRET: Joi.string().required(),
JWT_REFRESH_EXPIRES_IN: Joi.string().default('7d'),
// File Upload
MAX_FILE_SIZE: Joi.number().default(104857600), // 100MB
UPLOAD_DIR: Joi.string().default('uploads'),
ALLOWED_FILE_TYPES: Joi.string().default('application/pdf'),
// LLM
LLM_PROVIDER: Joi.string().valid('openai', 'anthropic').default('openai'),
OPENAI_API_KEY: Joi.string().when('LLM_PROVIDER', {
is: 'openai',
then: Joi.required(),
otherwise: Joi.optional()
}),
ANTHROPIC_API_KEY: Joi.string().when('LLM_PROVIDER', {
is: 'anthropic',
then: Joi.required(),
otherwise: Joi.optional()
}),
LLM_MODEL: Joi.string().default('gpt-4'),
LLM_MAX_TOKENS: Joi.number().default(4000),
LLM_TEMPERATURE: Joi.number().min(0).max(2).default(0.1),
// Storage
STORAGE_TYPE: Joi.string().valid('local', 's3').default('local'),
AWS_ACCESS_KEY_ID: Joi.string().when('STORAGE_TYPE', {
is: 's3',
then: Joi.required(),
otherwise: Joi.optional()
}),
AWS_SECRET_ACCESS_KEY: Joi.string().when('STORAGE_TYPE', {
is: 's3',
then: Joi.required(),
otherwise: Joi.optional()
}),
AWS_REGION: Joi.string().when('STORAGE_TYPE', {
is: 's3',
then: Joi.required(),
otherwise: Joi.optional()
}),
AWS_S3_BUCKET: Joi.string().when('STORAGE_TYPE', {
is: 's3',
then: Joi.required(),
otherwise: Joi.optional()
}),
// Security
BCRYPT_ROUNDS: Joi.number().default(12),
RATE_LIMIT_WINDOW_MS: Joi.number().default(900000), // 15 minutes
RATE_LIMIT_MAX_REQUESTS: Joi.number().default(100),
// Logging
LOG_LEVEL: Joi.string().valid('error', 'warn', 'info', 'debug').default('info'),
LOG_FILE: Joi.string().default('logs/app.log'),
}).unknown();
// Validate environment variables
const { error, value: envVars } = envSchema.validate(process.env);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
// Export validated configuration
export const config = {
env: envVars.NODE_ENV,
nodeEnv: envVars.NODE_ENV,
port: envVars.PORT,
frontendUrl: process.env['FRONTEND_URL'] || 'http://localhost:3000',
database: {
url: envVars.DATABASE_URL,
host: envVars.DB_HOST,
port: envVars.DB_PORT,
name: envVars.DB_NAME,
user: envVars.DB_USER,
password: envVars.DB_PASSWORD,
},
redis: {
url: envVars.REDIS_URL,
host: envVars.REDIS_HOST,
port: envVars.REDIS_PORT,
},
jwt: {
secret: envVars.JWT_SECRET,
expiresIn: envVars.JWT_EXPIRES_IN,
refreshSecret: envVars.JWT_REFRESH_SECRET,
refreshExpiresIn: envVars.JWT_REFRESH_EXPIRES_IN,
},
upload: {
maxFileSize: envVars.MAX_FILE_SIZE,
uploadDir: envVars.UPLOAD_DIR,
allowedFileTypes: envVars.ALLOWED_FILE_TYPES.split(','),
},
llm: {
provider: envVars.LLM_PROVIDER,
openaiApiKey: envVars.OPENAI_API_KEY,
anthropicApiKey: envVars.ANTHROPIC_API_KEY,
model: envVars.LLM_MODEL,
maxTokens: envVars.LLM_MAX_TOKENS,
temperature: envVars.LLM_TEMPERATURE,
},
storage: {
type: envVars.STORAGE_TYPE,
aws: {
accessKeyId: envVars.AWS_ACCESS_KEY_ID,
secretAccessKey: envVars.AWS_SECRET_ACCESS_KEY,
region: envVars.AWS_REGION,
bucket: envVars.AWS_S3_BUCKET,
},
},
security: {
bcryptRounds: envVars.BCRYPT_ROUNDS,
rateLimit: {
windowMs: envVars.RATE_LIMIT_WINDOW_MS,
maxRequests: envVars.RATE_LIMIT_MAX_REQUESTS,
},
},
logging: {
level: envVars.LOG_LEVEL,
file: envVars.LOG_FILE,
},
};
export default config;