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:
Kelly
2025-12-09 00:05:34 -07:00
parent 9711d594db
commit 2f483b3084
83 changed files with 16700 additions and 1277 deletions

View File

@@ -30,8 +30,8 @@ import {
discoverState,
getDiscoveryStats,
seedKnownCities,
ARIZONA_CITIES,
} from '../discovery';
import { getCitiesForState } from '../discovery/location-discovery';
// Parse command line arguments
function parseArgs() {
@@ -204,16 +204,22 @@ async function main() {
process.exit(1);
}
let cities: any[] = [];
if (stateCode.toUpperCase() === 'AZ') {
cities = ARIZONA_CITIES;
} else {
console.error(`No predefined cities for state: ${stateCode}`);
console.error('Add cities to city-discovery.ts ARIZONA_CITIES array (or add new state arrays)');
// Dynamically fetch cities from Dutchie
console.log(`\nFetching cities for ${stateCode} from Dutchie...\n`);
const cityNames = await getCitiesForState(stateCode.toUpperCase());
if (cityNames.length === 0) {
console.error(`No cities found for state: ${stateCode}`);
process.exit(1);
}
console.log(`\nSeeding ${cities.length} cities for ${stateCode}...\n`);
const cities = cityNames.map(name => ({
name,
slug: name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''),
stateCode: stateCode.toUpperCase(),
}));
console.log(`Seeding ${cities.length} cities for ${stateCode}...\n`);
const result = await seedKnownCities(pool, cities);
console.log(`Created: ${result.created} new cities`);
console.log(`Updated: ${result.updated} existing cities`);