1 import type { Express, RequestHandler } from "express";
2 import { createServer, type Server } from "http";
3 import { storage } from "./storage";
4 import { insertRepairGuideSchema, insertTroubleshootingFlowSchema, insertDeviceComponentSchema, insertFavoriteSchema } from "@shared/schema";
5 import { requireAdmin } from "./middleware/adminAuth";
6 import { isAuthenticated } from "./auth";
7 import { upload } from "./middleware/upload";
8
9 export async function registerRoutes(app: Express): Promise<Server> {
10 // Auth & Profile routes
11 app.get('/api/auth/user', isAuthenticated, (req, res) => {
12 res.json(req.user);
13 });
14
15 app.patch('/api/user/profile', isAuthenticated, async (req, res) => {
16 try {
17 const updatedUser = await storage.updateUser(req.user!.id, req.body);
18 res.json(updatedUser);
19 } catch (error) {
20 res.status(500).json({ message: "Failed to update profile" });
21 }
22 });
23
24 app.post('/api/user/profile/photo', isAuthenticated, upload.single('photo'), async (req, res) => {
25 try {
26 if (!req.file) {
27 return res.status(400).json({ message: "No file uploaded" });
28 }
29
30 const photoUrl = `/uploads/profiles/${req.file.filename}`;
31 const updatedUser = await storage.updateUser(req.user!.id, { profileImageUrl: photoUrl });
32 res.json(updatedUser);
33 } catch (error) {
34 res.status(500).json({ message: "Failed to upload photo" });
35 }
36 });
37
38 // Favorites routes
39 app.get('/api/favorites', isAuthenticated, async (req: any, res) => {
40 try {
41 const userId = req.user.id;
42 const favorites = await storage.getUserFavorites(userId);
43 res.json(favorites);
44 } catch (error) {
45 console.error("Error fetching favorites:", error);
46 res.status(500).json({ message: "Failed to fetch favorites" });
47 }
48 });
49
50 app.post('/api/favorites', isAuthenticated, async (req: any, res) => {
51 try {
52 const userId = req.user.id;
53 const favoriteData = insertFavoriteSchema.parse(req.body);
54 const favorite = await storage.addToFavorites(userId, favoriteData);
55 res.json(favorite);
56 } catch (error) {
57 console.error("Error adding favorite:", error);
58 res.status(500).json({ message: "Failed to add favorite" });
59 }
60 });
61
62 app.delete('/api/favorites/:itemId/:itemType', isAuthenticated, async (req: any, res) => {
63 try {
64 const userId = req.user.id;
65 const { itemId, itemType } = req.params;
66 await storage.removeFromFavorites(userId, itemId, itemType);
67 res.json({ success: true });
68 } catch (error) {
69 console.error("Error removing favorite:", error);
70 res.status(500).json({ message: "Failed to remove favorite" });
71 }
72 });
73
74 app.get('/api/favorites/check/:itemId/:itemType', isAuthenticated, async (req: any, res) => {
75 try {
76 const userId = req.user.id;
77 const { itemId, itemType } = req.params;
78 const isFavorited = await storage.isFavorited(userId, itemId, itemType);
79 res.json({ isFavorited });
80 } catch (error) {
81 console.error("Error checking favorite:", error);
82 res.status(500).json({ message: "Failed to check favorite status" });
83 }
84 });
85 // Repair Guides
86 app.get("/api/repair-guides", async (req, res) => {
87 try {
88 const { deviceType, category, difficulty } = req.query;
89 const guides = await storage.getRepairGuides({
90 deviceType: deviceType as string,
91 category: category as string,
92 difficulty: difficulty as string,
93 });
94 res.json(guides);
95 } catch (error) {
96 res.status(500).json({ message: "Failed to fetch repair guides" });
97 }
98 });
99
100 app.get("/api/repair-guides/search", async (req, res) => {
101 try {
102 const { q } = req.query;
103 if (!q || typeof q !== "string") {
104 return res.status(400).json({ message: "Search query is required" });
105 }
106 const guides = await storage.searchRepairGuides(q);
107 res.json(guides);
108 } catch (error) {
109 res.status(500).json({ message: "Failed to search repair guides" });
110 }
111 });
112
113 app.get("/api/repair-guides/:id", async (req, res) => {
114 try {
115 const guide = await storage.getRepairGuide(req.params.id);
116 if (!guide) {
117 return res.status(404).json({ message: "Repair guide not found" });
118 }
119 // Increment view count when guide is viewed
120 await storage.updateViewCount(req.params.id);
121 res.json(guide);
122 } catch (error) {
123 res.status(500).json({ message: "Failed to fetch repair guide" });
124 }
125 });
126
127 app.post("/api/repair-guides", async (req, res) => {
128 try {
129 const validatedData = insertRepairGuideSchema.parse(req.body);
130 const guide = await storage.createRepairGuide(validatedData);
131 res.status(201).json(guide);
132 } catch (error) {
133 res.status(400).json({ message: "Invalid repair guide data" });
134 }
135 });
136
137 // User-created guides endpoint
138 app.post("/api/user-guides", async (req, res) => {
139 try {
140 const validatedData = insertRepairGuideSchema.parse(req.body);
141 const guide = await storage.createUserGuide(validatedData);
142 res.status(201).json(guide);
143 } catch (error) {
144 console.error("Error creating user guide:", error);
145 res.status(400).json({ message: "Invalid user guide data" });
146 }
147 });
148
149 // Track download for a specific guide
150 app.post("/api/repair-guides/:id/download", async (req, res) => {
151 try {
152 const guide = await storage.getRepairGuide(req.params.id);
153 if (!guide) {
154 return res.status(404).json({ message: "Repair guide not found" });
155 }
156 // Increment download count when guide is downloaded
157 await storage.updateDownloadCount(req.params.id);
158 res.json({ success: true, message: "Download tracked" });
159 } catch (error) {
160 res.status(500).json({ message: "Failed to track download" });
161 }
162 });
163
164 // Version endpoint
165 app.get("/api/version", async (req, res) => {
166 try {
167 const versionInfo = {
168 version: "1.0.0",
169 name: "JCR Guide Pro",
170 build: "2025.08.18",
171 release: "Major Release"
172 };
173 res.json(versionInfo);
174 } catch (error) {
175 res.status(500).json({ message: "Failed to fetch version info" });
176 }
177 });
178
179 // Admin routes
180 app.get("/api/admin/users", requireAdmin, async (req, res) => {
181 try {
182 res.json({ message: "Admin access confirmed", adminEmail: req.adminUser?.email });
183 } catch (error) {
184 res.status(500).json({ message: "Failed to fetch admin data" });
185 }
186 });
187
188 app.get("/api/admin/stats", requireAdmin, async (req, res) => {
189 try {
190 const guides = await storage.getRepairGuides();
191 const totalViews = guides.reduce((sum, guide) => sum + (guide.viewCount || 0), 0);
192 const totalDownloads = guides.reduce((sum, guide) => sum + (guide.downloadCount || 0), 0);
193
194 res.json({
195 totalGuides: guides.length,
196 totalViews,
197 totalDownloads,
198 adminUser: req.adminUser?.email
199 });
200 } catch (error) {
201 res.status(500).json({ message: "Failed to fetch admin stats" });
202 }
203 });
204
205 // Bulk download endpoint
206 app.post("/api/download/bulk", async (req, res) => {
207 try {
208 const { type, filters } = req.body;
209 let data: any[] = [];
210
211 switch (type) {
212 case 'repair-guides':
213 data = await storage.getRepairGuides(filters || {});
214 break;
215 case 'troubleshooting-flows':
216 data = await storage.getTroubleshootingFlows();
217 break;
218 case 'device-components':
219 data = await storage.getDeviceComponents();
220 break;
221 case 'complete':
222 const [guides, flows, components] = await Promise.all([
223 storage.getRepairGuides({}),
224 storage.getTroubleshootingFlows(),
225 storage.getDeviceComponents()
226 ]);
227 const exportData = {
228 exportInfo: {
229 title: 'JCR Guide Pro Complete Export',
230 version: '1.0',
231 exportedAt: new Date().toISOString(),
232 totalItems: guides.length + flows.length + components.length
233 },
234 repairGuides: guides,
235 troubleshootingFlows: flows,
236 deviceComponents: components
237 };
238 data = exportData as any;
239 break;
240 default:
241 return res.status(400).json({ message: "Invalid download type" });
242 }
243
244 res.json({
245 success: true,
246 data,
247 count: Array.isArray(data) ? data.length : Object.keys(data).length - 1,
248 timestamp: new Date().toISOString()
249 });
250 } catch (error) {
251 console.error("Bulk download error:", error);
252 res.status(500).json({ message: "Failed to prepare bulk download" });
253 }
254 });
255
256 app.patch("/api/repair-guides/:id/bookmark", async (req, res) => {
257 try {
258 const guide = await storage.toggleBookmark(req.params.id);
259 if (!guide) {
260 return res.status(404).json({ message: "Repair guide not found" });
261 }
262 res.json(guide);
263 } catch (error) {
264 res.status(500).json({ message: "Failed to toggle bookmark" });
265 }
266 });
267
268 // Troubleshooting Flows
269 app.get("/api/troubleshooting-flows", async (req, res) => {
270 try {
271 const flows = await storage.getTroubleshootingFlows();
272 res.json(flows);
273 } catch (error) {
274 res.status(500).json({ message: "Failed to fetch troubleshooting flows" });
275 }
276 });
277
278 app.get("/api/troubleshooting-flows/:id", async (req, res) => {
279 try {
280 const flow = await storage.getTroubleshootingFlow(req.params.id);
281 if (!flow) {
282 return res.status(404).json({ message: "Troubleshooting flow not found" });
283 }
284 res.json(flow);
285 } catch (error) {
286 res.status(500).json({ message: "Failed to fetch troubleshooting flow" });
287 }
288 });
289
290 app.post("/api/troubleshooting-flows", async (req, res) => {
291 try {
292 const validatedData = insertTroubleshootingFlowSchema.parse(req.body);
293 const flow = await storage.createTroubleshootingFlow(validatedData);
294 res.status(201).json(flow);
295 } catch (error) {
296 res.status(400).json({ message: "Invalid troubleshooting flow data" });
297 }
298 });
299
300 // Device Components
301 app.get("/api/device-components", async (req, res) => {
302 try {
303 const { deviceType } = req.query;
304 const components = await storage.getDeviceComponents(deviceType as string);
305 res.json(components);
306 } catch (error) {
307 res.status(500).json({ message: "Failed to fetch device components" });
308 }
309 });
310
311 app.get("/api/device-components/:id", async (req, res) => {
312 try {
313 const component = await storage.getDeviceComponent(req.params.id);
314 if (!component) {
315 return res.status(404).json({ message: "Device component not found" });
316 }
317 res.json(component);
318 } catch (error) {
319 res.status(500).json({ message: "Failed to fetch device component" });
320 }
321 });
322
323 app.post("/api/device-components", async (req, res) => {
324 try {
325 const validatedData = insertDeviceComponentSchema.parse(req.body);
326 const component = await storage.createDeviceComponent(validatedData);
327 res.status(201).json(component);
328 } catch (error) {
329 res.status(400).json({ message: "Invalid device component data" });
330 }
331 });
332
333 // Device Brands routes
334 app.get("/api/device-brands", async (req, res) => {
335 try {
336 const { deviceType } = req.query;
337 const brands = await storage.getDeviceBrands(deviceType as string);
338 res.json(brands);
339 } catch (error) {
340 console.error("Error fetching device brands:", error);
341 res.status(500).json({ message: "Failed to fetch device brands" });
342 }
343 });
344
345 // Device Models routes
346 app.get("/api/device-models", async (req, res) => {
347 try {
348 const { brandId, deviceType, search } = req.query;
349
350 if (search) {
351 const models = await storage.searchDeviceModels(search as string, deviceType as string);
352 res.json(models);
353 } else {
354 const models = await storage.getDeviceModels(brandId as string, deviceType as string);
355 res.json(models);
356 }
357 } catch (error) {
358 console.error("Error fetching device models:", error);
359 res.status(500).json({ message: "Failed to fetch device models" });
360 }
361 });
362
363 app.get("/api/device-models/:id", async (req, res) => {
364 try {
365 const { id } = req.params;
366 const model = await storage.getDeviceModel(id);
367 if (!model) {
368 return res.status(404).json({ message: "Device model not found" });
369 }
370 res.json(model);
371 } catch (error) {
372 console.error("Error fetching device model:", error);
373 res.status(500).json({ message: "Failed to fetch device model" });
374 }
375 });
376
377 app.get("/api/brands/:brandId/models", async (req, res) => {
378 try {
379 const { brandId } = req.params;
380 const models = await storage.getModelsByBrand(brandId);
381 res.json(models);
382 } catch (error) {
383 console.error("Error fetching models by brand:", error);
384 res.status(500).json({ message: "Failed to fetch models by brand" });
385 }
386 });
387
388 // Guide Compatibility routes
389 app.get("/api/models/:modelId/guides", async (req, res) => {
390 try {
391 const { modelId } = req.params;
392 const guides = await storage.getCompatibleGuides(modelId);
393 res.json(guides);
394 } catch (error) {
395 console.error("Error fetching compatible guides:", error);
396 res.status(500).json({ message: "Failed to fetch compatible guides" });
397 }
398 });
399
400 app.get("/api/guides/:guideId/models", async (req, res) => {
401 try {
402 const { guideId } = req.params;
403 const models = await storage.getModelCompatibility(guideId);
404 res.json(models);
405 } catch (error) {
406 console.error("Error fetching model compatibility:", error);
407 res.status(500).json({ message: "Failed to fetch model compatibility" });
408 }
409 });
410
411 // Digital Asset Links for TWA
412 app.get("/.well-known/assetlinks.json", (req, res) => {
413 res.json([{
414 relation: ["delegate_permission/common.handle_all_urls"],
415 target: {
416 namespace: "android_app",
417 package_name: "com.jcrguidepro.app",
418 sha256_cert_fingerprints: ["73:8B:D2:DB:FC:57:4D:5B:F1:A5:28:C5:01:B6:17:C7:7F:80:74:EE:B5:FD:0C:DB:12:CE:03:C8:35:D3:08:BE"]
419 }
420 }]);
421 });
422
423 const httpServer = createServer(app);
424 return httpServer;
425 }