Browse Source

docs: simplify README and create API documentation #2

- Fix TypeScript build errors (boolean vs number comparison in SQLite)
- Simplify README.md with concise documentation
- Create comprehensive API documentation in docs/API.md
- Document all endpoints with examples and response formats
- Include filtering, scheduling, and change detection documentation

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

Co-Authored-By: Claude <noreply@anthropic.com>
claude 8 months ago
parent
commit
4c73db48cf
4 changed files with 625 additions and 202 deletions
  1. 44 194
      README.md
  2. 573 0
      docs/API.md
  3. 3 3
      package-lock.json
  4. 5 5
      src/api/routes.ts

+ 44 - 194
README.md

@@ -1,260 +1,110 @@
 # Webshop Scraper
 
-A TypeScript-based web scraper for extracting information from webshops (ShopRenter, WooCommerce, Shopify). The application provides a REST API for submitting scraping jobs and retrieving results.
+A TypeScript-based web scraper for extracting information from webshops with persistent storage, scheduled scraping, and change detection.
 
 ## Features
 
-- **Multi-webshop Support**: Automatically detects and scrapes ShopRenter, WooCommerce, and Shopify stores
-- **REST API**: HTTP API for job submission and status tracking
-- **Bearer Authentication**: Secure API access with pre-shared key
-- **Job Queue**: Prevents system overload and race conditions with configurable concurrency
-- **Content Extraction**: Extracts shipping information, contacts, terms & conditions, and FAQ
-- **Markdown Conversion**: Cleans HTML and converts to markdown format
-- **Systemd Integration**: Install as a system service with proper logging
-- **Logging**: Outputs to stdout/stderr for systemd/journalctl integration
+- **Multi-webshop Support**: ShopRenter, WooCommerce, and Shopify
+- **Persistent Storage**: SQLite database with UUID tracking and analytics
+- **Scheduled Scraping**: Automated scraping based on sitemap frequency rules
+- **Content Change Detection**: MD5 hash-based change tracking for all content
+- **REST API**: Full CRUD operations for shops and scraping jobs
+- **Bearer Authentication**: Secure API access
+- **Job Queue**: Configurable concurrency control
+- **Systemd Integration**: Install as a system service
 
-## Requirements
-
-- Node.js (v16 or higher)
-- npm
-- Linux with systemd (for service installation)
-
-## Installation
+## Quick Start
 
-### As a systemd service (recommended)
+### Installation
 
-1. Clone the repository:
+1. Clone and install:
 ```bash
 git clone <repository-url>
 cd webshop-scraper
-```
-
-2. Run the installation script:
-```bash
-sudo ./scripts/install.sh
-```
-
-The script will:
-- Prompt for the API key
-- Ask for port number (default: 3000)
-- Ask for max concurrent jobs (default: 3)
-- Install dependencies
-- Build the application
-- Create a systemd service
-- Start the service
-
-### Manual installation
-
-1. Install dependencies:
-```bash
 npm install
 ```
 
-2. Build the application:
+2. Build:
 ```bash
 npm run build
 ```
 
-3. Create a `.env` file:
+3. Configure environment:
 ```bash
-cp .env.example .env
-```
-
-4. Edit `.env` and set your configuration:
-```
-API_KEY=your-secret-key-here
+API_KEY=your-secret-key
 PORT=3000
 MAX_CONCURRENT_JOBS=3
 ```
 
-5. Start the application:
+4. Start:
 ```bash
 npm start
 ```
 
-## Usage
-
-### Starting the service
+### As a systemd service
 
 ```bash
-sudo systemctl start webshop-scraper
-```
-
-### Stopping the service
-
-```bash
-sudo systemctl stop webshop-scraper
-```
-
-### Viewing logs
-
-```bash
-sudo journalctl -u webshop-scraper -f
-```
-
-### API Endpoints
-
-All API endpoints (except `/health`) require Bearer authentication.
-
-#### Health Check
-
-```bash
-GET /health
-```
-
-Example:
-```bash
-curl http://localhost:3000/health
+sudo ./scripts/install.sh
 ```
 
-#### Create Scraping Job
+## API Documentation
 
-```bash
-POST /api/jobs
-Authorization: Bearer <your-api-key>
-Content-Type: application/json
+See [API Documentation](./docs/API.md) for complete endpoint reference.
 
-{
-  "url": "https://example-shop.com"
-}
-```
+### Quick Examples
 
