fix: Add missing SEO pages list and sync endpoints

- GET /api/seo/pages - List all SEO pages with filters
- POST /api/seo/sync-state-pages - Create pages for states with dispensaries

🤖 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-07 22:51:35 -07:00
parent 3bc0effa33
commit 7a1835778b

View File

@@ -121,6 +121,130 @@ router.post('/validate', async (req: Request, res: Response) => {
}
});
/**
* GET /api/seo/pages - List all SEO pages with optional filters
*/
router.get('/pages', authMiddleware, async (req: Request, res: Response) => {
try {
const { type, search } = req.query;
const pool = getPool();
let query = `
SELECT p.id, p.type, p.slug, p.page_key, p.primary_keyword,
p.status, p.last_generated_at, p.last_reviewed_at,
p.created_at, p.updated_at
FROM seo_pages p
WHERE 1=1
`;
const params: any[] = [];
if (type && typeof type === 'string') {
params.push(type);
query += ` AND p.type = $${params.length}`;
}
if (search && typeof search === 'string') {
params.push(`%${search}%`);
query += ` AND (p.slug ILIKE $${params.length} OR p.page_key ILIKE $${params.length} OR p.primary_keyword ILIKE $${params.length})`;
}
query += ` ORDER BY p.type, p.slug`;
const result = await pool.query(query, params);
// Get metrics for state pages
const pages = await Promise.all(result.rows.map(async (page) => {
let metrics = null;
if (page.type === 'state') {
const stateCode = page.slug.replace('dispensaries-', '').toUpperCase();
const metricsResult = await pool.query(`
SELECT COUNT(DISTINCT d.id) as dispensary_count,
COUNT(DISTINCT p.id) as product_count,
COUNT(DISTINCT p.brand_name) as brand_count
FROM dispensaries d
LEFT JOIN dutchie_products p ON p.dispensary_id = d.id
WHERE d.state = $1
`, [stateCode]);
const m = metricsResult.rows[0];
metrics = {
dispensaryCount: parseInt(m.dispensary_count, 10) || 0,
productCount: parseInt(m.product_count, 10) || 0,
brandCount: parseInt(m.brand_count, 10) || 0,
};
}
return {
id: page.id,
type: page.type,
slug: page.slug,
pageKey: page.page_key,
primaryKeyword: page.primary_keyword,
status: page.status,
lastGeneratedAt: page.last_generated_at,
lastReviewedAt: page.last_reviewed_at,
metrics,
};
}));
res.json({ pages });
} catch (error: any) {
console.error('[SEO] Error listing pages:', error.message);
res.status(500).json({ error: 'Failed to list SEO pages' });
}
});
/**
* POST /api/seo/sync-state-pages - Create SEO pages for all states with dispensaries
*/
router.post('/sync-state-pages', authMiddleware, async (req: Request, res: Response) => {
try {
const pool = getPool();
// Get all states that have dispensaries
const statesResult = await pool.query(`
SELECT DISTINCT state, COUNT(*) as dispensary_count
FROM dispensaries
WHERE state IS NOT NULL AND state != ''
GROUP BY state
HAVING COUNT(*) > 0
ORDER BY state
`);
const states = statesResult.rows;
let created = 0;
let updated = 0;
for (const { state, dispensary_count } of states) {
const slug = `dispensaries-${state.toLowerCase()}`;
const pageKey = `state-${state.toLowerCase()}`;
const primaryKeyword = `${state} dispensaries`;
const result = await pool.query(`
INSERT INTO seo_pages (type, slug, page_key, primary_keyword, status, created_at, updated_at)
VALUES ('state', $1, $2, $3, 'pending_generation', NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET
updated_at = NOW()
RETURNING (xmax = 0) as is_new
`, [slug, pageKey, primaryKeyword]);
if (result.rows[0]?.is_new) {
created++;
} else {
updated++;
}
}
res.json({
message: `Synced ${states.length} state pages`,
created,
updated,
states: states.map(s => s.state),
});
} catch (error: any) {
console.error('[SEO] Error syncing state pages:', error.message);
res.status(500).json({ error: 'Failed to sync state pages' });
}
});
/**
* GET /api/seo/state/:stateCode - State SEO data with metrics
*/