1import { NextRequest, NextResponse } from 'next/server';
2import { promises as fs } from 'fs';
3import path from 'path';
4import { stores, transformManifestStore, type Store } from '@/lib/stores';
6interface StoresManifest {
12const MANIFEST_PATH = path.join(process.cwd(), 'data', 'config', 'stores-manifest.json');
13const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
15let manifestCache: { data: StoresManifest | null; timestamp: number } = {
21 * Read stores from manifest file
23async function readManifest(): Promise<StoresManifest> {
25 const fileContent = await fs.readFile(MANIFEST_PATH, 'utf-8');
26 return JSON.parse(fileContent);
28 console.warn('[Stores API] Failed to read manifest, using fallback:', error);
29 // Return fallback data
32 lastUpdated: new Date().toISOString(),
39 * Write stores to manifest file
41async function writeManifest(manifest: StoresManifest): Promise<void> {
43 await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest, null, 2), 'utf-8');
44 console.log('[Stores API] Manifest updated successfully');
46 console.error('[Stores API] Failed to write manifest:', error);
52 * GET /api/auth/stores
53 * Returns available stores for authentication
55 * Dynamically fetches stores from manifest and transforms them
56 * to use friendly IDs (iig, cowra, griffith, murwillumbah) for authentication
59 * - sync=true: Force sync with Fieldpine locations API
61export async function GET(request: NextRequest) {
63 const searchParams = request.nextUrl.searchParams;
64 const forceSync = searchParams.get('sync') === 'true';
66 const now = Date.now();
67 const cacheExpired = now - manifestCache.timestamp > CACHE_TTL_MS;
69 // Use cache if valid and not forcing sync
70 if (!forceSync && manifestCache.data && !cacheExpired) {
71 // Transform manifest stores to authentication format
72 const authStores = manifestCache.data.stores
73 .map(transformManifestStore)
74 .filter((store): store is Store => store !== null);
76 console.log(`[Stores API] Returning ${authStores.length} cached authentication stores`);
77 return NextResponse.json({
82 lastUpdated: manifestCache.data.lastUpdated,
86 // Read from manifest file
87 const manifest = await readManifest();
95 // Transform manifest stores to authentication format
96 const authStores = manifest.stores
97 .map(transformManifestStore)
98 .filter((store): store is Store => store !== null);
100 console.log(`[Stores API] Returning ${authStores.length} authentication stores from manifest`);
102 return NextResponse.json({
107 lastUpdated: manifest.lastUpdated,
109 } catch (error: any) {
110 console.error('[Stores API] Error:', error);
112 // Fallback to static stores on error
113 return NextResponse.json(
118 error: error.message,
126 * POST /api/auth/stores/sync
127 * Sync stores from Fieldpine locations API to manifest
129export async function POST(request: NextRequest) {
131 console.log('[Stores API] Syncing stores from Fieldpine...');
133 // Fetch locations from Fieldpine
134 const locationsUrl = `${request.nextUrl.origin}/api/v1/locations`;
135 console.log('[Stores API] Fetching from:', locationsUrl);
137 const response = await fetch(locationsUrl, {
139 'Cookie': request.headers.get('Cookie') || '',
144 throw new Error(`Failed to fetch locations: ${response.status}`);
147 const locations = await response.json();
148 console.log('[Stores API] Fetched locations:', locations.length);
150 // Map Fieldpine locations to Store format
151 const stores = locations.map((loc: any) => {
152 const id = loc.f100 || loc.id;
153 const name = loc.f101 || loc.name || `Store ${id}`;
154 const storeType = loc.f164 || loc.storeType || 'store';
155 const phone = loc.f150 || loc.phone;
156 const email = loc.f151 || loc.email;
158 // Create store URL based on name or ID
161 .replace(/cartridge world\s*/i, '')
162 .replace(/[^a-z0-9]+/g, '-')
163 .replace(/^-+|-+$/g, '') || id;
166 id: id?.toString() || `store-${Math.random().toString(36).substr(2, 9)}`,
168 url: `https://${urlSlug}.cwanz.online`,
169 type: storeType === 'management' ? 'management' : 'store',
170 ...(phone && { phone }),
171 ...(email && { email }),
172 ...(loc.f102 && loc.f103 && {
174 latitude: parseFloat(loc.f102),
175 longitude: parseFloat(loc.f103),
182 const newManifest: StoresManifest = {
184 lastUpdated: new Date().toISOString(),
188 await writeManifest(newManifest);
191 manifestCache = { data: null, timestamp: 0 };
193 console.log('[Stores API] Sync complete:', stores.length, 'stores');
195 return NextResponse.json({
197 message: 'Stores manifest synced successfully',
198 storeCount: stores.length,
199 lastUpdated: newManifest.lastUpdated,
201 } catch (error: any) {
202 console.error('[Stores API] Sync error:', error);
203 return NextResponse.json(
206 error: error.message || 'Failed to sync stores',