-Example:
+**Create a scraping job:**
 ```bash
 curl -X POST http://localhost:3000/api/jobs \
-  -H "Authorization: Bearer your-secret-key-here" \
+  -H "Authorization: Bearer your-secret-key" \
   -H "Content-Type: application/json" \
   -d '{"url": "https://example-shop.com"}'
 ```
 
-Response:
-```json
-{
-  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
-  "status": "pending",
-  "message": "Job created successfully"
-}
-```
-
-#### Get Job Status
-
+**Get shop information:**
 ```bash
-GET /api/jobs/:id
-Authorization: Bearer <your-api-key>
+curl http://localhost:3000/api/shops/:id \
+  -H "Authorization: Bearer your-secret-key"
 ```
 
-Example:
+**Get shop results:**
 ```bash
-curl http://localhost:3000/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-  -H "Authorization: Bearer your-secret-key-here"
+curl "http://localhost:3000/api/shops/:id/results?limit=10" \
+  -H "Authorization: Bearer your-secret-key"
 ```
 
-Response (when completed):
-```json
-{
-  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
-  "status": "completed",
-  "created_at": "2024-01-01T12:00:00.000Z",
-  "updated_at": "2024-01-01T12:05:00.000Z",
-  "result": {
-    "initial_sitemap": "https://example-shop.com/sitemap.xml",
-    "shipping_informations": {
-      "url": "https://example-shop.com/shipping",
-      "content": "# Shipping Information\n\n..."
-    },
-    "contacts": {
-      "url": "https://example-shop.com/contact",
-      "content": "# Contact Us\n\n..."
-    },
-    "terms_of_conditions": {
-      "url": "https://example-shop.com/terms",
-      "content": "# Terms and Conditions\n\n..."
-    },
-    "faq": {
-      "url": "https://example-shop.com/faq",
-      "content": "# FAQ\n\n..."
-    }
-  }
-}
-```
-
-If a page type is not found, it will be `null`:
-```json
-{
-  "faq": null
-}
-```
-
-#### List All Jobs
-
-```bash
-GET /api/jobs
-Authorization: Bearer <your-api-key>
-```
-
-Example:
-```bash
-curl http://localhost:3000/api/jobs \
-  -H "Authorization: Bearer your-secret-key-here"
-```
-
-## Configuration
-
-Configuration is done via environment variables:
-
-- `API_KEY`: Pre-shared key for Bearer authentication (required)
-- `PORT`: HTTP server port (default: 3000)
-- `MAX_CONCURRENT_JOBS`: Maximum number of concurrent scraping jobs (default: 3)
-
-## Uninstallation
-
-To remove the service and all files:
+## Requirements
 
-```bash
-sudo ./scripts/uninstall.sh
-```
+- Node.js v16 or higher
+- npm
+- Linux with systemd (for service installation)
 
 ## Development
 
-### Run in development mode
-
 ```bash
+# Development mode
 npm run dev
-```
 
-### Build
+# Watch mode
+npm run watch
 
-```bash
+# Build
 npm run build
 ```
 
-### Watch mode
-
-```bash
-npm run watch
-```
+## Database
 
-## Architecture
-
-- **REST API Server**: Express.js server with Bearer authentication
-- **Job Queue**: In-memory queue with concurrency control
-- **Sitemap Parser**: Detects webshop type and parses sitemap.xml
-- **Content Extractor**: Fetches pages and extracts main content
-- **HTML to Markdown**: Converts HTML to clean markdown using Turndown
+Data is stored in `data/shops.db` (SQLite). The database includes:
+- Shop information with UUID tracking
+- Scrape analytics and history
+- Content with change detection
+- Scheduled jobs queue
 
 ## Logging
 
-All logs are written to stdout/stderr and can be viewed using journalctl:
-
+View logs using journalctl:
 ```bash
-# Follow logs
 sudo journalctl -u webshop-scraper -f
-
-# View logs from today
-sudo journalctl -u webshop-scraper --since today
-
-# View last 100 lines
-sudo journalctl -u webshop-scraper -n 100
 ```
 
 ## License

+ 573 - 0
docs/API.md

