Add Dutchie AZ data pipeline and public API v1

- Add dutchie-az module with GraphQL product crawler, scheduler, and admin UI
- Add public API v1 endpoints (/api/v1/products, /categories, /brands, /specials, /menu)
- API key auth maps dispensary to dutchie_az store for per-dispensary data access
- Add frontend pages for Dutchie AZ stores, store details, and schedule management
- Update Layout with Dutchie AZ navigation section

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-02 09:43:26 -07:00
parent 511629b4e6
commit 917e91297e
22 changed files with 8201 additions and 45 deletions

View File

@@ -0,0 +1,78 @@
/**
* Dutchie AZ Database Connection
*
* Isolated database connection for Dutchie Arizona data.
* Uses a separate database/schema to prevent cross-contamination with main app data.
*/
import { Pool, PoolClient } from 'pg';
// Environment variable for Dutchie AZ database (falls back to main DB with schema prefix)
const DUTCHIE_AZ_DATABASE_URL =
process.env.DUTCHIE_AZ_DATABASE_URL ||
process.env.DATABASE_URL ||
'postgresql://dutchie:dutchie_local_pass@localhost:54320/dutchie_az';
let pool: Pool | null = null;
/**
* Get the Dutchie AZ database pool (singleton)
*/
export function getDutchieAZPool(): Pool {
if (!pool) {
pool = new Pool({
connectionString: DUTCHIE_AZ_DATABASE_URL,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
pool.on('error', (err) => {
console.error('[DutchieAZ DB] Unexpected error on idle client:', err);
});
console.log('[DutchieAZ DB] Pool initialized');
}
return pool;
}
/**
* Execute a query on the Dutchie AZ database
*/
export async function query<T = any>(text: string, params?: any[]): Promise<{ rows: T[]; rowCount: number }> {
const p = getDutchieAZPool();
const result = await p.query(text, params);
return { rows: result.rows as T[], rowCount: result.rowCount || 0 };
}
/**
* Get a client from the pool for transaction use
*/
export async function getClient(): Promise<PoolClient> {
const p = getDutchieAZPool();
return p.connect();
}
/**
* Close the pool connection
*/
export async function closePool(): Promise<void> {
if (pool) {
await pool.end();
pool = null;
console.log('[DutchieAZ DB] Pool closed');
}
}
/**
* Check if the database is accessible
*/
export async function healthCheck(): Promise<boolean> {
try {
const result = await query('SELECT 1 as ok');
return result.rows.length > 0 && result.rows[0].ok === 1;
} catch (error) {
console.error('[DutchieAZ DB] Health check failed:', error);
return false;
}
}