| # Technical Architecture |
|
|
| Complete technical documentation for the modern Job Apply AI React SaaS application. |
|
|
| ## Overview |
|
|
| The application uses a **monolithic architecture** with: |
| - **Frontend**: React 18 + TypeScript + Vite |
| - **Backend**: Flask REST API |
| - **Communication**: JSON over HTTP |
| - **State**: Zustand (frontend), Session (backend) |
| - **Styling**: Tailwind CSS + Framer Motion |
|
|
| ## Application Flow |
|
|
| ``` |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β USER BROWSER β |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ |
| β React App (Port 3000) β |
| β βββ HomePage (landing page) β |
| β βββ WorkflowPage (3-step wizard) β |
| β βββ JobListPage (job browsing + selection) β |
| β βββ SettingsModal (configuration) β |
| β β |
| β State: Zustand Store β |
| β βββ jobs: Job[] β |
| β βββ cvTemplate: CVTemplate β |
| β βββ tailoringMode: 'local' | 'api' β |
| β βββ notifications: Toast[] β |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β (REST API Calls) |
| β (JSON requests/responses) |
| β |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β FLASK SERVER (Port 5050) β |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ |
| β Route Handlers β |
| β βββ GET /api/health β |
| β βββ GET /api/config β |
| β βββ POST /api/upload-cv β |
| β βββ POST /api/search β |
| β βββ GET /api/jobs β |
| β βββ POST /api/generate-cv/<id> β |
| β βββ POST /api/generate-all-cvs β |
| β βββ GET /api/download/<filename> β |
| β β |
| β Business Logic β |
| β βββ LinkedInScraper (job collection) β |
| β βββ CVAnalyzer (skill extraction) β |
| β βββ CVModifier (CV customization) β |
| β β |
| β Data Storage β |
| β βββ .runtime/ (local filesystem) β |
| β βββ uploads/cv_templates/ β |
| β βββ uploads/cvs/generated_cvs/ β |
| β βββ uploads/jobs/excel_exports/ β |
| β βββ uploads/session_state/json_files/ β |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| ``` |
|
|
| ## Component Hierarchy |
|
|
| ``` |
| App |
| βββ HomePage |
| β βββ Header |
| β βββ HeroSection |
| β βββ FeaturesGrid |
| β βββ Footer |
| β |
| βββ WorkflowPage |
| β βββ Header |
| β βββ ProgressSteps |
| β βββ CVUpload (step 1) |
| β βββ JobSearch (step 2) |
| β βββ ReviewSection (step 3) |
| β |
| βββ JobListPage |
| β βββ JobListHeader |
| β βββ SelectionControls |
| β βββ JobCard (repeated) |
| β β βββ JobHeader |
| β β βββ SkillBadges |
| β β βββ ExpandedDetails |
| β β βββ GenerateButton |
| β βββ BatchProgress (floating) |
| β |
| βββ SettingsModal |
| β βββ TailoringModeSelector |
| β βββ LLMProviderSelector |
| β βββ AdvancedOptions |
| β |
| βββ Toast (notification) |
| ``` |
|
|
| ## Data Models |
|
|
| ### Frontend (TypeScript) |
|
|
| ```typescript |
| interface Job { |
| id: string; |
| title: string; |
| company: string; |
| link: string; |
| source: 'LinkedIn' | 'Indeed' | 'Other'; |
| posted_days_ago: number; |
| description: string; |
| matched_skills: string[]; |
| matched_categories: Record<string, string[]>; |
| salary?: string; |
| location?: string; |
| } |
| |
| interface CVTemplate { |
| filename: string; |
| uploadedAt: string; |
| size: number; |
| content?: ArrayBuffer; |
| } |
| |
| interface GeneratedCV { |
| jobId: string; |
| jobTitle: string; |
| company: string; |
| filename: string; |
| url: string; |
| generatedAt: string; |
| status: 'success' | 'failed'; |
| error?: string; |
| } |
| |
| interface AppState { |
| jobs: Job[]; |
| cvTemplate: CVTemplate | null; |
| tailoringMode: 'local' | 'api'; |
| isSearching: boolean; |
| isGenerating: boolean; |
| selectedJobIds: Set<string>; |
| batchProgress: BatchProgress; |
| notification: Toast | null; |
| // ... setters and methods |
| } |
| ``` |
|
|
| ### Backend (Python/JSON) |
|
|
| ```python |
| # Job response from /api/search |
| { |
| "jobs": [ |
| { |
| "id": "job_0", |
| "title": "Senior React Developer", |
| "company": "Tech Corp", |
| "link": "https://...", |
| "source": "LinkedIn", |
| "posted_days_ago": 3, |
| "description": "Full job description...", |
| "location": "San Francisco, CA", |
| "matched_skills": ["React", "TypeScript", "REST API"], |
| "matched_categories": { |
| "Frameworks & Libraries": ["React"], |
| "Programming Languages": ["TypeScript"], |
| "Tools & Platforms": ["REST API"] |
| } |
| } |
| ], |
| "excel_file": "linkedin_jobs_2024-01-15_1705353600.xlsx" |
| } |
| |
| # CV generation response from /api/generate-cv/<id> |
| { |
| "success": true, |
| "filename": "CV_20240115_120530_TechCorp_ReactDeveloper.docx", |
| "job_title": "Senior React Developer", |
| "company": "Tech Corp", |
| "message": "CV generated successfully" |
| } |
| ``` |
|
|
| ## Request/Response Examples |
|
|
| ### Upload CV |
|
|
| **Request:** |
| ``` |
| POST /api/upload-cv |
| Content-Type: multipart/form-data |
| |
| file: (binary .docx file) |
| ``` |
|
|
| **Response:** |
| ```json |
| { |
| "success": true, |
| "filename": "resume.docx", |
| "message": "CV template uploaded successfully" |
| } |
| ``` |
|
|
| ### Search Jobs |
|
|
| **Request:** |
| ```json |
| POST /api/search |
| { |
| "keyword": "React Developer", |
| "location": "San Francisco", |
| "max_jobs": 10, |
| "max_days_old": 14, |
| "tailoring_mode": "local" |
| } |
| ``` |
|
|
| **Response:** |
| ```json |
| { |
| "success": true, |
| "jobs": [/* job objects */], |
| "excel_file": "linkedin_jobs_2024-01-15_1234567890.xlsx", |
| "message": "Found 10 jobs" |
| } |
| ``` |
|
|
| ### Generate All CVs |
|
|
| **Request:** |
| ```json |
| POST /api/generate-all-cvs |
| { |
| "job_indices": [0, 2, 5] // null for all |
| } |
| ``` |
|
|
| **Response:** |
| ```json |
| { |
| "success": true, |
| "successful": [ |
| { |
| "job_index": 0, |
| "filename": "CV_..._TechCorp_ReactDev.docx", |
| "job_title": "Senior React Developer", |
| "company": "Tech Corp" |
| } |
| ], |
| "failed": [ |
| { |
| "job_index": 2, |
| "error": "File processing error", |
| "job_title": "Mid-level Engineer" |
| } |
| ], |
| "zip_filename": "CVs_20240115_120530.zip", |
| "total_generated": 2, |
| "total_failed": 1 |
| } |
| ``` |
|
|
| ## State Management Flow |
|
|
| ### Zustand Store Pattern |
|
|
| ```typescript |
| // 1. Define store with getter/setter/action methods |
| export const useJobStore = create<AppState>()( |
| persist((set, get) => ({ |
| jobs: [], |
| setJobs: (jobs) => set({ jobs }), |
| addJob: (job) => set((state) => ({ |
| jobs: [...state.jobs, job] |
| })), |
| toggleJobSelection: (jobId) => set((state) => ({ |
| selectedJobIds: new Set(...) |
| })), |
| // ... more methods |
| }), { |
| name: 'job-apply-store', // localStorage key |
| partialize: (state) => ({ // what to persist |
| tailoringMode: state.tailoringMode, |
| llmProvider: state.llmProvider, |
| // Don't persist large data |
| }) |
| }) |
| ); |
| |
| // 2. Use in components |
| const MyComponent = () => { |
| const { jobs, setJobs, isSearching } = useJobStore(); |
| // Component re-renders when state changes |
| }; |
| |
| // 3. Update state |
| await handleSearch(); // calls setJobs() |
| // Component automatically re-renders |
| ``` |
|
|
| ### Session Flow |
|
|
| ``` |
| 1. Browser β Upload CV β Flask saves to .runtime/uploads/ |
| 2. Browser β Search β Flask queries LinkedIn |
| 3. Flask β Processes results β Saves to .runtime/session_state/{uuid}.json |
| 4. Browser β Get jobs from session |
| 5. Browser β Generate CV β Flask reads job from session state |
| 6. Flask β Modifies document β Saves to .runtime/uploads/cvs/ |
| 7. Browser β Download CV |
| ``` |
|
|
| ## File System Structure |
|
|
| ``` |
| .runtime/ |
| βββ uploads/ |
| β βββ cv_template_1705353600.docx |
| β β |
| β βββ cvs/ |
| β β βββ CV_20240115_120530_TechCorp_ReactDeveloper.docx |
| β β βββ CV_20240115_120531_DataCorp_Engineer.docx |
| β β βββ CVs_20240115_120530.zip |
| β β |
| β βββ jobs/ |
| β β βββ linkedin_jobs_2024-01-15_1705353600.xlsx |
| β β |
| β βββ session_state/ |
| β βββ c8f9d2e1-4b3c-5a7f-8e9c-2d4f6a8b9c1d.json |
| ``` |
|
|
| ## API Error Handling |
|
|
| ### Client-Side |
|
|
| ```typescript |
| try { |
| const result = await jobsAPI.searchJobs(filters); |
| setJobs(result.jobs); |
| } catch (error) { |
| setNotification({ |
| type: 'error', |
| message: error.message |
| }); |
| } |
| ``` |
|
|
| ### Server-Side |
|
|
| ```python |
| @app.route('/api/search', methods=['POST']) |
| def api_search_jobs(): |
| try: |
| # Validate input |
| if not keyword: return jsonify({...}, 400) |
| |
| # Process |
| jobs = scraper.scrape_job_listings(...) |
| |
| # Return |
| return jsonify({'success': True, 'jobs': jobs}) |
| except Exception as e: |
| logger.error(str(e)) |
| return jsonify({'success': False, 'error': str(e)}, 500) |
| ``` |
|
|
| ### Error Types |
|
|
| | Type | HTTPCode | User Message | |
| |------|----------|--------------| |
| | Missing required field | 400 | "Keyword and location are required" | |
| | Invalid file type | 400 | "Only .docx files are supported" | |
| | File not found | 404 | "CV template not found" | |
| | Server error | 500 | "An error occurred. Please try again" | |
|
|
| ## Performance Considerations |
|
|
| ### Frontend |
|
|
| - **Code Splitting**: Route-based chunk splitting via Vite |
| - **Lazy Loading**: Components load on demand |
| - **Memoization**: React.memo for expensive components |
| - **Debouncing**: Search input debounced |
| - **CSS**: Tailwind purges unused styles |
|
|
| ### Backend |
|
|
| - **Batch Operations**: Process multiple CVs in single request |
| - **Session Caching**: Job data cached in .runtime/ |
| - **Connection Pooling**: Selenium reuses browser window |
| - **Async**: Non-blocking operations where possible |
|
|
| ### Network |
|
|
| - **JSON": Compact data format vs XML/Form |
| - **Compression**: Gzip enabled by default |
| - **Caching": Etag headers for static assets |
| - **Streaming**: Files streamed for download |
|
|
| ## Security Measures |
|
|
| ### Input Validation |
|
|
| ```python |
| # File type check |
| if not file.filename.endswith('.docx'): |
| return error |
| |
| # Size limit |
| if file.size > 10_000_000: |
| return error |
| |
| # Path traversal prevention |
| if '..' in filename or '/' in filename: |
| return error |
| ``` |
|
|
| ### Session Security |
|
|
| ```python |
| # Unique session key per run |
| app.config['SESSION_COOKIE_NAME'] = f"job_apply_ai_{int(time.time())}" |
| |
| # Secure path storage |
| _session_state_path(state_id) # Safe path construction |
| |
| # CORS configuration |
| CORS(app, resources={r"/api/*":{"origins":"*"}}) |
| ``` |
|
|
| ### Data Protection |
|
|
| - No passwords stored |
| - No PII logged |
| - Uploaded files deleted after processing (optional) |
| - Session files cleaned up |
|
|
| ## Scaling Architecture |
|
|
| ### Horizontal Scaling |
|
|
| ``` |
| User β Load Balancer |
| βββ Flask Server 1 β Shared Session Store (Redis) |
| βββ Flask Server 2 β Shared File Storage (S3/NFS) |
| βββ Flask Server 3 |
| ``` |
|
|
| ### Vertical Scaling |
|
|
| ``` |
| Single Machine |
| βββ Increase Worker Processes |
| βββ Add RAM for larger job batches |
| βββ SSD for faster CV processing |
| βββ Dedicated GPU for future AI tasks |
| ``` |
|
|
| ## Deployment Targets |
|
|
| ### Development |
| - Vite dev server on :3000 |
| - Flask dev server on :5050 |
| - Hot reload enabled |
|
|
| ### Staging |
| - Docker containers |
| - Cloud platform (AWS/GCP/Azure) |
| - Full testing suite |
|
|
| ### Production |
| - Built React app on :3000 (or CDN) |
| - Gunicorn server on :5050 |
| - Database for sessions |
| - Cloud storage for files |
|
|
| ## Future Architectural Improvements |
|
|
| ### Phase 2 |
| - [ ] Authentication/Authorization system |
| - [ ] Database (PostgreSQL) for persistent storage |
| - [ ] Redis for session caching and queue |
| - [ ] Celery for async job processing |
| - [ ] WebSocket for real-time progress |
|
|
| ### Phase 3 |
| - [ ] Microservices architecture |
| - [ ] Kubernetes orchestration |
| - [ ] Message queue (RabbitMQ) |
| - [ ] API rate limiting |
| - [ ] Advanced caching |
|
|
| ### Phase 4 |
| - [ ] GraphQL API option |
| - [ ] Event streaming (Kafka) |
| - [ ] Service mesh (Istio) |
| - [ ] Distributed tracing |
| - [ ] Advanced analytics |
|
|
| ## Technology Decision Rationale |
|
|
| | Choice | Why | |
| |--------|-----| |
| | React | Large ecosystem, component reusability, strong community | |
| | Zustand | Simple, no boilerplate compared to Redux | |
| | Tailwind | Fast development, consistent design system | |
| | Framer Motion | Smooth animations, good perf, learning curve | |
| | Vite | Fast builds, excellent DX, modern tooling | |
| | Flask | Lightweight, Pythonic, good for API + rendering | |
| | pandas | Data processing, Excel export | |
| | Selenium | Web automation, JS-heavy site support | |
| | spaCy | NLP, good performance, pre-trained models | |
|
|
| ## Monitoring & Observability |
|
|
| ### Logging |
| ```python |
| logger.info("Job search initiated") |
| logger.warning("CV generation slow") |
| logger.error("Scraping failed") |
| ``` |
|
|
| ### Metrics to Track |
| - Page load times |
| - API response times |
| - CV generation duration |
| - Success/failure rates |
| - File upload sizes |
|
|
| ### Debugging |
| ``` |
| Browser DevTools β Network β Check API responses |
| Browser Console β Check React errors |
| Flask Terminal β Check server logs |
| Browser Storage β Check localStorage/state |
| ``` |
|
|
| --- |
|
|
| This architecture provides a solid foundation for growth and future enhancements while maintaining simplicity and ease of development. |
|
|