@@ -0,0 +1,573 @@
+# API Documentation
+
+All endpoints (except `/health`) require Bearer authentication using the `Authorization` header:
+
+```
+Authorization: Bearer <your-api-key>
+```
+
+## Table of Contents
+
+- [Health Check](#health-check)
+- [Jobs](#jobs)
+  - [Create Job](#create-job)
+  - [Get Job Status](#get-job-status)
+  - [List All Jobs](#list-all-jobs)
+- [Shops](#shops)
+  - [List All Shops](#list-all-shops)
+  - [Get Shop Details](#get-shop-details)
+  - [Get Shop Results](#get-shop-results)
+  - [Enable/Disable Schedule](#enabledisable-schedule)
+  - [Delete Shop](#delete-shop)
+
+---
+
+## Health Check
+
+Check the health status of the application.
+
+**Endpoint:** `GET /health`
+
+**Authentication:** Not required
+
+**Response:**
+```json
+{
+  "status": "ok",
+  "timestamp": "2025-01-01T12:00:00.000Z",
+  "queue_stats": {
+    "total": 10,
+    "pending": 2,
+    "running": 1,
+    "completed": 6,
+    "failed": 1
+  }
+}
+```
+
+---
+
+## Jobs
+
+### Create Job
+
+Create a new scraping job for a webshop.
+
+**Endpoint:** `POST /api/jobs`
+
+**Request Body:**
+```json
+{
+  "url": "https://example-shop.com"
+}
+```
+
+**Response:** `201 Created`
+```json
+{
+  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+  "shop_id": "550e8400-e29b-41d4-a716-446655440000",
+  "status": "pending",
+  "message": "Job created successfully"
+}
+```
+
+**Error Responses:**
+- `400 Bad Request` - Missing or invalid URL
+- `500 Internal Server Error` - Server error
+
+**Example:**
+```bash
+curl -X POST http://localhost:3000/api/jobs \
+  -H "Authorization: Bearer your-secret-key" \
+  -H "Content-Type: application/json" \
+  -d '{"url": "https://example-shop.com"}'
+```
+
+---
+
+### Get Job Status
+
+Get the status and result of a specific job.
+
+**Endpoint:** `GET /api/jobs/:id`
+
+**URL Parameters:**
+- `id` - Job UUID
+
+**Response:**
+```json
+{
+  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+  "status": "completed",
+  "created_at": "2025-01-01T12:00:00.000Z",
+  "updated_at": "2025-01-01T12:05:00.000Z",
+  "result": {
+    "initial_sitemap": "https://example-shop.com/sitemap.xml",
+    "shipping_informations": [
+      {
+        "url": "https://example-shop.com/shipping",
+        "content": "# Shipping Information\n\n..."
+      }
+    ],
+    "contacts": [
+      {
+        "url": "https://example-shop.com/contact",
+        "content": "# Contact Us\n\n..."
+      }
+    ],
+    "terms_of_conditions": [
+      {
+        "url": "https://example-shop.com/terms",
+        "content": "# Terms and Conditions\n\n..."
+      }
+    ],
+    "faq": [
+      {
+        "url": "https://example-shop.com/faq",
+        "content": "# FAQ\n\n..."
+      }
+    ]
+  }
+}
+```
+
+**Status Values:**
+- `pending` - Job is waiting in queue
+- `running` - Job is currently being processed
+- `completed` - Job finished successfully
+- `failed` - Job failed (includes `error` field)
+
+**Error Responses:**
+- `404 Not Found` - Job not found
+- `500 Internal Server Error` - Server error
+
+**Example:**
+```bash
+curl http://localhost:3000/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+---
+
+### List All Jobs
+
+Get a list of all jobs with queue statistics.
+
+**Endpoint:** `GET /api/jobs`
+
+**Response:**
+```json
+{
+  "stats": {
+    "total": 10,
+    "pending": 2,
+    "running": 1,
+    "completed": 6,
+    "failed": 1
+  },
+  "jobs": [
+    {
+      "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+      "status": "completed",
+      "sitemap_url": "https://example-shop.com",
+      "created_at": "2025-01-01T12:00:00.000Z",
+      "updated_at": "2025-01-01T12:05:00.000Z"
+    }
+  ]
+}
+```
+
+**Example:**
+```bash
+curl http://localhost:3000/api/jobs \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+---
+
+## Shops
+
+### List All Shops
+
+Get a list of all shops with their analytics.
+
+**Endpoint:** `GET /api/shops`
+
+**Response:**
+```json
+{
+  "shops": [
+    {
+      "id": "550e8400-e29b-41d4-a716-446655440000",
+      "url": "https://example-shop.com",
+      "sitemap_url": "https://example-shop.com/sitemap.xml",
+      "webshop_type": "shopify",
+      "created_at": "2025-01-01T10:00:00.000Z",
+      "updated_at": "2025-01-01T12:00:00.000Z",
+      "analytics": {
+        "total_scrapes": 5,
+        "last_scraped_at": "2025-01-01T12:00:00.000Z",
+        "next_scrape_at": "2025-01-02T12:00:00.000Z",
+        "total_urls_found": 42,
+        "average_scrape_time": 5432.5,
+        "average_page_scrape_time": 234.8
+      }
+    }
+  ]
+}
+```
+
+**Error Responses:**
+- `503 Service Unavailable` - Database not available
+- `500 Internal Server Error` - Server error
+
+**Example:**
+```bash
+curl http://localhost:3000/api/shops \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+---
+
+### Get Shop Details
+
+Get detailed information about a specific shop including analytics, content metadata, scrape history, and scheduled jobs.
+
+**Endpoint:** `GET /api/shops/:id`
+
+**URL Parameters:**
+- `id` - Shop UUID
+
+**Response:**
+```json
+{
+  "shop": {
+    "id": "550e8400-e29b-41d4-a716-446655440000",
+    "url": "https://example-shop.com",
+    "sitemap_url": "https://example-shop.com/sitemap.xml",
+    "webshop_type": "shopify",
+    "created_at": "2025-01-01T10:00:00.000Z",
+    "updated_at": "2025-01-01T12:00:00.000Z"
+  },
+  "analytics": {
+    "total_scrapes": 5,
+    "last_scraped_at": "2025-01-01T12:00:00.000Z",
+    "next_scrape_at": "2025-01-02T12:00:00.000Z",
+    "total_urls_found": 42,
+    "average_scrape_time": 5432.5,
+    "average_page_scrape_time": 234.8
+  },
+  "content_metadata": {
+    "shipping_informations": [
+      {
+        "url": "https://example-shop.com/shipping",
+        "changed": false,
+        "last_updated": "2025-01-01T12:00:00.000Z"
+      }
+    ],
+    "contacts": [
+      {
+        "url": "https://example-shop.com/contact",
+        "changed": true,
+        "last_updated": "2025-01-01T12:00:00.000Z"
+      }
+    ],
+    "terms_of_conditions": [],
+    "faq": []
+  },
+  "scrape_history": [
+    {
+      "id": "history-uuid-1",
+      "job_id": "job-uuid-1",
+      "started_at": "2025-01-01T12:00:00.000Z",
+      "completed_at": "2025-01-01T12:05:00.000Z",
+      "status": "completed",
+      "error": null,
+      "scrape_time_ms": 5432,
+      "urls_found": 42
+    }
+  ],
+  "scheduled_jobs": [
+    {
+      "id": "schedule-uuid-1",
+      "next_run_at": "2025-01-02T12:00:00.000Z",
+      "frequency": "daily",
+      "last_modified": "2025-01-01T10:00:00.000Z",
+      "status": "pending",
+      "enabled": true,
+      "created_at": "2025-01-01T10:00:00.000Z"
+    }
+  ]
+}
+```
+
+**Notes:**
+- This endpoint returns content metadata only (URLs, change status, last updated)
+- To get full content, use the `/api/shops/:id/results` endpoint
+- `changed` flag indicates if content has changed since the previous scrape
+
+**Error Responses:**
+- `404 Not Found` - Shop not found
+- `503 Service Unavailable` - Database not available
+- `500 Internal Server Error` - Server error
+
+**Example:**
+```bash
+curl http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000 \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+---
+
+### Get Shop Results
+
+Get shop scraping results with full content. Supports filtering by date range, content type, and limit.
+
+**Endpoint:** `GET /api/shops/:id/results`
+
+**URL Parameters:**
+- `id` - Shop UUID
+
+**Query Parameters:**
+- `limit` (optional) - Maximum number of results to return
+- `date_from` (optional) - Filter results from this date (ISO 8601 format)
+- `date_to` (optional) - Filter results to this date (ISO 8601 format)
+- `content_type` (optional) - Filter by content type: `shipping`, `contacts`, `terms`, or `faq`
+
+**Response:**
+```json
+{
+  "shop_id": "550e8400-e29b-41d4-a716-446655440000",
+  "filters": {
+    "limit": 10,
+    "date_from": "2025-01-01",
+    "date_to": null,
+    "content_type": null
+  },
+  "results": {
+    "shipping_informations": [
+      {
+        "url": "https://example-shop.com/shipping",
+        "content": "# Shipping Information\n\nDetailed shipping content...",
+        "changed": false,
+        "last_updated": "2025-01-01T12:00:00.000Z"
+      },
+      {
+        "url": "https://example-shop.com/delivery-info",
+        "content": "# Delivery Information\n\nDetailed delivery content...",
+        "changed": true,
+        "last_updated": "2025-01-01T12:00:00.000Z"
+      }
+    ],
+    "contacts": [
+      {
+        "url": "https://example-shop.com/contact",
+        "content": "# Contact Us\n\nEmail: contact@example.com\nPhone: +1234567890",
+        "changed": true,
+        "last_updated": "2025-01-01T12:00:00.000Z"
+      }
+    ],
+    "terms_of_conditions": [
+      {
+        "url": "https://example-shop.com/terms",
+        "content": "# Terms and Conditions\n\nDetailed terms...",
+        "changed": false,
+        "last_updated": "2025-01-01T12:00:00.000Z"
+      }
+    ],
+    "faq": [
+      {
+        "url": "https://example-shop.com/faq",
+        "content": "# Frequently Asked Questions\n\nQ: Question 1?\nA: Answer 1...",
+        "changed": false,
+        "last_updated": "2025-01-01T12:00:00.000Z"
+      }
+    ]
+  }
+}
+```
+
+**Notes:**
+- All content categories use arrays to support multiple pages per category
+- `changed` flag indicates if content has changed since the previous scrape
+- Content is in Markdown format, converted from HTML
+
+**Error Responses:**
+- `404 Not Found` - Shop not found
+- `503 Service Unavailable` - Database not available
+- `500 Internal Server Error` - Server error
+
+**Examples:**
+
+Get all results:
+```bash
+curl "http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000/results" \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+Get limited results:
+```bash
+curl "http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000/results?limit=10" \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+Get results from a specific date:
+```bash
+curl "http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000/results?date_from=2025-01-01" \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+Get only shipping information:
+```bash
+curl "http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000/results?content_type=shipping" \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+Get results with date range and limit:
+```bash
+curl "http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000/results?date_from=2025-01-01&date_to=2025-01-31&limit=50" \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+---
+
+### Enable/Disable Schedule
+
+Enable or disable scheduled scraping for a shop.
+
+**Endpoint:** `PATCH /api/shops/:id/schedule`
+
+**URL Parameters:**
+- `id` - Shop UUID
+
+**Request Body:**
+```json
+{
+  "enabled": true
+}
+```
+
+**Response:**
+```json
+{
+  "shop_id": "550e8400-e29b-41d4-a716-446655440000",
+  "schedule_enabled": true,
+  "message": "Schedule enabled successfully"
+}
+```
+
+**Error Responses:**
+- `400 Bad Request` - Missing or invalid `enabled` field
+- `404 Not Found` - Shop not found
+- `503 Service Unavailable` - Database not available
+- `500 Internal Server Error` - Server error
+
+**Examples:**
+
+Enable scheduling:
+```bash
+curl -X PATCH http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000/schedule \
+  -H "Authorization: Bearer your-secret-key" \
+  -H "Content-Type: application/json" \
+  -d '{"enabled": true}'
+```
+
+Disable scheduling:
+```bash
+curl -X PATCH http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000/schedule \
+  -H "Authorization: Bearer your-secret-key" \
+  -H "Content-Type: application/json" \
+  -d '{"enabled": false}'
+```
+
+---
+
+### Delete Shop
+
+Delete a shop and all related data (analytics, scrape history, content, scheduled jobs).
+
+**Endpoint:** `DELETE /api/shops/:id`
+
+**URL Parameters:**
+- `id` - Shop UUID
+
+**Response:**
+```json
+{
+  "message": "Shop and all related data deleted successfully",
+  "shop_id": "550e8400-e29b-41d4-a716-446655440000"
+}
+```
+
+**Notes:**
+- This operation is irreversible
+- Deletes all associated data:
+  - Shop record
+  - Analytics
+  - Scrape history
+  - All content records
+  - Scheduled jobs
+
+**Error Responses:**
+- `404 Not Found` - Shop not found
+- `503 Service Unavailable` - Database not available
+- `500 Internal Server Error` - Server error
+
+**Example:**
+```bash
+curl -X DELETE http://localhost:3000/api/shops/550e8400-e29b-41d4-a716-446655440000 \
+  -H "Authorization: Bearer your-secret-key"
+```
+
+---
+
+## Error Handling
+
+All endpoints return standard HTTP status codes:
+
+- `200 OK` - Request successful
+- `201 Created` - Resource created successfully
+- `400 Bad Request` - Invalid request parameters
+- `404 Not Found` - Resource not found
+- `500 Internal Server Error` - Server error
+- `503 Service Unavailable` - Service (e.g., database) unavailable
+
+Error responses include a JSON body with an `error` field:
+
+```json
+{
+  "error": "Description of the error"
+}
+```
+
+## Content Types
+
+The scraper categorizes content into four types:
+
+1. **shipping** (shipping_informations) - Shipping, delivery, and logistics information
+2. **contacts** - Contact information (email, phone, addresses)
+3. **terms** (terms_of_conditions) - Terms of service, privacy policy, legal information
+4. **faq** - Frequently asked questions and help pages
+
+Each category can contain multiple pages (returned as arrays).
+
+## Scheduled Scraping
+
+The application automatically schedules scraping based on sitemap rules:
+
+- **Frequency rules** from sitemap: `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`
+- **Last modified dates** from sitemap
+- Schedules can be enabled/disabled per shop
+- Scheduler checks for due jobs every minute
+- After each successful scrape, the next scrape is automatically scheduled
+
+## Content Change Detection
+
+The system uses MD5 hashing to detect content changes:
+
+- Each content piece is hashed when scraped
+- Compared with the previous version's hash
+- `changed` flag set to `true` if hashes differ
+- Change detection works for all content types
+- Useful for monitoring when shop policies or information are updated

+ 3 - 3
package-lock.json

@@ -1226,9 +1226,9 @@
       }
     },
     "node_modules/node-abi": {
-      "version": "3.83.0",
-      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.83.0.tgz",
-      "integrity": "sha512-o2PH88PgFlfoSDjU5oq/b/p9m+DJaPfslRI5FzNqcK1ea1i2/8xo/FL850kdgw0EAQJ/cSyyi2W2fBjHBdg5rA==",
+      "version": "3.85.0",
+      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz",
+      "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==",
       "dependencies": {
         "semver": "^7.3.5"
       },

+ 5 - 5
src/api/routes.ts

@@ -192,22 +192,22 @@ export function createRouter(jobQueue: JobQueue, db?: ShopDatabase): Router {
       const contentMetadata: any = {
         shipping_informations: latestContent.shipping.map(c => ({
           url: c.url,
-          changed: c.changed === 1,
+          changed: Boolean(c.changed),
           last_updated: c.updated_at
         })),
         contacts: latestContent.contacts.map(c => ({
           url: c.url,
-          changed: c.changed === 1,
+          changed: Boolean(c.changed),
           last_updated: c.updated_at
         })),
         terms_of_conditions: latestContent.terms.map(c => ({
           url: c.url,
-          changed: c.changed === 1,
+          changed: Boolean(c.changed),
           last_updated: c.updated_at
         })),
         faq: latestContent.faq.map(c => ({
           url: c.url,
-          changed: c.changed === 1,
+          changed: Boolean(c.changed),
           last_updated: c.updated_at
         }))
       };
@@ -303,7 +303,7 @@ export function createRouter(jobQueue: JobQueue, db?: ShopDatabase): Router {
           urlMap.set(key, {
             url: content.url,
             content: content.content,
-            changed: content.changed === 1,
+            changed: Boolean(content.changed),
             last_updated: content.updated_at
           });
         }