Expose AZ data under neutral /api/az and update frontend routes

This commit is contained in:
Kelly
2025-12-02 10:19:44 -07:00
parent 62ecbed076
commit 53a0918233
6 changed files with 43 additions and 36 deletions

View File

@@ -49,9 +49,9 @@ export default function App() {
<Route path="/scraper-tools" element={<PrivateRoute><ScraperTools /></PrivateRoute>} />
<Route path="/scraper-monitor" element={<PrivateRoute><ScraperMonitor /></PrivateRoute>} />
<Route path="/scraper-schedule" element={<PrivateRoute><ScraperSchedule /></PrivateRoute>} />
<Route path="/dutchie-az-schedule" element={<PrivateRoute><DutchieAZSchedule /></PrivateRoute>} />
<Route path="/dutchie-az" element={<PrivateRoute><DutchieAZStores /></PrivateRoute>} />
<Route path="/dutchie-az/stores/:id" element={<PrivateRoute><DutchieAZStoreDetail /></PrivateRoute>} />
<Route path="/az-schedule" element={<PrivateRoute><DutchieAZSchedule /></PrivateRoute>} />
<Route path="/az" element={<PrivateRoute><DutchieAZStores /></PrivateRoute>} />
<Route path="/az/stores/:id" element={<PrivateRoute><DutchieAZStoreDetail /></PrivateRoute>} />
<Route path="/api-permissions" element={<PrivateRoute><ApiPermissions /></PrivateRoute>} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>

View File

@@ -163,18 +163,18 @@ export function Layout({ children }: LayoutProps) {
/>
</NavSection>
<NavSection title="Dutchie AZ">
<NavSection title="AZ Data">
<NavLink
to="/dutchie-az"
to="/az"
icon={<Store className="w-4 h-4" />}
label="AZ Stores"
isActive={isActive('/dutchie-az', false)}
isActive={isActive('/az', false)}
/>
<NavLink
to="/dutchie-az-schedule"
to="/az-schedule"
icon={<Calendar className="w-4 h-4" />}
label="AZ Schedule"
isActive={isActive('/dutchie-az-schedule')}
isActive={isActive('/az-schedule')}
/>
</NavSection>

View File

@@ -527,7 +527,7 @@ class ApiClient {
}
// ============================================================
// DUTCHIE AZ API
// AZ DATA API (formerly dutchie-az)
// ============================================================
// Dutchie AZ Dashboard
@@ -540,7 +540,7 @@ class ApiClient {
failedJobCount: number;
brandCount: number;
categoryCount: number;
}>('/api/dutchie-az/dashboard');
}>('/api/az/dashboard');
}
// Dutchie AZ Schedules (CRUD)
@@ -562,7 +562,7 @@ class ApiClient {
createdAt: string;
updatedAt: string;
}>;
}>('/api/dutchie-az/admin/schedules');
}>('/api/az/admin/schedules');
}
async getDutchieAZSchedule(id: number) {
@@ -581,7 +581,7 @@ class ApiClient {
jobConfig: Record<string, any> | null;
createdAt: string;
updatedAt: string;
}>(`/api/dutchie-az/admin/schedules/${id}`);
}>(`/api/az/admin/schedules/${id}`);
}
async createDutchieAZSchedule(data: {
@@ -593,7 +593,7 @@ class ApiClient {
jobConfig?: Record<string, any>;
startImmediately?: boolean;
}) {
return this.request<any>('/api/dutchie-az/admin/schedules', {
return this.request<any>('/api/az/admin/schedules', {
method: 'POST',
body: JSON.stringify(data),
});
@@ -606,26 +606,26 @@ class ApiClient {
jitterMinutes?: number;
jobConfig?: Record<string, any>;
}) {
return this.request<any>(`/api/dutchie-az/admin/schedules/${id}`, {
return this.request<any>(`/api/az/admin/schedules/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async deleteDutchieAZSchedule(id: number) {
return this.request<{ success: boolean; message: string }>(`/api/dutchie-az/admin/schedules/${id}`, {
return this.request<{ success: boolean; message: string }>(`/api/az/admin/schedules/${id}`, {
method: 'DELETE',
});
}
async triggerDutchieAZSchedule(id: number) {
return this.request<{ success: boolean; message: string }>(`/api/dutchie-az/admin/schedules/${id}/trigger`, {
return this.request<{ success: boolean; message: string }>(`/api/az/admin/schedules/${id}/trigger`, {
method: 'POST',
});
}
async initDutchieAZSchedules() {
return this.request<{ success: boolean; schedules: any[] }>('/api/dutchie-az/admin/schedules/init', {
return this.request<{ success: boolean; schedules: any[] }>('/api/az/admin/schedules/init', {
method: 'POST',
});
}
@@ -636,7 +636,7 @@ class ApiClient {
if (limit) params.append('limit', limit.toString());
if (offset) params.append('offset', offset.toString());
const queryString = params.toString() ? `?${params.toString()}` : '';
return this.request<{ logs: any[]; total: number }>(`/api/dutchie-az/admin/schedules/${scheduleId}/logs${queryString}`);
return this.request<{ logs: any[]; total: number }>(`/api/az/admin/schedules/${scheduleId}/logs${queryString}`);
}
async getDutchieAZRunLogs(options?: { scheduleId?: number; jobName?: string; limit?: number; offset?: number }) {
@@ -646,28 +646,28 @@ class ApiClient {
if (options?.limit) params.append('limit', options.limit.toString());
if (options?.offset) params.append('offset', options.offset.toString());
const queryString = params.toString() ? `?${params.toString()}` : '';
return this.request<{ logs: any[]; total: number }>(`/api/dutchie-az/admin/run-logs${queryString}`);
return this.request<{ logs: any[]; total: number }>(`/api/az/admin/run-logs${queryString}`);
}
// Dutchie AZ Scheduler Control
async getDutchieAZSchedulerStatus() {
return this.request<{ running: boolean; pollIntervalMs: number }>('/api/dutchie-az/admin/scheduler/status');
return this.request<{ running: boolean; pollIntervalMs: number }>('/api/az/admin/scheduler/status');
}
async startDutchieAZScheduler() {
return this.request<{ success: boolean; message: string }>('/api/dutchie-az/admin/scheduler/start', {
return this.request<{ success: boolean; message: string }>('/api/az/admin/scheduler/start', {
method: 'POST',
});
}
async stopDutchieAZScheduler() {
return this.request<{ success: boolean; message: string }>('/api/dutchie-az/admin/scheduler/stop', {
return this.request<{ success: boolean; message: string }>('/api/az/admin/scheduler/stop', {
method: 'POST',
});
}
async triggerDutchieAZImmediateCrawl() {
return this.request<{ success: boolean; message: string }>('/api/dutchie-az/admin/scheduler/trigger', {
return this.request<{ success: boolean; message: string }>('/api/az/admin/scheduler/trigger', {
method: 'POST',
});
}
@@ -680,11 +680,11 @@ class ApiClient {
if (params?.limit) searchParams.append('limit', params.limit.toString());
if (params?.offset) searchParams.append('offset', params.offset.toString());
const queryString = searchParams.toString() ? `?${searchParams.toString()}` : '';
return this.request<{ stores: any[]; total: number }>(`/api/dutchie-az/stores${queryString}`);
return this.request<{ stores: any[]; total: number }>(`/api/az/stores${queryString}`);
}
async getDutchieAZStore(id: number) {
return this.request<any>(`/api/dutchie-az/stores/${id}`);
return this.request<any>(`/api/az/stores/${id}`);
}
async getDutchieAZStoreSummary(id: number) {
@@ -700,7 +700,7 @@ class ApiClient {
brandCount: number;
categoryCount: number;
lastCrawl: any | null;
}>(`/api/dutchie-az/stores/${id}/summary`);
}>(`/api/az/stores/${id}/summary`);
}
async getDutchieAZStoreProducts(id: number, params?: {
@@ -752,19 +752,19 @@ class ApiClient {
total: number;
limit: number;
offset: number;
}>(`/api/dutchie-az/stores/${id}/products${queryString}`);
}>(`/api/az/stores/${id}/products${queryString}`);
}
async getDutchieAZStoreBrands(id: number) {
return this.request<{
brands: Array<{ brand: string; product_count: number }>;
}>(`/api/dutchie-az/stores/${id}/brands`);
}>(`/api/az/stores/${id}/brands`);
}
async getDutchieAZStoreCategories(id: number) {
return this.request<{
categories: Array<{ type: string; subcategory: string; product_count: number }>;
}>(`/api/dutchie-az/stores/${id}/categories`);
}>(`/api/az/stores/${id}/categories`);
}
// Dutchie AZ Debug
@@ -795,7 +795,7 @@ class ApiClient {
dispensary_name: string;
crawled_at: string;
}>;
}>('/api/dutchie-az/debug/summary');
}>('/api/az/debug/summary');
}
async getDutchieAZDebugStore(id: number) {
@@ -823,11 +823,11 @@ class ApiClient {
outOfStock: any[];
};
categories: Array<{ type: string; subcategory: string; count: string }>;
}>(`/api/dutchie-az/debug/store/${id}`);
}>(`/api/az/debug/store/${id}`);
}
async triggerDutchieAZCrawl(id: number, options?: { pricingType?: string; useBothModes?: boolean }) {
return this.request<any>(`/api/dutchie-az/admin/crawl/${id}`, {
return this.request<any>(`/api/az/admin/crawl/${id}`, {
method: 'POST',
body: JSON.stringify(options || {}),
});

View File

@@ -114,7 +114,14 @@ export function DutchieAZSchedule() {
const handleUpdateSchedule = async (id: number, updates: Partial<JobSchedule>) => {
try {
await api.updateDutchieAZSchedule(id, updates);
const payload = {
description: updates.description ?? undefined,
enabled: updates.enabled,
baseIntervalMinutes: updates.baseIntervalMinutes,
jitterMinutes: updates.jitterMinutes,
jobConfig: updates.jobConfig ?? undefined,
};
await api.updateDutchieAZSchedule(id, payload);
setEditingSchedule(null);
await loadData();
} catch (error) {

View File

@@ -140,11 +140,11 @@ export function DutchieAZStoreDetail() {
{/* Header */}
<div className="flex items-center justify-between gap-4">
<button
onClick={() => navigate('/dutchie-az')}
onClick={() => navigate('/az')}
className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900"
>
<ArrowLeft className="w-4 h-4" />
Back to Dutchie AZ Stores
Back to AZ Stores
</button>
{/* Update Button */}

View File

@@ -175,7 +175,7 @@ export function DutchieAZStores() {
</td>
<td>
<button
onClick={() => navigate(`/dutchie-az/stores/${store.id}`)}
onClick={() => navigate(`/az/stores/${store.id}`)}
className="btn btn-sm btn-primary"
disabled={!store.platform_dispensary_id}
>