Parcourir la source

feat: validate and auto-scrape custom URLs

Changes:
- Added validation to prevent root URLs (e.g., https://example.com or https://example.com/)
- Added validation to ensure custom URLs are on the same domain as the shop
- Trigger immediate scraping when custom URL is added via job queue
- Updated CustomUrlsEndpoint to accept JobQueue dependency
- Updated routes.ts to inject JobQueue into CustomUrlsEndpoint
- Enhanced API response to include job_id and scrape_status

Validation:
- Root URL check: pathname must not be empty or "/"
- Domain check: hostname must match shop domain
- Returns helpful error message with example URL

Auto-scraping:
- Creates and queues a scraping job immediately after custom URL is added
- Respects existing job queue system with concurrency control
- Returns job_id in response for tracking scrape progress

Example error response:
{
  "error": "URL cannot point to the root of the website. Please specify a specific page URL.",
  "example": "https://example.com/some-page"
}

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

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh il y a 8 mois
Parent
commit
3fc9b58813
2 fichiers modifiés avec 40 ajouts et 6 suppressions
  1. 39 5
      src/api/components/CustomUrlsEndpoint.ts
  2. 1 1
      src/api/routes.ts

+ 39 - 5
src/api/components/CustomUrlsEndpoint.ts

@@ -1,10 +1,13 @@
 import { Request, Response } from 'express';
+import { v4 as uuidv4 } from 'uuid';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { logger } from '../../utils/logger';
 import { ShopDatabase } from '../../database/Database';
+import { JobQueue } from '../../queue/JobQueue';
+import { ScraperJob } from '../../types';
 
 export class CustomUrlsEndpoint extends BaseEndpointComponent {
-  constructor(private db: ShopDatabase) {
+  constructor(private db: ShopDatabase, private jobQueue?: JobQueue) {
     super();
   }
 
@@ -16,7 +19,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
         this.addCustomUrl.bind(this),
         {
           summary: 'Add a custom URL to scrape for a shop',
-          description: 'Adds a custom URL to be scraped for specific content type',
+          description: 'Adds a custom URL to be scraped for specific content type. The URL must be on the same domain and cannot be the root URL. Triggers immediate scraping via the job queue.',
           tags: ['Custom URLs'],
           parameters: [
             {
@@ -63,7 +66,9 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
                       enabled: { type: 'boolean' },
                       created_at: { type: 'string' }
                     }
-                  }
+                  },
+                  job_id: { type: 'string', description: 'ID of the scraping job (if queued)' },
+                  scrape_status: { type: 'string', description: 'Status of the scraping job' }
                 }
               }
             },
@@ -284,6 +289,17 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
         return;
       }
 
+      // Validate that the URL is not pointing to the root of the website
+      // Root URLs have no path or only "/" path
+      const urlPath = parsedUrl.pathname;
+      if (urlPath === '/' || urlPath === '') {
+        res.status(400).json({
+          error: 'URL cannot point to the root of the website. Please specify a specific page URL.',
+          example: `${parsedUrl.origin}/some-page`
+        });
+        return;
+      }
+
       // Check if URL was already scraped
       const alreadyScraped = this.db.isUrlAlreadyScraped(shop.id, url);
       if (alreadyScraped) {
@@ -298,8 +314,25 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
       try {
         const customUrl = this.db.createCustomUrl(shop.id, url, content_type);
 
+        // Trigger immediate scraping if job queue is available
+        let jobId: string | undefined;
+        if (this.jobQueue) {
+          const job: ScraperJob = {
+            id: uuidv4(),
+            sitemapUrl: shop.url, // Use shop URL for sitemap
+            status: 'pending',
+            createdAt: new Date(),
+            updatedAt: new Date(),
+            shopId: shop.id
+          };
+
+          this.jobQueue.addJob(job);
+          jobId = job.id;
+          logger.info(`Triggered scraping job ${job.id} for custom URL ${url} in shop ${shop.id}`);
+        }
+
         res.status(201).json({
-          message: 'Custom URL added successfully',
+          message: 'Custom URL added successfully' + (jobId ? ' and scraping started' : ''),
           custom_url: {
             id: customUrl.id,
             shop_id: customUrl.shop_id,
@@ -307,7 +340,8 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
             content_type: customUrl.content_type,
             enabled: customUrl.enabled,
             created_at: customUrl.created_at
-          }
+          },
+          ...(jobId && { job_id: jobId, scrape_status: 'queued' })
         });
       } catch (error: any) {
         if (error.message?.includes('already been added')) {

+ 1 - 1
src/api/routes.ts

@@ -35,7 +35,7 @@ export function createRouter(jobQueue: JobQueue, apiKey: string, db?: ShopDataba
     components.push(
       new ShopsEndpoint(db),
       new WebhooksEndpoint(db),
-      new CustomUrlsEndpoint(db),
+      new CustomUrlsEndpoint(db, jobQueue),
       new ContentEndpoint(db, config),
       new McpEndpoint(db)
     );