Expose AZ data under neutral /api/az and update frontend routes
This commit is contained in:
@@ -49,9 +49,9 @@ export default function App() {
|
|||||||
<Route path="/scraper-tools" element={<PrivateRoute><ScraperTools /></PrivateRoute>} />
|
<Route path="/scraper-tools" element={<PrivateRoute><ScraperTools /></PrivateRoute>} />
|
||||||
<Route path="/scraper-monitor" element={<PrivateRoute><ScraperMonitor /></PrivateRoute>} />
|
<Route path="/scraper-monitor" element={<PrivateRoute><ScraperMonitor /></PrivateRoute>} />
|
||||||
<Route path="/scraper-schedule" element={<PrivateRoute><ScraperSchedule /></PrivateRoute>} />
|
<Route path="/scraper-schedule" element={<PrivateRoute><ScraperSchedule /></PrivateRoute>} />
|
||||||
<Route path="/dutchie-az-schedule" element={<PrivateRoute><DutchieAZSchedule /></PrivateRoute>} />
|
<Route path="/az-schedule" element={<PrivateRoute><DutchieAZSchedule /></PrivateRoute>} />
|
||||||
<Route path="/dutchie-az" element={<PrivateRoute><DutchieAZStores /></PrivateRoute>} />
|
<Route path="/az" element={<PrivateRoute><DutchieAZStores /></PrivateRoute>} />
|
||||||
<Route path="/dutchie-az/stores/:id" element={<PrivateRoute><DutchieAZStoreDetail /></PrivateRoute>} />
|
<Route path="/az/stores/:id" element={<PrivateRoute><DutchieAZStoreDetail /></PrivateRoute>} />
|
||||||
<Route path="/api-permissions" element={<PrivateRoute><ApiPermissions /></PrivateRoute>} />
|
<Route path="/api-permissions" element={<PrivateRoute><ApiPermissions /></PrivateRoute>} />
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -163,18 +163,18 @@ export function Layout({ children }: LayoutProps) {
|
|||||||
/>
|
/>
|
||||||
</NavSection>
|
</NavSection>
|
||||||
|
|
||||||
<NavSection title="Dutchie AZ">
|
<NavSection title="AZ Data">
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/dutchie-az"
|
to="/az"
|
||||||
icon={<Store className="w-4 h-4" />}
|
icon={<Store className="w-4 h-4" />}
|
||||||
label="AZ Stores"
|
label="AZ Stores"
|
||||||
isActive={isActive('/dutchie-az', false)}
|
isActive={isActive('/az', false)}
|
||||||
/>
|
/>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/dutchie-az-schedule"
|
to="/az-schedule"
|
||||||
icon={<Calendar className="w-4 h-4" />}
|
icon={<Calendar className="w-4 h-4" />}
|
||||||
label="AZ Schedule"
|
label="AZ Schedule"
|
||||||
isActive={isActive('/dutchie-az-schedule')}
|
isActive={isActive('/az-schedule')}
|
||||||
/>
|
/>
|
||||||
</NavSection>
|
</NavSection>
|
||||||
|
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ class ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// DUTCHIE AZ API
|
// AZ DATA API (formerly dutchie-az)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Dutchie AZ Dashboard
|
// Dutchie AZ Dashboard
|
||||||
@@ -540,7 +540,7 @@ class ApiClient {
|
|||||||
failedJobCount: number;
|
failedJobCount: number;
|
||||||
brandCount: number;
|
brandCount: number;
|
||||||
categoryCount: number;
|
categoryCount: number;
|
||||||
}>('/api/dutchie-az/dashboard');
|
}>('/api/az/dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dutchie AZ Schedules (CRUD)
|
// Dutchie AZ Schedules (CRUD)
|
||||||
@@ -562,7 +562,7 @@ class ApiClient {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}>;
|
}>;
|
||||||
}>('/api/dutchie-az/admin/schedules');
|
}>('/api/az/admin/schedules');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDutchieAZSchedule(id: number) {
|
async getDutchieAZSchedule(id: number) {
|
||||||
@@ -581,7 +581,7 @@ class ApiClient {
|
|||||||
jobConfig: Record<string, any> | null;
|
jobConfig: Record<string, any> | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}>(`/api/dutchie-az/admin/schedules/${id}`);
|
}>(`/api/az/admin/schedules/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createDutchieAZSchedule(data: {
|
async createDutchieAZSchedule(data: {
|
||||||
@@ -593,7 +593,7 @@ class ApiClient {
|
|||||||
jobConfig?: Record<string, any>;
|
jobConfig?: Record<string, any>;
|
||||||
startImmediately?: boolean;
|
startImmediately?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return this.request<any>('/api/dutchie-az/admin/schedules', {
|
return this.request<any>('/api/az/admin/schedules', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
@@ -606,26 +606,26 @@ class ApiClient {
|
|||||||
jitterMinutes?: number;
|
jitterMinutes?: number;
|
||||||
jobConfig?: Record<string, any>;
|
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',
|
method: 'PUT',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteDutchieAZSchedule(id: number) {
|
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',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async triggerDutchieAZSchedule(id: number) {
|
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',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async initDutchieAZSchedules() {
|
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',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -636,7 +636,7 @@ class ApiClient {
|
|||||||
if (limit) params.append('limit', limit.toString());
|
if (limit) params.append('limit', limit.toString());
|
||||||
if (offset) params.append('offset', offset.toString());
|
if (offset) params.append('offset', offset.toString());
|
||||||
const queryString = params.toString() ? `?${params.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 }) {
|
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?.limit) params.append('limit', options.limit.toString());
|
||||||
if (options?.offset) params.append('offset', options.offset.toString());
|
if (options?.offset) params.append('offset', options.offset.toString());
|
||||||
const queryString = params.toString() ? `?${params.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
|
// Dutchie AZ Scheduler Control
|
||||||
async getDutchieAZSchedulerStatus() {
|
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() {
|
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',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopDutchieAZScheduler() {
|
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',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async triggerDutchieAZImmediateCrawl() {
|
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',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -680,11 +680,11 @@ class ApiClient {
|
|||||||
if (params?.limit) searchParams.append('limit', params.limit.toString());
|
if (params?.limit) searchParams.append('limit', params.limit.toString());
|
||||||
if (params?.offset) searchParams.append('offset', params.offset.toString());
|
if (params?.offset) searchParams.append('offset', params.offset.toString());
|
||||||
const queryString = searchParams.toString() ? `?${searchParams.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) {
|
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) {
|
async getDutchieAZStoreSummary(id: number) {
|
||||||
@@ -700,7 +700,7 @@ class ApiClient {
|
|||||||
brandCount: number;
|
brandCount: number;
|
||||||
categoryCount: number;
|
categoryCount: number;
|
||||||
lastCrawl: any | null;
|
lastCrawl: any | null;
|
||||||
}>(`/api/dutchie-az/stores/${id}/summary`);
|
}>(`/api/az/stores/${id}/summary`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDutchieAZStoreProducts(id: number, params?: {
|
async getDutchieAZStoreProducts(id: number, params?: {
|
||||||
@@ -752,19 +752,19 @@ class ApiClient {
|
|||||||
total: number;
|
total: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
}>(`/api/dutchie-az/stores/${id}/products${queryString}`);
|
}>(`/api/az/stores/${id}/products${queryString}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDutchieAZStoreBrands(id: number) {
|
async getDutchieAZStoreBrands(id: number) {
|
||||||
return this.request<{
|
return this.request<{
|
||||||
brands: Array<{ brand: string; product_count: number }>;
|
brands: Array<{ brand: string; product_count: number }>;
|
||||||
}>(`/api/dutchie-az/stores/${id}/brands`);
|
}>(`/api/az/stores/${id}/brands`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDutchieAZStoreCategories(id: number) {
|
async getDutchieAZStoreCategories(id: number) {
|
||||||
return this.request<{
|
return this.request<{
|
||||||
categories: Array<{ type: string; subcategory: string; product_count: number }>;
|
categories: Array<{ type: string; subcategory: string; product_count: number }>;
|
||||||
}>(`/api/dutchie-az/stores/${id}/categories`);
|
}>(`/api/az/stores/${id}/categories`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dutchie AZ Debug
|
// Dutchie AZ Debug
|
||||||
@@ -795,7 +795,7 @@ class ApiClient {
|
|||||||
dispensary_name: string;
|
dispensary_name: string;
|
||||||
crawled_at: string;
|
crawled_at: string;
|
||||||
}>;
|
}>;
|
||||||
}>('/api/dutchie-az/debug/summary');
|
}>('/api/az/debug/summary');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDutchieAZDebugStore(id: number) {
|
async getDutchieAZDebugStore(id: number) {
|
||||||
@@ -823,11 +823,11 @@ class ApiClient {
|
|||||||
outOfStock: any[];
|
outOfStock: any[];
|
||||||
};
|
};
|
||||||
categories: Array<{ type: string; subcategory: string; count: string }>;
|
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 }) {
|
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',
|
method: 'POST',
|
||||||
body: JSON.stringify(options || {}),
|
body: JSON.stringify(options || {}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -114,7 +114,14 @@ export function DutchieAZSchedule() {
|
|||||||
|
|
||||||
const handleUpdateSchedule = async (id: number, updates: Partial<JobSchedule>) => {
|
const handleUpdateSchedule = async (id: number, updates: Partial<JobSchedule>) => {
|
||||||
try {
|
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);
|
setEditingSchedule(null);
|
||||||
await loadData();
|
await loadData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -140,11 +140,11 @@ export function DutchieAZStoreDetail() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/dutchie-az')}
|
onClick={() => navigate('/az')}
|
||||||
className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900"
|
className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
Back to Dutchie AZ Stores
|
Back to AZ Stores
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Update Button */}
|
{/* Update Button */}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export function DutchieAZStores() {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/dutchie-az/stores/${store.id}`)}
|
onClick={() => navigate(`/az/stores/${store.id}`)}
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
disabled={!store.platform_dispensary_id}
|
disabled={!store.platform_dispensary_id}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user