EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
route.ts
Go to the documentation of this file.
1import { NextRequest, NextResponse } from 'next/server';
2import { promises as fs } from 'fs';
3import path from 'path';
4import { stores, transformManifestStore, type Store } from '@/lib/stores';
5
6interface StoresManifest {
7 version: string;
8 lastUpdated: string;
9 stores: any[];
10}
11
12const MANIFEST_PATH = path.join(process.cwd(), 'data', 'config', 'stores-manifest.json');
13const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
14
15let manifestCache: { data: StoresManifest | null; timestamp: number } = {
16 data: null,
17 timestamp: 0,
18};
19
20/**
21 * Read stores from manifest file
22 */
23async function readManifest(): Promise<StoresManifest> {
24 try {
25 const fileContent = await fs.readFile(MANIFEST_PATH, 'utf-8');
26 return JSON.parse(fileContent);
27 } catch (error) {
28 console.warn('[Stores API] Failed to read manifest, using fallback:', error);
29 // Return fallback data
30 return {
31 version: '1.0.0',
32 lastUpdated: new Date().toISOString(),
33 stores: stores,
34 };
35 }
36}
37
38/**
39 * Write stores to manifest file
40 */
41async function writeManifest(manifest: StoresManifest): Promise<void> {
42 try {
43 await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest, null, 2), 'utf-8');
44 console.log('[Stores API] Manifest updated successfully');
45 } catch (error) {
46 console.error('[Stores API] Failed to write manifest:', error);
47 throw error;
48 }
49}
50
51/**
52 * GET /api/auth/stores
53 * Returns available stores for authentication
54 *
55 * Dynamically fetches stores from manifest and transforms them
56 * to use friendly IDs (iig, cowra, griffith, murwillumbah) for authentication
57 *
58 * Query params:
59 * - sync=true: Force sync with Fieldpine locations API
60 */
61export async function GET(request: NextRequest) {
62 try {
63 const searchParams = request.nextUrl.searchParams;
64 const forceSync = searchParams.get('sync') === 'true';
65
66 const now = Date.now();
67 const cacheExpired = now - manifestCache.timestamp > CACHE_TTL_MS;
68
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);
75
76 console.log(`[Stores API] Returning ${authStores.length} cached authentication stores`);
77 return NextResponse.json({
78 success: true,
79 data: authStores,
80 cached: true,
81 source: 'manifest',
82 lastUpdated: manifestCache.data.lastUpdated,
83 });
84 }
85
86 // Read from manifest file
87 const manifest = await readManifest();
88
89 // Update cache
90 manifestCache = {
91 data: manifest,
92 timestamp: now,
93 };
94
95 // Transform manifest stores to authentication format
96 const authStores = manifest.stores
97 .map(transformManifestStore)
98 .filter((store): store is Store => store !== null);
99
100 console.log(`[Stores API] Returning ${authStores.length} authentication stores from manifest`);
101
102 return NextResponse.json({
103 success: true,
104 data: authStores,
105 cached: false,
106 source: 'manifest',
107 lastUpdated: manifest.lastUpdated,
108 });
109 } catch (error: any) {
110 console.error('[Stores API] Error:', error);
111
112 // Fallback to static stores on error
113 return NextResponse.json(
114 {
115 success: true,
116 data: stores,
117 fallback: true,
118 error: error.message,
119 },
120 { status: 200 }
121 );
122 }
123}
124
125/**
126 * POST /api/auth/stores/sync
127 * Sync stores from Fieldpine locations API to manifest
128 */
129export async function POST(request: NextRequest) {
130 try {
131 console.log('[Stores API] Syncing stores from Fieldpine...');
132
133 // Fetch locations from Fieldpine
134 const locationsUrl = `${request.nextUrl.origin}/api/v1/locations`;
135 console.log('[Stores API] Fetching from:', locationsUrl);
136
137 const response = await fetch(locationsUrl, {
138 headers: {
139 'Cookie': request.headers.get('Cookie') || '',
140 },
141 });
142
143 if (!response.ok) {
144 throw new Error(`Failed to fetch locations: ${response.status}`);
145 }
146
147 const locations = await response.json();
148 console.log('[Stores API] Fetched locations:', locations.length);
149
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;
157
158 // Create store URL based on name or ID
159 const urlSlug = name
160 .toLowerCase()
161 .replace(/cartridge world\s*/i, '')
162 .replace(/[^a-z0-9]+/g, '-')
163 .replace(/^-+|-+$/g, '') || id;
164
165 return {
166 id: id?.toString() || `store-${Math.random().toString(36).substr(2, 9)}`,
167 name: name,
168 url: `https://${urlSlug}.cwanz.online`,
169 type: storeType === 'management' ? 'management' : 'store',
170 ...(phone && { phone }),
171 ...(email && { email }),
172 ...(loc.f102 && loc.f103 && {
173 coordinates: {
174 latitude: parseFloat(loc.f102),
175 longitude: parseFloat(loc.f103),
176 },
177 }),
178 };
179 });
180
181 // Update manifest
182 const newManifest: StoresManifest = {
183 version: '1.0.0',
184 lastUpdated: new Date().toISOString(),
185 stores: stores,
186 };
187
188 await writeManifest(newManifest);
189
190 // Clear cache
191 manifestCache = { data: null, timestamp: 0 };
192
193 console.log('[Stores API] Sync complete:', stores.length, 'stores');
194
195 return NextResponse.json({
196 success: true,
197 message: 'Stores manifest synced successfully',
198 storeCount: stores.length,
199 lastUpdated: newManifest.lastUpdated,
200 });
201 } catch (error: any) {
202 console.error('[Stores API] Sync error:', error);
203 return NextResponse.json(
204 {
205 success: false,
206 error: error.message || 'Failed to sync stores',
207 },
208 { status: 500 }
209 );
210 }
211}