interview-assistant/angular.json
nokker a75bacea5e Implement secure N8N webhook integration and resolve CORS issues
### 🔧 Technical Solution
- **API Key Authentication**: Migrated from Authelia session auth to X-N8N-API-KEY header authentication
- **CORS Resolution**: Eliminated preflight failures by removing authentication redirects from webhook endpoints
- **Error Handling**: Added graceful fallback for empty N8N responses with intelligent question generation
- **Type Safety**: Updated TypeScript interfaces for enhanced response format support

### 🛡️ Security Enhancements
- **Maintained Security**: N8N UI still protected by Authelia while webhooks use API key authentication
- **Audit Trail**: All webhook requests logged with API key identification for security monitoring
- **Rate Limiting**: Applied through Traefik middleware to prevent API abuse
- **Easy Key Rotation**: API keys can be changed instantly without affecting user sessions

### 📱 Application Updates
- **N8nSyncService**: Complete migration from Authelia to API key authentication
- **CV Upload Component**: Simplified flow without authentication popups for N8N integration
- **Fallback System**: Intelligent question generation based on CV content when N8N unavailable
- **User Experience**: Seamless PDF upload to analysis workflow without CORS barriers

### 🐳 Docker Configuration
- **Multi-stage Build**: Optimized Dockerfile with Node.js 20 and nginx:alpine
- **Docker Compose**: Complete service orchestration with port 3007 mapping
- **Nginx Configuration**: Custom MIME types for PDF.js worker files and SPA routing
- **SSL Integration**: Traefik labels for automatic HTTPS with proper CORS headers

### 🧪 Testing Results
-  **PDF Processing**: Successfully extracts text from uploaded CVs (2871+ characters)
-  **CORS Success**: OPTIONS and POST requests work without authentication redirects
-  **Webhook Integration**: Connects to N8N with X-N8N-API-KEY header
-  **Fallback Questions**: Generates contextual questions when N8N workflow unavailable
-  **Type Safety**: No TypeScript compilation errors with updated interfaces

### 💡 Intelligent Fallback Features
- **Technical Questions**: Generated based on actual CV skills (e.g., JavaScript experience)
- **Behavioral Questions**: Standard problem-solving and teamwork assessments
- **Experience-Specific**: Company and role-specific questions from work history
- **Career Development**: Growth and motivation questions tailored to experience level

### 🔗 Integration Points
- **Environment Config**: Added N8N API key and base URL configuration
- **Service Communication**: Direct HTTP with API key headers (no session dependency)
- **Response Handling**: Support for both N8N workflow responses and local fallback
- **Error Recovery**: Graceful degradation when external services unavailable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 21:38:22 +02:00

154 lines
4.1 KiB
JSON

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"interview-assistant": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
},
{
"glob": "**/*",
"input": "node_modules/pdfjs-dist/build",
"output": "pdfjs-dist/build"
}
],
"styles": [
"src/styles.scss"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2MB",
"maximumError": "5MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "20kB",
"maximumError": "50kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "interview-assistant:build:production"
},
"development": {
"buildTarget": "interview-assistant:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "interview-assistant:serve"
},
"configurations": {
"production": {
"devServerTarget": "interview-assistant:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
},
"ct": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "interview-assistant:serve",
"watch": true,
"headless": false,
"testingType": "component"
},
"configurations": {
"development": {
"devServerTarget": "interview-assistant:serve:development"
}
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "interview-assistant:serve",
"watch": true,
"headless": false
},
"configurations": {
"production": {
"devServerTarget": "interview-assistant:serve:production"
}
}
}
}
}
},
"cli": {
"schematicCollections": [
"@cypress/schematic",
"@schematics/angular"
]
}
}