feat: SEO template library, discovery pipeline, and orchestrator enhancements
## SEO Template Library - Add complete template library with 7 page types (state, city, category, brand, product, search, regeneration) - Add Template Library tab in SEO Orchestrator with accordion-based editors - Add template preview, validation, and variable injection engine - Add API endpoints: /api/seo/templates, preview, validate, generate, regenerate ## Discovery Pipeline - Add promotion.ts for discovery location validation and promotion - Add discover-all-states.ts script for multi-state discovery - Add promotion log migration (067) - Enhance discovery routes and types ## Orchestrator & Admin - Add crawl_enabled filter to stores page - Add API permissions page - Add job queue management - Add price analytics routes - Add markets and intelligence routes - Enhance dashboard and worker monitoring ## Infrastructure - Add migrations for worker definitions, SEO settings, field alignment - Add canonical pipeline for scraper v2 - Update hydration and sync orchestrator - Enhance multi-state query service 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -319,12 +319,13 @@ export function createMultiStateRoutes(pool: Pool): Router {
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* GET /api/analytics/compare/brand/:brandId
|
||||
* GET /api/analytics/compare/brand/:brandIdOrName
|
||||
* Compare a brand across multiple states
|
||||
* Accepts either numeric brand ID or brand name (URL encoded)
|
||||
*/
|
||||
router.get('/analytics/compare/brand/:brandId', async (req: Request, res: Response) => {
|
||||
router.get('/analytics/compare/brand/:brandIdOrName', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const brandId = parseInt(req.params.brandId);
|
||||
const { brandIdOrName } = req.params;
|
||||
const statesParam = req.query.states as string;
|
||||
|
||||
// Parse states - either comma-separated or get all active states
|
||||
@@ -336,7 +337,22 @@ export function createMultiStateRoutes(pool: Pool): Router {
|
||||
states = activeStates.map(s => s.code);
|
||||
}
|
||||
|
||||
const comparison = await stateService.compareBrandAcrossStates(brandId, states);
|
||||
// Check if it's a numeric ID or a brand name
|
||||
const brandId = parseInt(brandIdOrName);
|
||||
let comparison;
|
||||
|
||||
if (!isNaN(brandId)) {
|
||||
// Try by ID first
|
||||
try {
|
||||
comparison = await stateService.compareBrandAcrossStates(brandId, states);
|
||||
} catch (idErr: any) {
|
||||
// If brand ID not found, try as name
|
||||
comparison = await stateService.compareBrandByNameAcrossStates(brandIdOrName, states);
|
||||
}
|
||||
} else {
|
||||
// Use brand name directly
|
||||
comparison = await stateService.compareBrandByNameAcrossStates(decodeURIComponent(brandIdOrName), states);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user