Procházet zdrojové kódy

refactor: rename all backend code shops to sites

Rename all API endpoints, scraper classes, service references, MCP tools,
webhooks, and scheduler from shop/shops to site/sites naming. Routes
change from /api/shops to /api/sites. Classes renamed: ShopsEndpoint to
SitesEndpoint, WebshopScraper to WebScraper, ShopDatabase to SiteDatabase.
fszontagh před 3 měsíci
rodič
revize
e864673bf2

+ 29 - 29
src/api/components/ContentEndpoint.ts

@@ -1,7 +1,7 @@
 import { Request, Response } from 'express';
 import { Request, Response } from 'express';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { QdrantService } from '../../services/QdrantService';
 import { QdrantService } from '../../services/QdrantService';
 import { ContentExtractor } from '../../scraper/ContentExtractor';
 import { ContentExtractor } from '../../scraper/ContentExtractor';
 import { QdrantEmbeddingWorkflow } from '../../services/QdrantEmbeddingWorkflow';
 import { QdrantEmbeddingWorkflow } from '../../services/QdrantEmbeddingWorkflow';
@@ -13,7 +13,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
   private contentExtractor: ContentExtractor;
   private contentExtractor: ContentExtractor;
   private embeddingWorkflow?: QdrantEmbeddingWorkflow;
   private embeddingWorkflow?: QdrantEmbeddingWorkflow;
 
 
