From 49bccf8f15a8645f106a4009946264d0dfade0c2 Mon Sep 17 00:00:00 2001 From: Giancarmine Salucci Date: Wed, 18 Feb 2026 01:21:44 +0100 Subject: [PATCH] simplify --- .prettierrc | 5 +- README.md | 40 +- docker-compose.yml | 24 +- docker-compose_ori.yml | 8 +- docs/API.md | 486 +- docs/ARCHITECTURE.md | 51 + docs/CODE_STYLE.md | 346 +- docs/FINDINGS.md | 611 +- docs/MIGRATION.md | 135 +- docs/SVELTEKIT_SSR_GUIDE.md | 103 +- docs/TESTING.md | 158 +- docs/Tandoor (2.3.6).yaml | 8788 ++++++++--------- eslint.config.js | 14 +- package.json | 122 +- playwright.config.ts | 48 +- scripts/gen-auth.js | 40 +- scripts/gen-favicon-ico.js | 76 +- scripts/gen-favicon.js | 84 +- scripts/generate-icon-512.js | 96 +- secrets/auth.json | 268 +- src/app.html | 2 +- src/app.server.ts | 54 +- src/hooks.server.ts | 64 +- src/lib/client/PWAInstallManager.ts | 336 +- src/lib/client/PushNotificationManager.ts | 634 +- src/lib/client/ServiceWorkerMessageHandler.ts | 340 +- src/lib/server/api/errorHandler.ts | 88 +- src/lib/server/api/errors.ts | 32 +- src/lib/server/browser.ts | 240 +- src/lib/server/extraction.ts | 3326 ++++--- src/lib/server/llm.ts | 6 +- .../notifications/PushNotificationService.ts | 406 +- src/lib/server/parser.ts | 420 +- src/lib/server/queue/QueueManager.ts | 799 +- src/lib/server/queue/QueueProcessor.ts | 794 +- src/lib/server/queue/config.ts | 6 +- src/lib/server/queue/types.ts | 268 +- src/lib/server/scheduler.ts | 396 +- src/lib/server/tandoor-config.ts | 24 +- src/lib/server/tandoor.ts | 1010 +- src/lib/server/utils/logger.ts | 100 +- src/lib/server/validation/instagram-url.ts | 10 +- src/routes/api/extract/+server.ts | 10 +- src/routes/api/health/+server.ts | 93 +- src/routes/api/llm-health/+server.ts | 28 +- .../api/notifications/subscribe/+server.ts | 135 +- src/routes/api/notifications/test/+server.ts | 120 +- .../api/notifications/vapid-key/+server.ts | 45 +- src/routes/api/queue/+server.ts | 230 +- src/routes/api/queue/[id]/+server.ts | 134 +- src/routes/api/queue/[id]/retry/+server.ts | 99 +- src/routes/api/queue/stream/+server.ts | 394 +- src/routes/api/tandoor-config/+server.ts | 10 +- src/routes/api/tandoor/+server.ts | 86 +- src/routes/page.svelte.spec.ts | 2 +- src/service-worker.ts | 427 +- src/tests/error-handler-logging.spec.ts | 130 +- src/tests/extraction-logging.spec.ts | 51 +- ...raction-url-validation.integration.spec.ts | 18 +- src/tests/favicon-ico.spec.ts | 18 +- src/tests/favicon.spec.ts | 42 +- src/tests/fixtures.ts | 328 +- src/tests/icon-512.test.ts | 68 +- .../instagram-caption-extraction.e2e.spec.ts | 97 +- .../instagram-caption-extraction.unit.spec.ts | 100 +- src/tests/llm-logging.spec.ts | 5 +- src/tests/logger.spec.ts | 303 +- src/tests/notification-test-api.spec.ts | 288 +- src/tests/parser-logging.spec.ts | 15 +- src/tests/push-notification-service.spec.ts | 299 +- src/tests/push-notifications.e2e.spec.ts | 294 +- src/tests/queue-api.spec.ts | 1192 +-- src/tests/queue-manager-logging.spec.ts | 139 +- src/tests/queue-manager.spec.ts | 692 +- src/tests/queue-processor-logging.spec.ts | 156 +- src/tests/queue-processor.spec.ts | 457 +- src/tests/queue-sse.spec.ts | 262 +- src/tests/scheduler.integration.spec.ts | 268 +- src/tests/scheduler.spec.ts | 410 +- src/tests/sse-extraction.spec.ts | 34 +- src/tests/tandoor-logging.spec.ts | 26 +- src/tests/thumbnail-validation.spec.ts | 2 +- svelte.config.js | 2 +- vite.config.ts | 32 +- 84 files changed, 14474 insertions(+), 13925 deletions(-) diff --git a/.prettierrc b/.prettierrc index 891c0f8..819fa57 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,10 +3,7 @@ "singleQuote": true, "trailingComma": "none", "printWidth": 100, - "plugins": [ - "prettier-plugin-svelte", - "prettier-plugin-tailwindcss" - ], + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "overrides": [ { "files": "*.svelte", diff --git a/README.md b/README.md index b16054b..0509cef 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A modern web application that extracts recipes from Instagram posts and saves th ## 🚀 Features ### Core Functionality + - **Async Queue Processing**: Fire-and-forget recipe extraction with background processing - **Real-time Updates**: Server-Sent Events for live progress tracking - **Push Notifications**: Background notifications when recipes complete @@ -13,6 +14,7 @@ A modern web application that extracts recipes from Instagram posts and saves th - **PWA Support**: Installable Progressive Web App with offline capabilities ### User Experience + - **Queue Dashboard**: Monitor all recipe extractions in real-time - **Share Integration**: Browser share target for easy URL submission - **Responsive Design**: Works on desktop, tablet, and mobile @@ -20,6 +22,7 @@ A modern web application that extracts recipes from Instagram posts and saves th - **Progress Tracking**: Visual progress through extraction phases ### Technical Architecture + - **SvelteKit Frontend**: Modern reactive UI with TypeScript - **Hexagonal Architecture**: Clean separation of concerns - **In-Memory Queue**: High-performance processing with configurable concurrency @@ -29,6 +32,7 @@ A modern web application that extracts recipes from Instagram posts and saves th ## 📋 API Endpoints ### Queue Management + - `POST /api/queue` - Enqueue Instagram URL for processing - `GET /api/queue` - List queue items with filtering and pagination - `GET /api/queue/{id}` - Get specific queue item details @@ -36,18 +40,21 @@ A modern web application that extracts recipes from Instagram posts and saves th - `GET /api/queue/stream` - Server-Sent Events for real-time updates ### Push Notifications + - `POST /api/notifications/subscribe` - Subscribe to push notifications - `DELETE /api/notifications/subscribe` - Unsubscribe from notifications - `GET /api/notifications/vapid-key` - Get VAPID public key ### Legacy Endpoints (Deprecated) + - ~~`POST /api/extract`~~ - Use `/api/queue` instead - ~~`GET /api/extract-stream`~~ - Use `/api/queue/stream` instead ## 🛠 Development Setup ### Prerequisites -- Node.js 18+ + +- Node.js 18+ - npm or pnpm - Tandoor Recipe Manager instance (optional) - LLM API access (OpenAI, Anthropic, or local) @@ -79,6 +86,7 @@ open https://localhost:5173 ``` The app runs on HTTPS by default for: + - Service worker support (required for PWA) - Push notifications - Browser share target API @@ -89,6 +97,7 @@ The app runs on HTTPS by default for: The application uses HTTPS in development with SSL certificates signed by an external Caddy CA container. The current certificate is valid until **December 20, 2035** (10 years). **Certificate Information:** + - Location: `.ssl/` directory - CA Certificate: `.ssl/root.crt` (already trusted on the system) - Server Certificate: `.ssl/localhost.crt` @@ -97,18 +106,21 @@ The application uses HTTPS in development with SSL certificates signed by an ext Since the Caddy CA is already trusted on the system, the certificate should work without additional trust steps. If you encounter browser warnings: **Linux (Ubuntu/Debian):** + ```bash sudo cp .ssl/root.crt /usr/local/share/ca-certificates/caddy-local.crt sudo update-ca-certificates ``` **Chrome/Chromium:** + 1. Go to `chrome://settings/certificates` 2. Click "Authorities" → "Import" 3. Select `.ssl/root.crt` 4. Check "Trust this certificate for identifying websites" **Checking Certificate Expiration:** + ```bash openssl x509 -enddate -noout -in .ssl/localhost.crt ``` @@ -220,6 +232,7 @@ To enable web push notifications: ## 🏗 Architecture Overview ### Queue System + ``` User submits URL → Queue Manager → Queue Processor ↓ @@ -231,7 +244,7 @@ User submits URL → Queue Manager → Queue Processor ### Processing Pipeline 1. **Extraction Phase**: Browser automation extracts text and images -2. **Parsing Phase**: LLM converts text to structured recipe data +2. **Parsing Phase**: LLM converts text to structured recipe data 3. **Upload Phase**: Automatic upload to Tandoor (if configured) Each phase tracks progress and can fail independently with proper error handling. @@ -247,9 +260,9 @@ Each phase tracks progress and can fail independently with proper error handling # Run all tests npm test -# Run specific test suites +# Run specific test suites npm run test:unit # Unit tests only -npm run test:client # Browser tests only +npm run test:client # Browser tests only npm run test:server # Server tests only # Run tests in watch mode @@ -257,9 +270,10 @@ npm run test:watch ``` Test Coverage: + - **138 total tests** covering all major components - Queue Manager: 28 tests -- Queue Processor: 5 integration tests +- Queue Processor: 5 integration tests - API Endpoints: 17 tests - SSE Streaming: 6 tests - Frontend Components: Browser tests @@ -279,11 +293,13 @@ npm run preview ### Deployment The app is built as a Node.js application with the following outputs: + - `/.svelte-kit/output/server/` - Server bundle -- `/.svelte-kit/output/client/` - Static assets +- `/.svelte-kit/output/client/` - Static assets - `/build/` - Adapter output Deploy the server bundle with: + ```bash node build/index.js ``` @@ -307,13 +323,15 @@ CMD ["node", "build"] The app was migrated from a synchronous extraction system to an async queue-based system: **Before (Synchronous)**: + - User waited for entire extraction process to complete - No progress tracking during processing -- No retry capability for failures +- No retry capability for failures - Single-threaded processing - Limited error handling **After (Async Queue)**: + - Fire-and-forget: submit URL and redirect immediately - Real-time progress tracking via SSE - Comprehensive retry system for failures @@ -324,16 +342,18 @@ The app was migrated from a synchronous extraction system to an async queue-base ### API Migration **Old Synchronous Endpoints** (Deprecated): + ```bash POST /api/extract # Submit URL and wait for completion GET /api/extract-stream # Long-polling for progress ``` **New Queue Endpoints**: + ```bash POST /api/queue # Submit URL, get queue ID immediately GET /api/queue # List all queue items -GET /api/queue/{id} # Get specific item status +GET /api/queue/{id} # Get specific item status POST /api/queue/{id}/retry # Retry failed items GET /api/queue/stream # Real-time SSE updates ``` @@ -344,13 +364,14 @@ If migrating from the old system: 1. **Update Client Code**: Replace `/api/extract` calls with `/api/queue` 2. **Handle Async Responses**: Process queue ID instead of waiting for completion -3. **Add Progress Tracking**: Implement SSE listeners for real-time updates +3. **Add Progress Tracking**: Implement SSE listeners for real-time updates 4. **Update Error Handling**: Handle new error classification system 5. **Add Retry Logic**: Implement retry functionality for failed items ### Backward Compatibility The legacy endpoints are still available but deprecated: + - They will return `410 Gone` status with migration instructions - Support will be removed in a future version - All new development should use the queue endpoints @@ -383,4 +404,3 @@ This project is licensed under the MIT License - see the LICENSE file for detail - [Tandoor Recipe Manager](https://docs.tandoor.dev/) - Recipe management system - [Workbox](https://developers.google.com/web/tools/workbox) - PWA capabilities - [fastq](https://github.com/mcollina/fastq) - High-performance queue processing - diff --git a/docker-compose.yml b/docker-compose.yml index 43c57ab..ddd9051 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,34 +4,34 @@ services: container_name: insta-recipe network_mode: host ports: - - "3000:3000" + - '3000:3000' environment: # LLM Configuration (Required) - OPENAI_BASE_URL=${OPENAI_BASE_URL} - OPENAI_API_KEY=${OPENAI_API_KEY} - LLM_MODEL=${LLM_MODEL:-google/gemma-3-4b} - + # Queue Configuration (Optional) - QUEUE_CONCURRENCY=${QUEUE_CONCURRENCY:-2} - QUEUE_MAX_RETRIES=${QUEUE_MAX_RETRIES:-3} - + # Tandoor Integration (Optional) - TANDOOR_ENABLED=${TANDOOR_ENABLED:-false} - TANDOOR_SERVER_URL=${TANDOOR_SERVER_URL} - TANDOOR_SPACE=${TANDOOR_SPACE:-1} - TANDOOR_TOKEN=${TANDOOR_TOKEN} - + # Push Notifications (Optional) - VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY} - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY} - + # Authentication Scheduler (Optional) - AUTH_SCHEDULER_ENABLED=${AUTH_SCHEDULER_ENABLED:-false} - AUTH_SCHEDULER_INTERVAL_MINUTES=${AUTH_SCHEDULER_INTERVAL_MINUTES:-720} - + # Playwright Configuration - DISPLAY=:99 - + # Node.js Environment - NODE_ENV=production security_opt: @@ -40,8 +40,14 @@ services: - ./secrets:/app/secrets restart: unless-stopped healthcheck: - test: ["CMD", "node", "-e", "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"] + test: + [ + 'CMD', + 'node', + '-e', + "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))" + ] interval: 30s timeout: 10s retries: 3 - start_period: 40s \ No newline at end of file + start_period: 40s diff --git a/docker-compose_ori.yml b/docker-compose_ori.yml index 466de75..94993ee 100644 --- a/docker-compose_ori.yml +++ b/docker-compose_ori.yml @@ -2,7 +2,7 @@ services: app: build: . ports: - - "5173:5173" + - '5173:5173' environment: - PLAYWRIGHT_WS_ENDPOINT=ws://playwright-service:3000 - OPENAI_BASE_URL=http://ollama:11434/v1 @@ -18,7 +18,7 @@ services: playwright-service: build: ./playwright-service ipc: host - ports: ["3000:3000"] + ports: ['3000:3000'] environment: - DISPLAY=:99 security_opt: @@ -26,9 +26,9 @@ services: ollama: image: ollama/ollama:latest - ports: ["11434:11434"] + ports: ['11434:11434'] volumes: - ollama_data:/root/.ollama volumes: - ollama_data: \ No newline at end of file + ollama_data: diff --git a/docs/API.md b/docs/API.md index ae0ec43..999210a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -5,6 +5,7 @@ This document describes the InstaRecipe API endpoints for the async queue-based ## Base URL All API endpoints are relative to your InstaRecipe instance: + ``` https://your-instarecipe-instance.com/api ``` @@ -23,13 +24,16 @@ All endpoints return standardized error responses: ```json { - "error": "Error type", - "message": "Human-readable error message", - "details": { /* Additional error context */ } + "error": "Error type", + "message": "Human-readable error message", + "details": { + /* Additional error context */ + } } ``` HTTP status codes follow REST conventions: + - `200` - Success - `201` - Created - `400` - Bad Request (invalid input) @@ -45,13 +49,15 @@ HTTP status codes follow REST conventions: Enqueue an Instagram URL for async processing. **Request:** + ```json { - "url": "https://instagram.com/p/abc123" + "url": "https://instagram.com/p/abc123" } ``` **Supported URL Formats:** + - Posts: `https://instagram.com/p/{post-id}` - Posts (www): `https://www.instagram.com/p/{post-id}` - Reels: `https://instagram.com/reel/{reel-id}` @@ -59,12 +65,14 @@ Enqueue an Instagram URL for async processing. - With query parameters: `https://instagram.com/reel/{reel-id}?utm_source=share` **URL Requirements:** + - Must use HTTPS protocol - Hostname must be `instagram.com` or `www.instagram.com` - Any Instagram path is accepted (posts, reels, IGTV, etc.) - Query parameters and hash fragments are allowed **Examples:** + ```json // Post URL { "url": "https://instagram.com/p/ABC123" } @@ -77,34 +85,36 @@ Enqueue an Instagram URL for async processing. ``` **Response (201 Created):** + ```json { - "id": "550e8400-e29b-41d4-a716-446655440000", - "url": "https://instagram.com/reel/DSevV5CDcNm/?utm_source=ig_web_copy_link", - "status": "pending", - "phases": [ - { - "name": "extraction", - "status": "pending", - "progress": 0 - }, - { - "name": "parsing", - "status": "pending", - "progress": 0 - }, - { - "name": "uploading", - "status": "pending", - "progress": 0 - } - ], - "createdAt": "2024-12-21T10:30:00Z", - "updatedAt": "2024-12-21T10:30:00Z" + "id": "550e8400-e29b-41d4-a716-446655440000", + "url": "https://instagram.com/reel/DSevV5CDcNm/?utm_source=ig_web_copy_link", + "status": "pending", + "phases": [ + { + "name": "extraction", + "status": "pending", + "progress": 0 + }, + { + "name": "parsing", + "status": "pending", + "progress": 0 + }, + { + "name": "uploading", + "status": "pending", + "progress": 0 + } + ], + "createdAt": "2024-12-21T10:30:00Z", + "updatedAt": "2024-12-21T10:30:00Z" } ``` **Errors:** + - `400` - Invalid URL format (not a valid URL) - `400` - URL must use HTTPS protocol - `400` - URL must be from instagram.com domain @@ -115,6 +125,7 @@ Enqueue an Instagram URL for async processing. List queue items with optional filtering, pagination, and sorting. **Query Parameters:** + - `status` (optional): Filter by status (`pending`, `in_progress`, `success`, `error`, `unhealthy`) - `limit` (optional): Number of items to return (default: 50, max: 100) - `offset` (optional): Number of items to skip (default: 0) @@ -122,6 +133,7 @@ List queue items with optional filtering, pagination, and sorting. - `order` (optional): Sort order (`asc`, `desc`) (default: `desc`) **Examples:** + ```bash GET /api/queue # All items GET /api/queue?status=error # Failed items only @@ -130,67 +142,68 @@ GET /api/queue?sort=status&order=asc # Sort by status ``` **Response (200 OK):** + ```json { - "items": [ - { - "id": "550e8400-e29b-41d4-a716-446655440000", - "url": "https://instagram.com/p/abc123", - "status": "success", - "phases": [ - { - "name": "extraction", - "status": "completed", - "startedAt": "2024-12-21T10:30:01Z", - "completedAt": "2024-12-21T10:30:15Z", - "progress": 100 - }, - { - "name": "parsing", - "status": "completed", - "startedAt": "2024-12-21T10:30:15Z", - "completedAt": "2024-12-21T10:30:25Z", - "progress": 100 - }, - { - "name": "uploading", - "status": "completed", - "startedAt": "2024-12-21T10:30:25Z", - "completedAt": "2024-12-21T10:30:30Z", - "progress": 100 - } - ], - "results": { - "recipe": { - "name": "Chocolate Chip Cookies", - "description": "Delicious homemade cookies", - "servings": 24, - "ingredients": [ - { - "food": "flour", - "amount": 2.25, - "unit": "cups" - } - ], - "steps": [ - { - "instruction": "Preheat oven to 375°F", - "time": 5 - } - ], - "keywords": ["cookies", "dessert", "chocolate"], - "image": "https://instagram.com/image.jpg" - }, - "tandoorUrl": "https://tandoor.example.com/recipe/123", - "extractedText": "Raw extracted text...", - "thumbnail": "https://instagram.com/thumbnail.jpg" - }, - "createdAt": "2024-12-21T10:30:00Z", - "updatedAt": "2024-12-21T10:30:30Z" - } - ], - "total": 42, - "hasMore": true + "items": [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "url": "https://instagram.com/p/abc123", + "status": "success", + "phases": [ + { + "name": "extraction", + "status": "completed", + "startedAt": "2024-12-21T10:30:01Z", + "completedAt": "2024-12-21T10:30:15Z", + "progress": 100 + }, + { + "name": "parsing", + "status": "completed", + "startedAt": "2024-12-21T10:30:15Z", + "completedAt": "2024-12-21T10:30:25Z", + "progress": 100 + }, + { + "name": "uploading", + "status": "completed", + "startedAt": "2024-12-21T10:30:25Z", + "completedAt": "2024-12-21T10:30:30Z", + "progress": 100 + } + ], + "results": { + "recipe": { + "name": "Chocolate Chip Cookies", + "description": "Delicious homemade cookies", + "servings": 24, + "ingredients": [ + { + "food": "flour", + "amount": 2.25, + "unit": "cups" + } + ], + "steps": [ + { + "instruction": "Preheat oven to 375°F", + "time": 5 + } + ], + "keywords": ["cookies", "dessert", "chocolate"], + "image": "https://instagram.com/image.jpg" + }, + "tandoorUrl": "https://tandoor.example.com/recipe/123", + "extractedText": "Raw extracted text...", + "thumbnail": "https://instagram.com/thumbnail.jpg" + }, + "createdAt": "2024-12-21T10:30:00Z", + "updatedAt": "2024-12-21T10:30:30Z" + } + ], + "total": 42, + "hasMore": true } ``` @@ -199,12 +212,14 @@ GET /api/queue?sort=status&order=asc # Sort by status Get details for a specific queue item. **Path Parameters:** + - `id`: Queue item UUID **Response (200 OK):** Returns the same queue item structure as in the list response. **Errors:** + - `400` - Invalid UUID format - `404` - Queue item not found @@ -213,22 +228,25 @@ Returns the same queue item structure as in the list response. Retry a failed queue item. Only items with `error` or `unhealthy` status can be retried. **Path Parameters:** + - `id`: Queue item UUID **Response (200 OK):** + ```json { - "success": true, - "message": "Item queued for retry", - "item": { - "id": "550e8400-e29b-41d4-a716-446655440000", - "status": "pending", - "updatedAt": "2024-12-21T11:00:00Z" - } + "success": true, + "message": "Item queued for retry", + "item": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "status": "pending", + "updatedAt": "2024-12-21T11:00:00Z" + } } ``` **Errors:** + - `400` - Invalid UUID format - `404` - Queue item not found - `409` - Cannot retry item with current status (only `error` and `unhealthy` can be retried) @@ -240,10 +258,12 @@ Retry a failed queue item. Only items with `error` or `unhealthy` status can be Server-Sent Events (SSE) endpoint for real-time queue updates. **Query Parameters:** + - `itemId` (optional): Filter updates for specific item - `status` (optional): Filter updates by status **Headers:** + ``` Accept: text/event-stream Cache-Control: no-cache @@ -253,19 +273,23 @@ Cache-Control: no-cache SSE stream with the following event types: #### connection + Sent when connection is established: + ``` event: connection data: {"message": "Connected to queue stream", "timestamp": "2024-12-21T10:30:00Z"} ``` -#### queue-update +#### queue-update + Sent when queue item status changes: + ``` event: queue-update data: { "itemId": "550e8400-e29b-41d4-a716-446655440000", - "status": "in_progress", + "status": "in_progress", "timestamp": "2024-12-21T10:30:01Z", "progress": [ { @@ -279,7 +303,9 @@ data: { ``` #### ping + Keep-alive ping sent every 30 seconds: + ``` event: ping data: {"timestamp": "2024-12-21T10:30:30Z"} @@ -288,30 +314,32 @@ data: {"timestamp": "2024-12-21T10:30:30Z"} **Usage Examples:** **JavaScript:** + ```javascript const eventSource = new EventSource('/api/queue/stream'); eventSource.addEventListener('connection', (event) => { - console.log('Connected:', JSON.parse(event.data)); + console.log('Connected:', JSON.parse(event.data)); }); eventSource.addEventListener('queue-update', (event) => { - const update = JSON.parse(event.data); - console.log('Queue update:', update); - updateUI(update); + const update = JSON.parse(event.data); + console.log('Queue update:', update); + updateUI(update); }); eventSource.addEventListener('ping', (event) => { - console.log('Keep-alive ping'); + console.log('Keep-alive ping'); }); eventSource.onerror = (error) => { - console.error('SSE error:', error); - // Reconnect logic here + console.error('SSE error:', error); + // Reconnect logic here }; ``` **curl:** + ```bash curl -N -H "Accept: text/event-stream" \ "https://your-instance.com/api/queue/stream?itemId=550e8400-e29b-41d4-a716-446655440000" @@ -324,10 +352,11 @@ curl -N -H "Accept: text/event-stream" \ Get the VAPID public key required for push notification subscriptions. **Response (200 OK):** + ```json { - "publicKey": "BDummyPublicKeyForDevelopment...", - "applicationServerKey": "BDummyPublicKeyForDevelopment..." + "publicKey": "BDummyPublicKeyForDevelopment...", + "applicationServerKey": "BDummyPublicKeyForDevelopment..." } ``` @@ -336,29 +365,32 @@ Get the VAPID public key required for push notification subscriptions. Subscribe to push notifications for queue processing updates. **Request:** + ```json { - "subscription": { - "endpoint": "https://fcm.googleapis.com/fcm/send/...", - "keys": { - "p256dh": "BIBn3E_YUVpW5f6_Eq_GH...", - "auth": "tBiH_Y1nPSuVh7TRMhcf..." - } - }, - "clientId": "unique-client-identifier" + "subscription": { + "endpoint": "https://fcm.googleapis.com/fcm/send/...", + "keys": { + "p256dh": "BIBn3E_YUVpW5f6_Eq_GH...", + "auth": "tBiH_Y1nPSuVh7TRMhcf..." + } + }, + "clientId": "unique-client-identifier" } ``` **Response (200 OK):** + ```json { - "success": true, - "message": "Successfully subscribed to push notifications", - "subscriptionCount": 5 + "success": true, + "message": "Successfully subscribed to push notifications", + "subscriptionCount": 5 } ``` **Errors:** + - `400` - Invalid subscription object or missing clientId ### DELETE /api/notifications/subscribe @@ -366,18 +398,20 @@ Subscribe to push notifications for queue processing updates. Unsubscribe from push notifications. **Request:** + ```json { - "clientId": "unique-client-identifier" + "clientId": "unique-client-identifier" } ``` **Response (200 OK):** + ```json { - "success": true, - "message": "Successfully unsubscribed from push notifications", - "subscriptionCount": 4 + "success": true, + "message": "Successfully unsubscribed from push notifications", + "subscriptionCount": 4 } ``` @@ -390,18 +424,19 @@ Unsubscribe from push notifications. This synchronous extraction endpoint is deprecated. Use `POST /api/queue` instead. **Migration:** + ```javascript // ❌ Old synchronous approach const response = await fetch('/api/extract', { - method: 'POST', - body: JSON.stringify({ url }) + method: 'POST', + body: JSON.stringify({ url }) }); const result = await response.json(); // Wait 30-60 seconds -// ✅ New async queue approach +// ✅ New async queue approach const response = await fetch('/api/queue', { - method: 'POST', - body: JSON.stringify({ url }) + method: 'POST', + body: JSON.stringify({ url }) }); const queueItem = await response.json(); // Immediate response ``` @@ -413,17 +448,18 @@ const queueItem = await response.json(); // Immediate response This SSE endpoint is deprecated. Use `POST /api/queue` + `GET /api/queue/stream` instead. **Migration:** + ```javascript // ❌ Old approach const response = await fetch('/api/extract-stream', { - method: 'POST', - body: JSON.stringify({ url }) + method: 'POST', + body: JSON.stringify({ url }) }); // ✅ New approach const queueResponse = await fetch('/api/queue', { - method: 'POST', - body: JSON.stringify({ url }) + method: 'POST', + body: JSON.stringify({ url }) }); const item = await queueResponse.json(); @@ -436,28 +472,28 @@ const eventSource = new EventSource(`/api/queue/stream?itemId=${item.id}`); ```typescript interface QueueItem { - id: string; // UUID v4 - url: string; // Instagram URL - status: 'pending' | 'in_progress' | 'success' | 'error' | 'unhealthy'; - - phases: Array<{ - name: 'extraction' | 'parsing' | 'uploading'; - status: 'pending' | 'in_progress' | 'completed' | 'error'; - startedAt?: string; // ISO 8601 timestamp - completedAt?: string; // ISO 8601 timestamp - progress?: number; // 0-100 - }>; - - results?: { - recipe?: Recipe; // Structured recipe data - tandoorUrl?: string; // Tandoor recipe URL - extractedText?: string; // Raw extracted text - thumbnail?: string; // Image URL - }; - - error?: string; // Error message - createdAt: string; // ISO 8601 timestamp - updatedAt: string; // ISO 8601 timestamp + id: string; // UUID v4 + url: string; // Instagram URL + status: 'pending' | 'in_progress' | 'success' | 'error' | 'unhealthy'; + + phases: Array<{ + name: 'extraction' | 'parsing' | 'uploading'; + status: 'pending' | 'in_progress' | 'completed' | 'error'; + startedAt?: string; // ISO 8601 timestamp + completedAt?: string; // ISO 8601 timestamp + progress?: number; // 0-100 + }>; + + results?: { + recipe?: Recipe; // Structured recipe data + tandoorUrl?: string; // Tandoor recipe URL + extractedText?: string; // Raw extracted text + thumbnail?: string; // Image URL + }; + + error?: string; // Error message + createdAt: string; // ISO 8601 timestamp + updatedAt: string; // ISO 8601 timestamp } ``` @@ -465,32 +501,33 @@ interface QueueItem { ```typescript interface Recipe { - name: string; - description?: string; - servings?: number; - prepTime?: number; // Minutes - cookTime?: number; // Minutes - totalTime?: number; // Minutes - - ingredients: Array<{ - food: string; - amount?: number; - unit?: string; - }>; - - steps: Array<{ - instruction: string; - time?: number; // Minutes - }>; - - keywords?: string[]; // Recipe tags - image?: string; // Image URL - nutrition?: { // Nutritional information - calories?: number; - protein?: number; - carbs?: number; - fat?: number; - }; + name: string; + description?: string; + servings?: number; + prepTime?: number; // Minutes + cookTime?: number; // Minutes + totalTime?: number; // Minutes + + ingredients: Array<{ + food: string; + amount?: number; + unit?: string; + }>; + + steps: Array<{ + instruction: string; + time?: number; // Minutes + }>; + + keywords?: string[]; // Recipe tags + image?: string; // Image URL + nutrition?: { + // Nutritional information + calories?: number; + protein?: number; + carbs?: number; + fat?: number; + }; } ``` @@ -517,59 +554,58 @@ When implementing clients, consider these error recovery strategies: ```javascript async function processInstagramUrl(url) { - try { - // 1. Enqueue URL - const queueResponse = await fetch('/api/queue', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ url }) - }); - - const queueItem = await queueResponse.json(); - console.log('Enqueued:', queueItem.id); - - // 2. Listen for real-time updates - const eventSource = new EventSource(`/api/queue/stream?itemId=${queueItem.id}`); - - return new Promise((resolve, reject) => { - eventSource.addEventListener('queue-update', (event) => { - const update = JSON.parse(event.data); - - if (update.status === 'success') { - eventSource.close(); - resolve(update.results); - } else if (update.status === 'error') { - eventSource.close(); - reject(new Error(update.error)); - } - - // Handle progress updates - console.log('Progress:', update.progress); - }); - - eventSource.onerror = (error) => { - eventSource.close(); - reject(error); - }; - }); - - } catch (error) { - console.error('Processing failed:', error); - throw error; - } + try { + // 1. Enqueue URL + const queueResponse = await fetch('/api/queue', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url }) + }); + + const queueItem = await queueResponse.json(); + console.log('Enqueued:', queueItem.id); + + // 2. Listen for real-time updates + const eventSource = new EventSource(`/api/queue/stream?itemId=${queueItem.id}`); + + return new Promise((resolve, reject) => { + eventSource.addEventListener('queue-update', (event) => { + const update = JSON.parse(event.data); + + if (update.status === 'success') { + eventSource.close(); + resolve(update.results); + } else if (update.status === 'error') { + eventSource.close(); + reject(new Error(update.error)); + } + + // Handle progress updates + console.log('Progress:', update.progress); + }); + + eventSource.onerror = (error) => { + eventSource.close(); + reject(error); + }; + }); + } catch (error) { + console.error('Processing failed:', error); + throw error; + } } // Usage processInstagramUrl('https://instagram.com/p/abc123') - .then(results => { - console.log('Recipe extracted:', results.recipe); - if (results.tandoorUrl) { - console.log('Uploaded to Tandoor:', results.tandoorUrl); - } - }) - .catch(error => { - console.error('Extraction failed:', error.message); - }); + .then((results) => { + console.log('Recipe extracted:', results.recipe); + if (results.tandoorUrl) { + console.log('Uploaded to Tandoor:', results.tandoorUrl); + } + }) + .catch((error) => { + console.error('Extraction failed:', error.message); + }); ``` -For more examples and integration guides, see the [Migration Documentation](/docs/MIGRATION.md). \ No newline at end of file +For more examples and integration guides, see the [Migration Documentation](/docs/MIGRATION.md). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8499d49..e63c245 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -91,21 +91,27 @@ insta-recipe/ ## Key Directories ### `/src/lib/server/` + Server-side business logic following Hexagonal Architecture principles. Contains domain logic, adapters for external systems (Instagram, Tandoor, LLM), and port definitions. ### `/src/lib/client/` + Client-side utilities for PWA features (push notifications, install prompts, service worker messaging). ### `/src/routes/api/` + RESTful API endpoints implemented as SvelteKit server routes. Each directory contains `+server.ts` files exporting HTTP verb handlers. ### `/src/routes/share/` + Share target page allowing users to share Instagram URLs directly from their browser or mobile apps. ### `/src/lib/server/queue/` + Queue management system with in-memory storage, processor workers, and type definitions. ### `/docs/` + Comprehensive documentation including plans, outcomes, API specs, and migration guides. --- @@ -113,33 +119,43 @@ Comprehensive documentation including plans, outcomes, API specs, and migration ## Design Patterns ### Singleton Pattern + Used for shared service instances: + - `QueueManager` (`queueManager` exported instance) - `QueueProcessor` (`queueProcessor` exported instance) - `PushNotificationService` (`pushNotificationService` exported instance) - `ServiceWorkerMessageHandler` (`serviceWorkerMessageHandler` exported instance) ### Factory Pattern + Used for creating configured instances: + - `createLLM()` - Creates OpenAI client with environment configuration - `createBrowserContext()` - Creates Playwright browser context with options - `initializeBrowser()` - Initializes Chromium browser instance ### Observer Pattern + Implemented in QueueManager for real-time updates: + - Subscribers receive notifications on queue item changes - Server-Sent Events (SSE) stream queue updates to clients - Push notifications notify users of completion events ### Adapter Pattern (Hexagonal Architecture) + External systems accessed via adapters: + - **Instagram Adapter**: `extraction.ts` - Extracts content via Playwright - **LLM Adapter**: `llm.ts`, `parser.ts` - Recipe parsing via OpenAI - **Tandoor Adapter**: `tandoor.ts` - Recipe management system integration - **Browser Adapter**: `browser.ts` - Playwright browser automation ### Strategy Pattern + Multiple extraction strategies with fallback: + 1. Embedded JSON extraction 2. DOM selector extraction 3. GraphQL API extraction @@ -150,28 +166,34 @@ Multiple extraction strategies with fallback: ## Key Components ### Queue Management System + **Location**: `src/lib/server/queue/` Three-phase processing pipeline: + 1. **Extraction Phase**: Extract text and thumbnail from Instagram 2. **Parsing Phase**: Parse recipe using LLM 3. **Uploading Phase**: Upload to Tandoor (if enabled) **Components**: + - `QueueManager`: In-memory FIFO queue with CRUD operations - `QueueProcessor`: Worker that processes items with configurable concurrency - `types.ts`: Comprehensive type definitions for queue items and updates ### API Layer + **Location**: `src/routes/api/` RESTful endpoints for: + - Queue operations (`POST /api/queue`, `GET /api/queue`, `GET /api/queue/[id]`) - Real-time updates (`GET /api/queue/stream` - SSE) - Push notifications (`POST /api/notifications/subscribe`) - Health checks (`GET /api/health`, `GET /api/llm-health`) ### Client-Side Services + **Location**: `src/lib/client/` - **PushNotificationManager**: Manages Web Push API subscriptions @@ -179,14 +201,17 @@ RESTful endpoints for: - **ServiceWorkerMessageHandler**: Processes service worker messages ### Instagram Extraction + **Location**: `src/lib/server/extraction.ts` Multi-method extraction with intelligent fallback: + - Progress callbacks for real-time feedback - Retry logic with configurable attempts - Thumbnail extraction and validation ### LLM Integration + **Location**: `src/lib/server/parser.ts`, `src/lib/server/llm.ts` - Recipe detection endpoint @@ -198,6 +223,7 @@ Multi-method extraction with intelligent fallback: ## Dependencies ### Production Dependencies + - **@types/uuid** (^10.0.0) - UUID type definitions - **date-fns** (^4.1.0) - Date utility library - **openai** (^4.20.0) - OpenAI API client @@ -206,6 +232,7 @@ Multi-method extraction with intelligent fallback: - **zod** (^3.23.0) - Schema validation ### Development Dependencies + - **@sveltejs/kit** (^2.48.5) - SvelteKit framework - **@sveltejs/adapter-node** (^5.4.0) - Node.js adapter - **svelte** (^5.43.8) - Svelte 5 framework @@ -223,12 +250,14 @@ Multi-method extraction with intelligent fallback: ## Module Organization ### SvelteKit Path Aliases + - `$lib` → `src/lib/` - `$lib/*` → `src/lib/*` - `$app/*` → SvelteKit app imports - `$env/dynamic/private` → Environment variables (server-side) ### Directory Structure Conventions + - **Server-only code**: `src/lib/server/` (not bundled to client) - **Client-only code**: `src/lib/client/` (not executed on server) - **Shared code**: `src/lib/` (available to both) @@ -240,6 +269,7 @@ Multi-method extraction with intelligent fallback: ## Data Flow ### Recipe Extraction Flow + ``` User submits URL ↓ @@ -261,6 +291,7 @@ SSE updates notify client ``` ### Real-time Updates Flow + ``` Client connects to GET /api/queue/stream (SSE) ↓ @@ -274,6 +305,7 @@ Client updates UI reactively ``` ### Push Notification Flow + ``` Client requests permission ↓ @@ -295,37 +327,44 @@ Notification displayed to user ## Build System ### Build Command + ```bash npm run build ``` Generates production-ready build in `build/` directory using: + - Vite for bundling - `@sveltejs/adapter-node` for Node.js deployment - TypeScript compilation - SvelteKit prerendering and optimization ### Test Command + ```bash npm test ``` Runs test suite using Vitest with two projects: + 1. **Server tests**: Node environment for server-side code 2. **Client tests**: Playwright browser for Svelte components ### Development Server + ```bash npm run dev ``` Starts Vite dev server with: + - HTTPS enabled (certificates in `.ssl/`) - Hot module replacement - TypeScript checking - File watching ### Linting & Formatting + ```bash npm run lint # ESLint + Prettier check npm run format # Prettier write @@ -336,19 +375,24 @@ npm run format # Prettier write ## Deployment ### Docker Deployment + Dockerfile includes: + - Node.js 22 Alpine base image - Playwright Chromium installation - Production build - Port 3000 exposure Run with: + ```bash docker-compose up ``` ### Environment Variables + Required configuration: + - `OPENAI_API_KEY` - LLM API access - `TANDOOR_URL` - Tandoor instance URL (optional) - `TANDOOR_TOKEN` - Tandoor API token (optional) @@ -360,13 +404,16 @@ Required configuration: ## Testing Architecture ### Test Categories + 1. **Unit Tests**: Individual function testing 2. **Integration Tests**: Multi-component workflows 3. **API Tests**: Endpoint behavior validation 4. **Browser Tests**: Svelte component rendering ### Test Coverage + 138 tests covering: + - Queue management operations - Instagram URL validation - SSE streaming @@ -375,6 +422,7 @@ Required configuration: - Notification service ### Test Configuration + - **Server tests**: Node environment with mocked dependencies - **Client tests**: Playwright Chromium browser with Svelte testing library @@ -383,15 +431,18 @@ Required configuration: ## Security Considerations ### SSL/TLS + - Development uses local SSL certificates signed by external Caddy CA - Certificates stored in `.ssl/` (git-ignored) - Required for PWA features (Service Worker, Push API) ### Authentication + - Basic auth for scheduled tasks (username/password from environment) - Tandoor integration uses bearer token authentication ### Input Validation + - Instagram URL validation with regex patterns - Zod schema validation for API payloads - Error handling with custom error classes diff --git a/docs/CODE_STYLE.md b/docs/CODE_STYLE.md index c690c83..f9c2625 100644 --- a/docs/CODE_STYLE.md +++ b/docs/CODE_STYLE.md @@ -19,12 +19,14 @@ ### Files & Directories #### SvelteKit Route Files + - Route pages: `+page.svelte` - Route servers: `+server.ts` - Route layouts: `+layout.svelte` - Type definitions: `$types.ts` (auto-generated) **Example:** + ``` src/routes/api/queue/ ├── [id]/ @@ -37,19 +39,23 @@ src/routes/api/queue/ ``` #### Library Files + - **PascalCase** for classes and managers: `QueueManager.ts`, `PushNotificationService.ts` - **kebab-case** for utilities and configs: `tandoor-config.ts`, `instagram-url.ts` - **lowercase** for general modules: `browser.ts`, `extraction.ts`, `parser.ts` **Examples from codebase:** + - `src/lib/server/queue/QueueManager.ts` - `src/lib/server/tandoor-config.ts` - `src/lib/client/PushNotificationManager.ts` #### Test Files + Pattern: `.spec.ts` or `.test.ts` **Examples:** + - `queue-manager.spec.ts` - `instagram-url-validation.spec.ts` - `page.svelte.spec.ts` @@ -57,10 +63,12 @@ Pattern: `.spec.ts` or `.test.ts` ### Variables & Functions #### Variables + - **camelCase** for local variables and parameters - **SCREAMING_SNAKE_CASE** for constants **Examples:** + ```typescript // From QueueManager.ts private items: Map = new Map(); @@ -76,10 +84,12 @@ const unsubscribe = queueManager.subscribe(callback); ``` #### Functions + - **camelCase** for function names - **Descriptive action verbs**: `enqueue`, `extractRecipe`, `uploadRecipeImage` **Examples:** + ```typescript // From QueueManager.ts enqueue(url: string): QueueItem { ... } @@ -99,62 +109,62 @@ export async function extractRecipe(text: string): Promise { ... } ### Types & Interfaces #### Interfaces & Types + - **PascalCase** for interface names - Prefix with `I` is **NOT** used - Exported types use `export type` or `export interface` **Examples:** + ```typescript // From queue/types.ts export interface QueueItem { - id: string; - url: string; - status: QueueItemStatus; - enqueuedAt: string; - // ... + id: string; + url: string; + status: QueueItemStatus; + enqueuedAt: string; + // ... } export interface QueueStatusUpdate { - type: string; - itemId: string; - status: QueueItemStatus; - // ... + type: string; + itemId: string; + status: QueueItemStatus; + // ... } -export type QueueItemStatus = - | 'pending' - | 'in_progress' - | 'completed' - | 'failed'; +export type QueueItemStatus = 'pending' | 'in_progress' | 'completed' | 'failed'; // From extraction.ts export interface ExtractedContent { - text: string; - thumbnailUrl?: string; + text: string; + thumbnailUrl?: string; } export type ProgressCallback = (event: ProgressEvent) => void; ``` #### Zod Schemas + - **PascalCase** with `Schema` suffix - Inferred types without suffix **Examples:** + ```typescript // From parser.ts const RecipeSchema = z.object({ - name: z.string(), - description: z.string(), - servings: z.number(), - // ... + name: z.string(), + description: z.string(), + servings: z.number() + // ... }); export type Recipe = z.infer; // From tandoor.ts const TandoorRecipeSchema = z.object({ - // ... + // ... }); export type TandoorRecipe = z.infer; @@ -163,35 +173,38 @@ export type TandoorRecipe = z.infer; ### Classes #### Class Names + - **PascalCase** for class names - Descriptive suffixes: `Manager`, `Service`, `Processor`, `Handler` **Examples:** + ```typescript // From QueueManager.ts export class QueueManager { - private items: Map = new Map(); - // ... + private items: Map = new Map(); + // ... } // From QueueProcessor.ts export class QueueProcessor { - private processing: Set = new Set(); - // ... + private processing: Set = new Set(); + // ... } // From PushNotificationService.ts class PushNotificationService { - private subscriptions: Map = new Map(); - // ... + private subscriptions: Map = new Map(); + // ... } ``` #### Singleton Export Pattern + ```typescript // Class definition export class QueueManager { - // Implementation + // Implementation } // Singleton instance export @@ -203,6 +216,7 @@ export const queueManager = new QueueManager(); ## Indentation & Formatting ### General Rules + - **Indentation:** 2 spaces (enforced by Prettier) - **No tabs** - **Max line length:** 100 characters (soft limit, not enforced) @@ -213,6 +227,7 @@ export const queueManager = new QueueManager(); ### Code Examples #### Function Declarations + ```typescript // From QueueManager.ts enqueue(url: string): QueueItem { @@ -234,43 +249,45 @@ enqueue(url: string): QueueItem { retryCount: 0, maxRetries: 3 }; - + this.items.set(item.id, item); return item; } ``` #### Async Functions + ```typescript // From extraction.ts export async function extractTextAndThumbnail( - url: string, - onProgress?: ProgressCallback + url: string, + onProgress?: ProgressCallback ): Promise { - const browser = await getBrowser(); - const context = await createBrowserContext(browser); - const page = await context.newPage(); - - try { - await page.goto(url, { waitUntil: 'networkidle' }); - // ... - } finally { - await context.close(); - } + const browser = await getBrowser(); + const context = await createBrowserContext(browser); + const page = await context.newPage(); + + try { + await page.goto(url, { waitUntil: 'networkidle' }); + // ... + } finally { + await context.close(); + } } ``` #### Object Destructuring + ```typescript // From route handlers export const POST: RequestHandler = async ({ request }) => { - const { url } = await request.json(); - // ... + const { url } = await request.json(); + // ... }; export const GET: RequestHandler = async ({ params }) => { - const { id } = params; - // ... + const { id } = params; + // ... }; ``` @@ -279,12 +296,14 @@ export const GET: RequestHandler = async ({ params }) => { ## Import Patterns ### Import Order + 1. External dependencies (Node.js built-ins, npm packages) 2. SvelteKit imports (`$lib`, `$app`, `$env`) 3. Relative imports (`./ `, `../`) 4. Type imports (separate from value imports when beneficial) **Example:** + ```typescript // From QueueProcessor.ts @@ -307,6 +326,7 @@ import type { QueueItem } from './types'; ### Import Styles #### Named Imports (Preferred) + ```typescript import { json } from '@sveltejs/kit'; import { queueManager } from '$lib/server/queue/QueueManager'; @@ -314,12 +334,14 @@ import { validateInstagramUrl } from '$lib/server/validation/instagram-url'; ``` #### Type-Only Imports + ```typescript import type { RequestHandler } from './$types'; import type { QueueItem, QueueItemStatus } from './types'; ``` #### Default Imports + ```typescript import OpenAI from 'openai'; import fs from 'fs'; @@ -329,6 +351,7 @@ import path from 'path'; ### Export Patterns #### Named Exports (Preferred) + ```typescript // Export functions export async function extractRecipe(text: string): Promise { ... } @@ -345,6 +368,7 @@ export interface QueueItem { ... } ``` #### Singleton Pattern Export + ```typescript // Define class export class QueueManager { ... } @@ -358,16 +382,18 @@ export const queueManager = new QueueManager(); ## Comments & Documentation ### JSDoc Style + Used extensively for public APIs and exported functions. **Function Documentation:** -```typescript + +````typescript /** * Add URL to processing queue - * + * * @param url - Instagram URL to process * @returns Newly created queue item - * + * * @example * ```typescript * const item = queueManager.enqueue('https://instagram.com/p/abc123'); @@ -377,41 +403,43 @@ Used extensively for public APIs and exported functions. enqueue(url: string): QueueItem { // Implementation } -``` +```` **Class Documentation:** -```typescript + +````typescript /** * Singleton queue manager for processing Instagram URLs - * + * * Features: * - FIFO queue with unique IDs * - Status tracking and updates * - Progress event accumulation * - Retry support for failed items * - Pub/sub for real-time updates - * + * * @example * ```typescript * import { queueManager } from './QueueManager'; - * + * * // Add item to queue * const item = queueManager.enqueue('https://instagram.com/p/abc123'); * ``` */ export class QueueManager { - // Implementation + // Implementation } -``` +```` **Module-Level Documentation:** + ```typescript /** * Queue Manager - Core queue operations and event management - * + * * Manages an in-memory queue of Instagram URL processing jobs. * Provides CRUD operations and pub/sub mechanism for queue updates. - * + * * Architecture: Domain Layer (Hexagonal Architecture) * - Port: Defines queue operations interface * - Implementation: In-memory Map-based storage @@ -421,19 +449,21 @@ export class QueueManager { ### Inline Comments #### Single-line Comments + ```typescript // Set restrictive permissions fs.chmodSync(authFile, 0o600); // FIFO order - get oldest pending item -const pendingItems = Array.from(this.items.values()) - .filter(item => item.status === 'pending'); +const pendingItems = Array.from(this.items.values()).filter((item) => item.status === 'pending'); ``` #### Block Comments (Avoided) + Single-line comments preferred. Block comments used only for large comment blocks or temporarily disabling code during development. ### TODO Comments + ```typescript // TODO: Add retry logic with exponential backoff // FIXME: Handle race condition when multiple workers dequeue @@ -446,17 +476,19 @@ Single-line comments preferred. Block comments used only for large comment block ### Type Safety #### Strict Mode Enabled + ```json // tsconfig.json { - "compilerOptions": { - "strict": true, - "forceConsistentCasingInFileNames": true - } + "compilerOptions": { + "strict": true, + "forceConsistentCasingInFileNames": true + } } ``` #### Type Annotations + ```typescript // Explicit return types for public functions export async function extractRecipe(text: string): Promise { ... } @@ -469,35 +501,24 @@ const items = queueManager.getAll(); // Type inferred ``` ### Union Types + ```typescript -export type QueueItemStatus = - | 'pending' - | 'in_progress' - | 'completed' - | 'failed'; +export type QueueItemStatus = 'pending' | 'in_progress' | 'completed' | 'failed'; -export type ProcessingPhase = - | 'extraction' - | 'parsing' - | 'uploading'; +export type ProcessingPhase = 'extraction' | 'parsing' | 'uploading'; -export type ProgressEventType = - | 'status' - | 'method' - | 'retry' - | 'error' - | 'thumbnail' - | 'complete'; +export type ProgressEventType = 'status' | 'method' | 'retry' | 'error' | 'thumbnail' | 'complete'; ``` ### Generics + ```typescript // Generic function async function fetchFromTandoor( - url: string, - options: Partial = { method: 'GET' } + url: string, + options: Partial = { method: 'GET' } ): Promise<{ ok: boolean; data?: T; error?: string }> { - // Implementation + // Implementation } ``` @@ -508,6 +529,7 @@ async function fetchFromTandoor( ### Runes (Reactivity) #### $state (Reactive Variables) + ```svelte