Files
cannaiq/backend/src/routes/api-permissions.ts
Kelly 2f483b3084 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>
2025-12-09 00:05:34 -07:00

192 lines
5.7 KiB
TypeScript

import { Router } from 'express';
import { authMiddleware, requireRole } from '../auth/middleware';
import { pool } from '../db/pool';
import crypto from 'crypto';
const router = Router();
router.use(authMiddleware);
// Generate secure random API key (64-character hex)
function generateApiKey(): string {
return crypto.randomBytes(32).toString('hex');
}
// Get all API permissions
router.get('/', requireRole('superadmin', 'admin'), async (req, res) => {
try {
const result = await pool.query(`
SELECT *
FROM wp_dutchie_api_permissions
ORDER BY created_at DESC
`);
res.json({ permissions: result.rows });
} catch (error) {
console.error('Error fetching API permissions:', error);
res.status(500).json({ error: 'Failed to fetch API permissions' });
}
});
// Get all dispensaries for dropdown (must be before /:id to avoid route conflict)
router.get('/dispensaries', requireRole('superadmin', 'admin'), async (req, res) => {
try {
const result = await pool.query(`
SELECT id, name
FROM dispensaries
ORDER BY name
`);
res.json({ dispensaries: result.rows });
} catch (error) {
console.error('Error fetching dispensaries:', error);
res.status(500).json({ error: 'Failed to fetch dispensaries' });
}
});
// Get single API permission
router.get('/:id', requireRole('superadmin', 'admin'), async (req, res) => {
try {
const { id } = req.params;
const result = await pool.query(`
SELECT *
FROM wp_dutchie_api_permissions
WHERE id = $1
`, [id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Permission not found' });
}
res.json({ permission: result.rows[0] });
} catch (error) {
console.error('Error fetching API permission:', error);
res.status(500).json({ error: 'Failed to fetch API permission' });
}
});
// Create new API permission
router.post('/', requireRole('superadmin', 'admin'), async (req, res) => {
try {
// Support both store_id (existing) and dispensary_id (for compatibility)
const { user_name, allowed_ips, allowed_domains, store_id, dispensary_id } = req.body;
const storeIdToUse = store_id || dispensary_id;
if (!user_name) {
return res.status(400).json({ error: 'User name is required' });
}
if (!storeIdToUse) {
return res.status(400).json({ error: 'Store/Dispensary is required' });
}
// Get dispensary name for display
const dispensaryResult = await pool.query('SELECT name FROM dispensaries WHERE id = $1', [storeIdToUse]);
if (dispensaryResult.rows.length === 0) {
return res.status(400).json({ error: 'Invalid store/dispensary ID' });
}
const storeName = dispensaryResult.rows[0].name;
const apiKey = generateApiKey();
const result = await pool.query(`
INSERT INTO wp_dutchie_api_permissions (
user_name,
api_key,
allowed_ips,
allowed_domains,
is_active,
store_id,
store_name
)
VALUES ($1, $2, $3, $4, 1, $5, $6)
RETURNING *
`, [
user_name,
apiKey,
allowed_ips || null,
allowed_domains || null,
storeIdToUse,
storeName
]);
res.status(201).json({
permission: result.rows[0],
message: 'API permission created successfully. Save the API key securely - it cannot be retrieved later.'
});
} catch (error) {
console.error('Error creating API permission:', error);
res.status(500).json({ error: 'Failed to create API permission' });
}
});
// Update API permission
router.put('/:id', requireRole('superadmin', 'admin'), async (req, res) => {
try {
const { id } = req.params;
const { user_name, allowed_ips, allowed_domains, is_active } = req.body;
const result = await pool.query(`
UPDATE wp_dutchie_api_permissions
SET
user_name = COALESCE($1, user_name),
allowed_ips = COALESCE($2, allowed_ips),
allowed_domains = COALESCE($3, allowed_domains),
is_active = COALESCE($4, is_active)
WHERE id = $5
RETURNING *
`, [user_name, allowed_ips, allowed_domains, is_active, id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Permission not found' });
}
res.json({ permission: result.rows[0] });
} catch (error) {
console.error('Error updating API permission:', error);
res.status(500).json({ error: 'Failed to update API permission' });
}
});
// Toggle permission active status
router.patch('/:id/toggle', requireRole('superadmin', 'admin'), async (req, res) => {
try {
const { id } = req.params;
const result = await pool.query(`
UPDATE wp_dutchie_api_permissions
SET is_active = CASE WHEN is_active = 1 THEN 0 ELSE 1 END
WHERE id = $1
RETURNING *
`, [id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Permission not found' });
}
res.json({ permission: result.rows[0] });
} catch (error) {
console.error('Error toggling API permission:', error);
res.status(500).json({ error: 'Failed to toggle API permission' });
}
});
// Delete API permission
router.delete('/:id', requireRole('superadmin'), async (req, res) => {
try {
const { id } = req.params;
const result = await pool.query('DELETE FROM wp_dutchie_api_permissions WHERE id = $1 RETURNING *', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Permission not found' });
}
res.json({ message: 'API permission deleted successfully' });
} catch (error) {
console.error('Error deleting API permission:', error);
res.status(500).json({ error: 'Failed to delete API permission' });
}
});
export default router;