-  constructor(private db: ShopDatabase, private config?: AppConfig) {
+  constructor(private db: SiteDatabase, private config?: AppConfig) {
     super();
     super();
     this.contentExtractor = new ContentExtractor();
     this.contentExtractor = new ContentExtractor();
     if (config) {
     if (config) {
@@ -26,11 +26,11 @@ export class ContentEndpoint extends BaseEndpointComponent {
     return [
     return [
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops/:id/content',
+        '/api/sites/:id/content',
         this.getAllContent.bind(this),
         this.getAllContent.bind(this),
         {
         {
-          summary: 'List all content for a shop',
-          description: 'Retrieves all content (from sitemap and custom URLs) for a shop, including disabled content',
+          summary: 'List all content for a site',
+          description: 'Retrieves all content (from sitemap and custom URLs) for a site, including disabled content',
           tags: ['Content'],
           tags: ['Content'],
           parameters: [
           parameters: [
             {
             {
@@ -38,7 +38,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             },
             },
             {
             {
               name: 'include_disabled',
               name: 'include_disabled',
@@ -54,7 +54,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   content: {
                   content: {
                     type: 'array',
                     type: 'array',
                     items: {
                     items: {
@@ -74,7 +74,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -82,7 +82,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'PATCH',
         'PATCH',
-        '/api/shops/:id/content/:contentId',
+        '/api/sites/:id/content/:contentId',
         this.updateContent.bind(this),
         this.updateContent.bind(this),
         {
         {
           summary: 'Enable or disable content',
           summary: 'Enable or disable content',
@@ -94,7 +94,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             },
             },
             {
             {
               name: 'contentId',
               name: 'contentId',
@@ -134,7 +134,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
               description: 'Invalid request data'
               description: 'Invalid request data'
             },
             },
             '404': {
             '404': {
-              description: 'Shop or content not found'
+              description: 'Site or content not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -153,16 +153,16 @@ export class ContentEndpoint extends BaseEndpointComponent {
       const { id } = req.params;
       const { id } = req.params;
       const includeDisabled = req.query.include_disabled !== 'false'; // Default true
       const includeDisabled = req.query.include_disabled !== 'false'; // Default true
 
 
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      const content = this.db.getAllShopContent(shop.id, includeDisabled);
+      const content = this.db.getAllSiteContent(site.id, includeDisabled);
 
 
       res.json({
       res.json({
-        shop_id: shop.id,
+        site_id: site.id,
         content: content.map(c => ({
         content: content.map(c => ({
           id: c.id,
           id: c.id,
           url: c.url,
           url: c.url,
@@ -194,22 +194,22 @@ export class ContentEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      // Verify shop exists
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Verify site exists
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      // Verify content exists and belongs to this shop
+      // Verify content exists and belongs to this site
       const content = this.db.getContentById(contentId);
       const content = this.db.getContentById(contentId);
       if (!content) {
       if (!content) {
         res.status(404).json({ error: 'Content not found' });
         res.status(404).json({ error: 'Content not found' });
         return;
         return;
       }
       }
 
 
-      if (content.shop_id !== shop.id) {
-        res.status(404).json({ error: 'Content does not belong to this shop' });
+      if (content.site_id !== site.id) {
+        res.status(404).json({ error: 'Content does not belong to this site' });
         return;
         return;
       }
       }
 
 
@@ -231,7 +231,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
         // First, delete from Qdrant if available
         // First, delete from Qdrant if available
         if (this.qdrantService) {
         if (this.qdrantService) {
           try {
           try {
-            const embeddings = this.db.getShopEmbeddings(contentId);
+            const embeddings = this.db.getSiteEmbeddings(contentId);
             for (const emb of embeddings) {
             for (const emb of embeddings) {
               await this.qdrantService.deletePointsByContentId(emb.collection_name, contentId);
               await this.qdrantService.deletePointsByContentId(emb.collection_name, contentId);
               logger.info(`Deleted Qdrant points for content ${contentId} from collection ${emb.collection_name}`);
               logger.info(`Deleted Qdrant points for content ${contentId} from collection ${emb.collection_name}`);
@@ -248,11 +248,11 @@ export class ContentEndpoint extends BaseEndpointComponent {
         // Then delete content data (clears content but keeps URL record)
         // Then delete content data (clears content but keeps URL record)
         this.db.deleteContentData(contentId);
         this.db.deleteContentData(contentId);
         actionTaken = 'content_and_embeddings_deleted';
         actionTaken = 'content_and_embeddings_deleted';
-        logger.info(`Disabled content ${contentId} and cleared data for shop ${shop.id}`);
+        logger.info(`Disabled content ${contentId} and cleared data for site ${site.id}`);
       } else {
       } else {
         // Re-enabling: update the flag and scrape immediately
         // Re-enabling: update the flag and scrape immediately
         this.db.setContentEnabled(contentId, true);
         this.db.setContentEnabled(contentId, true);
-        logger.info(`Re-enabled content ${contentId} for shop ${shop.id}, starting immediate scrape`);
+        logger.info(`Re-enabled content ${contentId} for site ${site.id}, starting immediate scrape`);
 
 
         // Scrape the content immediately
         // Scrape the content immediately
         try {
         try {
@@ -260,7 +260,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
 
 
           // Save the scraped content
           // Save the scraped content
           const saveResult = this.db.saveContent(
           const saveResult = this.db.saveContent(
-            shop.id,
+            site.id,
             content.content_type as 'shipping' | 'contacts' | 'terms' | 'faq',
             content.content_type as 'shipping' | 'contacts' | 'terms' | 'faq',
             content.url,
             content.url,
             extractionResult.content,
             extractionResult.content,
@@ -271,7 +271,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
           if (this.embeddingWorkflow && !saveResult.skipped) {
           if (this.embeddingWorkflow && !saveResult.skipped) {
             const contentHash = createHash('sha256').update(extractionResult.content).digest('hex');
             const contentHash = createHash('sha256').update(extractionResult.content).digest('hex');
             await this.embeddingWorkflow.processContentForEmbedding(
             await this.embeddingWorkflow.processContentForEmbedding(
-              shop.id,
+              site.id,
               saveResult.contentId,
               saveResult.contentId,
               content.content_type as 'shipping' | 'contacts' | 'terms' | 'faq',
               content.content_type as 'shipping' | 'contacts' | 'terms' | 'faq',
               content.url,
               content.url,
@@ -283,7 +283,7 @@ export class ContentEndpoint extends BaseEndpointComponent {
           }
           }
 
 
           actionTaken = saveResult.skipped ? 'skipped_duplicate' : 'rescraped_and_saved';
           actionTaken = saveResult.skipped ? 'skipped_duplicate' : 'rescraped_and_saved';
-          logger.info(`Successfully re-scraped content ${contentId} for shop ${shop.id}`);
+          logger.info(`Successfully re-scraped content ${contentId} for site ${site.id}`);
         } catch (error) {
         } catch (error) {
           logger.error(`Failed to re-scrape content ${contentId}:`, error);
           logger.error(`Failed to re-scrape content ${contentId}:`, error);
           actionTaken = 'enabled_but_scrape_failed';
           actionTaken = 'enabled_but_scrape_failed';

+ 54 - 54
src/api/components/CustomUrlsEndpoint.ts

@@ -2,12 +2,12 @@ import { Request, Response } from 'express';
 import { v4 as uuidv4 } from 'uuid';
 import { v4 as uuidv4 } from 'uuid';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { JobQueue } from '../../queue/JobQueue';
 import { JobQueue } from '../../queue/JobQueue';
 import { ScraperJob } from '../../types';
 import { ScraperJob } from '../../types';
 
 
 export class CustomUrlsEndpoint extends BaseEndpointComponent {
 export class CustomUrlsEndpoint extends BaseEndpointComponent {
-  constructor(private db: ShopDatabase, private jobQueue?: JobQueue) {
+  constructor(private db: SiteDatabase, private jobQueue?: JobQueue) {
     super();
     super();
   }
   }
 
 
@@ -15,10 +15,10 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
     return [
     return [
       this.createEndpoint(
       this.createEndpoint(
         'POST',
         'POST',
-        '/api/shops/:id/custom-urls',
+        '/api/sites/:id/custom-urls',
         this.addCustomUrl.bind(this),
         this.addCustomUrl.bind(this),
         {
         {
-          summary: 'Add a custom URL to scrape for a shop',
+          summary: 'Add a custom URL to scrape for a site',
           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.',
           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'],
           tags: ['Custom URLs'],
           parameters: [
           parameters: [
@@ -27,7 +27,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           requestBody: {
           requestBody: {
@@ -38,7 +38,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
                 url: {
                 url: {
                   type: 'string',
                   type: 'string',
                   format: 'url',
                   format: 'url',
-                  description: 'URL to scrape (must belong to the same domain as the shop)'
+                  description: 'URL to scrape (must belong to the same domain as the site)'
                 },
                 },
                 content_type: {
                 content_type: {
                   type: 'string',
                   type: 'string',
@@ -60,7 +60,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
                     type: 'object',
                     type: 'object',
                     properties: {
                     properties: {
                       id: { type: 'string' },
                       id: { type: 'string' },
-                      shop_id: { type: 'string' },
+                      site_id: { type: 'string' },
                       url: { type: 'string' },
                       url: { type: 'string' },
                       content_type: { type: 'string' },
                       content_type: { type: 'string' },
                       enabled: { type: 'boolean' },
                       enabled: { type: 'boolean' },
@@ -76,7 +76,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               description: 'Invalid request data or URL not in same domain'
               description: 'Invalid request data or URL not in same domain'
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             },
             },
             '409': {
             '409': {
               description: 'URL already exists or already scraped'
               description: 'URL already exists or already scraped'
@@ -87,11 +87,11 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops/:id/custom-urls',
+        '/api/sites/:id/custom-urls',
         this.getCustomUrls.bind(this),
         this.getCustomUrls.bind(this),
         {
         {
-          summary: 'List all custom URLs for a shop',
-          description: 'Retrieves all custom URLs configured for a shop',
+          summary: 'List all custom URLs for a site',
+          description: 'Retrieves all custom URLs configured for a site',
           tags: ['Custom URLs'],
           tags: ['Custom URLs'],
           parameters: [
           parameters: [
             {
             {
@@ -99,7 +99,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           responses: {
           responses: {
@@ -108,7 +108,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   custom_urls: {
                   custom_urls: {
                     type: 'array',
                     type: 'array',
                     items: {
                     items: {
@@ -126,7 +126,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -134,7 +134,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'PATCH',
         'PATCH',
-        '/api/shops/:id/custom-urls/:customUrlId',
+        '/api/sites/:id/custom-urls/:customUrlId',
         this.updateCustomUrl.bind(this),
         this.updateCustomUrl.bind(this),
         {
         {
           summary: 'Enable or disable a custom URL',
           summary: 'Enable or disable a custom URL',
@@ -146,7 +146,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             },
             },
             {
             {
               name: 'customUrlId',
               name: 'customUrlId',
@@ -182,10 +182,10 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               }
               }
             },
             },
             '403': {
             '403': {
-              description: 'Custom URL does not belong to this shop'
+              description: 'Custom URL does not belong to this site'
             },
             },
             '404': {
             '404': {
-              description: 'Shop or custom URL not found'
+              description: 'Site or custom URL not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -193,11 +193,11 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'DELETE',
         'DELETE',
-        '/api/shops/:id/custom-urls/:customUrlId',
+        '/api/sites/:id/custom-urls/:customUrlId',
         this.deleteCustomUrl.bind(this),
         this.deleteCustomUrl.bind(this),
         {
         {
           summary: 'Delete a custom URL',
           summary: 'Delete a custom URL',
-          description: 'Permanently removes a custom URL from the shop configuration',
+          description: 'Permanently removes a custom URL from the site configuration',
           tags: ['Custom URLs'],
           tags: ['Custom URLs'],
           parameters: [
           parameters: [
             {
             {
@@ -205,7 +205,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             },
             },
             {
             {
               name: 'customUrlId',
               name: 'customUrlId',
@@ -227,10 +227,10 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
               }
               }
             },
             },
             '403': {
             '403': {
-              description: 'Custom URL does not belong to this shop'
+              description: 'Custom URL does not belong to this site'
             },
             },
             '404': {
             '404': {
-              description: 'Shop or custom URL not found'
+              description: 'Site or custom URL not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -273,18 +273,18 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      // Check if shop exists (try custom_id first, then internal ID)
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Check if site exists (try custom_id first, then internal ID)
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      // Validate that the URL belongs to the same domain as the shop
-      const shopUrl = new URL(shop.url);
-      if (parsedUrl.hostname !== shopUrl.hostname) {
+      // Validate that the URL belongs to the same domain as the site
+      const siteUrl = new URL(site.url);
+      if (parsedUrl.hostname !== siteUrl.hostname) {
         res.status(400).json({
         res.status(400).json({
-          error: `URL must belong to the same domain as the shop (${shopUrl.hostname})`
+          error: `URL must belong to the same domain as the site (${siteUrl.hostname})`
         });
         });
         return;
         return;
       }
       }
@@ -301,7 +301,7 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
       }
       }
 
 
       // Check if URL was already scraped
       // Check if URL was already scraped
-      const alreadyScraped = this.db.isUrlAlreadyScraped(shop.id, url);
+      const alreadyScraped = this.db.isUrlAlreadyScraped(site.id, url);
       if (alreadyScraped) {
       if (alreadyScraped) {
         res.status(409).json({
         res.status(409).json({
           error: 'This URL has already been scraped from the sitemap',
           error: 'This URL has already been scraped from the sitemap',
@@ -312,30 +312,30 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
 
 
       // Create custom URL
       // Create custom URL
       try {
       try {
-        const customUrl = this.db.createCustomUrl(shop.id, url, content_type);
+        const customUrl = this.db.createCustomUrl(site.id, url, content_type);
 
 
         // Trigger immediate scraping if job queue is available
         // Trigger immediate scraping if job queue is available
         let jobId: string | undefined;
         let jobId: string | undefined;
         if (this.jobQueue) {
         if (this.jobQueue) {
           const job: ScraperJob = {
           const job: ScraperJob = {
             id: uuidv4(),
             id: uuidv4(),
-            sitemapUrl: shop.url, // Use shop URL for sitemap
+            sitemapUrl: site.url, // Use site URL for sitemap
             status: 'pending',
             status: 'pending',
             createdAt: new Date(),
             createdAt: new Date(),
             updatedAt: new Date(),
             updatedAt: new Date(),
-            shopId: shop.id
+            siteId: site.id
           };
           };
 
 
           this.jobQueue.addJob(job);
           this.jobQueue.addJob(job);
           jobId = job.id;
           jobId = job.id;
-          logger.info(`Triggered scraping job ${job.id} for custom URL ${url} in shop ${shop.id}`);
+          logger.info(`Triggered scraping job ${job.id} for custom URL ${url} in site ${site.id}`);
         }
         }
 
 
         res.status(201).json({
         res.status(201).json({
           message: 'Custom URL added successfully' + (jobId ? ' and scraping started' : ''),
           message: 'Custom URL added successfully' + (jobId ? ' and scraping started' : ''),
           custom_url: {
           custom_url: {
             id: customUrl.id,
             id: customUrl.id,
-            shop_id: customUrl.shop_id,
+            site_id: customUrl.site_id,
             url: customUrl.url,
             url: customUrl.url,
             content_type: customUrl.content_type,
             content_type: customUrl.content_type,
             enabled: customUrl.enabled,
             enabled: customUrl.enabled,
@@ -364,17 +364,17 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
       }
       }
 
 
       const { id } = req.params;
       const { id } = req.params;
-      const shop = this.db.getShopByAnyId(id);
+      const site = this.db.getSiteByAnyId(id);
 
 
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      const customUrls = this.db.getCustomUrls(shop.id);
+      const customUrls = this.db.getCustomUrls(site.id);
 
 
       res.json({
       res.json({
-        shop_id: shop.id,
+        site_id: site.id,
         custom_urls: customUrls.map(cu => ({
         custom_urls: customUrls.map(cu => ({
           id: cu.id,
           id: cu.id,
           url: cu.url,
           url: cu.url,
@@ -404,10 +404,10 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      // Get shop by any ID (try custom_id first, then internal ID)
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Get site by any ID (try custom_id first, then internal ID)
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
@@ -417,8 +417,8 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      if (customUrl.shop_id !== shop.id) {
-        res.status(403).json({ error: 'Custom URL does not belong to this shop' });
+      if (customUrl.site_id !== site.id) {
+        res.status(403).json({ error: 'Custom URL does not belong to this site' });
         return;
         return;
       }
       }
 
 
@@ -444,10 +444,10 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
 
 
       const { id, customUrlId } = req.params;
       const { id, customUrlId } = req.params;
 
 
-      // Get shop by any ID (try custom_id first, then internal ID)
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Get site by any ID (try custom_id first, then internal ID)
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
@@ -457,8 +457,8 @@ export class CustomUrlsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      if (customUrl.shop_id !== shop.id) {
-        res.status(403).json({ error: 'Custom URL does not belong to this shop' });
+      if (customUrl.site_id !== site.id) {
+        res.status(403).json({ error: 'Custom URL does not belong to this site' });
         return;
         return;
       }
       }
 
 

+ 18 - 18
src/api/components/JobsEndpoint.ts

@@ -4,12 +4,12 @@ import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { JobQueue } from '../../queue/JobQueue';
 import { JobQueue } from '../../queue/JobQueue';
 import { ScraperJob } from '../../types';
 import { ScraperJob } from '../../types';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 
 
 export class JobsEndpoint extends BaseEndpointComponent {
 export class JobsEndpoint extends BaseEndpointComponent {
   constructor(
   constructor(
     private jobQueue: JobQueue,
     private jobQueue: JobQueue,
-    private db?: ShopDatabase
+    private db?: SiteDatabase
   ) {
   ) {
     super();
     super();
   }
   }
@@ -22,7 +22,7 @@ export class JobsEndpoint extends BaseEndpointComponent {
         this.createJob.bind(this),
         this.createJob.bind(this),
         {
         {
           summary: 'Create a new scraping job',
           summary: 'Create a new scraping job',
-          description: 'Creates a new job to scrape a webshop. If database is available, creates or finds the shop record.',
+          description: 'Creates a new job to scrape a website. If database is available, creates or finds the site record.',
           tags: ['Jobs'],
           tags: ['Jobs'],
           requestBody: {
           requestBody: {
             required: true,
             required: true,
@@ -32,12 +32,12 @@ export class JobsEndpoint extends BaseEndpointComponent {
                 url: {
                 url: {
                   type: 'string',
                   type: 'string',
                   format: 'url',
                   format: 'url',
-                  description: 'The URL of the webshop to scrape'
+                  description: 'The URL of the website to scrape'
                 },
                 },
                 custom_id: {
                 custom_id: {
                   type: 'string',
                   type: 'string',
                   pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
                   pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
-                  description: 'Optional custom UUID for the shop'
+                  description: 'Optional custom UUID for the site'
                 }
                 }
               },
               },
               required: ['url']
               required: ['url']
@@ -50,7 +50,7 @@ export class JobsEndpoint extends BaseEndpointComponent {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
                   job_id: { type: 'string' },
                   job_id: { type: 'string' },
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   status: { type: 'string' },
                   status: { type: 'string' },
                   message: { type: 'string' }
                   message: { type: 'string' }
                 }
                 }
@@ -169,15 +169,15 @@ export class JobsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      let shopId: string | undefined;
+      let siteId: string | undefined;
 
 
-      // If database is available, create or find shop
+      // If database is available, create or find site
       if (this.db) {
       if (this.db) {
         const { SitemapParser } = require('../../scraper/SitemapParser');
         const { SitemapParser } = require('../../scraper/SitemapParser');
-        const { sitemapUrl, webshopType } = SitemapParser.getSitemapUrl(url);
+        const { sitemapUrl, sitePlatform } = SitemapParser.getSitemapUrl(url);
 
 
-        let shop = this.db.getShopByUrl(url);
-        if (!shop) {
+        let site = this.db.getSiteByUrl(url);
+        if (!site) {
           // Validate custom_id format if provided
           // Validate custom_id format if provided
           if (custom_id && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(custom_id)) {
           if (custom_id && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(custom_id)) {
             res.status(400).json({ error: 'custom_id must be a valid UUID format' });
             res.status(400).json({ error: 'custom_id must be a valid UUID format' });
@@ -185,15 +185,15 @@ export class JobsEndpoint extends BaseEndpointComponent {
           }
           }
 
 
           // Check if custom_id is already in use
           // Check if custom_id is already in use
-          if (custom_id && this.db.getShopByCustomId(custom_id)) {
+          if (custom_id && this.db.getSiteByCustomId(custom_id)) {
             res.status(409).json({ error: 'custom_id is already in use' });
             res.status(409).json({ error: 'custom_id is already in use' });
             return;
             return;
           }
           }
 
 
-          shop = this.db.createShop(url, sitemapUrl, webshopType, custom_id);
-          logger.info(`Created new shop: ${shop.id}${shop.custom_id ? ` with custom ID ${shop.custom_id}` : ''}`);
+          site = this.db.createSite(url, sitemapUrl, sitePlatform, custom_id);
+          logger.info(`Created new site: ${site.id}${site.custom_id ? ` with custom ID ${site.custom_id}` : ''}`);
         }
         }
-        shopId = shop.id;
+        siteId = site.id;
       }
       }
 
 
       // Create new job
       // Create new job
@@ -203,16 +203,16 @@ export class JobsEndpoint extends BaseEndpointComponent {
         status: 'pending',
         status: 'pending',
         createdAt: new Date(),
         createdAt: new Date(),
         updatedAt: new Date(),
         updatedAt: new Date(),
-        shopId
+        siteId
       };
       };
 
 
       this.jobQueue.addJob(job);
       this.jobQueue.addJob(job);
 
 
-      logger.info(`New job created: ${job.id} for shop: ${shopId || 'N/A'}`);
+      logger.info(`New job created: ${job.id} for site: ${siteId || 'N/A'}`);
 
 
       res.status(201).json({
       res.status(201).json({
         job_id: job.id,
         job_id: job.id,
-        shop_id: shopId,
+        site_id: siteId,
         status: job.status,
         status: job.status,
         message: 'Job created successfully'
         message: 'Job created successfully'
       });
       });

+ 68 - 68
src/api/components/McpEndpoint.ts

@@ -1,10 +1,10 @@
 import { Request, Response } from 'express';
 import { Request, Response } from 'express';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 
 
 export class McpEndpoint extends BaseEndpointComponent {
 export class McpEndpoint extends BaseEndpointComponent {
-  constructor(private db: ShopDatabase) {
+  constructor(private db: SiteDatabase) {
     super();
     super();
   }
   }
 
 
@@ -40,7 +40,7 @@ export class McpEndpoint extends BaseEndpointComponent {
               in: 'query',
               in: 'query',
               type: 'string',
               type: 'string',
               required: false,
               required: false,
-              description: 'Group results by: day, hour, shop, tool'
+              description: 'Group results by: day, hour, site, tool'
             }
             }
           ],
           ],
           responses: {
           responses: {
@@ -58,7 +58,7 @@ export class McpEndpoint extends BaseEndpointComponent {
                     type: 'object',
                     type: 'object',
                     additionalProperties: { type: 'number' }
                     additionalProperties: { type: 'number' }
                   },
                   },
-                  shop_usage: {
+                  site_usage: {
                     type: 'object',
                     type: 'object',
                     additionalProperties: { type: 'number' }
                     additionalProperties: { type: 'number' }
                   },
                   },
@@ -82,20 +82,20 @@ export class McpEndpoint extends BaseEndpointComponent {
 
 
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops/:shopId/mcp/analytics',
+        '/api/sites/:siteId/mcp/analytics',
         this.getShopMcpAnalytics.bind(this),
         this.getShopMcpAnalytics.bind(this),
         {
         {
-          summary: 'Get shop MCP analytics',
-          description: 'Get MCP usage analytics for a specific shop',
+          summary: 'Get site MCP analytics',
+          description: 'Get MCP usage analytics for a specific site',
           tags: ['MCP'],
           tags: ['MCP'],
           requiresAuth: true,
           requiresAuth: true,
           parameters: [
           parameters: [
             {
             {
-              name: 'shopId',
+              name: 'siteId',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID'
+              description: 'Site ID'
             },
             },
             {
             {
               name: 'dateFrom',
               name: 'dateFrom',
@@ -114,11 +114,11 @@ export class McpEndpoint extends BaseEndpointComponent {
           ],
           ],
           responses: {
           responses: {
             '200': {
             '200': {
-              description: 'Shop MCP analytics',
+              description: 'Site MCP analytics',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   total_calls: { type: 'number' },
                   total_calls: { type: 'number' },
                   successful_calls: { type: 'number' },
                   successful_calls: { type: 'number' },
                   failed_calls: { type: 'number' },
                   failed_calls: { type: 'number' },
@@ -146,7 +146,7 @@ export class McpEndpoint extends BaseEndpointComponent {
                 }
                 }
               }
               }
             },
             },
-            '404': { description: 'Shop not found' }
+            '404': { description: 'Site not found' }
           }
           }
         }
         }
       ),
       ),
@@ -163,11 +163,11 @@ export class McpEndpoint extends BaseEndpointComponent {
           requiresAuth: true,
           requiresAuth: true,
           parameters: [
           parameters: [
             {
             {
-              name: 'shopId',
+              name: 'siteId',
               in: 'query',
               in: 'query',
               type: 'string',
               type: 'string',
               required: false,
               required: false,
-              description: 'Filter by shop ID'
+              description: 'Filter by site ID'
             },
             },
             {
             {
               name: 'toolName',
               name: 'toolName',
@@ -210,7 +210,7 @@ export class McpEndpoint extends BaseEndpointComponent {
                       type: 'object',
                       type: 'object',
                       properties: {
                       properties: {
                         id: { type: 'string' },
                         id: { type: 'string' },
-                        shop_id: { type: 'string' },
+                        site_id: { type: 'string' },
                         tool_name: { type: 'string' },
                         tool_name: { type: 'string' },
                         query: { type: 'string' },
                         query: { type: 'string' },
                         parameters: { type: 'object' },
                         parameters: { type: 'object' },
@@ -263,7 +263,7 @@ export class McpEndpoint extends BaseEndpointComponent {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
                   id: { type: 'string' },
                   id: { type: 'string' },
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   tool_name: { type: 'string' },
                   tool_name: { type: 'string' },
                   query: { type: 'string' },
                   query: { type: 'string' },
                   parameters: { type: 'object' },
                   parameters: { type: 'object' },
@@ -416,7 +416,7 @@ export class McpEndpoint extends BaseEndpointComponent {
                       type: 'object',
                       type: 'object',
                       properties: {
                       properties: {
                         id: { type: 'string' },
                         id: { type: 'string' },
-                        shop_id: { type: 'string' },
+                        site_id: { type: 'string' },
                         tool_name: { type: 'string' },
                         tool_name: { type: 'string' },
                         query: { type: 'string' },
                         query: { type: 'string' },
                         success: { type: 'boolean' },
                         success: { type: 'boolean' },
@@ -433,23 +433,23 @@ export class McpEndpoint extends BaseEndpointComponent {
         }
         }
       ),
       ),
 
 
-      // Shop MCP Logs
+      // Site MCP Logs
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/mcp/shops/:shopId/logs',
+        '/api/mcp/sites/:siteId/logs',
         this.getShopMcpLogs.bind(this),
         this.getShopMcpLogs.bind(this),
         {
         {
-          summary: 'Get shop MCP logs',
-          description: 'Get MCP logs for a specific shop',
+          summary: 'Get site MCP logs',
+          description: 'Get MCP logs for a specific site',
           tags: ['MCP'],
           tags: ['MCP'],
           requiresAuth: true,
           requiresAuth: true,
           parameters: [
           parameters: [
             {
             {
-              name: 'shopId',
+              name: 'siteId',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID'
+              description: 'Site ID'
             },
             },
             {
             {
               name: 'limit',
               name: 'limit',
@@ -475,7 +475,7 @@ export class McpEndpoint extends BaseEndpointComponent {
           ],
           ],
           responses: {
           responses: {
             '200': {
             '200': {
-              description: 'Shop MCP logs',
+              description: 'Site MCP logs',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
@@ -485,7 +485,7 @@ export class McpEndpoint extends BaseEndpointComponent {
                       type: 'object',
                       type: 'object',
                       properties: {
                       properties: {
                         id: { type: 'string' },
                         id: { type: 'string' },
-                        shop_id: { type: 'string' },
+                        site_id: { type: 'string' },
                         tool_name: { type: 'string' },
                         tool_name: { type: 'string' },
                         query: { type: 'string' },
                         query: { type: 'string' },
                         success: { type: 'boolean' },
                         success: { type: 'boolean' },
@@ -498,28 +498,28 @@ export class McpEndpoint extends BaseEndpointComponent {
                 }
                 }
               }
               }
             },
             },
-            '404': { description: 'Shop not found' }
+            '404': { description: 'Site not found' }
           }
           }
         }
         }
       ),
       ),
 
 
-      // Clear Shop MCP Logs
+      // Clear Site MCP Logs
       this.createEndpoint(
       this.createEndpoint(
         'DELETE',
         'DELETE',
-        '/api/mcp/shops/:shopId/logs',
+        '/api/mcp/sites/:siteId/logs',
         this.clearShopMcpLogs.bind(this),
         this.clearShopMcpLogs.bind(this),
         {
         {
-          summary: 'Clear shop MCP logs',
-          description: 'Delete all MCP logs for a specific shop',
+          summary: 'Clear site MCP logs',
+          description: 'Delete all MCP logs for a specific site',
           tags: ['MCP'],
           tags: ['MCP'],
           requiresAuth: true,
           requiresAuth: true,
           parameters: [
           parameters: [
             {
             {
-              name: 'shopId',
+              name: 'siteId',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID'
+              description: 'Site ID'
             }
             }
           ],
           ],
           responses: {
           responses: {
@@ -532,7 +532,7 @@ export class McpEndpoint extends BaseEndpointComponent {
                 }
                 }
               }
               }
             },
             },
-            '404': { description: 'Shop not found' }
+            '404': { description: 'Site not found' }
           }
           }
         }
         }
       )
       )
@@ -557,8 +557,8 @@ export class McpEndpoint extends BaseEndpointComponent {
       // Get tool usage breakdown
       // Get tool usage breakdown
       const toolUsage = this.db.getMcpToolUsage(options);
       const toolUsage = this.db.getMcpToolUsage(options);
 
 
-      // Get shop usage breakdown
-      const shopUsage = this.db.getMcpShopUsage(options);
+      // Get site usage breakdown
+      const siteUsage = this.db.getMcpSiteUsage(options);
 
 
       // Get timeline data if requested
       // Get timeline data if requested
       let timeline: any[] = [];
       let timeline: any[] = [];
@@ -573,7 +573,7 @@ export class McpEndpoint extends BaseEndpointComponent {
         success_rate: successRate,
         success_rate: successRate,
         avg_response_time: analytics.avg_response_time,
         avg_response_time: analytics.avg_response_time,
         tool_usage: toolUsage,
         tool_usage: toolUsage,
-        shop_usage: shopUsage,
+        site_usage: siteUsage,
         timeline
         timeline
       });
       });
     } catch (error) {
     } catch (error) {
@@ -584,30 +584,30 @@ export class McpEndpoint extends BaseEndpointComponent {
 
 
   private async getShopMcpAnalytics(req: Request, res: Response): Promise<void> {
   private async getShopMcpAnalytics(req: Request, res: Response): Promise<void> {
     try {
     try {
-      const { shopId } = req.params;
+      const { siteId } = req.params;
       const { dateFrom, dateTo } = req.query;
       const { dateFrom, dateTo } = req.query;
 
 
-      // Get shop by any ID (try custom_id first, then internal ID)
-      const shop = this.db.getShopByAnyId(shopId);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Get site by any ID (try custom_id first, then internal ID)
+      const site = this.db.getSiteByAnyId(siteId);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      const options: any = { shopId };
+      const options: any = { siteId };
       if (dateFrom) options.dateFrom = dateFrom as string;
       if (dateFrom) options.dateFrom = dateFrom as string;
       if (dateTo) options.dateTo = dateTo as string;
       if (dateTo) options.dateTo = dateTo as string;
 
 
       const analytics = this.db.getMcpAnalytics(options);
       const analytics = this.db.getMcpAnalytics(options);
       const toolUsage = this.db.getMcpToolUsage(options);
       const toolUsage = this.db.getMcpToolUsage(options);
-      const recentCalls = this.db.getMcpLogs({ shopId, limit: 20 });
+      const recentCalls = this.db.getMcpLogs({ siteId, limit: 20 });
 
 
       const successRate = analytics.total_calls > 0
       const successRate = analytics.total_calls > 0
         ? (analytics.successful_calls / analytics.total_calls) * 100
         ? (analytics.successful_calls / analytics.total_calls) * 100
         : 0;
         : 0;
 
 
       res.json({
       res.json({
-        shop_id: shopId,
+        site_id: siteId,
         total_calls: analytics.total_calls,
         total_calls: analytics.total_calls,
         successful_calls: analytics.successful_calls,
         successful_calls: analytics.successful_calls,
         failed_calls: analytics.failed_calls,
         failed_calls: analytics.failed_calls,
@@ -617,21 +617,21 @@ export class McpEndpoint extends BaseEndpointComponent {
         recent_calls: recentCalls.logs
         recent_calls: recentCalls.logs
       });
       });
     } catch (error) {
     } catch (error) {
-      logger.error('Failed to get shop MCP analytics:', error);
+      logger.error('Failed to get site MCP analytics:', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }
 
 
   private async getMcpLogs(req: Request, res: Response): Promise<void> {
   private async getMcpLogs(req: Request, res: Response): Promise<void> {
     try {
     try {
-      const { shopId, toolName, success, limit, offset } = req.query;
+      const { siteId, toolName, success, limit, offset } = req.query;
 
 
       const options: any = {
       const options: any = {
         limit: parseInt(limit as string) || 100,
         limit: parseInt(limit as string) || 100,
         offset: parseInt(offset as string) || 0
         offset: parseInt(offset as string) || 0
       };
       };
 
 
-      if (shopId) options.shopId = shopId as string;
+      if (siteId) options.siteId = siteId as string;
       if (toolName) options.toolName = toolName as string;
       if (toolName) options.toolName = toolName as string;
       if (success !== undefined) options.success = success === 'true';
       if (success !== undefined) options.success = success === 'true';
 
 
@@ -725,22 +725,22 @@ export class McpEndpoint extends BaseEndpointComponent {
 
 
   private async getShopMcpLogs(req: Request, res: Response): Promise<void> {
   private async getShopMcpLogs(req: Request, res: Response): Promise<void> {
     try {
     try {
-      const { shopId } = req.params;
+      const { siteId } = req.params;
       const { limit, success, tool_name } = req.query;
       const { limit, success, tool_name } = req.query;
 
 
-      // Get shop from database - try both internal ID and custom ID
-      // Get shop by any ID (try custom_id first, then internal ID)
-      let shop = this.db.getShopByAnyId(shopId);
-      if (!shop) {
-        shop = this.db.getShopByCustomId(shopId);
+      // Get site from database - try both internal ID and custom ID
+      // Get site by any ID (try custom_id first, then internal ID)
+      let site = this.db.getSiteByAnyId(siteId);
+      if (!site) {
+        site = this.db.getSiteByCustomId(siteId);
       }
       }
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
       const options: any = {
       const options: any = {
-        shopId: shop.id,
+        siteId: site.id,
         limit: parseInt(limit as string) || 100,
         limit: parseInt(limit as string) || 100,
         offset: 0
         offset: 0
       };
       };
@@ -752,31 +752,31 @@ export class McpEndpoint extends BaseEndpointComponent {
 
 
       res.json({ logs: result.logs });
       res.json({ logs: result.logs });
     } catch (error) {
     } catch (error) {
-      logger.error('Failed to get shop MCP logs:', error);
+      logger.error('Failed to get site MCP logs:', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }
 
 
   private async clearShopMcpLogs(req: Request, res: Response): Promise<void> {
   private async clearShopMcpLogs(req: Request, res: Response): Promise<void> {
     try {
     try {
-      const { shopId } = req.params;
+      const { siteId } = req.params;
 
 
-      // Get shop from database - try both internal ID and custom ID
-      // Get shop by any ID (try custom_id first, then internal ID)
-      let shop = this.db.getShopByAnyId(shopId);
-      if (!shop) {
-        shop = this.db.getShopByCustomId(shopId);
+      // Get site from database - try both internal ID and custom ID
+      // Get site by any ID (try custom_id first, then internal ID)
+      let site = this.db.getSiteByAnyId(siteId);
+      if (!site) {
+        site = this.db.getSiteByCustomId(siteId);
       }
       }
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      this.db.clearShopMcpLogs(shop.id);
+      this.db.clearSiteMcpLogs(site.id);
 
 
-      res.json({ message: `Cleared all MCP logs for shop ${shop.id}` });
+      res.json({ message: `Cleared all MCP logs for site ${site.id}` });
     } catch (error) {
     } catch (error) {
-      logger.error('Failed to clear shop MCP logs:', error);
+      logger.error('Failed to clear site MCP logs:', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 215 - 215
src/api/components/QdrantEndpoint.ts


+ 24 - 24
src/api/components/SearchEndpoint.ts

@@ -1,7 +1,7 @@
 import { Request, Response } from 'express';
 import { Request, Response } from 'express';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { QdrantService } from '../../services/QdrantService';
 import { QdrantService } from '../../services/QdrantService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { McpLogger } from '../../mcp/McpLogger';
 import { McpLogger } from '../../mcp/McpLogger';
@@ -15,7 +15,7 @@ export class SearchEndpoint extends BaseEndpointComponent {
   private embeddingService?: EmbeddingService;
   private embeddingService?: EmbeddingService;
   private mcpLogger?: McpLogger;
   private mcpLogger?: McpLogger;
 
 
-  constructor(private db: ShopDatabase, private config: AppConfig) {
+  constructor(private db: SiteDatabase, private config: AppConfig) {
     super();
     super();
 
 
     try {
     try {
@@ -31,15 +31,15 @@ export class SearchEndpoint extends BaseEndpointComponent {
     return [
     return [
       this.createEndpoint(
       this.createEndpoint(
         'POST',
         'POST',
-        '/api/shops/:id/search',
+        '/api/sites/:id/search',
         this.search.bind(this),
         this.search.bind(this),
         {
         {
           summary: 'Semantic search in scraped content',
           summary: 'Semantic search in scraped content',
           description:
           description:
-            'Run a semantic (vector) search over a shop\'s scraped content. ' +
+            'Run a semantic (vector) search over a site\'s scraped content. ' +
             'Omit `category` to search all four buckets in parallel and return a merged, score-ranked list. ' +
             'Omit `category` to search all four buckets in parallel and return a merged, score-ranked list. ' +
             'Use `?format=text` for plain-text output suitable for voice/LLM pipelines; JSON is the default. ' +
             'Use `?format=text` for plain-text output suitable for voice/LLM pipelines; JSON is the default. ' +
-            'Requires the shop to have Qdrant enabled and a custom_id set.',
+            'Requires the site to have Qdrant enabled and a custom_id set.',
           tags: ['Search'],
           tags: ['Search'],
           requiresAuth: true,
           requiresAuth: true,
           parameters: [
           parameters: [
@@ -48,7 +48,7 @@ export class SearchEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop identifier (internal UUID or custom_id)',
+              description: 'Site identifier (internal UUID or custom_id)',
             },
             },
             {
             {
               name: 'format',
               name: 'format',
@@ -82,8 +82,8 @@ export class SearchEndpoint extends BaseEndpointComponent {
           responses: {
           responses: {
             '200': { description: 'Search results' },
             '200': { description: 'Search results' },
             '400': { description: 'Invalid query, category or limit' },
             '400': { description: 'Invalid query, category or limit' },
-            '404': { description: 'Shop not found' },
-            '409': { description: 'Qdrant not enabled for this shop' },
+            '404': { description: 'Site not found' },
+            '409': { description: 'Qdrant not enabled for this site' },
             '503': { description: 'Qdrant or embedding service unavailable' },
             '503': { description: 'Qdrant or embedding service unavailable' },
           },
           },
         }
         }
@@ -132,31 +132,31 @@ export class SearchEndpoint extends BaseEndpointComponent {
       limit = Math.floor(parsed);
       limit = Math.floor(parsed);
     }
     }
 
 
-    // Resolve shop
-    const shop = this.db.getShopByAnyId(identifier);
-    if (!shop) {
-      res.status(404).json({ error: 'Shop not found' });
+    // Resolve site
+    const site = this.db.getSiteByAnyId(identifier);
+    if (!site) {
+      res.status(404).json({ error: 'Site not found' });
       return;
       return;
     }
     }
-    if (!shop.custom_id) {
+    if (!site.custom_id) {
       res.status(409).json({
       res.status(409).json({
-        error: 'Shop has no custom_id; set one before enabling Qdrant search',
+        error: 'Site has no custom_id; set one before enabling Qdrant search',
       });
       });
       return;
       return;
     }
     }
-    if (!shop.qdrant_enabled) {
-      res.status(409).json({ error: 'Qdrant is not enabled for this shop' });
+    if (!site.qdrant_enabled) {
+      res.status(409).json({ error: 'Qdrant is not enabled for this site' });
       return;
       return;
     }
     }
 
 
-    const shopCustomId = shop.custom_id;
+    const siteCustomId = site.custom_id;
 
 
     try {
     try {
       // Generate query embedding
       // Generate query embedding
       const embeddingResult = await this.embeddingService.generateQueryEmbedding(q);
       const embeddingResult = await this.embeddingService.generateQueryEmbedding(q);
       if (!embeddingResult.success) {
       if (!embeddingResult.success) {
         logger.warn('Failed to generate query embedding', { error: embeddingResult.error });
         logger.warn('Failed to generate query embedding', { error: embeddingResult.error });
-        await this.logSearch(shopCustomId, q, 0, Date.now() - startTime, false);
+        await this.logSearch(siteCustomId, q, 0, Date.now() - startTime, false);
         res.status(503).json({ error: 'Failed to generate query embedding' });
         res.status(503).json({ error: 'Failed to generate query embedding' });
         return;
         return;
       }
       }
@@ -166,7 +166,7 @@ export class SearchEndpoint extends BaseEndpointComponent {
 
 
       const perCategory = await Promise.all(
       const perCategory = await Promise.all(
         categoriesToSearch.map((cat) =>
         categoriesToSearch.map((cat) =>
-          this.qdrantService!.searchInCategory(shopCustomId, cat, embeddingResult.embedding, limit)
+          this.qdrantService!.searchInCategory(siteCustomId, cat, embeddingResult.embedding, limit)
         )
         )
       );
       );
 
 
@@ -177,7 +177,7 @@ export class SearchEndpoint extends BaseEndpointComponent {
       const durationMs = Date.now() - startTime;
       const durationMs = Date.now() - startTime;
 
 
       // Log to mcp_logs under a dedicated tool name so REST and MCP stats stay unified
       // Log to mcp_logs under a dedicated tool name so REST and MCP stats stay unified
-      await this.logSearch(shopCustomId, q, flat.length, durationMs, true);
+      await this.logSearch(siteCustomId, q, flat.length, durationMs, true);
 
 
       if (format === 'text') {
       if (format === 'text') {
         res.setHeader('Content-Type', 'text/plain; charset=utf-8');
         res.setHeader('Content-Type', 'text/plain; charset=utf-8');
@@ -187,7 +187,7 @@ export class SearchEndpoint extends BaseEndpointComponent {
 
 
       // Default JSON response
       // Default JSON response
       res.json({
       res.json({
-        shop_id: identifier,
+        site_id: identifier,
         query: q,
         query: q,
         category: category || null,
         category: category || null,
         total: flat.length,
         total: flat.length,
@@ -205,14 +205,14 @@ export class SearchEndpoint extends BaseEndpointComponent {
       });
       });
     } catch (error: any) {
     } catch (error: any) {
       const durationMs = Date.now() - startTime;
       const durationMs = Date.now() - startTime;
-      await this.logSearch(shopCustomId, q, 0, durationMs, false);
+      await this.logSearch(siteCustomId, q, 0, durationMs, false);
       logger.error('Search endpoint error:', error);
       logger.error('Search endpoint error:', error);
       res.status(500).json({ error: error?.message || 'Search failed' });
       res.status(500).json({ error: error?.message || 'Search failed' });
     }
     }
   }
   }
 
 
   private async logSearch(
   private async logSearch(
-    shopCustomId: string,
+    siteCustomId: string,
     query: string,
     query: string,
     resultsCount: number,
     resultsCount: number,
     executionTimeMs: number,
     executionTimeMs: number,
@@ -221,7 +221,7 @@ export class SearchEndpoint extends BaseEndpointComponent {
     if (!this.mcpLogger) return;
     if (!this.mcpLogger) return;
     try {
     try {
       await this.mcpLogger.logToolCall({
       await this.mcpLogger.logToolCall({
-        shopId: shopCustomId,
+        siteId: siteCustomId,
         toolName: 'rest_search',
         toolName: 'rest_search',
         query,
         query,
         responseSummary: success ? `${resultsCount} results` : 'Error: search failed',
         responseSummary: success ? `${resultsCount} results` : 'Error: search failed',

+ 130 - 130
src/api/components/SitesEndpoint.ts

@@ -1,10 +1,10 @@
 import { Request, Response } from 'express';
 import { Request, Response } from 'express';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 
 
-export class ShopsEndpoint extends BaseEndpointComponent {
-  constructor(private db: ShopDatabase) {
+export class SitesEndpoint extends BaseEndpointComponent {
+  constructor(private db: SiteDatabase) {
     super();
     super();
   }
   }
 
 
@@ -12,33 +12,33 @@ export class ShopsEndpoint extends BaseEndpointComponent {
     return [
     return [
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops',
+        '/api/sites',
         this.getAllShops.bind(this),
         this.getAllShops.bind(this),
         {
         {
-          summary: 'List all shops',
-          description: 'Retrieves a list of all shops with their analytics',
-          tags: ['Shops'],
+          summary: 'List all sites',
+          description: 'Retrieves a list of all sites with their analytics',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'limit',
               name: 'limit',
               in: 'query',
               in: 'query',
               type: 'number',
               type: 'number',
-              description: 'Maximum number of shops to return (default: 100)'
+              description: 'Maximum number of sites to return (default: 100)'
             },
             },
             {
             {
               name: 'offset',
               name: 'offset',
               in: 'query',
               in: 'query',
               type: 'number',
               type: 'number',
-              description: 'Number of shops to skip for pagination (default: 0)'
+              description: 'Number of sites to skip for pagination (default: 0)'
             }
             }
           ],
           ],
           responses: {
           responses: {
             '200': {
             '200': {
-              description: 'List of shops with analytics',
+              description: 'List of sites with analytics',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shops: {
+                  sites: {
                     type: 'array',
                     type: 'array',
                     items: {
                     items: {
                       type: 'object',
                       type: 'object',
@@ -47,7 +47,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
                         custom_id: { type: 'string', nullable: true },
                         custom_id: { type: 'string', nullable: true },
                         url: { type: 'string' },
                         url: { type: 'string' },
                         sitemap_url: { type: 'string' },
                         sitemap_url: { type: 'string' },
-                        webshop_type: { type: 'string' },
+                        site_type: { type: 'string' },
                         created_at: { type: 'string' },
                         created_at: { type: 'string' },
                         updated_at: { type: 'string' },
                         updated_at: { type: 'string' },
                         analytics: {
                         analytics: {
@@ -85,28 +85,28 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops/:id',
+        '/api/sites/:id',
         this.getShop.bind(this),
         this.getShop.bind(this),
         {
         {
-          summary: 'Get detailed shop information',
-          description: 'Retrieves detailed information about a specific shop including analytics, content metadata, scrape history, and scheduled jobs',
-          tags: ['Shops'],
+          summary: 'Get detailed site information',
+          description: 'Retrieves detailed information about a specific site including analytics, content metadata, scrape history, and scheduled jobs',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'id',
               name: 'id',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           responses: {
           responses: {
             '200': {
             '200': {
-              description: 'Detailed shop information',
+              description: 'Detailed site information',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop: { type: 'object' },
+                  site: { type: 'object' },
                   analytics: { type: 'object' },
                   analytics: { type: 'object' },
                   content_metadata: { type: 'object' },
                   content_metadata: { type: 'object' },
                   scrape_history: { type: 'array' },
                   scrape_history: { type: 'array' },
@@ -115,7 +115,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -123,19 +123,19 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops/:id/results',
+        '/api/sites/:id/results',
         this.getShopResults.bind(this),
         this.getShopResults.bind(this),
         {
         {
-          summary: 'Get shop results with full content',
-          description: 'Retrieves scraped content for a shop with optional filtering',
-          tags: ['Shops'],
+          summary: 'Get site results with full content',
+          description: 'Retrieves scraped content for a site with optional filtering',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'id',
               name: 'id',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             },
             },
             {
             {
               name: 'limit',
               name: 'limit',
@@ -170,11 +170,11 @@ export class ShopsEndpoint extends BaseEndpointComponent {
           ],
           ],
           responses: {
           responses: {
             '200': {
             '200': {
-              description: 'Shop results with content',
+              description: 'Site results with content',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   filters: { type: 'object' },
                   filters: { type: 'object' },
                   results: {
                   results: {
                     type: 'object',
                     type: 'object',
@@ -189,7 +189,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -197,19 +197,19 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'PATCH',
         'PATCH',
-        '/api/shops/:id/schedule',
+        '/api/sites/:id/schedule',
         this.updateSchedule.bind(this),
         this.updateSchedule.bind(this),
         {
         {
           summary: 'Enable or disable scheduled scraping',
           summary: 'Enable or disable scheduled scraping',
-          description: 'Updates the scheduling status for a shop',
-          tags: ['Shops'],
+          description: 'Updates the scheduling status for a site',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'id',
               name: 'id',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID'
+              description: 'Site ID'
             }
             }
           ],
           ],
           requestBody: {
           requestBody: {
@@ -231,14 +231,14 @@ export class ShopsEndpoint extends BaseEndpointComponent {
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   schedule_enabled: { type: 'boolean' },
                   schedule_enabled: { type: 'boolean' },
                   message: { type: 'string' }
                   message: { type: 'string' }
                 }
                 }
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -246,19 +246,19 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'PATCH',
         'PATCH',
-        '/api/shops/:id/custom-id',
+        '/api/sites/:id/custom-id',
         this.updateCustomId.bind(this),
         this.updateCustomId.bind(this),
         {
         {
-          summary: 'Set or update custom ID for a shop',
-          description: 'Updates the custom UUID identifier for a shop',
-          tags: ['Shops'],
+          summary: 'Set or update custom ID for a site',
+          description: 'Updates the custom UUID identifier for a site',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'id',
               name: 'id',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or current custom UUID)'
+              description: 'Site ID (internal UUID or current custom UUID)'
             }
             }
           ],
           ],
           requestBody: {
           requestBody: {
@@ -282,7 +282,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   custom_id: { type: 'string', nullable: true },
                   custom_id: { type: 'string', nullable: true },
                   message: { type: 'string' }
                   message: { type: 'string' }
                 }
                 }
@@ -292,7 +292,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
               description: 'Invalid custom ID format'
               description: 'Invalid custom ID format'
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             },
             },
             '409': {
             '409': {
               description: 'Custom ID already in use'
               description: 'Custom ID already in use'
@@ -303,19 +303,19 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'PATCH',
         'PATCH',
-        '/api/shops/:id/qdrant',
+        '/api/sites/:id/qdrant',
         this.updateQdrantStatus.bind(this),
         this.updateQdrantStatus.bind(this),
         {
         {
           summary: 'Enable or disable Qdrant vector search',
           summary: 'Enable or disable Qdrant vector search',
-          description: 'Updates the Qdrant integration status for a shop',
-          tags: ['Shops'],
+          description: 'Updates the Qdrant integration status for a site',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'id',
               name: 'id',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           requestBody: {
           requestBody: {
@@ -337,14 +337,14 @@ export class ShopsEndpoint extends BaseEndpointComponent {
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   qdrant_enabled: { type: 'boolean' },
                   qdrant_enabled: { type: 'boolean' },
                   message: { type: 'string' }
                   message: { type: 'string' }
                 }
                 }
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -352,34 +352,34 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'DELETE',
         'DELETE',
-        '/api/shops/:id',
+        '/api/sites/:id',
         this.deleteShop.bind(this),
         this.deleteShop.bind(this),
         {
         {
-          summary: 'Soft delete a shop',
-          description: 'Marks a shop as deleted without removing data immediately. The shop will not appear in API responses.',
-          tags: ['Shops'],
+          summary: 'Soft delete a site',
+          description: 'Marks a site as deleted without removing data immediately. The site will not appear in API responses.',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'id',
               name: 'id',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           responses: {
           responses: {
             '200': {
             '200': {
-              description: 'Shop deleted successfully',
+              description: 'Site deleted successfully',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
                   message: { type: 'string' },
                   message: { type: 'string' },
-                  shop_id: { type: 'string' }
+                  site_id: { type: 'string' }
                 }
                 }
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -387,19 +387,19 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops/deleted',
+        '/api/sites/deleted',
         this.getDeletedShops.bind(this),
         this.getDeletedShops.bind(this),
         {
         {
           summary: 'List deleted shops',
           summary: 'List deleted shops',
-          description: 'Retrieves a list of soft-deleted shops that can be force-deleted',
-          tags: ['Shops'],
+          description: 'Retrieves a list of soft-deleted sites that can be force-deleted',
+          tags: ['Sites'],
           responses: {
           responses: {
             '200': {
             '200': {
               description: 'List of deleted shops',
               description: 'List of deleted shops',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shops: {
+                  sites: {
                     type: 'array',
                     type: 'array',
                     items: {
                     items: {
                       type: 'object',
                       type: 'object',
@@ -420,34 +420,34 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'DELETE',
         'DELETE',
-        '/api/shops/:id/force',
+        '/api/sites/:id/force',
         this.forceDeleteShop.bind(this),
         this.forceDeleteShop.bind(this),
         {
         {
-          summary: 'Force delete a shop',
-          description: 'Permanently removes a shop and all associated data including scrape history, scheduled jobs, content, and queues Qdrant cleanup',
-          tags: ['Shops'],
+          summary: 'Force delete a site',
+          description: 'Permanently removes a site and all associated data including scrape history, scheduled jobs, content, and queues Qdrant cleanup',
+          tags: ['Sites'],
           parameters: [
           parameters: [
             {
             {
               name: 'id',
               name: 'id',
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           responses: {
           responses: {
             '200': {
             '200': {
-              description: 'Shop force deleted successfully',
+              description: 'Site force deleted successfully',
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
                   message: { type: 'string' },
                   message: { type: 'string' },
-                  shop_id: { type: 'string' }
+                  site_id: { type: 'string' }
                 }
                 }
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -468,14 +468,14 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       const offset = req.query.offset ? parseInt(req.query.offset as string) : 0;
       const offset = req.query.offset ? parseInt(req.query.offset as string) : 0;
 
 
       // Use optimized method that avoids N+1 query problem
       // Use optimized method that avoids N+1 query problem
-      const shopsWithAnalytics = this.db.getAllShopsWithAnalytics({ limit, offset });
+      const sitesWithAnalytics = this.db.getAllSitesWithAnalytics({ limit, offset });
 
 
       // Count total items for pagination
       // Count total items for pagination
-      const totalReturned = shopsWithAnalytics.length;
+      const totalReturned = sitesWithAnalytics.length;
       const hasMore = totalReturned === limit;
       const hasMore = totalReturned === limit;
 
 
       res.json({
       res.json({
-        shops: shopsWithAnalytics,
+        sites: sitesWithAnalytics,
         pagination: {
         pagination: {
           total_returned: totalReturned,
           total_returned: totalReturned,
           limit,
           limit,
@@ -484,7 +484,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
         }
         }
       });
       });
     } catch (error) {
     } catch (error) {
-      logger.error('Error listing shops', error);
+      logger.error('Error listing sites', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }
@@ -497,19 +497,19 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       }
       }
 
 
       const { id } = req.params;
       const { id } = req.params;
-      const shop = this.db.getShopByAnyId(id);
+      const site = this.db.getSiteByAnyId(id);
 
 
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
       // Note: better-sqlite3 is synchronous, but we keep these calls organized
       // Note: better-sqlite3 is synchronous, but we keep these calls organized
       // They execute fast due to proper indexing
       // They execute fast due to proper indexing
-      const analytics = this.db.getShopAnalytics(shop.id);
-      const scrapeHistory = this.db.getScrapeHistory(shop.id, 10);
-      const latestContent = this.db.getLatestContentByType(shop.id);
-      const scheduledJobs = this.db.getScheduledJobs(shop.id);
+      const analytics = this.db.getSiteAnalytics(site.id);
+      const scrapeHistory = this.db.getScrapeHistory(site.id, 10);
+      const latestContent = this.db.getLatestContentByType(site.id);
+      const scheduledJobs = this.db.getScheduledJobs(site.id);
 
 
       // Build response with content change markers and URLs only (no content)
       // Build response with content change markers and URLs only (no content)
       const contentMetadata: any = {
       const contentMetadata: any = {
@@ -544,15 +544,15 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       };
       };
 
 
       res.json({
       res.json({
-        shop: {
-          id: shop.id,
-          custom_id: shop.custom_id,
-          url: shop.url,
-          sitemap_url: shop.sitemap_url,
-          webshop_type: shop.webshop_type,
-          qdrant_enabled: shop.qdrant_enabled,
-          created_at: shop.created_at,
-          updated_at: shop.updated_at
+        site: {
+          id: site.id,
+          custom_id: site.custom_id,
+          url: site.url,
+          sitemap_url: site.sitemap_url,
+          site_type: site.site_type,
+          qdrant_enabled: site.qdrant_enabled,
+          created_at: site.created_at,
+          updated_at: site.updated_at
         },
         },
         analytics: {
         analytics: {
           total_scrapes: analytics?.total_scrapes || 0,
           total_scrapes: analytics?.total_scrapes || 0,
@@ -584,7 +584,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
         }))
         }))
       });
       });
     } catch (error) {
     } catch (error) {
-      logger.error('Error fetching shop details', error);
+      logger.error('Error fetching site details', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }
@@ -597,10 +597,10 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       }
       }
 
 
       const { id } = req.params;
       const { id } = req.params;
-      const shop = this.db.getShopByAnyId(id);
+      const site = this.db.getSiteByAnyId(id);
 
 
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
@@ -612,7 +612,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       const contentType = req.query.content_type as string | undefined;
       const contentType = req.query.content_type as string | undefined;
 
 
       // Get content with filters (now returns only latest versions)
       // Get content with filters (now returns only latest versions)
-      const allContent = this.db.getAllContent(shop.id, {
+      const allContent = this.db.getAllContent(site.id, {
         limit,
         limit,
         offset,
         offset,
         dateFrom,
         dateFrom,
@@ -650,7 +650,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       const hasMore = totalItems === limit;
       const hasMore = totalItems === limit;
 
 
       res.json({
       res.json({
-        shop_id: shop.id,
+        site_id: site.id,
         filters: {
         filters: {
           limit,
           limit,
           offset,
           offset,
@@ -667,7 +667,7 @@ export class ShopsEndpoint extends BaseEndpointComponent {
         results: grouped
         results: grouped
       });
       });
     } catch (error) {
     } catch (error) {
-      logger.error('Error fetching shop results', error);
+      logger.error('Error fetching site results', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }
@@ -687,17 +687,17 @@ export class ShopsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      // Get shop by any ID (try custom_id first, then internal ID)
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Get site by any ID (try custom_id first, then internal ID)
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      this.db.setScheduleEnabled(shop.id, enabled);
+      this.db.setScheduleEnabled(site.id, enabled);
 
 
       res.json({
       res.json({
-        shop_id: shop.id,
+        site_id: site.id,
         schedule_enabled: enabled,
         schedule_enabled: enabled,
         message: `Schedule ${enabled ? 'enabled' : 'disabled'} successfully`
         message: `Schedule ${enabled ? 'enabled' : 'disabled'} successfully`
       });
       });
@@ -717,9 +717,9 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       const { id } = req.params;
       const { id } = req.params;
       const { custom_id } = req.body;
       const { custom_id } = req.body;
 
 
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
@@ -729,23 +729,23 @@ export class ShopsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      // Check if custom_id is already in use by another shop
+      // Check if custom_id is already in use by another site
       if (custom_id) {
       if (custom_id) {
-        const existingShop = this.db.getShopByCustomId(custom_id);
-        if (existingShop && existingShop.id !== shop.id) {
+        const existingShop = this.db.getSiteByCustomId(custom_id);
+        if (existingShop && existingShop.id !== site.id) {
           res.status(409).json({ error: 'custom_id is already in use' });
           res.status(409).json({ error: 'custom_id is already in use' });
           return;
           return;
         }
         }
       }
       }
 
 
-      const success = this.db.setCustomId(shop.id, custom_id);
+      const success = this.db.setCustomId(site.id, custom_id);
       if (!success) {
       if (!success) {
         res.status(500).json({ error: 'Failed to update custom_id' });
         res.status(500).json({ error: 'Failed to update custom_id' });
         return;
         return;
       }
       }
 
 
       res.json({
       res.json({
-        shop_id: shop.id,
+        site_id: site.id,
         custom_id: custom_id,
         custom_id: custom_id,
         message: custom_id ? `Custom ID set to ${custom_id}` : 'Custom ID removed'
         message: custom_id ? `Custom ID set to ${custom_id}` : 'Custom ID removed'
       });
       });
@@ -770,16 +770,16 @@ export class ShopsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      this.db.setQdrantEnabled(shop.id, enabled);
+      this.db.setQdrantEnabled(site.id, enabled);
 
 
       res.json({
       res.json({
-        shop_id: shop.id,
+        site_id: site.id,
         qdrant_enabled: enabled,
         qdrant_enabled: enabled,
         message: `Qdrant ${enabled ? 'enabled' : 'disabled'} successfully`
         message: `Qdrant ${enabled ? 'enabled' : 'disabled'} successfully`
       });
       });
@@ -797,21 +797,21 @@ export class ShopsEndpoint extends BaseEndpointComponent {
       }
       }
 
 
       const { id } = req.params;
       const { id } = req.params;
-      const shop = this.db.getShopByAnyId(id);
+      const site = this.db.getSiteByAnyId(id);
 
 
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      this.db.deleteShop(shop.id);
+      this.db.deleteSite(site.id);
 
 
       res.json({
       res.json({
-        message: 'Shop soft deleted successfully. Use force delete to permanently remove.',
-        shop_id: shop.id
+        message: 'Site soft deleted successfully. Use force delete to permanently remove.',
+        site_id: site.id
       });
       });
     } catch (error) {
     } catch (error) {
-      logger.error('Error deleting shop', error);
+      logger.error('Error deleting site', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }
@@ -823,11 +823,11 @@ export class ShopsEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      const shops = this.db.getDeletedShops();
+      const shops = this.db.getDeletedSites();
 
 
       res.json({ shops });
       res.json({ shops });
     } catch (error) {
     } catch (error) {
-      logger.error('Error listing deleted shops', error);
+      logger.error('Error listing deleted sites', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }
@@ -841,24 +841,24 @@ export class ShopsEndpoint extends BaseEndpointComponent {
 
 
       const { id } = req.params;
       const { id } = req.params;
 
 
-      // Find shop without filtering deleted_at
+      // Find site without filtering deleted_at
       const stmt = (this.db as any).db.prepare('SELECT * FROM shops WHERE id = ? OR custom_id = ?');
       const stmt = (this.db as any).db.prepare('SELECT * FROM shops WHERE id = ? OR custom_id = ?');
       const result = stmt.get(id, id) as any;
       const result = stmt.get(id, id) as any;
-      const shop = result ? { ...result, qdrant_enabled: Boolean(result.qdrant_enabled) } : null;
+      const site = result ? { ...result, qdrant_enabled: Boolean(result.qdrant_enabled) } : null;
 
 
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      this.db.forceDeleteShop(shop.id);
+      this.db.forceDeleteSite(site.id);
 
 
       res.json({
       res.json({
-        message: 'Shop and all related data permanently deleted. Qdrant cleanup queued.',
-        shop_id: shop.id
+        message: 'Site and all related data permanently deleted. Qdrant cleanup queued.',
+        site_id: site.id
       });
       });
     } catch (error) {
     } catch (error) {
-      logger.error('Error force deleting shop', error);
+      logger.error('Error force deleting site', error);
       res.status(500).json({ error: 'Internal server error' });
       res.status(500).json({ error: 'Internal server error' });
     }
     }
   }
   }

+ 54 - 54
src/api/components/WebhooksEndpoint.ts

@@ -1,10 +1,10 @@
 import { Request, Response } from 'express';
 import { Request, Response } from 'express';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { BaseEndpointComponent, EndpointMethod } from '../core/types';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 
 
 export class WebhooksEndpoint extends BaseEndpointComponent {
 export class WebhooksEndpoint extends BaseEndpointComponent {
-  constructor(private db: ShopDatabase) {
+  constructor(private db: SiteDatabase) {
     super();
     super();
   }
   }
 
 
@@ -12,11 +12,11 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
     return [
     return [
       this.createEndpoint(
       this.createEndpoint(
         'POST',
         'POST',
-        '/api/shops/:id/webhooks',
+        '/api/sites/:id/webhooks',
         this.createWebhook.bind(this),
         this.createWebhook.bind(this),
         {
         {
-          summary: 'Create or replace webhook for a shop',
-          description: 'Creates a new webhook or updates an existing one for the specified shop',
+          summary: 'Create or replace webhook for a site',
+          description: 'Creates a new webhook or updates an existing one for the specified site',
           tags: ['Webhooks'],
           tags: ['Webhooks'],
           parameters: [
           parameters: [
             {
             {
@@ -24,7 +24,7 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID'
+              description: 'Site ID'
             }
             }
           ],
           ],
           requestBody: {
           requestBody: {
@@ -70,7 +70,7 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
               description: 'Invalid webhook URL'
               description: 'Invalid webhook URL'
             },
             },
             '404': {
             '404': {
-              description: 'Shop not found'
+              description: 'Site not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -78,10 +78,10 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'GET',
         'GET',
-        '/api/shops/:id/webhooks',
+        '/api/sites/:id/webhooks',
         this.getWebhook.bind(this),
         this.getWebhook.bind(this),
         {
         {
-          summary: 'Get webhook configuration for a shop',
+          summary: 'Get webhook configuration for a site',
           description: 'Retrieves webhook configuration and recent delivery history',
           description: 'Retrieves webhook configuration and recent delivery history',
           tags: ['Webhooks'],
           tags: ['Webhooks'],
           parameters: [
           parameters: [
@@ -90,7 +90,7 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           responses: {
           responses: {
@@ -105,7 +105,7 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop or webhook not found'
+              description: 'Site or webhook not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -113,7 +113,7 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'PATCH',
         'PATCH',
-        '/api/shops/:id/webhooks',
+        '/api/sites/:id/webhooks',
         this.updateWebhook.bind(this),
         this.updateWebhook.bind(this),
         {
         {
           summary: 'Enable or disable webhook',
           summary: 'Enable or disable webhook',
@@ -125,7 +125,7 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID'
+              description: 'Site ID'
             }
             }
           ],
           ],
           requestBody: {
           requestBody: {
@@ -147,14 +147,14 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
               schema: {
               schema: {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
-                  shop_id: { type: 'string' },
+                  site_id: { type: 'string' },
                   webhook_enabled: { type: 'boolean' },
                   webhook_enabled: { type: 'boolean' },
                   message: { type: 'string' }
                   message: { type: 'string' }
                 }
                 }
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop or webhook not found'
+              description: 'Site or webhook not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -162,11 +162,11 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
       ),
       ),
       this.createEndpoint(
       this.createEndpoint(
         'DELETE',
         'DELETE',
-        '/api/shops/:id/webhooks',
+        '/api/sites/:id/webhooks',
         this.deleteWebhook.bind(this),
         this.deleteWebhook.bind(this),
         {
         {
-          summary: 'Delete webhook for a shop',
-          description: 'Permanently removes the webhook configuration for a shop',
+          summary: 'Delete webhook for a site',
+          description: 'Permanently removes the webhook configuration for a site',
           tags: ['Webhooks'],
           tags: ['Webhooks'],
           parameters: [
           parameters: [
             {
             {
@@ -174,7 +174,7 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
               in: 'path',
               in: 'path',
               type: 'string',
               type: 'string',
               required: true,
               required: true,
-              description: 'Shop ID (internal UUID or custom UUID)'
+              description: 'Site ID (internal UUID or custom UUID)'
             }
             }
           ],
           ],
           responses: {
           responses: {
@@ -184,12 +184,12 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
                 type: 'object',
                 type: 'object',
                 properties: {
                 properties: {
                   message: { type: 'string' },
                   message: { type: 'string' },
-                  shop_id: { type: 'string' }
+                  site_id: { type: 'string' }
                 }
                 }
               }
               }
             },
             },
             '404': {
             '404': {
-              description: 'Shop or webhook not found'
+              description: 'Site or webhook not found'
             }
             }
           },
           },
           requiresAuth: true
           requiresAuth: true
@@ -221,25 +221,25 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      // Get shop by any ID (try custom_id first, then internal ID)
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Get site by any ID (try custom_id first, then internal ID)
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
       // Check if webhook already exists
       // Check if webhook already exists
-      const existingWebhook = this.db.getWebhook(shop.id);
+      const existingWebhook = this.db.getWebhook(site.id);
       if (existingWebhook) {
       if (existingWebhook) {
         // Update existing webhook
         // Update existing webhook
-        this.db.updateWebhook(shop.id, url, secret);
-        const updated = this.db.getWebhook(shop.id);
+        this.db.updateWebhook(site.id, url, secret);
+        const updated = this.db.getWebhook(site.id);
 
 
         res.json({
         res.json({
           message: 'Webhook updated successfully',
           message: 'Webhook updated successfully',
           webhook: {
           webhook: {
             id: updated!.id,
             id: updated!.id,
-            shop_id: updated!.shop_id,
+            site_id: updated!.site_id,
             url: updated!.url,
             url: updated!.url,
             enabled: updated!.enabled,
             enabled: updated!.enabled,
             created_at: updated!.created_at,
             created_at: updated!.created_at,
@@ -248,14 +248,14 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
         });
         });
       } else {
       } else {
         // Create new webhook
         // Create new webhook
-        const webhookId = this.db.createWebhook(shop.id, url, secret);
-        const webhook = this.db.getWebhook(shop.id);
+        const webhookId = this.db.createWebhook(site.id, url, secret);
+        const webhook = this.db.getWebhook(site.id);
 
 
         res.status(201).json({
         res.status(201).json({
           message: 'Webhook created successfully',
           message: 'Webhook created successfully',
           webhook: {
           webhook: {
             id: webhook!.id,
             id: webhook!.id,
-            shop_id: webhook!.shop_id,
+            site_id: webhook!.site_id,
             url: webhook!.url,
             url: webhook!.url,
             enabled: webhook!.enabled,
             enabled: webhook!.enabled,
             created_at: webhook!.created_at,
             created_at: webhook!.created_at,
@@ -277,27 +277,27 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
       }
       }
 
 
       const { id } = req.params;
       const { id } = req.params;
-      const shop = this.db.getShopByAnyId(id);
+      const site = this.db.getSiteByAnyId(id);
 
 
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      const webhook = this.db.getWebhook(shop.id);
+      const webhook = this.db.getWebhook(site.id);
 
 
       if (!webhook) {
       if (!webhook) {
-        res.status(404).json({ error: 'No webhook configured for this shop' });
+        res.status(404).json({ error: 'No webhook configured for this site' });
         return;
         return;
       }
       }
 
 
       // Get recent deliveries
       // Get recent deliveries
-      const deliveries = this.db.getWebhookDeliveries(shop.id, 10);
+      const deliveries = this.db.getWebhookDeliveries(site.id, 10);
 
 
       res.json({
       res.json({
         webhook: {
         webhook: {
           id: webhook.id,
           id: webhook.id,
-          shop_id: webhook.shop_id,
+          site_id: webhook.site_id,
           url: webhook.url,
           url: webhook.url,
           enabled: webhook.enabled,
           enabled: webhook.enabled,
           created_at: webhook.created_at,
           created_at: webhook.created_at,
@@ -333,23 +333,23 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
         return;
         return;
       }
       }
 
 
-      // Get shop by any ID (try custom_id first, then internal ID)
-      const shop = this.db.getShopByAnyId(id);
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      // Get site by any ID (try custom_id first, then internal ID)
+      const site = this.db.getSiteByAnyId(id);
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      const webhook = this.db.getWebhook(shop.id);
+      const webhook = this.db.getWebhook(site.id);
       if (!webhook && enabled) {
       if (!webhook && enabled) {
-        res.status(404).json({ error: 'No webhook configured for this shop' });
+        res.status(404).json({ error: 'No webhook configured for this site' });
         return;
         return;
       }
       }
 
 
-      this.db.setWebhookEnabled(shop.id, enabled);
+      this.db.setWebhookEnabled(site.id, enabled);
 
 
       res.json({
       res.json({
-        shop_id: shop.id,
+        site_id: site.id,
         webhook_enabled: enabled,
         webhook_enabled: enabled,
         message: `Webhook ${enabled ? 'enabled' : 'disabled'} successfully`
         message: `Webhook ${enabled ? 'enabled' : 'disabled'} successfully`
       });
       });
@@ -367,24 +367,24 @@ export class WebhooksEndpoint extends BaseEndpointComponent {
       }
       }
 
 
       const { id } = req.params;
       const { id } = req.params;
-      const shop = this.db.getShopByAnyId(id);
+      const site = this.db.getSiteByAnyId(id);
 
 
-      if (!shop) {
-        res.status(404).json({ error: 'Shop not found' });
+      if (!site) {
+        res.status(404).json({ error: 'Site not found' });
         return;
         return;
       }
       }
 
 
-      const webhook = this.db.getWebhook(shop.id);
+      const webhook = this.db.getWebhook(site.id);
       if (!webhook) {
       if (!webhook) {
-        res.status(404).json({ error: 'No webhook configured for this shop' });
+        res.status(404).json({ error: 'No webhook configured for this site' });
         return;
         return;
       }
       }
 
 
-      this.db.deleteWebhook(shop.id);
+      this.db.deleteWebhook(site.id);
 
 
       res.json({
       res.json({
         message: 'Webhook deleted successfully',
         message: 'Webhook deleted successfully',
-        shop_id: shop.id
+        site_id: site.id
       });
       });
     } catch (error) {
     } catch (error) {
       logger.error('Error deleting webhook', error);
       logger.error('Error deleting webhook', error);

+ 4 - 4
src/api/routes.ts

@@ -1,6 +1,6 @@
 import { Router } from 'express';
 import { Router } from 'express';
 import { JobQueue } from '../queue/JobQueue';
 import { JobQueue } from '../queue/JobQueue';
-import { ShopDatabase } from '../database/Database';
+import { SiteDatabase } from '../database/Database';
 import { EndpointRegistry } from './core/EndpointRegistry';
 import { EndpointRegistry } from './core/EndpointRegistry';
 import { authMiddleware } from './middleware/auth';
 import { authMiddleware } from './middleware/auth';
 import { BaseEndpointComponent } from './core/types';
 import { BaseEndpointComponent } from './core/types';
@@ -8,7 +8,7 @@ import { BaseEndpointComponent } from './core/types';
 // Import all endpoint components
 // Import all endpoint components
 import { DocumentationEndpoint } from './components/DocumentationEndpoint';
 import { DocumentationEndpoint } from './components/DocumentationEndpoint';
 import { JobsEndpoint } from './components/JobsEndpoint';
 import { JobsEndpoint } from './components/JobsEndpoint';
-import { ShopsEndpoint } from './components/ShopsEndpoint';
+import { SitesEndpoint } from './components/SitesEndpoint';
 import { WebhooksEndpoint } from './components/WebhooksEndpoint';
 import { WebhooksEndpoint } from './components/WebhooksEndpoint';
 import { CustomUrlsEndpoint } from './components/CustomUrlsEndpoint';
 import { CustomUrlsEndpoint } from './components/CustomUrlsEndpoint';
 import { ContentEndpoint } from './components/ContentEndpoint';
 import { ContentEndpoint } from './components/ContentEndpoint';
@@ -18,7 +18,7 @@ import { McpEndpoint } from './components/McpEndpoint';
 import { SearchEndpoint } from './components/SearchEndpoint';
 import { SearchEndpoint } from './components/SearchEndpoint';
 import type { AppConfig } from '../config';
 import type { AppConfig } from '../config';
 
 
-export function createRouter(jobQueue: JobQueue, apiKey: string, db?: ShopDatabase, config?: AppConfig): Router {
+export function createRouter(jobQueue: JobQueue, apiKey: string, db?: SiteDatabase, config?: AppConfig): Router {
   // Create the endpoint registry
   // Create the endpoint registry
   const registry = new EndpointRegistry();
   const registry = new EndpointRegistry();
 
 
@@ -34,7 +34,7 @@ export function createRouter(jobQueue: JobQueue, apiKey: string, db?: ShopDataba
   // Only register database-dependent endpoints if database is available
   // Only register database-dependent endpoints if database is available
   if (db) {
   if (db) {
     components.push(
     components.push(
-      new ShopsEndpoint(db),
+      new SitesEndpoint(db),
       new WebhooksEndpoint(db),
       new WebhooksEndpoint(db),
       new CustomUrlsEndpoint(db, jobQueue),
       new CustomUrlsEndpoint(db, jobQueue),
       new ContentEndpoint(db, config),
       new ContentEndpoint(db, config),

+ 2 - 2
src/api/server.ts

@@ -4,11 +4,11 @@ import path from 'path';
 import { JobQueue } from '../queue/JobQueue';
 import { JobQueue } from '../queue/JobQueue';
 import { createRouter } from './routes';
 import { createRouter } from './routes';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
-import { ShopDatabase } from '../database/Database';
+import { SiteDatabase } from '../database/Database';
 import { McpServer } from '../mcp/McpServer';
 import { McpServer } from '../mcp/McpServer';
 import type { AppConfig } from '../config';
 import type { AppConfig } from '../config';
 
 
-export function createServer(apiKey: string, jobQueue: JobQueue, db?: ShopDatabase, config?: AppConfig): Express {
+export function createServer(apiKey: string, jobQueue: JobQueue, db?: SiteDatabase, config?: AppConfig): Express {
   const app = express();
   const app = express();
 
 
   // Trust proxy to get real IP addresses when behind reverse proxy
   // Trust proxy to get real IP addresses when behind reverse proxy

+ 22 - 22
src/index.ts

@@ -1,27 +1,27 @@
 import { loadConfig } from './config';
 import { loadConfig } from './config';
 import { createServer } from './api/server';
 import { createServer } from './api/server';
 import { JobQueue } from './queue/JobQueue';
 import { JobQueue } from './queue/JobQueue';
-import { WebshopScraper } from './scraper/WebshopScraper';
+import { WebScraper } from './scraper/WebScraper';
 import { logger } from './utils/logger';
 import { logger } from './utils/logger';
-import { ShopDatabase } from './database/Database';
+import { SiteDatabase } from './database/Database';
 import { ScrapeScheduler } from './scheduler/ScrapeScheduler';
 import { ScrapeScheduler } from './scheduler/ScrapeScheduler';
 import { WebhookManager } from './webhooks/WebhookManager';
 import { WebhookManager } from './webhooks/WebhookManager';
 
 
 async function main() {
 async function main() {
   try {
   try {
-    logger.info('Starting Webshop Scraper application...');
+    logger.info('Starting Web Scraper application...');
 
 
     // Load configuration
     // Load configuration
     const config = loadConfig();
     const config = loadConfig();
 
 
     // Initialize database
     // Initialize database
-    const db = new ShopDatabase();
+    const db = new SiteDatabase();
 
 
     // Initialize job queue
     // Initialize job queue
     const jobQueue = new JobQueue(config.maxConcurrentJobs);
     const jobQueue = new JobQueue(config.maxConcurrentJobs);
 
 
     // Initialize scraper with database and config
     // Initialize scraper with database and config
-    const scraper = new WebshopScraper(db, config);
+    const scraper = new WebScraper(db, config);
 
 
     // Initialize scheduler
     // Initialize scheduler
     const scheduler = new ScrapeScheduler(db, jobQueue);
     const scheduler = new ScrapeScheduler(db, jobQueue);
@@ -37,20 +37,20 @@ async function main() {
       let scrapeHistoryId: string | undefined;
       let scrapeHistoryId: string | undefined;
 
 
       try {
       try {
-        // Create scrape history record if shopId is available
-        if (job.shopId) {
-          scrapeHistoryId = db.createScrapeHistory(job.shopId, job.id);
+        // Create scrape history record if siteId is available
+        if (job.siteId) {
+          scrapeHistoryId = db.createScrapeHistory(job.siteId, job.id);
           db.updateScrapeHistory(scrapeHistoryId, { status: 'processing' });
           db.updateScrapeHistory(scrapeHistoryId, { status: 'processing' });
 
 
           // Trigger webhook: scrape_started
           // Trigger webhook: scrape_started
           await webhookManager.notifyScrapeStarted(
           await webhookManager.notifyScrapeStarted(
-            job.shopId,
+            job.siteId,
             job.id,
             job.id,
             job.sitemapUrl
             job.sitemapUrl
           );
           );
         }
         }
 
 
-        const result = await scraper.scrape(job.sitemapUrl, job.shopId);
+        const result = await scraper.scrape(job.sitemapUrl, job.siteId);
 
 
         jobQueue.updateJob(job.id, {
         jobQueue.updateJob(job.id, {
           status: 'completed',
           status: 'completed',
@@ -68,7 +68,7 @@ async function main() {
         }
         }
 
 
         // Trigger webhook: scrape_completed
         // Trigger webhook: scrape_completed
-        if (job.shopId) {
+        if (job.siteId) {
           const totalUrls = (result.shipping_informations?.length || 0) +
           const totalUrls = (result.shipping_informations?.length || 0) +
                            (result.contacts?.length || 0) +
                            (result.contacts?.length || 0) +
                            (result.terms_of_conditions?.length || 0) +
                            (result.terms_of_conditions?.length || 0) +
@@ -77,7 +77,7 @@ async function main() {
           const totalPages = totalUrls;
           const totalPages = totalUrls;
 
 
           await webhookManager.notifyScrapeCompleted(
           await webhookManager.notifyScrapeCompleted(
-            job.shopId,
+            job.siteId,
             job.id,
             job.id,
             {
             {
               scrape_time_ms: Date.now() - startTime,
               scrape_time_ms: Date.now() - startTime,
@@ -87,15 +87,15 @@ async function main() {
           );
           );
         }
         }
 
 
-        // Schedule next scrape if this is a new shop or first scrape
-        if (job.shopId) {
-          const existingSchedules = db.getScheduledJobs(job.shopId);
+        // Schedule next scrape if this is a new site or first scrape
+        if (job.siteId) {
+          const existingSchedules = db.getScheduledJobs(job.siteId);
           const hasQueuedJobs = existingSchedules.some(s => s.status === 'queued');
           const hasQueuedJobs = existingSchedules.some(s => s.status === 'queued');
 
 
           if (!hasQueuedJobs) {
           if (!hasQueuedJobs) {
-            // Create initial schedule for this shop
-            scheduler.createInitialSchedule(job.shopId, 'weekly', null);
-            logger.info(`Created initial schedule for shop ${job.shopId}`);
+            // Create initial schedule for this site
+            scheduler.createInitialSchedule(job.siteId, 'weekly', null);
+            logger.info(`Created initial schedule for site ${job.siteId}`);
           }
           }
         }
         }
 
 
@@ -117,9 +117,9 @@ async function main() {
         }
         }
 
 
         // Trigger webhook: scrape_failed
         // Trigger webhook: scrape_failed
-        if (job.shopId) {
+        if (job.siteId) {
           await webhookManager.notifyScrapeFailed(
           await webhookManager.notifyScrapeFailed(
-            job.shopId,
+            job.siteId,
             job.id,
             job.id,
             errorMessage
             errorMessage
           );
           );
@@ -138,8 +138,8 @@ async function main() {
       logger.info(`  POST   /api/jobs        - Create new scraping job`);
       logger.info(`  POST   /api/jobs        - Create new scraping job`);
       logger.info(`  GET    /api/jobs/:id    - Get job status and result`);
       logger.info(`  GET    /api/jobs/:id    - Get job status and result`);
       logger.info(`  GET    /api/jobs        - List all jobs`);
       logger.info(`  GET    /api/jobs        - List all jobs`);
-      logger.info(`  GET    /api/shops       - List all shops`);
-      logger.info(`  GET    /api/shops/:id   - Get shop details and analytics`);
+      logger.info(`  GET    /api/sites       - List all sites`);
+      logger.info(`  GET    /api/sites/:id   - Get site details and analytics`);
       logger.info(`  GET    /health          - Health check`);
       logger.info(`  GET    /health          - Health check`);
       logger.info('');
       logger.info('');
       logger.info(`Web UI available at: http://${config.host === '0.0.0.0' ? 'localhost' : config.host}:${config.port}/ui`);
       logger.info(`Web UI available at: http://${config.host === '0.0.0.0' ? 'localhost' : config.host}:${config.port}/ui`);

+ 28 - 28
src/mcp/McpLogger.ts

@@ -1,9 +1,9 @@
 import { v4 as uuidv4 } from 'uuid';
 import { v4 as uuidv4 } from 'uuid';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
-import { ShopDatabase, McpLog } from '../database/Database';
+import { SiteDatabase, McpLog } from '../database/Database';
 
 
 export interface McpLogEntry {
 export interface McpLogEntry {
-  shopId: string;
+  siteId: string;
   toolName: string;
   toolName: string;
   query: string;
   query: string;
   responseSummary: string | null;
   responseSummary: string | null;
@@ -21,12 +21,12 @@ export interface McpLogAnalytics {
   averageExecutionTime: number;
   averageExecutionTime: number;
   averageResultsCount: number;
   averageResultsCount: number;
   topTools: { tool: string; count: number }[];
   topTools: { tool: string; count: number }[];
-  topShops: { shopId: string; count: number }[];
+  topShops: { siteId: string; count: number }[];
   recentErrors: McpLog[];
   recentErrors: McpLog[];
 }
 }
 
 
 export class McpLogger {
 export class McpLogger {
-  constructor(private database: ShopDatabase) {}
+  constructor(private database: SiteDatabase) {}
 
 
   /**
   /**
    * Log a tool call
    * Log a tool call
@@ -38,7 +38,7 @@ export class McpLogger {
 
 
       const logEntry: Omit<McpLog, 'id'> & { id: string } = {
       const logEntry: Omit<McpLog, 'id'> & { id: string } = {
         id,
         id,
-        shop_id: entry.shopId,
+        site_id: entry.siteId,
         tool_name: entry.toolName,
         tool_name: entry.toolName,
         query: entry.query,
         query: entry.query,
         response_summary: entry.responseSummary,
         response_summary: entry.responseSummary,
@@ -53,12 +53,12 @@ export class McpLogger {
 
 
       // Log to application logger as well
       // Log to application logger as well
       if (entry.success) {
       if (entry.success) {
-        logger.debug(`MCP call logged: ${entry.toolName} for shop ${entry.shopId}`, {
+        logger.debug(`MCP call logged: ${entry.toolName} for site ${entry.siteId}`, {
           executionTime: entry.executionTimeMs,
           executionTime: entry.executionTimeMs,
           resultsCount: entry.resultsCount,
           resultsCount: entry.resultsCount,
         });
         });
       } else {
       } else {
-        logger.warn(`MCP call failed and logged: ${entry.toolName} for shop ${entry.shopId}`, {
+        logger.warn(`MCP call failed and logged: ${entry.toolName} for site ${entry.siteId}`, {
           error: entry.responseSummary,
           error: entry.responseSummary,
           executionTime: entry.executionTimeMs,
           executionTime: entry.executionTimeMs,
         });
         });
@@ -70,13 +70,13 @@ export class McpLogger {
   }
   }
 
 
   /**
   /**
-   * Get MCP logs for a specific shop
+   * Get MCP logs for a specific site
    */
    */
-  getShopLogs(shopId: string, limit: number = 100): McpLog[] {
+  getShopLogs(siteId: string, limit: number = 100): McpLog[] {
     try {
     try {
-      return this.database.getMcpLogsSimple(shopId, limit);
+      return this.database.getMcpLogsSimple(siteId, limit);
     } catch (error) {
     } catch (error) {
-      logger.error(`Failed to get MCP logs for shop ${shopId}:`, error);
+      logger.error(`Failed to get MCP logs for site ${siteId}:`, error);
       return [];
       return [];
     }
     }
   }
   }
@@ -96,7 +96,7 @@ export class McpLogger {
   /**
   /**
    * Get analytics for MCP usage
    * Get analytics for MCP usage
    */
    */
-  getAnalytics(shopId?: string, daysPast: number = 30): McpLogAnalytics {
+  getAnalytics(siteId?: string, daysPast: number = 30): McpLogAnalytics {
     try {
     try {
       const cutoffDate = new Date();
       const cutoffDate = new Date();
       cutoffDate.setDate(cutoffDate.getDate() - daysPast);
       cutoffDate.setDate(cutoffDate.getDate() - daysPast);
@@ -104,8 +104,8 @@ export class McpLogger {
 
 
       // Get recent logs
       // Get recent logs
       let logs: McpLog[];
       let logs: McpLog[];
-      if (shopId) {
-        logs = this.database.getMcpLogsSimple(shopId, 1000);
+      if (siteId) {
+        logs = this.database.getMcpLogsSimple(siteId, 1000);
       } else {
       } else {
         logs = this.database.getMcpLogsSimple(undefined, 1000);
         logs = this.database.getMcpLogsSimple(undefined, 1000);
       }
       }
@@ -136,15 +136,15 @@ export class McpLogger {
         .sort((a, b) => b.count - a.count)
         .sort((a, b) => b.count - a.count)
         .slice(0, 10);
         .slice(0, 10);
 
 
-      // Top shops (if not filtering by specific shop)
-      const topShops: { shopId: string; count: number }[] = [];
-      if (!shopId) {
+      // Top shops (if not filtering by specific site)
+      const topShops: { siteId: string; count: number }[] = [];
+      if (!siteId) {
         const shopCounts = new Map<string, number>();
         const shopCounts = new Map<string, number>();
         recentLogs.forEach(log => {
         recentLogs.forEach(log => {
-          shopCounts.set(log.shop_id, (shopCounts.get(log.shop_id) || 0) + 1);
+          shopCounts.set(log.site_id, (shopCounts.get(log.site_id) || 0) + 1);
         });
         });
         topShops.push(...Array.from(shopCounts.entries())
         topShops.push(...Array.from(shopCounts.entries())
-          .map(([shopId, count]) => ({ shopId, count }))
+          .map(([siteId, count]) => ({ siteId, count }))
           .sort((a, b) => b.count - a.count)
           .sort((a, b) => b.count - a.count)
           .slice(0, 10));
           .slice(0, 10));
       }
       }
@@ -184,7 +184,7 @@ export class McpLogger {
   /**
   /**
    * Get performance metrics for a specific tool
    * Get performance metrics for a specific tool
    */
    */
-  getToolPerformance(toolName: string, shopId?: string): {
+  getToolPerformance(toolName: string, siteId?: string): {
     totalCalls: number;
     totalCalls: number;
     averageExecutionTime: number;
     averageExecutionTime: number;
     averageResultsCount: number;
     averageResultsCount: number;
@@ -193,8 +193,8 @@ export class McpLogger {
   } {
   } {
     try {
     try {
       let logs: McpLog[];
       let logs: McpLog[];
-      if (shopId) {
-        logs = this.database.getMcpLogsSimple(shopId, 1000);
+      if (siteId) {
+        logs = this.database.getMcpLogsSimple(siteId, 1000);
       } else {
       } else {
         logs = this.database.getMcpLogsSimple(undefined, 1000);
         logs = this.database.getMcpLogsSimple(undefined, 1000);
       }
       }
@@ -240,7 +240,7 @@ export class McpLogger {
   /**
   /**
    * Get usage trends over time
    * Get usage trends over time
    */
    */
-  getUsageTrends(shopId?: string, days: number = 30): {
+  getUsageTrends(siteId?: string, days: number = 30): {
     daily: { date: string; calls: number; averageTime: number }[];
     daily: { date: string; calls: number; averageTime: number }[];
     hourly: { hour: number; calls: number; averageTime: number }[];
     hourly: { hour: number; calls: number; averageTime: number }[];
   } {
   } {
@@ -250,8 +250,8 @@ export class McpLogger {
       const cutoffIso = cutoffDate.toISOString();
       const cutoffIso = cutoffDate.toISOString();
 
 
       let logs: McpLog[];
       let logs: McpLog[];
-      if (shopId) {
-        logs = this.database.getMcpLogsSimple(shopId, 10000);
+      if (siteId) {
+        logs = this.database.getMcpLogsSimple(siteId, 10000);
       } else {
       } else {
         logs = this.database.getMcpLogsSimple(undefined, 10000);
         logs = this.database.getMcpLogsSimple(undefined, 10000);
       }
       }
@@ -313,14 +313,14 @@ export class McpLogger {
    */
    */
   searchLogs(
   searchLogs(
     searchTerm: string,
     searchTerm: string,
-    shopId?: string,
+    siteId?: string,
     toolName?: string,
     toolName?: string,
     limit: number = 50
     limit: number = 50
   ): McpLog[] {
   ): McpLog[] {
     try {
     try {
       let logs: McpLog[];
       let logs: McpLog[];
-      if (shopId) {
-        logs = this.database.getMcpLogsSimple(shopId, 1000);
+      if (siteId) {
+        logs = this.database.getMcpLogsSimple(siteId, 1000);
       } else {
       } else {
         logs = this.database.getMcpLogsSimple(undefined, 1000);
         logs = this.database.getMcpLogsSimple(undefined, 1000);
       }
       }

+ 15 - 15
src/mcp/McpServer.ts

@@ -3,7 +3,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
 import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
 import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
 import type { Express, Request, Response } from 'express';
 import type { Express, Request, Response } from 'express';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
-import { ShopDatabase } from '../database/Database';
+import { SiteDatabase } from '../database/Database';
 import { QdrantService } from '../services/QdrantService';
 import { QdrantService } from '../services/QdrantService';
 import { EmbeddingService } from '../services/EmbeddingService';
 import { EmbeddingService } from '../services/EmbeddingService';
 import { McpLogger } from './McpLogger';
 import { McpLogger } from './McpLogger';
@@ -17,7 +17,7 @@ import type { AppConfig } from '../config';
 export class McpServer {
 export class McpServer {
   private server!: Server;
   private server!: Server;
   private transport!: StreamableHTTPServerTransport;
   private transport!: StreamableHTTPServerTransport;
-  private database!: ShopDatabase;
+  private database!: SiteDatabase;
   private qdrantService!: QdrantService;
   private qdrantService!: QdrantService;
   private embeddingService!: EmbeddingService;
   private embeddingService!: EmbeddingService;
   private mcpLogger!: McpLogger;
   private mcpLogger!: McpLogger;
@@ -33,7 +33,7 @@ export class McpServer {
     }
     }
 
 
     // Initialize services
     // Initialize services
-    this.database = new ShopDatabase();
+    this.database = new SiteDatabase();
     this.qdrantService = new QdrantService(config);
     this.qdrantService = new QdrantService(config);
     this.embeddingService = new EmbeddingService(config);
     this.embeddingService = new EmbeddingService(config);
     this.mcpLogger = new McpLogger(this.database);
     this.mcpLogger = new McpLogger(this.database);
@@ -139,31 +139,31 @@ export class McpServer {
         }
         }
 
 
         // Validate shop_id parameter
         // Validate shop_id parameter
-        if (!args || typeof args !== 'object' || !args.shop_id) {
+        if (!args || typeof args !== 'object' || !args.site_id) {
           throw new Error('Missing required parameter: shop_id');
           throw new Error('Missing required parameter: shop_id');
         }
         }
 
 
-        const shopId = String(args.shop_id);
+        const siteId = String(args.site_id);
 
 
-        // Check if shop exists and has Qdrant enabled
-        const shop = this.database.getShopByCustomId(shopId);
-        if (!shop) {
+        // Check if site exists and has Qdrant enabled
+        const site = this.database.getSiteByCustomId(siteId);
+        if (!site) {
           return {
           return {
             content: [
             content: [
               {
               {
                 type: 'text',
                 type: 'text',
-                text: 'Search not available for this shop',
+                text: 'Search not available for this site',
               },
               },
             ],
             ],
           };
           };
         }
         }
 
 
-        if (!shop.qdrant_enabled) {
+        if (!site.qdrant_enabled) {
           return {
           return {
             content: [
             content: [
               {
               {
                 type: 'text',
                 type: 'text',
-                text: 'Search not available for this shop',
+                text: 'Search not available for this site',
               },
               },
             ],
             ],
           };
           };
@@ -177,7 +177,7 @@ export class McpServer {
 
 
         // Log successful tool call
         // Log successful tool call
         await this.mcpLogger.logToolCall({
         await this.mcpLogger.logToolCall({
-          shopId,
+          siteId,
           toolName: name,
           toolName: name,
           query: String(args.query || ''),
           query: String(args.query || ''),
           responseSummary: this.summarizeResult(result),
           responseSummary: this.summarizeResult(result),
@@ -189,7 +189,7 @@ export class McpServer {
         });
         });
 
 
         logger.info(`MCP tool call completed: ${name}`, {
         logger.info(`MCP tool call completed: ${name}`, {
-          shopId,
+          siteId,
           executionTime,
           executionTime,
           resultsCount: this.countResults(result),
           resultsCount: this.countResults(result),
         });
         });
@@ -203,9 +203,9 @@ export class McpServer {
         logger.error(`MCP tool call failed: ${name}`, { error: errorMessage, args });
         logger.error(`MCP tool call failed: ${name}`, { error: errorMessage, args });
 
 
         // Log failed tool call
         // Log failed tool call
-        if (args && args.shop_id) {
+        if (args && args.site_id) {
           await this.mcpLogger.logToolCall({
           await this.mcpLogger.logToolCall({
-            shopId: String(args.shop_id),
+            siteId: String(args.site_id),
             toolName: name,
             toolName: name,
             query: String(args.query || ''),
             query: String(args.query || ''),
             responseSummary: `Error: ${errorMessage}`,
             responseSummary: `Error: ${errorMessage}`,

+ 11 - 11
src/mcp/tools/BaseTool.ts

@@ -1,4 +1,4 @@
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { QdrantService } from '../../services/QdrantService';
 import { QdrantService } from '../../services/QdrantService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { McpLogger } from '../McpLogger';
 import { McpLogger } from '../McpLogger';
@@ -26,7 +26,7 @@ export abstract class BaseTool {
   protected contentType: 'shipping' | 'contacts' | 'terms' | 'faq';
   protected contentType: 'shipping' | 'contacts' | 'terms' | 'faq';
 
 
   constructor(
   constructor(
-    protected database: ShopDatabase,
+    protected database: SiteDatabase,
     protected qdrantService: QdrantService,
     protected qdrantService: QdrantService,
     protected embeddingService: EmbeddingService,
     protected embeddingService: EmbeddingService,
     protected mcpLogger: McpLogger,
     protected mcpLogger: McpLogger,
@@ -46,18 +46,18 @@ export abstract class BaseTool {
         return this.createErrorResult(validation.error || 'Invalid arguments');
         return this.createErrorResult(validation.error || 'Invalid arguments');
       }
       }
 
 
-      const shopId = String(args.shop_id);
+      const siteId = String(args.site_id);
       const query = String(args.query);
       const query = String(args.query);
       const limit = args.limit ? Math.min(Math.max(parseInt(args.limit), 1), 20) : 10;
       const limit = args.limit ? Math.min(Math.max(parseInt(args.limit), 1), 20) : 10;
 
 
-      // Check if shop exists and has Qdrant enabled
-      const shop = this.database.getShopByCustomId(shopId);
-      if (!shop) {
-        return this.createErrorResult('Search not available for this shop');
+      // Check if site exists and has Qdrant enabled
+      const site = this.database.getSiteByCustomId(siteId);
+      if (!site) {
+        return this.createErrorResult('Search not available for this site');
       }
       }
 
 
-      if (!shop.qdrant_enabled) {
-        return this.createErrorResult('Search not available for this shop');
+      if (!site.qdrant_enabled) {
+        return this.createErrorResult('Search not available for this site');
       }
       }
 
 
       // Generate query embedding
       // Generate query embedding
@@ -69,7 +69,7 @@ export abstract class BaseTool {
 
 
       // Search in Qdrant
       // Search in Qdrant
       const searchResults = await this.qdrantService.searchInCategory(
       const searchResults = await this.qdrantService.searchInCategory(
-        shopId,
+        siteId,
         this.contentType,
         this.contentType,
         embeddingResult.embedding,
         embeddingResult.embedding,
         limit
         limit
@@ -92,7 +92,7 @@ export abstract class BaseTool {
       return { valid: false, error: 'Arguments must be an object' };
       return { valid: false, error: 'Arguments must be an object' };
     }
     }
 
 
-    if (!args.shop_id || typeof args.shop_id !== 'string') {
+    if (!args.site_id || typeof args.site_id !== 'string') {
       return { valid: false, error: 'Missing required parameter: shop_id' };
       return { valid: false, error: 'Missing required parameter: shop_id' };
     }
     }
 
 

+ 6 - 6
src/mcp/tools/ContactsSearchTool.ts

@@ -1,12 +1,12 @@
 import { BaseTool } from './BaseTool';
 import { BaseTool } from './BaseTool';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { QdrantService } from '../../services/QdrantService';
 import { QdrantService } from '../../services/QdrantService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { McpLogger } from '../McpLogger';
 import { McpLogger } from '../McpLogger';
 
 
 export class ContactsSearchTool extends BaseTool {
 export class ContactsSearchTool extends BaseTool {
   constructor(
   constructor(
-    database: ShopDatabase,
+    database: SiteDatabase,
     qdrantService: QdrantService,
     qdrantService: QdrantService,
     embeddingService: EmbeddingService,
     embeddingService: EmbeddingService,
     mcpLogger: McpLogger
     mcpLogger: McpLogger
@@ -15,16 +15,16 @@ export class ContactsSearchTool extends BaseTool {
   }
   }
 
 
   getDescription(): string {
   getDescription(): string {
-    return 'Search for contact and company information in a webshop. Use this tool to find contact details, company information, about us pages, team information, physical addresses, phone numbers, email addresses, and business hours.';
+    return 'Search for contact and company information in a website. Use this tool to find contact details, company information, about us pages, team information, physical addresses, phone numbers, email addresses, and business hours.';
   }
   }
 
 
   getInputSchema(): object {
   getInputSchema(): object {
     return {
     return {
       type: 'object',
       type: 'object',
       properties: {
       properties: {
-        shop_id: {
+        site_id: {
           type: 'string',
           type: 'string',
-          description: 'The unique identifier of the webshop to search in',
+          description: 'The unique identifier of the website to search in',
         },
         },
         query: {
         query: {
           type: 'string',
           type: 'string',
@@ -37,7 +37,7 @@ export class ContactsSearchTool extends BaseTool {
           maximum: 20,
           maximum: 20,
         },
         },
       },
       },
-      required: ['shop_id', 'query'],
+      required: ['site_id', 'query'],
     };
     };
   }
   }
 }
 }

+ 6 - 6
src/mcp/tools/FaqSearchTool.ts

@@ -1,12 +1,12 @@
 import { BaseTool } from './BaseTool';
 import { BaseTool } from './BaseTool';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { QdrantService } from '../../services/QdrantService';
 import { QdrantService } from '../../services/QdrantService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { McpLogger } from '../McpLogger';
 import { McpLogger } from '../McpLogger';
 
 
 export class FaqSearchTool extends BaseTool {
 export class FaqSearchTool extends BaseTool {
   constructor(
   constructor(
-    database: ShopDatabase,
+    database: SiteDatabase,
     qdrantService: QdrantService,
     qdrantService: QdrantService,
     embeddingService: EmbeddingService,
     embeddingService: EmbeddingService,
     mcpLogger: McpLogger
     mcpLogger: McpLogger
@@ -15,16 +15,16 @@ export class FaqSearchTool extends BaseTool {
   }
   }
 
 
   getDescription(): string {
   getDescription(): string {
-    return 'Search for FAQ and help information in a webshop. Use this tool to find frequently asked questions, help documentation, customer support information, troubleshooting guides, user manuals, and support resources.';
+    return 'Search for FAQ and help information in a website. Use this tool to find frequently asked questions, help documentation, customer support information, troubleshooting guides, user manuals, and support resources.';
   }
   }
 
 
   getInputSchema(): object {
   getInputSchema(): object {
     return {
     return {
       type: 'object',
       type: 'object',
       properties: {
       properties: {
-        shop_id: {
+        site_id: {
           type: 'string',
           type: 'string',
-          description: 'The unique identifier of the webshop to search in',
+          description: 'The unique identifier of the website to search in',
         },
         },
         query: {
         query: {
           type: 'string',
           type: 'string',
@@ -37,7 +37,7 @@ export class FaqSearchTool extends BaseTool {
           maximum: 20,
           maximum: 20,
         },
         },
       },
       },
-      required: ['shop_id', 'query'],
+      required: ['site_id', 'query'],
     };
     };
   }
   }
 }
 }

+ 15 - 15
src/mcp/tools/ListContentsTool.ts

@@ -1,4 +1,4 @@
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { logger } from '../../utils/logger';
 import { logger } from '../../utils/logger';
 
 
 export interface ToolResult {
 export interface ToolResult {
@@ -18,7 +18,7 @@ const CONTENT_TYPE_TO_TOOL: Record<string, string> = {
 };
 };
 
 
 export class ListContentsTool {
 export class ListContentsTool {
-  constructor(private database: ShopDatabase) {}
+  constructor(private database: SiteDatabase) {}
 
 
   /**
   /**
    * Execute the tool - list all content titles with their associated search tools
    * Execute the tool - list all content titles with their associated search tools
@@ -31,23 +31,23 @@ export class ListContentsTool {
         return this.createErrorResult(validation.error || 'Invalid arguments');
         return this.createErrorResult(validation.error || 'Invalid arguments');
       }
       }
 
 
-      const shopId = String(args.shop_id);
+      const siteId = String(args.site_id);
 
 
-      // Check if shop exists
-      const shop = this.database.getShopByCustomId(shopId);
-      if (!shop) {
-        return this.createErrorResult('Shop not found');
+      // Check if site exists
+      const site = this.database.getSiteByCustomId(siteId);
+      if (!site) {
+        return this.createErrorResult('Site not found');
       }
       }
 
 
-      // Get all content for this shop (only enabled content)
-      const allContent = this.database.getAllShopContent(shop.id, false);
+      // Get all content for this site (only enabled content)
+      const allContent = this.database.getAllSiteContent(site.id, false);
 
 
       if (allContent.length === 0) {
       if (allContent.length === 0) {
         return {
         return {
           content: [
           content: [
             {
             {
               type: 'text',
               type: 'text',
-              text: `No content available for shop "${shopId}". The shop may not have been scraped yet.`,
+              text: `No content available for site "${siteId}". The site may not have been scraped yet.`,
             },
             },
           ],
           ],
         };
         };
@@ -114,7 +114,7 @@ export class ListContentsTool {
       return { valid: false, error: 'Arguments must be an object' };
       return { valid: false, error: 'Arguments must be an object' };
     }
     }
 
 
-    if (!args.shop_id || typeof args.shop_id !== 'string') {
+    if (!args.site_id || typeof args.site_id !== 'string') {
       return { valid: false, error: 'Missing required parameter: shop_id' };
       return { valid: false, error: 'Missing required parameter: shop_id' };
     }
     }
 
 
@@ -140,7 +140,7 @@ export class ListContentsTool {
    * Get tool description for MCP
    * Get tool description for MCP
    */
    */
   getDescription(): string {
   getDescription(): string {
-    return 'List all available content titles for a webshop with their associated search tools. Use this tool first to discover what content is available and which search tool to use for finding specific information.';
+    return 'List all available content titles for a website with their associated search tools. Use this tool first to discover what content is available and which search tool to use for finding specific information.';
   }
   }
 
 
   /**
   /**
@@ -150,12 +150,12 @@ export class ListContentsTool {
     return {
     return {
       type: 'object',
       type: 'object',
       properties: {
       properties: {
-        shop_id: {
+        site_id: {
           type: 'string',
           type: 'string',
-          description: 'The unique identifier of the webshop to list contents for',
+          description: 'The unique identifier of the website to list contents for',
         },
         },
       },
       },
-      required: ['shop_id'],
+      required: ['site_id'],
     };
     };
   }
   }
 }
 }

+ 6 - 6
src/mcp/tools/ShippingSearchTool.ts

@@ -1,12 +1,12 @@
 import { BaseTool } from './BaseTool';
 import { BaseTool } from './BaseTool';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { QdrantService } from '../../services/QdrantService';
 import { QdrantService } from '../../services/QdrantService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { McpLogger } from '../McpLogger';
 import { McpLogger } from '../McpLogger';
 
 
 export class ShippingSearchTool extends BaseTool {
 export class ShippingSearchTool extends BaseTool {
   constructor(
   constructor(
-    database: ShopDatabase,
+    database: SiteDatabase,
     qdrantService: QdrantService,
     qdrantService: QdrantService,
     embeddingService: EmbeddingService,
     embeddingService: EmbeddingService,
     mcpLogger: McpLogger
     mcpLogger: McpLogger
@@ -15,16 +15,16 @@ export class ShippingSearchTool extends BaseTool {
   }
   }
 
 
   getDescription(): string {
   getDescription(): string {
-    return 'Search for shipping and delivery information in a webshop. Use this tool to find information about delivery methods, shipping costs, delivery times, shipping policies, and related logistics information.';
+    return 'Search for shipping and delivery information in a website. Use this tool to find information about delivery methods, shipping costs, delivery times, shipping policies, and related logistics information.';
   }
   }
 
 
   getInputSchema(): object {
   getInputSchema(): object {
     return {
     return {
       type: 'object',
       type: 'object',
       properties: {
       properties: {
-        shop_id: {
+        site_id: {
           type: 'string',
           type: 'string',
-          description: 'The unique identifier of the webshop to search in',
+          description: 'The unique identifier of the website to search in',
         },
         },
         query: {
         query: {
           type: 'string',
           type: 'string',
@@ -37,7 +37,7 @@ export class ShippingSearchTool extends BaseTool {
           maximum: 20,
           maximum: 20,
         },
         },
       },
       },
-      required: ['shop_id', 'query'],
+      required: ['site_id', 'query'],
     };
     };
   }
   }
 }
 }

+ 6 - 6
src/mcp/tools/TermsSearchTool.ts

@@ -1,12 +1,12 @@
 import { BaseTool } from './BaseTool';
 import { BaseTool } from './BaseTool';
-import { ShopDatabase } from '../../database/Database';
+import { SiteDatabase } from '../../database/Database';
 import { QdrantService } from '../../services/QdrantService';
 import { QdrantService } from '../../services/QdrantService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { EmbeddingService } from '../../services/EmbeddingService';
 import { McpLogger } from '../McpLogger';
 import { McpLogger } from '../McpLogger';
 
 
 export class TermsSearchTool extends BaseTool {
 export class TermsSearchTool extends BaseTool {
   constructor(
   constructor(
-    database: ShopDatabase,
+    database: SiteDatabase,
     qdrantService: QdrantService,
     qdrantService: QdrantService,
     embeddingService: EmbeddingService,
     embeddingService: EmbeddingService,
     mcpLogger: McpLogger
     mcpLogger: McpLogger
@@ -15,16 +15,16 @@ export class TermsSearchTool extends BaseTool {
   }
   }
 
 
   getDescription(): string {
   getDescription(): string {
-    return 'Search for terms, policies, and legal information in a webshop. Use this tool to find terms of service, privacy policies, refund policies, return policies, GDPR information, cookie policies, and other legal documents.';
+    return 'Search for terms, policies, and legal information in a website. Use this tool to find terms of service, privacy policies, refund policies, return policies, GDPR information, cookie policies, and other legal documents.';
   }
   }
 
 
   getInputSchema(): object {
   getInputSchema(): object {
     return {
     return {
       type: 'object',
       type: 'object',
       properties: {
       properties: {
-        shop_id: {
+        site_id: {
           type: 'string',
           type: 'string',
-          description: 'The unique identifier of the webshop to search in',
+          description: 'The unique identifier of the website to search in',
         },
         },
         query: {
         query: {
           type: 'string',
           type: 'string',
@@ -37,7 +37,7 @@ export class TermsSearchTool extends BaseTool {
           maximum: 20,
           maximum: 20,
         },
         },
       },
       },
-      required: ['shop_id', 'query'],
+      required: ['site_id', 'query'],
     };
     };
   }
   }
 }
 }

+ 17 - 17
src/scheduler/ScrapeScheduler.ts

@@ -1,17 +1,17 @@
 import * as cron from 'node-cron';
 import * as cron from 'node-cron';
-import { ShopDatabase } from '../database/Database';
+import { SiteDatabase } from '../database/Database';
 import { JobQueue } from '../queue/JobQueue';
 import { JobQueue } from '../queue/JobQueue';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
 import { v4 as uuidv4 } from 'uuid';
 import { v4 as uuidv4 } from 'uuid';
 import { WebhookManager } from '../webhooks/WebhookManager';
 import { WebhookManager } from '../webhooks/WebhookManager';
 
 
 export class ScrapeScheduler {
 export class ScrapeScheduler {
-  private db: ShopDatabase;
+  private db: SiteDatabase;
   private jobQueue: JobQueue;
   private jobQueue: JobQueue;
   private cronTask: cron.ScheduledTask | null = null;
   private cronTask: cron.ScheduledTask | null = null;
   private webhookManager: WebhookManager;
   private webhookManager: WebhookManager;
 
 
-  constructor(db: ShopDatabase, jobQueue: JobQueue) {
+  constructor(db: SiteDatabase, jobQueue: JobQueue) {
     this.db = db;
     this.db = db;
     this.jobQueue = jobQueue;
     this.jobQueue = jobQueue;
     this.webhookManager = new WebhookManager(db);
     this.webhookManager = new WebhookManager(db);
@@ -57,8 +57,8 @@ export class ScrapeScheduler {
 
 
         for (const scheduledJob of dueJobs) {
         for (const scheduledJob of dueJobs) {
           try {
           try {
-            const shop = this.db.getShopById(scheduledJob.shop_id);
-            if (!shop) {
+            const site = this.db.getSiteById(scheduledJob.site_id);
+            if (!site) {
               logger.error(`Shop not found for scheduled job ${scheduledJob.id}`);
               logger.error(`Shop not found for scheduled job ${scheduledJob.id}`);
               continue;
               continue;
             }
             }
@@ -66,11 +66,11 @@ export class ScrapeScheduler {
             // Create a job in the queue
             // Create a job in the queue
             const job = {
             const job = {
               id: uuidv4(),
               id: uuidv4(),
-              sitemapUrl: shop.sitemap_url,
+              sitemapUrl: site.sitemap_url,
               status: 'pending' as const,
               status: 'pending' as const,
               createdAt: new Date(),
               createdAt: new Date(),
               updatedAt: new Date(),
               updatedAt: new Date(),
-              shopId: shop.id
+              siteId: site.id
             };
             };
 
 
             this.jobQueue.addJob(job);
             this.jobQueue.addJob(job);
@@ -83,7 +83,7 @@ export class ScrapeScheduler {
             // Schedule next run based on frequency
             // Schedule next run based on frequency
             this.scheduleNextRun(scheduledJob);
             this.scheduleNextRun(scheduledJob);
 
 
-            logger.info(`Queued job for shop ${shop.url} (scheduled job ${scheduledJob.id})`);
+            logger.info(`Queued job for site ${site.url} (scheduled job ${scheduledJob.id})`);
           } catch (error) {
           } catch (error) {
             logger.error(`Error queueing scheduled job ${scheduledJob.id}`, error);
             logger.error(`Error queueing scheduled job ${scheduledJob.id}`, error);
           }
           }
@@ -121,7 +121,7 @@ export class ScrapeScheduler {
 
 
     // Create a new scheduled job for the next run
     // Create a new scheduled job for the next run
     this.db.createScheduledJob(
     this.db.createScheduledJob(
-      scheduledJob.shop_id,
+      scheduledJob.site_id,
       nextRunAt.toISOString(),
       nextRunAt.toISOString(),
       scheduledJob.frequency,
       scheduledJob.frequency,
       scheduledJob.last_modified
       scheduledJob.last_modified
@@ -132,26 +132,26 @@ export class ScrapeScheduler {
       status: 'completed'
       status: 'completed'
     });
     });
 
 
-    // Update shop analytics with next scrape time
-    this.db.setNextScrapeTime(scheduledJob.shop_id, nextRunAt.toISOString());
+    // Update site analytics with next scrape time
+    this.db.setNextScrapeTime(scheduledJob.site_id, nextRunAt.toISOString());
 
 
     // Trigger webhook: schedule_created
     // Trigger webhook: schedule_created
     this.webhookManager.notifyScheduleCreated(
     this.webhookManager.notifyScheduleCreated(
-      scheduledJob.shop_id,
+      scheduledJob.site_id,
       nextRunAt.toISOString(),
       nextRunAt.toISOString(),
       scheduledJob.frequency
       scheduledJob.frequency
     ).catch(error => {
     ).catch(error => {
       logger.error('Error sending schedule_created webhook', error);
       logger.error('Error sending schedule_created webhook', error);
     });
     });
 
 
-    logger.info(`Scheduled next run for shop ${scheduledJob.shop_id} at ${nextRunAt.toISOString()}`);
+    logger.info(`Scheduled next run for site ${scheduledJob.site_id} at ${nextRunAt.toISOString()}`);
   }
   }
 
 
   /**
   /**
-   * Create initial schedule for a shop based on sitemap data
+   * Create initial schedule for a site based on sitemap data
    */
    */
   createInitialSchedule(
   createInitialSchedule(
-    shopId: string,
+    siteId: string,
     frequency: string | null,
     frequency: string | null,
     lastModified: string | null
     lastModified: string | null
   ): void {
   ): void {
@@ -159,12 +159,12 @@ export class ScrapeScheduler {
     const firstRun = new Date(Date.now() + 60 * 1000);
     const firstRun = new Date(Date.now() + 60 * 1000);
 
 
     this.db.createScheduledJob(
     this.db.createScheduledJob(
-      shopId,
+      siteId,
       firstRun.toISOString(),
       firstRun.toISOString(),
       frequency || 'weekly',
       frequency || 'weekly',
       lastModified
       lastModified
     );
     );
 
 
-    logger.info(`Created initial schedule for shop ${shopId}, first run at ${firstRun.toISOString()}`);
+    logger.info(`Created initial schedule for site ${siteId}, first run at ${firstRun.toISOString()}`);
   }
   }
 }
 }

+ 5 - 5
src/scraper/SitemapParser.ts

@@ -1,7 +1,7 @@
 import axios from 'axios';
 import axios from 'axios';
 import { parseString } from 'xml2js';
 import { parseString } from 'xml2js';
 import { promisify } from 'util';
 import { promisify } from 'util';
-import { SitemapUrl, WebshopType } from '../types';
+import { SitemapUrl, SitePlatform } from '../types';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
 import { getUserAgent } from '../version';
 import { getUserAgent } from '../version';
 
 
@@ -11,7 +11,7 @@ export class SitemapParser {
   /**
   /**
    * Detect webshop type from base URL and get sitemap URL
    * Detect webshop type from base URL and get sitemap URL
    */
    */
-  static getSitemapUrl(baseUrl: string): { sitemapUrl: string; webshopType: WebshopType } {
+  static getSitemapUrl(baseUrl: string): { sitemapUrl: string; sitePlatform: SitePlatform } {
     const url = new URL(baseUrl);
     const url = new URL(baseUrl);
     const hostname = url.hostname;
     const hostname = url.hostname;
 
 
@@ -19,7 +19,7 @@ export class SitemapParser {
     if (hostname.includes('shoprenter') || hostname.includes('.sr.hu')) {
     if (hostname.includes('shoprenter') || hostname.includes('.sr.hu')) {
       return {
       return {
         sitemapUrl: `${url.origin}/sitemap.xml`,
         sitemapUrl: `${url.origin}/sitemap.xml`,
-        webshopType: 'shoprenter'
+        sitePlatform: 'shoprenter'
       };
       };
     }
     }
 
 
@@ -27,14 +27,14 @@ export class SitemapParser {
     if (hostname.includes('myshopify.com') || hostname.includes('.shopify.')) {
     if (hostname.includes('myshopify.com') || hostname.includes('.shopify.')) {
       return {
       return {
         sitemapUrl: `${url.origin}/sitemap.xml`,
         sitemapUrl: `${url.origin}/sitemap.xml`,
-        webshopType: 'shopify'
+        sitePlatform: 'shopify'
       };
       };
     }
     }
 
 
     // WooCommerce (default - most common for custom domains)
     // WooCommerce (default - most common for custom domains)
     return {
     return {
       sitemapUrl: `${url.origin}/sitemap.xml`,
       sitemapUrl: `${url.origin}/sitemap.xml`,
-      webshopType: 'woocommerce'
+      sitePlatform: 'woocommerce'
     };
     };
   }
   }
 
 

+ 38 - 38
src/scraper/WebScraper.ts

@@ -2,18 +2,18 @@ import { ScraperResult, PageContent } from '../types';
 import { SitemapParser } from './SitemapParser';
 import { SitemapParser } from './SitemapParser';
 import { ContentExtractor } from './ContentExtractor';
 import { ContentExtractor } from './ContentExtractor';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
-import { ShopDatabase } from '../database/Database';
+import { SiteDatabase } from '../database/Database';
 import { WebhookManager } from '../webhooks/WebhookManager';
 import { WebhookManager } from '../webhooks/WebhookManager';
 import { QdrantEmbeddingWorkflow } from '../services/QdrantEmbeddingWorkflow';
 import { QdrantEmbeddingWorkflow } from '../services/QdrantEmbeddingWorkflow';
 import type { AppConfig } from '../config';
 import type { AppConfig } from '../config';
 
 
-export class WebshopScraper {
+export class WebScraper {
   private contentExtractor: ContentExtractor;
   private contentExtractor: ContentExtractor;
-  private db: ShopDatabase | null;
+  private db: SiteDatabase | null;
   private webhookManager: WebhookManager | null;
   private webhookManager: WebhookManager | null;
   private embeddingWorkflow: QdrantEmbeddingWorkflow | null;
   private embeddingWorkflow: QdrantEmbeddingWorkflow | null;
 
 
-  constructor(db?: ShopDatabase, config?: AppConfig) {
+  constructor(db?: SiteDatabase, config?: AppConfig) {
     this.contentExtractor = new ContentExtractor();
     this.contentExtractor = new ContentExtractor();
     this.db = db || null;
     this.db = db || null;
     this.webhookManager = db ? new WebhookManager(db) : null;
     this.webhookManager = db ? new WebhookManager(db) : null;
@@ -23,23 +23,23 @@ export class WebshopScraper {
   /**
   /**
    * Main scraping method
    * Main scraping method
    */
    */
-  async scrape(baseUrl: string, shopId?: string): Promise<ScraperResult> {
+  async scrape(baseUrl: string, siteId?: string): Promise<ScraperResult> {
     logger.info(`Starting scrape for: ${baseUrl}`);
     logger.info(`Starting scrape for: ${baseUrl}`);
     const startTime = Date.now();
     const startTime = Date.now();
 
 
     try {
     try {
       // Get sitemap URL based on webshop type
       // Get sitemap URL based on webshop type
-      const { sitemapUrl, webshopType } = SitemapParser.getSitemapUrl(baseUrl);
-      logger.info(`Detected webshop type: ${webshopType}, sitemap: ${sitemapUrl}`);
-
-      // If database is available and no shopId provided, find or create shop
-      if (this.db && !shopId) {
-        let shop = this.db.getShopByUrl(baseUrl);
-        if (!shop) {
-          shop = this.db.createShop(baseUrl, sitemapUrl, webshopType);
-          logger.info(`Created new shop record with ID: ${shop.id}`);
+      const { sitemapUrl, sitePlatform } = SitemapParser.getSitemapUrl(baseUrl);
+      logger.info(`Detected site type: ${sitePlatform}, sitemap: ${sitemapUrl}`);
+
+      // If database is available and no siteId provided, find or create site
+      if (this.db && !siteId) {
+        let site = this.db.getSiteByUrl(baseUrl);
+        if (!site) {
+          site = this.db.createSite(baseUrl, sitemapUrl, sitePlatform);
+          logger.info(`Created new site record with ID: ${site.id}`);
         }
         }
-        shopId = shop.id;
+        siteId = site.id;
       }
       }
 
 
       // Parse sitemap
       // Parse sitemap
@@ -51,9 +51,9 @@ export class WebshopScraper {
       // Filter URLs by page type
       // Filter URLs by page type
       const filteredUrls = SitemapParser.filterUrlsByType(urls);
       const filteredUrls = SitemapParser.filterUrlsByType(urls);
 
 
-      // Filter out disabled URLs if database and shopId are available
-      if (this.db && shopId) {
-        const disabledUrls = this.db.getDisabledContentUrls(shopId);
+      // Filter out disabled URLs if database and siteId are available
+      if (this.db && siteId) {
+        const disabledUrls = this.db.getDisabledContentUrls(siteId);
         const disabledUrlSet = new Set(disabledUrls);
         const disabledUrlSet = new Set(disabledUrls);
 
 
         // Remove disabled URLs from each category
         // Remove disabled URLs from each category
@@ -66,9 +66,9 @@ export class WebshopScraper {
         }
         }
       }
       }
 
 
-      // Merge custom URLs if database and shopId are available
-      if (this.db && shopId) {
-        const customUrls = this.db.getEnabledCustomUrls(shopId);
+      // Merge custom URLs if database and siteId are available
+      if (this.db && siteId) {
+        const customUrls = this.db.getEnabledCustomUrls(siteId);
 
 
         // Group custom URLs by content type
         // Group custom URLs by content type
         const customUrlsByType: { [key: string]: any[] } = {
         const customUrlsByType: { [key: string]: any[] } = {
@@ -122,13 +122,13 @@ export class WebshopScraper {
       const faqContent = await this.extractAllPageContent(filteredUrls.faq);
       const faqContent = await this.extractAllPageContent(filteredUrls.faq);
 
 
       // Save content to database if available
       // Save content to database if available
-      if (this.db && shopId) {
+      if (this.db && siteId) {
         // Collect all content for batch embedding processing
         // Collect all content for batch embedding processing
         const embeddingBatch: any[] = [];
         const embeddingBatch: any[] = [];
 
 
         // Save all shipping pages
         // Save all shipping pages
         for (const content of shippingContent) {
         for (const content of shippingContent) {
-          const result = this.db.saveContent(shopId, 'shipping', content.url, content.content, content.title);
+          const result = this.db.saveContent(siteId, 'shipping', content.url, content.content, content.title);
 
 
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           if (result.skipped) continue;
           if (result.skipped) continue;
@@ -148,12 +148,12 @@ export class WebshopScraper {
 
 
           // Trigger webhook if content changed
           // Trigger webhook if content changed
           if (result.changed && this.webhookManager) {
           if (result.changed && this.webhookManager) {
-            const previous = this.db.getContentHistory(shopId, 'shipping', 2);
+            const previous = this.db.getContentHistory(siteId, 'shipping', 2);
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
 
 
             await this.webhookManager.notifyContentChanged(
             await this.webhookManager.notifyContentChanged(
-              shopId,
+              siteId,
               'shipping',
               'shipping',
               content.url,
               content.url,
               oldHash,
               oldHash,
@@ -163,7 +163,7 @@ export class WebshopScraper {
         }
         }
         // Save all contact pages
         // Save all contact pages
         for (const content of contactsContent) {
         for (const content of contactsContent) {
-          const result = this.db.saveContent(shopId, 'contacts', content.url, content.content, content.title);
+          const result = this.db.saveContent(siteId, 'contacts', content.url, content.content, content.title);
 
 
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           if (result.skipped) continue;
           if (result.skipped) continue;
@@ -183,12 +183,12 @@ export class WebshopScraper {
 
 
           // Trigger webhook if content changed
           // Trigger webhook if content changed
           if (result.changed && this.webhookManager) {
           if (result.changed && this.webhookManager) {
-            const previous = this.db.getContentHistory(shopId, 'contacts', 2);
+            const previous = this.db.getContentHistory(siteId, 'contacts', 2);
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
 
 
             await this.webhookManager.notifyContentChanged(
             await this.webhookManager.notifyContentChanged(
-              shopId,
+              siteId,
               'contacts',
               'contacts',
               content.url,
               content.url,
               oldHash,
               oldHash,
@@ -198,7 +198,7 @@ export class WebshopScraper {
         }
         }
         // Save all terms pages
         // Save all terms pages
         for (const content of termsContent) {
         for (const content of termsContent) {
-          const result = this.db.saveContent(shopId, 'terms', content.url, content.content, content.title);
+          const result = this.db.saveContent(siteId, 'terms', content.url, content.content, content.title);
 
 
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           if (result.skipped) continue;
           if (result.skipped) continue;
@@ -218,12 +218,12 @@ export class WebshopScraper {
 
 
           // Trigger webhook if content changed
           // Trigger webhook if content changed
           if (result.changed && this.webhookManager) {
           if (result.changed && this.webhookManager) {
-            const previous = this.db.getContentHistory(shopId, 'terms', 2);
+            const previous = this.db.getContentHistory(siteId, 'terms', 2);
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
 
 
             await this.webhookManager.notifyContentChanged(
             await this.webhookManager.notifyContentChanged(
-              shopId,
+              siteId,
               'terms',
               'terms',
               content.url,
               content.url,
               oldHash,
               oldHash,
@@ -233,7 +233,7 @@ export class WebshopScraper {
         }
         }
         // Save all FAQ pages
         // Save all FAQ pages
         for (const content of faqContent) {
         for (const content of faqContent) {
-          const result = this.db.saveContent(shopId, 'faq', content.url, content.content, content.title);
+          const result = this.db.saveContent(siteId, 'faq', content.url, content.content, content.title);
 
 
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           // Skip embedding and webhooks if content was skipped (duplicate hash in another category)
           if (result.skipped) continue;
           if (result.skipped) continue;
@@ -253,12 +253,12 @@ export class WebshopScraper {
 
 
           // Trigger webhook if content changed
           // Trigger webhook if content changed
           if (result.changed && this.webhookManager) {
           if (result.changed && this.webhookManager) {
-            const previous = this.db.getContentHistory(shopId, 'faq', 2);
+            const previous = this.db.getContentHistory(siteId, 'faq', 2);
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const oldHash = previous.length > 1 ? previous[1].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
             const newHash = previous.length > 0 ? previous[0].content_hash : '';
 
 
             await this.webhookManager.notifyContentChanged(
             await this.webhookManager.notifyContentChanged(
-              shopId,
+              siteId,
               'faq',
               'faq',
               content.url,
               content.url,
               oldHash,
               oldHash,
@@ -272,7 +272,7 @@ export class WebshopScraper {
           logger.info(`Processing ${embeddingBatch.length} items for Qdrant embedding`);
           logger.info(`Processing ${embeddingBatch.length} items for Qdrant embedding`);
 
 
           try {
           try {
-            const embeddingResults = await this.embeddingWorkflow.processContentBatch(shopId, embeddingBatch);
+            const embeddingResults = await this.embeddingWorkflow.processContentBatch(siteId, embeddingBatch);
             const embeddedCount = embeddingResults.filter(r => r.embedded).length;
             const embeddedCount = embeddingResults.filter(r => r.embedded).length;
             const failedCount = embeddingResults.filter(r => !r.embedded).length;
             const failedCount = embeddingResults.filter(r => !r.embedded).length;
 
 
@@ -281,7 +281,7 @@ export class WebshopScraper {
             // Log embedding completion webhook event if any were successful
             // Log embedding completion webhook event if any were successful
             if (embeddedCount > 0 && this.webhookManager) {
             if (embeddedCount > 0 && this.webhookManager) {
               // Note: This would need a new webhook event type for embedding completion
               // Note: This would need a new webhook event type for embedding completion
-              // await this.webhookManager.notifyEmbeddingCompleted(shopId, embeddedCount, failedCount);
+              // await this.webhookManager.notifyEmbeddingCompleted(siteId, embeddedCount, failedCount);
             }
             }
           } catch (error) {
           } catch (error) {
             logger.error('Failed to process embedding batch:', error);
             logger.error('Failed to process embedding batch:', error);
@@ -292,7 +292,7 @@ export class WebshopScraper {
         const scrapeTimeMs = Date.now() - startTime;
         const scrapeTimeMs = Date.now() - startTime;
         const pageCount = shippingContent.length + contactsContent.length + termsContent.length + faqContent.length;
         const pageCount = shippingContent.length + contactsContent.length + termsContent.length + faqContent.length;
 
 
-        this.db.updateAnalytics(shopId, {
+        this.db.updateAnalytics(siteId, {
           scrapeTimeMs,
           scrapeTimeMs,
           urlsFound: urls.length,
           urlsFound: urls.length,
           pageCount
           pageCount
@@ -300,7 +300,7 @@ export class WebshopScraper {
 
 
         // Cleanup disabled URLs that are no longer in sitemap
         // Cleanup disabled URLs that are no longer in sitemap
         const currentSitemapUrls = urls.map((u: any) => u.loc);
         const currentSitemapUrls = urls.map((u: any) => u.loc);
-        const deletedCount = this.db.cleanupMissingDisabledUrls(shopId, currentSitemapUrls);
+        const deletedCount = this.db.cleanupMissingDisabledUrls(siteId, currentSitemapUrls);
         if (deletedCount > 0) {
         if (deletedCount > 0) {
           logger.info(`Cleaned up ${deletedCount} disabled URLs that are no longer in sitemap`);
           logger.info(`Cleaned up ${deletedCount} disabled URLs that are no longer in sitemap`);
         }
         }

+ 1 - 1
src/services/EmbeddingService.ts

@@ -5,7 +5,7 @@ import type { AppConfig } from '../config';
 export interface EmbeddingRequest {
 export interface EmbeddingRequest {
   text: string;
   text: string;
   contentId: string;
   contentId: string;
-  shopId: string;
+  siteId: string;
   contentType: 'shipping' | 'contacts' | 'terms' | 'faq';
   contentType: 'shipping' | 'contacts' | 'terms' | 'faq';
 }
 }
 
 

+ 16 - 16
src/services/QdrantCleanupService.ts

@@ -1,7 +1,7 @@
 import { v4 as uuidv4 } from 'uuid';
 import { v4 as uuidv4 } from 'uuid';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
 import type { AppConfig } from '../config';
 import type { AppConfig } from '../config';
-import { ShopDatabase, QdrantDeletionQueue, QdrantError } from '../database/Database';
+import { SiteDatabase, QdrantDeletionQueue, QdrantError } from '../database/Database';
 import { QdrantService } from './QdrantService';
 import { QdrantService } from './QdrantService';
 
 
 export interface CleanupStats {
 export interface CleanupStats {
@@ -17,7 +17,7 @@ export class QdrantCleanupService {
 
 
   constructor(
   constructor(
     private config: AppConfig,
     private config: AppConfig,
-    private database: ShopDatabase
+    private database: SiteDatabase
   ) {
   ) {
     this.qdrantService = new QdrantService(config);
     this.qdrantService = new QdrantService(config);
     this.deletionDelayHours = config.qdrant.deletionDelayHours;
     this.deletionDelayHours = config.qdrant.deletionDelayHours;
@@ -120,12 +120,12 @@ export class QdrantCleanupService {
    * Schedule collections for deletion after delay period
    * Schedule collections for deletion after delay period
    */
    */
   async scheduleCollectionsDeletion(
   async scheduleCollectionsDeletion(
-    shopId: string,
+    siteId: string,
     collectionNames: string[],
     collectionNames: string[],
     reason: 'shop_deleted' | 'qdrant_disabled'
     reason: 'shop_deleted' | 'qdrant_disabled'
   ): Promise<void> {
   ): Promise<void> {
     if (collectionNames.length === 0) {
     if (collectionNames.length === 0) {
-      logger.debug(`No collections to schedule for deletion for shop ${shopId}`);
+      logger.debug(`No collections to schedule for deletion for site ${siteId}`);
       return;
       return;
     }
     }
 
 
@@ -134,7 +134,7 @@ export class QdrantCleanupService {
 
 
     // Handle immediate deletion for shop_deleted
     // Handle immediate deletion for shop_deleted
     if (reason === 'shop_deleted') {
     if (reason === 'shop_deleted') {
-      logger.info(`Immediately deleting ${collectionNames.length} collections for deleted shop ${shopId}`);
+      logger.info(`Immediately deleting ${collectionNames.length} collections for deleted site ${siteId}`);
 
 
       for (const collectionName of collectionNames) {
       for (const collectionName of collectionNames) {
         try {
         try {
@@ -150,7 +150,7 @@ export class QdrantCleanupService {
     const id = uuidv4();
     const id = uuidv4();
     this.database.createQdrantDeletionItem({
     this.database.createQdrantDeletionItem({
       id,
       id,
-      shop_id: shopId,
+      site_id: siteId,
       collection_names: JSON.stringify(collectionNames),
       collection_names: JSON.stringify(collectionNames),
       scheduled_deletion_at: scheduledDeletion.toISOString(),
       scheduled_deletion_at: scheduledDeletion.toISOString(),
       reason,
       reason,
@@ -158,17 +158,17 @@ export class QdrantCleanupService {
       created_at: now.toISOString(),
       created_at: now.toISOString(),
     });
     });
 
 
-    logger.info(`Scheduled ${collectionNames.length} collections for deletion in ${this.deletionDelayHours} hours for shop ${shopId}`);
+    logger.info(`Scheduled ${collectionNames.length} collections for deletion in ${this.deletionDelayHours} hours for site ${siteId}`);
   }
   }
 
 
   /**
   /**
    * Cancel scheduled deletion (when Qdrant is re-enabled within delay period)
    * Cancel scheduled deletion (when Qdrant is re-enabled within delay period)
    */
    */
-  async cancelScheduledDeletion(shopId: string): Promise<number> {
-    const cancelledCount = this.database.cancelQdrantDeletion(shopId);
+  async cancelScheduledDeletion(siteId: string): Promise<number> {
+    const cancelledCount = this.database.cancelQdrantDeletion(siteId);
 
 
     if (cancelledCount > 0) {
     if (cancelledCount > 0) {
-      logger.info(`Cancelled ${cancelledCount} scheduled deletions for shop ${shopId}`);
+      logger.info(`Cancelled ${cancelledCount} scheduled deletions for site ${siteId}`);
     }
     }
 
 
     return cancelledCount;
     return cancelledCount;
@@ -212,7 +212,7 @@ export class QdrantCleanupService {
    * Log Qdrant operation error
    * Log Qdrant operation error
    */
    */
   async logQdrantError(
   async logQdrantError(
-    shopId: string,
+    siteId: string,
     operationType: QdrantError['operation_type'],
     operationType: QdrantError['operation_type'],
     errorMessage: string,
     errorMessage: string,
     errorDetails?: any
     errorDetails?: any
@@ -222,7 +222,7 @@ export class QdrantCleanupService {
 
 
     this.database.createQdrantError({
     this.database.createQdrantError({
       id,
       id,
-      shop_id: shopId,
+      site_id: siteId,
       operation_type: operationType,
       operation_type: operationType,
       error_message: errorMessage,
       error_message: errorMessage,
       error_details: errorDetails ? JSON.stringify(errorDetails) : null,
       error_details: errorDetails ? JSON.stringify(errorDetails) : null,
@@ -230,7 +230,7 @@ export class QdrantCleanupService {
       resolved_at: null,
       resolved_at: null,
     });
     });
 
 
-    logger.error(`Qdrant error logged for shop ${shopId} (${operationType}): ${errorMessage}`);
+    logger.error(`Qdrant error logged for site ${siteId} (${operationType}): ${errorMessage}`);
   }
   }
 
 
   /**
   /**
@@ -243,10 +243,10 @@ export class QdrantCleanupService {
   }
   }
 
 
   /**
   /**
-   * Get unresolved errors for a shop
+   * Get unresolved errors for a site
    */
    */
-  getUnresolvedErrors(shopId: string): QdrantError[] {
-    return this.database.getUnresolvedQdrantErrors(shopId);
+  getUnresolvedErrors(siteId: string): QdrantError[] {
+    return this.database.getUnresolvedQdrantErrors(siteId);
   }
   }
 
 
   /**
   /**

+ 28 - 28
src/services/QdrantEmbeddingWorkflow.ts

@@ -1,7 +1,7 @@
 import { v4 as uuidv4 } from 'uuid';
 import { v4 as uuidv4 } from 'uuid';
 import { createHash } from 'crypto';
 import { createHash } from 'crypto';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
-import { ShopDatabase, ShopContent } from '../database/Database';
+import { SiteDatabase, SiteContent } from '../database/Database';
 import { QdrantService } from './QdrantService';
 import { QdrantService } from './QdrantService';
 import { EmbeddingService } from './EmbeddingService';
 import { EmbeddingService } from './EmbeddingService';
 import { QdrantCleanupService } from './QdrantCleanupService';
 import { QdrantCleanupService } from './QdrantCleanupService';
@@ -26,7 +26,7 @@ export class QdrantEmbeddingWorkflow {
 
 
   constructor(
   constructor(
     private config: AppConfig,
     private config: AppConfig,
-    private database: ShopDatabase
+    private database: SiteDatabase
   ) {
   ) {
     this.qdrantService = new QdrantService(config);
     this.qdrantService = new QdrantService(config);
     this.embeddingService = new EmbeddingService(config);
     this.embeddingService = new EmbeddingService(config);
@@ -51,7 +51,7 @@ export class QdrantEmbeddingWorkflow {
    * Process content for embedding after it's saved to database
    * Process content for embedding after it's saved to database
    */
    */
   async processContentForEmbedding(
   async processContentForEmbedding(
-    shopId: string,
+    siteId: string,
     contentId: string,
     contentId: string,
     contentType: 'shipping' | 'contacts' | 'terms' | 'faq',
     contentType: 'shipping' | 'contacts' | 'terms' | 'faq',
     url: string,
     url: string,
@@ -61,26 +61,26 @@ export class QdrantEmbeddingWorkflow {
     contentChanged: boolean
     contentChanged: boolean
   ): Promise<EmbeddingWorkflowResult> {
   ): Promise<EmbeddingWorkflowResult> {
     try {
     try {
-      // Get shop details
-      const shop = this.database.getShopById(shopId);
-      if (!shop) {
-        return { contentId, embedded: false, error: 'Shop not found' };
+      // Get site details
+      const site = this.database.getSiteById(siteId);
+      if (!site) {
+        return { contentId, embedded: false, error: 'Site not found' };
       }
       }
 
 
-      // Check if Qdrant is enabled for this shop
-      if (!shop.qdrant_enabled || !shop.custom_id) {
-        logger.debug(`Skipping embedding for shop ${shopId}: Qdrant disabled or no custom_id`);
+      // Check if Qdrant is enabled for this site
+      if (!site.qdrant_enabled || !site.custom_id) {
+        logger.debug(`Skipping embedding for site ${siteId}: Qdrant disabled or no custom_id`);
         return { contentId, embedded: false, error: 'Qdrant not enabled or missing custom_id' };
         return { contentId, embedded: false, error: 'Qdrant not enabled or missing custom_id' };
       }
       }
 
 
       // Check if services are available
       // Check if services are available
       if (!this.embeddingService.isEnabled()) {
       if (!this.embeddingService.isEnabled()) {
-        logger.debug(`Skipping embedding for shop ${shopId}: EmbeddingService not available`);
+        logger.debug(`Skipping embedding for site ${siteId}: EmbeddingService not available`);
         return { contentId, embedded: false, error: 'Embedding service not available' };
         return { contentId, embedded: false, error: 'Embedding service not available' };
       }
       }
 
 
-      // Generate collection name for the category (one collection per shop+category)
-      const collectionName = this.qdrantService.generateCollectionName(shop.custom_id, contentType);
+      // Generate collection name for the category (one collection per site+category)
+      const collectionName = this.qdrantService.generateCollectionName(site.custom_id, contentType);
       logger.debug(`Using collection ${collectionName} for content ${contentId} (URL: ${url})`);
       logger.debug(`Using collection ${collectionName} for content ${contentId} (URL: ${url})`);
 
 
       // Check Qdrant FIRST before generating any embeddings
       // Check Qdrant FIRST before generating any embeddings
@@ -111,12 +111,12 @@ export class QdrantEmbeddingWorkflow {
       }
       }
 
 
       // Check database embedding records
       // Check database embedding records
-      const existingEmbeddings = this.database.getShopEmbeddings(contentId);
+      const existingEmbeddings = this.database.getSiteEmbeddings(contentId);
 
 
       // Delete old embedding records from database (we'll create new ones)
       // Delete old embedding records from database (we'll create new ones)
       if (existingEmbeddings.length > 0) {
       if (existingEmbeddings.length > 0) {
         for (const emb of existingEmbeddings) {
         for (const emb of existingEmbeddings) {
-          this.database.deleteShopEmbedding(emb.id);
+          this.database.deleteSiteEmbedding(emb.id);
         }
         }
         logger.debug(`Cleared ${existingEmbeddings.length} old embedding records for content ${contentId}`);
         logger.debug(`Cleared ${existingEmbeddings.length} old embedding records for content ${contentId}`);
       }
       }
@@ -146,7 +146,7 @@ export class QdrantEmbeddingWorkflow {
           const embeddingResult = await this.embeddingService.generateEmbedding({
           const embeddingResult = await this.embeddingService.generateEmbedding({
             text: processedChunk,
             text: processedChunk,
             contentId: `${contentId}-chunk-${chunk.index}`,
             contentId: `${contentId}-chunk-${chunk.index}`,
-            shopId,
+            siteId,
             contentType
             contentType
           });
           });
 
 
@@ -164,7 +164,7 @@ export class QdrantEmbeddingWorkflow {
             id: pointId,
             id: pointId,
             vector: embeddingResult.embedding,
             vector: embeddingResult.embedding,
             payload: {
             payload: {
-              shop_id: shop.custom_id,
+              site_id: site.custom_id,
               content_id: contentId,
               content_id: contentId,
               chunk_id: `${contentId}-chunk-${chunk.index}`,
               chunk_id: `${contentId}-chunk-${chunk.index}`,
               chunk_index: chunk.index,
               chunk_index: chunk.index,
@@ -198,9 +198,9 @@ export class QdrantEmbeddingWorkflow {
         const now = new Date().toISOString();
         const now = new Date().toISOString();
         const embeddingId = uuidv4();
         const embeddingId = uuidv4();
 
 
-        this.database.createShopEmbedding({
+        this.database.createSiteEmbedding({
           id: embeddingId,
           id: embeddingId,
-          shop_content_id: contentId,
+          site_content_id: contentId,
           collection_name: collectionName,
           collection_name: collectionName,
           point_id: pointIds[0], // Store first chunk's point ID
           point_id: pointIds[0], // Store first chunk's point ID
           embedding_status: successfulChunks === chunks.length ? 'completed' : 'failed',
           embedding_status: successfulChunks === chunks.length ? 'completed' : 'failed',
@@ -214,7 +214,7 @@ export class QdrantEmbeddingWorkflow {
         logger.warn(errorMsg);
         logger.warn(errorMsg);
 
 
         await this.cleanupService.logQdrantError(
         await this.cleanupService.logQdrantError(
-          shopId,
+          siteId,
           'embedding',
           'embedding',
           errorMsg,
           errorMsg,
           { contentId, contentType, url, chunks: chunks.length, successful: successfulChunks }
           { contentId, contentType, url, chunks: chunks.length, successful: successfulChunks }
@@ -226,7 +226,7 @@ export class QdrantEmbeddingWorkflow {
       // Trigger webhook for successful embedding
       // Trigger webhook for successful embedding
       if (successfulChunks > 0) {
       if (successfulChunks > 0) {
         try {
         try {
-          await this.webhookManager.notifyQdrantEmbeddingCompleted(shopId, {
+          await this.webhookManager.notifyQdrantEmbeddingCompleted(siteId, {
             content_id: contentId,
             content_id: contentId,
             content_type: contentType,
             content_type: contentType,
             collection_name: collectionName,
             collection_name: collectionName,
@@ -249,7 +249,7 @@ export class QdrantEmbeddingWorkflow {
 
 
       // Log error for UI display
       // Log error for UI display
       await this.cleanupService.logQdrantError(
       await this.cleanupService.logQdrantError(
-        shopId,
+        siteId,
         'embedding',
         'embedding',
         error.message || 'Embedding workflow failed',
         error.message || 'Embedding workflow failed',
         { contentId, contentType, url, error: error.stack }
         { contentId, contentType, url, error: error.stack }
@@ -257,7 +257,7 @@ export class QdrantEmbeddingWorkflow {
 
 
       // Trigger webhook for failed embedding
       // Trigger webhook for failed embedding
       try {
       try {
-        await this.webhookManager.notifyQdrantEmbeddingFailed(shopId, {
+        await this.webhookManager.notifyQdrantEmbeddingFailed(siteId, {
           content_id: contentId,
           content_id: contentId,
           content_type: contentType,
           content_type: contentType,
           error: error.message || 'Embedding workflow failed'
           error: error.message || 'Embedding workflow failed'
@@ -278,7 +278,7 @@ export class QdrantEmbeddingWorkflow {
    * Process multiple content items in batch
    * Process multiple content items in batch
    */
    */
   async processContentBatch(
   async processContentBatch(
-    shopId: string,
+    siteId: string,
     contentItems: Array<{
     contentItems: Array<{
       contentId: string;
       contentId: string;
       contentType: 'shipping' | 'contacts' | 'terms' | 'faq';
       contentType: 'shipping' | 'contacts' | 'terms' | 'faq';
@@ -296,7 +296,7 @@ export class QdrantEmbeddingWorkflow {
     for (const item of contentItems) {
     for (const item of contentItems) {
       try {
       try {
         const result = await this.processContentForEmbedding(
         const result = await this.processContentForEmbedding(
-          shopId,
+          siteId,
           item.contentId,
           item.contentId,
           item.contentType,
           item.contentType,
           item.url,
           item.url,
@@ -325,7 +325,7 @@ export class QdrantEmbeddingWorkflow {
     const successCount = results.filter(r => r.embedded).length;
     const successCount = results.filter(r => r.embedded).length;
     const failCount = results.filter(r => !r.embedded).length;
     const failCount = results.filter(r => !r.embedded).length;
 
 
-    logger.info(`Batch embedding completed for shop ${shopId}: ${successCount} success, ${failCount} failed`);
+    logger.info(`Batch embedding completed for site ${siteId}: ${successCount} success, ${failCount} failed`);
 
 
     // Trigger batch completion webhook if there were successful embeddings
     // Trigger batch completion webhook if there were successful embeddings
     if (successCount > 0) {
     if (successCount > 0) {
@@ -335,7 +335,7 @@ export class QdrantEmbeddingWorkflow {
           contentTypes[item.contentType] = (contentTypes[item.contentType] || 0) + 1;
           contentTypes[item.contentType] = (contentTypes[item.contentType] || 0) + 1;
         }
         }
 
 
-        await this.webhookManager.notifyBatchEmbeddingCompleted(shopId, {
+        await this.webhookManager.notifyBatchEmbeddingCompleted(siteId, {
           total_items: contentItems.length,
           total_items: contentItems.length,
           successful_embeddings: successCount,
           successful_embeddings: successCount,
           failed_embeddings: failCount,
           failed_embeddings: failCount,
@@ -387,7 +387,7 @@ export class QdrantEmbeddingWorkflow {
     pointId?: string;
     pointId?: string;
   } {
   } {
     try {
     try {
-      const embeddings = this.database.getShopEmbeddings(contentId);
+      const embeddings = this.database.getSiteEmbeddings(contentId);
 
 
       if (embeddings.length === 0) {
       if (embeddings.length === 0) {
         return { hasEmbedding: false, status: 'none' };
         return { hasEmbedding: false, status: 'none' };

+ 19 - 19
src/services/QdrantMigrationService.ts

@@ -1,6 +1,6 @@
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
 import { QdrantService } from './QdrantService';
 import { QdrantService } from './QdrantService';
-import { ShopDatabase } from '../database/Database';
+import { SiteDatabase } from '../database/Database';
 import type { AppConfig } from '../config';
 import type { AppConfig } from '../config';
 
 
 export interface MigrationResult {
 export interface MigrationResult {
@@ -18,17 +18,17 @@ export class QdrantMigrationService {
 
 
   constructor(
   constructor(
     private config: AppConfig,
     private config: AppConfig,
-    private database: ShopDatabase
+    private database: SiteDatabase
   ) {
   ) {
     this.qdrantService = new QdrantService(config);
     this.qdrantService = new QdrantService(config);
   }
   }
 
 
   /**
   /**
-   * Migrate a shop's collections from old structure to new structure
+   * Migrate a site's collections from old structure to new structure
    * Old: {shop_custom_id}-{category}_{url_hash} (many collections per category)
    * Old: {shop_custom_id}-{category}_{url_hash} (many collections per category)
    * New: {shop_custom_id}-{category} (one collection per category)
    * New: {shop_custom_id}-{category} (one collection per category)
    */
    */
-  async migrateShop(shopCustomId: string): Promise<MigrationResult> {
+  async migrateShop(siteCustomId: string): Promise<MigrationResult> {
     const result: MigrationResult = {
     const result: MigrationResult = {
       success: true,
       success: true,
       collectionsProcessed: 0,
       collectionsProcessed: 0,
@@ -37,13 +37,13 @@ export class QdrantMigrationService {
     };
     };
 
 
     try {
     try {
-      logger.info(`Starting migration for shop: ${shopCustomId}`);
+      logger.info(`Starting migration for site: ${siteCustomId}`);
 
 
-      // Get all collections for this shop
-      const collections = await this.qdrantService.getCollectionsForShop(shopCustomId);
+      // Get all collections for this site
+      const collections = await this.qdrantService.getCollectionsForShop(siteCustomId);
 
 
       if (collections.length === 0) {
       if (collections.length === 0) {
-        logger.info(`No collections found for shop ${shopCustomId}`);
+        logger.info(`No collections found for site ${siteCustomId}`);
         return result;
         return result;
       }
       }
 
 
@@ -74,7 +74,7 @@ export class QdrantMigrationService {
       for (const [category, oldCollections] of collectionsByCategory) {
       for (const [category, oldCollections] of collectionsByCategory) {
         try {
         try {
           const migratedPoints = await this.migrateCategory(
           const migratedPoints = await this.migrateCategory(
-            shopCustomId,
+            siteCustomId,
             category,
             category,
             oldCollections
             oldCollections
           );
           );
@@ -91,10 +91,10 @@ export class QdrantMigrationService {
         }
         }
       }
       }
 
 
-      logger.info(`Migration completed for shop ${shopCustomId}: ${result.pointsMigrated} points from ${result.collectionsProcessed} collections`);
+      logger.info(`Migration completed for site ${siteCustomId}: ${result.pointsMigrated} points from ${result.collectionsProcessed} collections`);
 
 
     } catch (error: any) {
     } catch (error: any) {
-      logger.error(`Migration failed for shop ${shopCustomId}:`, error);
+      logger.error(`Migration failed for site ${siteCustomId}:`, error);
       result.success = false;
       result.success = false;
       result.errors.push(error.message || 'Unknown error');
       result.errors.push(error.message || 'Unknown error');
     }
     }
@@ -106,14 +106,14 @@ export class QdrantMigrationService {
    * Migrate a single category's collections to the new format
    * Migrate a single category's collections to the new format
    */
    */
   private async migrateCategory(
   private async migrateCategory(
-    shopCustomId: string,
+    siteCustomId: string,
     category: string,
     category: string,
     oldCollections: string[]
     oldCollections: string[]
   ): Promise<number> {
   ): Promise<number> {
     let pointsMigrated = 0;
     let pointsMigrated = 0;
 
 
     // Generate new collection name
     // Generate new collection name
-    const newCollectionName = this.qdrantService.generateCollectionName(shopCustomId, category);
+    const newCollectionName = this.qdrantService.generateCollectionName(siteCustomId, category);
 
 
     // Create new collection if it doesn't exist
     // Create new collection if it doesn't exist
     if (!(await this.qdrantService.collectionExists(newCollectionName))) {
     if (!(await this.qdrantService.collectionExists(newCollectionName))) {
@@ -206,7 +206,7 @@ export class QdrantMigrationService {
    * Delete old collections after successful migration
    * Delete old collections after successful migration
    * WARNING: This will permanently delete the old collections!
    * WARNING: This will permanently delete the old collections!
    */
    */
-  async cleanupOldCollections(shopCustomId: string, dryRun: boolean = true): Promise<{
+  async cleanupOldCollections(siteCustomId: string, dryRun: boolean = true): Promise<{
     deleted: string[];
     deleted: string[];
     kept: string[];
     kept: string[];
     errors: string[];
     errors: string[];
@@ -218,7 +218,7 @@ export class QdrantMigrationService {
     };
     };
 
 
     try {
     try {
-      const collections = await this.qdrantService.getCollectionsForShop(shopCustomId);
+      const collections = await this.qdrantService.getCollectionsForShop(siteCustomId);
 
 
       for (const collectionName of collections) {
       for (const collectionName of collections) {
         const parsed = this.qdrantService.parseCollectionName(collectionName);
         const parsed = this.qdrantService.parseCollectionName(collectionName);
@@ -250,7 +250,7 @@ export class QdrantMigrationService {
       }
       }
 
 
     } catch (error: any) {
     } catch (error: any) {
-      logger.error(`Failed to cleanup old collections for shop ${shopCustomId}:`, error);
+      logger.error(`Failed to cleanup old collections for site ${siteCustomId}:`, error);
       result.errors.push(error.message || 'Unknown error');
       result.errors.push(error.message || 'Unknown error');
     }
     }
 
 
@@ -258,9 +258,9 @@ export class QdrantMigrationService {
   }
   }
 
 
   /**
   /**
-   * Get migration status for a shop
+   * Get migration status for a site
    */
    */
-  async getMigrationStatus(shopCustomId: string): Promise<{
+  async getMigrationStatus(siteCustomId: string): Promise<{
     needsMigration: boolean;
     needsMigration: boolean;
     oldCollections: number;
     oldCollections: number;
     newCollections: number;
     newCollections: number;
@@ -270,7 +270,7 @@ export class QdrantMigrationService {
       newCollectionExists: boolean;
       newCollectionExists: boolean;
     }[];
     }[];
   }> {
   }> {
-    const collections = await this.qdrantService.getCollectionsForShop(shopCustomId);
+    const collections = await this.qdrantService.getCollectionsForShop(siteCustomId);
 
 
     const categoriesMap = new Map<string, {
     const categoriesMap = new Map<string, {
       oldCollections: number;
       oldCollections: number;

+ 31 - 31
src/services/QdrantService.ts

@@ -7,7 +7,7 @@ export interface QdrantPoint {
   id: string;
   id: string;
   vector: number[];
   vector: number[];
   payload: {
   payload: {
-    shop_id: string;
+    site_id: string;
     content_id: string;
     content_id: string;
     content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
     content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
     url: string;
     url: string;
@@ -66,24 +66,24 @@ export class QdrantService {
    * Generate collection name using the format: {shop_custom_id}-{category_name}
    * Generate collection name using the format: {shop_custom_id}-{category_name}
    * One collection per category, with unique points per URL based on content hash
    * One collection per category, with unique points per URL based on content hash
    */
    */
-  generateCollectionName(shopCustomId: string, category: string, url?: string): string {
+  generateCollectionName(siteCustomId: string, category: string, url?: string): string {
     // URL parameter kept for backward compatibility but no longer used
     // URL parameter kept for backward compatibility but no longer used
-    return `${shopCustomId}-${category}`;
+    return `${siteCustomId}-${category}`;
   }
   }
 
 
   /**
   /**
-   * Parse collection name to extract shop custom ID and category
+   * Parse collection name to extract site custom ID and category
    * Supports multiple formats:
    * Supports multiple formats:
    * - Current: {shop_custom_id}-{category}
    * - Current: {shop_custom_id}-{category}
    * - Legacy with URL hash: {shop_custom_id}-{category}_{url_hash}
    * - Legacy with URL hash: {shop_custom_id}-{category}_{url_hash}
    * - Legacy with iterator: {shop_custom_id}-{category}_{iterator}
    * - Legacy with iterator: {shop_custom_id}-{category}_{iterator}
    */
    */
-  parseCollectionName(collectionName: string): { shopCustomId: string; category: string; urlHash?: string; iterator?: number } | null {
+  parseCollectionName(collectionName: string): { siteCustomId: string; category: string; urlHash?: string; iterator?: number } | null {
     // Try new format first: {shop_custom_id}-{category}
     // Try new format first: {shop_custom_id}-{category}
     const simpleMatch = collectionName.match(/^(.+)-([^_]+)$/);
     const simpleMatch = collectionName.match(/^(.+)-([^_]+)$/);
     if (simpleMatch) {
     if (simpleMatch) {
       return {
       return {
-        shopCustomId: simpleMatch[1],
+        siteCustomId: simpleMatch[1],
         category: simpleMatch[2]
         category: simpleMatch[2]
       };
       };
     }
     }
@@ -99,7 +99,7 @@ export class QdrantService {
     if (!isHash && !isIterator) return null;
     if (!isHash && !isIterator) return null;
 
 
     return {
     return {
-      shopCustomId: legacyMatch[1],
+      siteCustomId: legacyMatch[1],
       category: legacyMatch[2],
       category: legacyMatch[2],
       ...(isHash ? { urlHash: identifier } : { iterator: parseInt(identifier, 10) })
       ...(isHash ? { urlHash: identifier } : { iterator: parseInt(identifier, 10) })
     };
     };
@@ -493,11 +493,11 @@ export class QdrantService {
 
 
   /**
   /**
    * Search in a category collection
    * Search in a category collection
-   * Searches in the single collection for the shop+category combination.
+   * Searches in the single collection for the site+category combination.
    * Falls back to searching in legacy multi-collection format if needed.
    * Falls back to searching in legacy multi-collection format if needed.
    */
    */
   async searchInCategory(
   async searchInCategory(
-    shopCustomId: string,
+    siteCustomId: string,
     category: string,
     category: string,
     queryVector: number[],
     queryVector: number[],
     limit: number = 10
     limit: number = 10
@@ -506,7 +506,7 @@ export class QdrantService {
 
 
     try {
     try {
       // Try new single-collection format first
       // Try new single-collection format first
-      const newCollectionName = this.generateCollectionName(shopCustomId, category);
+      const newCollectionName = this.generateCollectionName(siteCustomId, category);
       const newCollectionExists = await this.collectionExists(newCollectionName);
       const newCollectionExists = await this.collectionExists(newCollectionName);
 
 
       if (newCollectionExists) {
       if (newCollectionExists) {
@@ -516,11 +516,11 @@ export class QdrantService {
       }
       }
 
 
       // Fallback: Search in legacy multi-collection format
       // Fallback: Search in legacy multi-collection format
-      logger.debug(`Using legacy multi-collection search for shop ${shopCustomId} category ${category}`);
-      const legacyCollections = await this.getCollectionsForCategory(shopCustomId, category);
+      logger.debug(`Using legacy multi-collection search for site ${siteCustomId} category ${category}`);
+      const legacyCollections = await this.getCollectionsForCategory(siteCustomId, category);
 
 
       if (legacyCollections.length === 0) {
       if (legacyCollections.length === 0) {
-        logger.debug(`No collections found for shop ${shopCustomId} category ${category}`);
+        logger.debug(`No collections found for site ${siteCustomId} category ${category}`);
         return [];
         return [];
       }
       }
 
 
@@ -541,15 +541,15 @@ export class QdrantService {
       return allResults.slice(0, limit);
       return allResults.slice(0, limit);
 
 
     } catch (error) {
     } catch (error) {
-      logger.error(`Failed to search in category ${category} for shop ${shopCustomId}:`, error);
+      logger.error(`Failed to search in category ${category} for site ${siteCustomId}:`, error);
       throw error;
       throw error;
     }
     }
   }
   }
 
 
   /**
   /**
-   * Get all collection names for a shop and category
+   * Get all collection names for a site and category
    */
    */
-  async getCollectionsForCategory(shopCustomId: string, category: string): Promise<string[]> {
+  async getCollectionsForCategory(siteCustomId: string, category: string): Promise<string[]> {
     if (!this.enabled) return [];
     if (!this.enabled) return [];
 
 
     try {
     try {
@@ -561,50 +561,50 @@ export class QdrantService {
         .map(col => col.name)
         .map(col => col.name)
         .filter(name => {
         .filter(name => {
           const parsed = this.parseCollectionName(name);
           const parsed = this.parseCollectionName(name);
-          return parsed && parsed.shopCustomId === shopCustomId && parsed.category === category;
+          return parsed && parsed.siteCustomId === siteCustomId && parsed.category === category;
         })
         })
         .sort(); // Sort to ensure consistent ordering
         .sort(); // Sort to ensure consistent ordering
 
 
       return matchingCollections;
       return matchingCollections;
     } catch (error) {
     } catch (error) {
-      logger.error(`Failed to get collections for shop ${shopCustomId} category ${category}:`, error);
+      logger.error(`Failed to get collections for site ${siteCustomId} category ${category}:`, error);
       throw error;
       throw error;
     }
     }
   }
   }
 
 
   /**
   /**
-   * Get all collection names for a shop
+   * Get all collection names for a site
    */
    */
-  async getCollectionsForShop(shopCustomId: string): Promise<string[]> {
+  async getCollectionsForShop(siteCustomId: string): Promise<string[]> {
     if (!this.enabled) return [];
     if (!this.enabled) return [];
 
 
     try {
     try {
       const response = await this.client.getCollections();
       const response = await this.client.getCollections();
       const collections = response.collections || [];
       const collections = response.collections || [];
 
 
-      // Filter collections that match the shop
+      // Filter collections that match the site
       const matchingCollections = collections
       const matchingCollections = collections
         .map(col => col.name)
         .map(col => col.name)
         .filter(name => {
         .filter(name => {
           const parsed = this.parseCollectionName(name);
           const parsed = this.parseCollectionName(name);
-          return parsed && parsed.shopCustomId === shopCustomId;
+          return parsed && parsed.siteCustomId === siteCustomId;
         });
         });
 
 
       return matchingCollections;
       return matchingCollections;
     } catch (error) {
     } catch (error) {
-      logger.error(`Failed to get collections for shop ${shopCustomId}:`, error);
+      logger.error(`Failed to get collections for site ${siteCustomId}:`, error);
       throw error;
       throw error;
     }
     }
   }
   }
 
 
   /**
   /**
-   * Delete all collections for a shop
+   * Delete all collections for a site
    */
    */
-  async deleteShopCollections(shopCustomId: string): Promise<void> {
+  async deleteShopCollections(siteCustomId: string): Promise<void> {
     if (!this.enabled) return;
     if (!this.enabled) return;
 
 
     try {
     try {
-      const collections = await this.getCollectionsForShop(shopCustomId);
+      const collections = await this.getCollectionsForShop(siteCustomId);
 
 
       for (const collectionName of collections) {
       for (const collectionName of collections) {
         try {
         try {
@@ -614,9 +614,9 @@ export class QdrantService {
         }
         }
       }
       }
 
 
-      logger.info(`Deleted ${collections.length} collections for shop ${shopCustomId}`);
+      logger.info(`Deleted ${collections.length} collections for site ${siteCustomId}`);
     } catch (error) {
     } catch (error) {
-      logger.error(`Failed to delete collections for shop ${shopCustomId}:`, error);
+      logger.error(`Failed to delete collections for site ${siteCustomId}:`, error);
       throw error;
       throw error;
     }
     }
   }
   }
@@ -625,11 +625,11 @@ export class QdrantService {
    * Get next iterator number for a category
    * Get next iterator number for a category
    * @deprecated This method is deprecated. Collection names now use URL hashes instead of iterators.
    * @deprecated This method is deprecated. Collection names now use URL hashes instead of iterators.
    */
    */
-  async getNextIterator(shopCustomId: string, category: string): Promise<number> {
+  async getNextIterator(siteCustomId: string, category: string): Promise<number> {
     if (!this.enabled) return 0;
     if (!this.enabled) return 0;
 
 
     try {
     try {
-      const collections = await this.getCollectionsForCategory(shopCustomId, category);
+      const collections = await this.getCollectionsForCategory(siteCustomId, category);
 
 
       if (collections.length === 0) return 0;
       if (collections.length === 0) return 0;
 
 
@@ -644,7 +644,7 @@ export class QdrantService {
 
 
       return maxIterator + 1;
       return maxIterator + 1;
     } catch (error) {
     } catch (error) {
-      logger.error(`Failed to get next iterator for shop ${shopCustomId} category ${category}:`, error);
+      logger.error(`Failed to get next iterator for site ${siteCustomId} category ${category}:`, error);
       throw error;
       throw error;
     }
     }
   }
   }

+ 21 - 21
src/services/SchedulerService.ts

@@ -1,5 +1,5 @@
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
-import { ShopDatabase } from '../database/Database';
+import { SiteDatabase } from '../database/Database';
 import { QdrantCleanupService } from './QdrantCleanupService';
 import { QdrantCleanupService } from './QdrantCleanupService';
 import { QdrantEmbeddingWorkflow } from './QdrantEmbeddingWorkflow';
 import { QdrantEmbeddingWorkflow } from './QdrantEmbeddingWorkflow';
 import { WebhookManager } from '../webhooks/WebhookManager';
 import { WebhookManager } from '../webhooks/WebhookManager';
@@ -40,7 +40,7 @@ export class SchedulerService {
 
 
   constructor(
   constructor(
     private appConfig: AppConfig,
     private appConfig: AppConfig,
-    private database: ShopDatabase
+    private database: SiteDatabase
   ) {
   ) {
     this.cleanupService = new QdrantCleanupService(appConfig, database);
     this.cleanupService = new QdrantCleanupService(appConfig, database);
     this.embeddingWorkflow = new QdrantEmbeddingWorkflow(appConfig, database);
     this.embeddingWorkflow = new QdrantEmbeddingWorkflow(appConfig, database);
@@ -209,10 +209,10 @@ export class SchedulerService {
         logger.warn('Health check failed:', health.errors);
         logger.warn('Health check failed:', health.errors);
 
 
         // Notify about health check failures for enabled shops
         // Notify about health check failures for enabled shops
-        const enabledShops = this.database.getQdrantEnabledShops();
-        for (const shop of enabledShops) {
+        const enabledSites = this.database.getQdrantEnabledSites();
+        for (const site of enabledSites) {
           try {
           try {
-            await this.webhookManager.notifyMcpHealthCheckFailed(shop.id, {
+            await this.webhookManager.notifyMcpHealthCheckFailed(site.id, {
               failed_services: Object.entries(health.services)
               failed_services: Object.entries(health.services)
                 .filter(([_, status]) => !status)
                 .filter(([_, status]) => !status)
                 .map(([service]) => service),
                 .map(([service]) => service),
@@ -223,7 +223,7 @@ export class SchedulerService {
               consecutive_failures: this.getConsecutiveFailures()
               consecutive_failures: this.getConsecutiveFailures()
             });
             });
           } catch (webhookError) {
           } catch (webhookError) {
-            logger.error(`Failed to send health check webhook for shop ${shop.id}:`, webhookError);
+            logger.error(`Failed to send health check webhook for site ${site.id}:`, webhookError);
           }
           }
         }
         }
 
 
@@ -253,7 +253,7 @@ export class SchedulerService {
     const startTime = Date.now();
     const startTime = Date.now();
 
 
     try {
     try {
-      const queuedDeletions = this.database.getShopsForQdrantDeletion();
+      const queuedDeletions = this.database.getSitesForQdrantDeletion();
 
 
       if (queuedDeletions.length === 0) {
       if (queuedDeletions.length === 0) {
         logger.debug('No shops queued for Qdrant deletion');
         logger.debug('No shops queued for Qdrant deletion');
@@ -264,24 +264,24 @@ export class SchedulerService {
 
 
       for (const deletion of queuedDeletions) {
       for (const deletion of queuedDeletions) {
         try {
         try {
-          // Get the shop to access the custom_id
-          const shop = this.database.getShopById(deletion.shop_id);
-          if (!shop || !shop.custom_id) {
-            logger.warn(`Cannot process deletion for shop ${deletion.shop_id}: shop not found or no custom_id`);
-            this.database.updateQdrantDeletionStatus(deletion.id, 'failed', 'Shop not found or no custom_id');
+          // Get the site to access the custom_id
+          const site = this.database.getSiteById(deletion.site_id);
+          if (!site || !site.custom_id) {
+            logger.warn(`Cannot process deletion for site ${deletion.site_id}: site not found or no custom_id`);
+            this.database.updateQdrantDeletionStatus(deletion.id, 'failed', 'Site not found or no custom_id');
             continue;
             continue;
           }
           }
 
 
-          // Delete shop collections using QdrantService via cleanupService
+          // Delete site collections using QdrantService via cleanupService
           try {
           try {
-            // Get collections for the shop first
+            // Get collections for the site first
             const collections = JSON.parse(deletion.collection_names || '[]');
             const collections = JSON.parse(deletion.collection_names || '[]');
             if (collections.length > 0) {
             if (collections.length > 0) {
               // Call the private qdrantService through the cleanup service
               // Call the private qdrantService through the cleanup service
-              await this.cleanupService.scheduleCollectionsDeletion(deletion.shop_id, collections, 'shop_deleted');
+              await this.cleanupService.scheduleCollectionsDeletion(deletion.site_id, collections, 'shop_deleted');
             }
             }
           } catch (error) {
           } catch (error) {
-            logger.error(`Failed to delete collections for shop ${shop.custom_id}:`, error);
+            logger.error(`Failed to delete collections for site ${site.custom_id}:`, error);
           }
           }
 
 
           // Mark as completed
           // Mark as completed
@@ -289,20 +289,20 @@ export class SchedulerService {
 
 
           // Send webhook notification
           // Send webhook notification
           try {
           try {
-            await this.webhookManager.notifyQdrantShopDeleted(deletion.shop_id, {
-              custom_id: shop.custom_id,
+            await this.webhookManager.notifyQdrantShopDeleted(deletion.site_id, {
+              custom_id: site.custom_id,
               collections_deleted: [], // Would be populated by cleanup service
               collections_deleted: [], // Would be populated by cleanup service
               total_vectors_deleted: 0, // Would be populated by cleanup service
               total_vectors_deleted: 0, // Would be populated by cleanup service
               deletion_scheduled_at: deletion.scheduled_deletion_at
               deletion_scheduled_at: deletion.scheduled_deletion_at
             });
             });
           } catch (webhookError) {
           } catch (webhookError) {
-            logger.error(`Failed to send shop deleted webhook for ${deletion.shop_id}:`, webhookError);
+            logger.error(`Failed to send site deleted webhook for ${deletion.site_id}:`, webhookError);
           }
           }
 
 
-          logger.info(`Successfully processed Qdrant deletion for shop ${deletion.shop_id}`);
+          logger.info(`Successfully processed Qdrant deletion for site ${deletion.site_id}`);
 
 
         } catch (error) {
         } catch (error) {
-          logger.error(`Failed to process deletion for shop ${deletion.shop_id}:`, error);
+          logger.error(`Failed to process deletion for site ${deletion.site_id}:`, error);
           this.database.updateQdrantDeletionStatus(
           this.database.updateQdrantDeletionStatus(
             deletion.id,
             deletion.id,
             'failed',
             'failed',

+ 44 - 44
src/webhooks/WebhookManager.ts

@@ -1,6 +1,6 @@
 import axios, { AxiosError } from 'axios';
 import axios, { AxiosError } from 'axios';
 import { logger } from '../utils/logger';
 import { logger } from '../utils/logger';
-import { ShopDatabase, Webhook } from '../database/Database';
+import { SiteDatabase, Webhook } from '../database/Database';
 import crypto from 'crypto';
 import crypto from 'crypto';
 
 
 export type WebhookEvent =
 export type WebhookEvent =
@@ -21,41 +21,41 @@ export type WebhookEvent =
 export interface WebhookPayload {
 export interface WebhookPayload {
   event: WebhookEvent;
   event: WebhookEvent;
   timestamp: string;
   timestamp: string;
-  shop_id: string;
-  shop_url?: string;
+  site_id: string;
+  site_url?: string;
   data: any;
   data: any;
 }
 }
 
 
 export class WebhookManager {
 export class WebhookManager {
-  private db: ShopDatabase;
+  private db: SiteDatabase;
   private retryAttempts: number = 3;
   private retryAttempts: number = 3;
   private retryDelay: number = 1000; // 1 second
   private retryDelay: number = 1000; // 1 second
 
 
-  constructor(db: ShopDatabase) {
+  constructor(db: SiteDatabase) {
     this.db = db;
     this.db = db;
   }
   }
 
 
   /**
   /**
-   * Trigger a webhook for a specific shop and event
+   * Trigger a webhook for a specific site and event
    */
    */
   async triggerWebhook(
   async triggerWebhook(
-    shopId: string,
+    siteId: string,
     event: WebhookEvent,
     event: WebhookEvent,
     data: any
     data: any
   ): Promise<void> {
   ): Promise<void> {
-    const webhook = this.db.getWebhook(shopId);
+    const webhook = this.db.getWebhook(siteId);
 
 
     if (!webhook || !webhook.enabled) {
     if (!webhook || !webhook.enabled) {
-      logger.debug(`No active webhook found for shop ${shopId}`);
+      logger.debug(`No active webhook found for site ${siteId}`);
       return;
       return;
     }
     }
 
 
-    const shop = this.db.getShopById(shopId);
+    const site = this.db.getSiteById(siteId);
     const payload: WebhookPayload = {
     const payload: WebhookPayload = {
       event,
       event,
       timestamp: new Date().toISOString(),
       timestamp: new Date().toISOString(),
-      shop_id: shopId,
-      shop_url: shop?.url,
+      site_id: siteId,
+      site_url: site?.url,
       data
       data
     };
     };
 
 
@@ -75,7 +75,7 @@ export class WebhookManager {
 
 
       const headers: any = {
       const headers: any = {
         'Content-Type': 'application/json',
         'Content-Type': 'application/json',
-        'User-Agent': 'WebshopScraper-Webhook/1.0'
+        'User-Agent': 'WebScraper-Webhook/1.0'
       };
       };
 
 
       // Add signature header if secret is configured
       // Add signature header if secret is configured
@@ -163,11 +163,11 @@ export class WebhookManager {
    * Trigger scrape started event
    * Trigger scrape started event
    */
    */
   async notifyScrapeStarted(
   async notifyScrapeStarted(
-    shopId: string,
+    siteId: string,
     jobId: string,
     jobId: string,
     sitemapUrl: string
     sitemapUrl: string
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'scrape_started', {
+    await this.triggerWebhook(siteId, 'scrape_started', {
       job_id: jobId,
       job_id: jobId,
       sitemap_url: sitemapUrl
       sitemap_url: sitemapUrl
     });
     });
@@ -177,7 +177,7 @@ export class WebhookManager {
    * Trigger scrape completed event
    * Trigger scrape completed event
    */
    */
   async notifyScrapeCompleted(
   async notifyScrapeCompleted(
-    shopId: string,
+    siteId: string,
     jobId: string,
     jobId: string,
     result: {
     result: {
       scrape_time_ms: number;
       scrape_time_ms: number;
@@ -185,7 +185,7 @@ export class WebhookManager {
       pages_scraped: number;
       pages_scraped: number;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'scrape_completed', {
+    await this.triggerWebhook(siteId, 'scrape_completed', {
       job_id: jobId,
       job_id: jobId,
       ...result
       ...result
     });
     });
@@ -195,11 +195,11 @@ export class WebhookManager {
    * Trigger scrape failed event
    * Trigger scrape failed event
    */
    */
   async notifyScrapeFailed(
   async notifyScrapeFailed(
-    shopId: string,
+    siteId: string,
     jobId: string,
     jobId: string,
     error: string
     error: string
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'scrape_failed', {
+    await this.triggerWebhook(siteId, 'scrape_failed', {
       job_id: jobId,
       job_id: jobId,
       error
       error
     });
     });
@@ -209,13 +209,13 @@ export class WebhookManager {
    * Trigger content changed event
    * Trigger content changed event
    */
    */
   async notifyContentChanged(
   async notifyContentChanged(
-    shopId: string,
+    siteId: string,
     contentType: string,
     contentType: string,
     url: string,
     url: string,
     oldHash: string,
     oldHash: string,
     newHash: string
     newHash: string
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'content_changed', {
+    await this.triggerWebhook(siteId, 'content_changed', {
       content_type: contentType,
       content_type: contentType,
       url,
       url,
       old_hash: oldHash,
       old_hash: oldHash,
@@ -227,11 +227,11 @@ export class WebhookManager {
    * Trigger schedule created event
    * Trigger schedule created event
    */
    */
   async notifyScheduleCreated(
   async notifyScheduleCreated(
-    shopId: string,
+    siteId: string,
     nextRunAt: string,
     nextRunAt: string,
     frequency: string | null
     frequency: string | null
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'schedule_created', {
+    await this.triggerWebhook(siteId, 'schedule_created', {
       next_run_at: nextRunAt,
       next_run_at: nextRunAt,
       frequency
       frequency
     });
     });
@@ -243,7 +243,7 @@ export class WebhookManager {
    * Trigger Qdrant embedding completed event
    * Trigger Qdrant embedding completed event
    */
    */
   async notifyQdrantEmbeddingCompleted(
   async notifyQdrantEmbeddingCompleted(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       content_id: string;
       content_id: string;
       content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
       content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
@@ -252,14 +252,14 @@ export class WebhookManager {
       processing_time_ms?: number;
       processing_time_ms?: number;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'qdrant_embedding_completed', data);
+    await this.triggerWebhook(siteId, 'qdrant_embedding_completed', data);
   }
   }
 
 
   /**
   /**
    * Trigger Qdrant embedding failed event
    * Trigger Qdrant embedding failed event
    */
    */
   async notifyQdrantEmbeddingFailed(
   async notifyQdrantEmbeddingFailed(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       content_id: string;
       content_id: string;
       content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
       content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
@@ -267,14 +267,14 @@ export class WebhookManager {
       retry_count?: number;
       retry_count?: number;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'qdrant_embedding_failed', data);
+    await this.triggerWebhook(siteId, 'qdrant_embedding_failed', data);
   }
   }
 
 
   /**
   /**
    * Trigger Qdrant collection created event
    * Trigger Qdrant collection created event
    */
    */
   async notifyQdrantCollectionCreated(
   async notifyQdrantCollectionCreated(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       collection_name: string;
       collection_name: string;
       content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
       content_type: 'shipping' | 'contacts' | 'terms' | 'faq';
@@ -282,14 +282,14 @@ export class WebhookManager {
       distance_metric: string;
       distance_metric: string;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'qdrant_collection_created', data);
+    await this.triggerWebhook(siteId, 'qdrant_collection_created', data);
   }
   }
 
 
   /**
   /**
    * Trigger Qdrant collection deleted event
    * Trigger Qdrant collection deleted event
    */
    */
   async notifyQdrantCollectionDeleted(
   async notifyQdrantCollectionDeleted(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       collection_name: string;
       collection_name: string;
       content_type?: 'shipping' | 'contacts' | 'terms' | 'faq';
       content_type?: 'shipping' | 'contacts' | 'terms' | 'faq';
@@ -297,14 +297,14 @@ export class WebhookManager {
       reason: 'manual' | 'shop_deletion' | 'cleanup';
       reason: 'manual' | 'shop_deletion' | 'cleanup';
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'qdrant_collection_deleted', data);
+    await this.triggerWebhook(siteId, 'qdrant_collection_deleted', data);
   }
   }
 
 
   /**
   /**
-   * Trigger Qdrant shop deleted event (for all collections)
+   * Trigger Qdrant site deleted event (for all collections)
    */
    */
   async notifyQdrantShopDeleted(
   async notifyQdrantShopDeleted(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       custom_id: string;
       custom_id: string;
       collections_deleted: string[];
       collections_deleted: string[];
@@ -312,7 +312,7 @@ export class WebhookManager {
       deletion_scheduled_at: string;
       deletion_scheduled_at: string;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'qdrant_shop_deleted', data);
+    await this.triggerWebhook(siteId, 'qdrant_shop_deleted', data);
   }
   }
 
 
   // MCP Webhook Events
   // MCP Webhook Events
@@ -321,7 +321,7 @@ export class WebhookManager {
    * Trigger MCP tool called event
    * Trigger MCP tool called event
    */
    */
   async notifyMcpToolCalled(
   async notifyMcpToolCalled(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       tool_name: string;
       tool_name: string;
       query: string;
       query: string;
@@ -330,14 +330,14 @@ export class WebhookManager {
       success: boolean;
       success: boolean;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'mcp_tool_called', data);
+    await this.triggerWebhook(siteId, 'mcp_tool_called', data);
   }
   }
 
 
   /**
   /**
    * Trigger MCP tool failed event
    * Trigger MCP tool failed event
    */
    */
   async notifyMcpToolFailed(
   async notifyMcpToolFailed(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       tool_name: string;
       tool_name: string;
       query: string;
       query: string;
@@ -346,21 +346,21 @@ export class WebhookManager {
       retry_count?: number;
       retry_count?: number;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'mcp_tool_failed', data);
+    await this.triggerWebhook(siteId, 'mcp_tool_failed', data);
   }
   }
 
 
   /**
   /**
    * Trigger MCP health check failed event
    * Trigger MCP health check failed event
    */
    */
   async notifyMcpHealthCheckFailed(
   async notifyMcpHealthCheckFailed(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       failed_services: string[];
       failed_services: string[];
       error_details: { [service: string]: string };
       error_details: { [service: string]: string };
       consecutive_failures: number;
       consecutive_failures: number;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'mcp_health_check_failed', data);
+    await this.triggerWebhook(siteId, 'mcp_health_check_failed', data);
   }
   }
 
 
   // Batch webhook notifications
   // Batch webhook notifications
@@ -369,7 +369,7 @@ export class WebhookManager {
    * Trigger batch embedding completion
    * Trigger batch embedding completion
    */
    */
   async notifyBatchEmbeddingCompleted(
   async notifyBatchEmbeddingCompleted(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       batch_id?: string;
       batch_id?: string;
       total_items: number;
       total_items: number;
@@ -379,7 +379,7 @@ export class WebhookManager {
       content_types: { [type: string]: number };
       content_types: { [type: string]: number };
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'qdrant_embedding_completed', {
+    await this.triggerWebhook(siteId, 'qdrant_embedding_completed', {
       ...data,
       ...data,
       batch_operation: true
       batch_operation: true
     });
     });
@@ -389,7 +389,7 @@ export class WebhookManager {
    * Notify high frequency MCP usage (rate limiting alert)
    * Notify high frequency MCP usage (rate limiting alert)
    */
    */
   async notifyHighMcpUsage(
   async notifyHighMcpUsage(
-    shopId: string,
+    siteId: string,
     data: {
     data: {
       calls_per_minute: number;
       calls_per_minute: number;
       threshold: number;
       threshold: number;
@@ -397,7 +397,7 @@ export class WebhookManager {
       top_tools: Array<{ tool_name: string; count: number }>;
       top_tools: Array<{ tool_name: string; count: number }>;
     }
     }
   ): Promise<void> {
   ): Promise<void> {
-    await this.triggerWebhook(shopId, 'mcp_tool_called', {
+    await this.triggerWebhook(siteId, 'mcp_tool_called', {
       ...data,
       ...data,
       rate_limit_alert: true
       rate_limit_alert: true
     });
     });

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů