|
|
@@ -614,6 +614,222 @@ export function createRouter(jobQueue: JobQueue, db?: ShopDatabase): Router {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
+ /**
|
|
|
+ * POST /shops/:id/custom-urls - Add a custom URL to scrape for a shop
|
|
|
+ */
|
|
|
+ router.post('/shops/:id/custom-urls', (req: Request, res: Response): void => {
|
|
|
+ try {
|
|
|
+ if (!db) {
|
|
|
+ res.status(503).json({ error: 'Database not available' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { id } = req.params;
|
|
|
+ const { url, content_type } = req.body;
|
|
|
+
|
|
|
+ // Validate required fields
|
|
|
+ if (!url || !content_type) {
|
|
|
+ res.status(400).json({ error: 'url and content_type fields are required' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate content_type
|
|
|
+ const validContentTypes = ['shipping', 'contacts', 'terms', 'faq'];
|
|
|
+ if (!validContentTypes.includes(content_type)) {
|
|
|
+ res.status(400).json({
|
|
|
+ error: 'content_type must be one of: shipping, contacts, terms, faq'
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate URL format
|
|
|
+ let parsedUrl: URL;
|
|
|
+ try {
|
|
|
+ parsedUrl = new URL(url);
|
|
|
+ } catch (error) {
|
|
|
+ res.status(400).json({ error: 'Invalid URL format' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if shop exists
|
|
|
+ const shop = db.getShopById(id);
|
|
|
+ if (!shop) {
|
|
|
+ res.status(404).json({ error: 'Shop not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate that the URL belongs to the same domain as the shop
|
|
|
+ const shopUrl = new URL(shop.url);
|
|
|
+ if (parsedUrl.hostname !== shopUrl.hostname) {
|
|
|
+ res.status(400).json({
|
|
|
+ error: `URL must belong to the same domain as the shop (${shopUrl.hostname})`
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if URL was already scraped
|
|
|
+ const alreadyScraped = db.isUrlAlreadyScraped(id, url);
|
|
|
+ if (alreadyScraped) {
|
|
|
+ res.status(409).json({
|
|
|
+ error: 'This URL has already been scraped from the sitemap',
|
|
|
+ message: 'URL already exists in scraped content'
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create custom URL
|
|
|
+ try {
|
|
|
+ const customUrl = db.createCustomUrl(id, url, content_type);
|
|
|
+
|
|
|
+ res.status(201).json({
|
|
|
+ message: 'Custom URL added successfully',
|
|
|
+ custom_url: {
|
|
|
+ id: customUrl.id,
|
|
|
+ shop_id: customUrl.shop_id,
|
|
|
+ url: customUrl.url,
|
|
|
+ content_type: customUrl.content_type,
|
|
|
+ enabled: customUrl.enabled,
|
|
|
+ created_at: customUrl.created_at
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (error: any) {
|
|
|
+ if (error.message?.includes('already been added')) {
|
|
|
+ res.status(409).json({ error: error.message });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Error creating custom URL', error);
|
|
|
+ res.status(500).json({ error: 'Internal server error' });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * GET /shops/:id/custom-urls - List all custom URLs for a shop
|
|
|
+ */
|
|
|
+ router.get('/shops/:id/custom-urls', (req: Request, res: Response): void => {
|
|
|
+ try {
|
|
|
+ if (!db) {
|
|
|
+ res.status(503).json({ error: 'Database not available' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { id } = req.params;
|
|
|
+ const shop = db.getShopById(id);
|
|
|
+
|
|
|
+ if (!shop) {
|
|
|
+ res.status(404).json({ error: 'Shop not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const customUrls = db.getCustomUrls(id);
|
|
|
+
|
|
|
+ res.json({
|
|
|
+ shop_id: id,
|
|
|
+ custom_urls: customUrls.map(cu => ({
|
|
|
+ id: cu.id,
|
|
|
+ url: cu.url,
|
|
|
+ content_type: cu.content_type,
|
|
|
+ enabled: cu.enabled,
|
|
|
+ created_at: cu.created_at
|
|
|
+ }))
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Error fetching custom URLs', error);
|
|
|
+ res.status(500).json({ error: 'Internal server error' });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * PATCH /shops/:id/custom-urls/:customUrlId - Enable or disable a custom URL
|
|
|
+ */
|
|
|
+ router.patch('/shops/:id/custom-urls/:customUrlId', (req: Request, res: Response): void => {
|
|
|
+ try {
|
|
|
+ if (!db) {
|
|
|
+ res.status(503).json({ error: 'Database not available' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { id, customUrlId } = req.params;
|
|
|
+ const { enabled } = req.body;
|
|
|
+
|
|
|
+ if (typeof enabled !== 'boolean') {
|
|
|
+ res.status(400).json({ error: 'enabled field must be a boolean' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const shop = db.getShopById(id);
|
|
|
+ if (!shop) {
|
|
|
+ res.status(404).json({ error: 'Shop not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const customUrl = db.getCustomUrlById(customUrlId);
|
|
|
+ if (!customUrl) {
|
|
|
+ res.status(404).json({ error: 'Custom URL not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (customUrl.shop_id !== id) {
|
|
|
+ res.status(403).json({ error: 'Custom URL does not belong to this shop' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ db.setCustomUrlEnabled(customUrlId, enabled);
|
|
|
+
|
|
|
+ res.json({
|
|
|
+ message: `Custom URL ${enabled ? 'enabled' : 'disabled'} successfully`,
|
|
|
+ custom_url_id: customUrlId,
|
|
|
+ enabled
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Error updating custom URL', error);
|
|
|
+ res.status(500).json({ error: 'Internal server error' });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * DELETE /shops/:id/custom-urls/:customUrlId - Delete a custom URL
|
|
|
+ */
|
|
|
+ router.delete('/shops/:id/custom-urls/:customUrlId', (req: Request, res: Response): void => {
|
|
|
+ try {
|
|
|
+ if (!db) {
|
|
|
+ res.status(503).json({ error: 'Database not available' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { id, customUrlId } = req.params;
|
|
|
+
|
|
|
+ const shop = db.getShopById(id);
|
|
|
+ if (!shop) {
|
|
|
+ res.status(404).json({ error: 'Shop not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const customUrl = db.getCustomUrlById(customUrlId);
|
|
|
+ if (!customUrl) {
|
|
|
+ res.status(404).json({ error: 'Custom URL not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (customUrl.shop_id !== id) {
|
|
|
+ res.status(403).json({ error: 'Custom URL does not belong to this shop' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ db.deleteCustomUrl(customUrlId);
|
|
|
+
|
|
|
+ res.json({
|
|
|
+ message: 'Custom URL deleted successfully',
|
|
|
+ custom_url_id: customUrlId
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Error deleting custom URL', error);
|
|
|
+ res.status(500).json({ error: 'Internal server error' });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
/**
|
|
|
* GET /health - Health check endpoint
|
|
|
*/
|