@shyntech-proximity/inventories 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +537 -0
  2. package/package.json +2 -2
package/index.js ADDED
@@ -0,0 +1,537 @@
1
+ import express from "express";
2
+ /*import { DeviceScan } from "../models/DeviceScan.js";
3
+ import { Vendor } from "../models/Vendor.js";
4
+
5
+ const router = express.Router();
6
+ router.proximityCache = {};
7
+ /**
8
+ * Browser client calls this endpoint.
9
+ * Server finds last scan from same IP and returns matching vendor inventories.
10
+ */
11
+ /*
12
+ // 🧠 Fallback Matching Logic
13
+ function findProximityKey(req) {
14
+ const ip = req.headers["public_ip"];
15
+ const deviceSig = req.headers["x-device-sig"];
16
+ const token = req.headers["x-ws-token"];
17
+ console.log("Device: ", deviceSig);
18
+ if (deviceSig) return deviceSig;
19
+ if (ip) return ip;
20
+ if (token) return token;
21
+ return null;
22
+ }
23
+
24
+
25
+
26
+
27
+
28
+
29
+ router.get("/inventory/:itemId", async (req, res) => {
30
+ try {
31
+ const itemId = req.params.itemId;
32
+
33
+ // Find the vendor that contains the inventory item
34
+ const vendor = await Vendor.findOne(
35
+ { "inventory.item_id": itemId },
36
+ { "inventory.$": 1, vendor_info: 1 } // project only the matching inventory item and vendor_info
37
+ ).lean();
38
+
39
+ if (!vendor || !vendor.inventory || vendor.inventory.length === 0) {
40
+ return res.status(404).json({ error: "Inventory item not found" });
41
+ }
42
+
43
+ res.json({
44
+ vendor: vendor.vendor_info,
45
+ item: vendor.inventory[0]
46
+ });
47
+ } catch (error) {
48
+ console.error(error);
49
+ res.status(500).json({ error: "Internal server error" });
50
+ }
51
+ });
52
+
53
+
54
+
55
+
56
+
57
+ router.get("/search", async (req, res) => {
58
+ try {
59
+ const { query } = req.query;
60
+
61
+ if (!query || query.trim() === "") {
62
+ return res.status(400).json({ error: "Query parameter is required" });
63
+ }
64
+
65
+ const regex = new RegExp(query, "i"); // case-insensitive fuzzy match
66
+
67
+ // Search inventory items
68
+ const vendors = await Vendor.find({
69
+ $or: [
70
+ { "vendor_info.name": regex },
71
+ { "vendor_info.address": regex },
72
+ { "inventory.name": regex },
73
+ { "inventory.category": regex },
74
+ { "inventory.tags": regex }
75
+ ]
76
+ }).lean();
77
+
78
+ const results = vendors.map(vendor => {
79
+ // Filter inventory items in vendor that match the query
80
+ const matchingItems = vendor.inventory.filter(item =>
81
+ regex.test(item.name) ||
82
+ regex.test(item.category) ||
83
+ item.tags.some(tag => regex.test(tag))
84
+ );
85
+
86
+ return {
87
+ vendor_id: vendor.vendor_info.vendor_id,
88
+ vendor_name: vendor.vendor_info.name,
89
+ address: vendor.vendor_info.address,
90
+ inventory: matchingItems
91
+ };
92
+ }).filter(vendor => vendor.inventory.length > 0 || regex.test(vendor.vendor_name) || regex.test(vendor.address));
93
+
94
+ res.json({
95
+ query,
96
+ results
97
+ });
98
+ } catch (error) {
99
+ console.error(error);
100
+ res.status(500).json({ error: "Internal server error" });
101
+ }
102
+ });
103
+
104
+
105
+
106
+
107
+
108
+ router.get("/inventory", async (req, res) => {
109
+ const deviceId = findProximityKey(req)|| req.socket.remoteAddress;
110
+
111
+ try {
112
+ const lastScan = await DeviceScan.findOne({ device_id: deviceId })
113
+ .sort({ timestamp: -1 })
114
+ .lean();
115
+ if (!lastScan) {
116
+ return res.status(404).json({ message: "No recent proximity data found for this IP" });
117
+ }
118
+ const ssids = lastScan?.detected_wifi.map(w => w.ssid);
119
+ console.log(ssids)
120
+ const vendors = await Vendor.find({ ssid: { $in: ssids } }).lean();
121
+ console.log(vendors);
122
+ res.json(vendors);
123
+ } catch (error) {
124
+ console.error(error);
125
+ res.status(500).json({ error: "Internal server error" });
126
+ }
127
+ });
128
+
129
+
130
+
131
+
132
+ // =========================
133
+ // Get vendor by phoneNumber
134
+ // =========================
135
+ router.get("/vendor", async (req, res) => {
136
+ try {
137
+ const { phoneNumber } = req.query;
138
+ console.log(phoneNumber);
139
+ if (!phoneNumber) {
140
+ return res
141
+ .status(400)
142
+ .json({ error: "phoneNumber query parameter is required" });
143
+ }
144
+
145
+ // Find vendor using vendor_info.phoneNumber
146
+ const vendor = await Vendor.findOne({ "phoneNumber": phoneNumber })
147
+ .lean();
148
+
149
+ if (!vendor) {
150
+ return res.status(404).json({ error: "Vendor not found" });
151
+ }
152
+
153
+ res.json(vendor);
154
+
155
+ } catch (error) {
156
+ console.error("Error fetching vendor by phoneNumber:", error);
157
+ res.status(500).json({ error: "Internal server error" });
158
+ }
159
+ });
160
+
161
+
162
+
163
+
164
+
165
+
166
+ // Get inventory for a specific vendor
167
+ router.get("/vendor/:vendorId/inventory", async (req, res) => {
168
+ try {
169
+ const vendorId = req.params.vendorId;
170
+ // Find the vendor by vendor_info.vendor_id
171
+ const vendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId })
172
+ .select("inventory") // only return inventory and vendor_info
173
+ .lean();
174
+
175
+ if (!vendor) {
176
+ return res.status(404).json({ error: "Vendor not found" });
177
+ }
178
+
179
+ res.json(vendor.inventory);
180
+ } catch (error) {
181
+ console.error(error);
182
+ res.status(500).json({ error: "Internal server error" });
183
+ }
184
+ });
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+ // STORE MANAGEMENT STARTS HERE
193
+ router.get("/vendor/:vendorId", async (req, res) => {
194
+ try {
195
+ const vendorId = req.params.vendorId;
196
+ // vendor_id is inside vendor_info
197
+ const vendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId }).lean();
198
+
199
+ if (!vendor) {
200
+ return res.status(404).json({ error: "Vendor not found" });
201
+ }
202
+
203
+ res.json(vendor);
204
+ } catch (error) {
205
+ console.error(error);
206
+ res.status(500).json({ error: "Internal server error" });
207
+ }
208
+ });
209
+
210
+
211
+
212
+
213
+ // POST /vendor
214
+ // body: { vendor_id, name, address, contact, gps_location, opening_hours, inventory }
215
+ router.post("/vendor", async (req, res) => {
216
+ try {
217
+ const vendorData = req.body;
218
+
219
+ // Check if vendor already exists
220
+ const existing = await Vendor.findOne({ vendor_id: vendorData.vendor_id });
221
+ if (existing) {
222
+ return res.status(400).json({ error: "Vendor with this ID already exists" });
223
+ }
224
+
225
+ const newVendor = await Vendor.create(vendorData);
226
+ res.status(201).json({
227
+ message: "Vendor created successfully",
228
+ vendor: newVendor,
229
+ });
230
+ } catch (error) {
231
+ console.error(error);
232
+ res.status(500).json({ error: "Internal server error" });
233
+ }
234
+ });
235
+
236
+
237
+
238
+
239
+
240
+ // body: fields to update: { name, address, contact, gps_location, opening_hours, inventory }
241
+ // PUT /vendor/:vendorId
242
+ router.put("/vendor/:vendorId", async (req, res) => {
243
+ try {
244
+ const { vendorId } = req.params;
245
+ const updateData = req.body;
246
+
247
+ // Check if a vendor with this vendor_id exists
248
+ const existingVendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId });
249
+ if (!existingVendor) {
250
+ return res.status(404).json({ error: "Vendor not found" });
251
+ }
252
+
253
+ // Flatten nested updateData to update specific fields (so we don’t overwrite everything)
254
+ const setFields = {};
255
+ for (const [key, value] of Object.entries(updateData)) {
256
+ setFields[`vendor_info.${key}`] = value;
257
+ }
258
+
259
+ // Update vendor info
260
+ const updatedVendor = await Vendor.findOneAndUpdate(
261
+ { "vendor_info.vendor_id": vendorId },
262
+ { $set: setFields },
263
+ { new: true }
264
+ ).lean();
265
+
266
+ res.json({
267
+ message: "Vendor updated successfully",
268
+ vendor: updatedVendor,
269
+ });
270
+ } catch (error) {
271
+ console.error("Error updating vendor:", error);
272
+ res.status(500).json({ error: "Internal server error" });
273
+ }
274
+ });
275
+
276
+
277
+
278
+
279
+
280
+ // DELETE /vendor/:vendorId
281
+ router.delete("/vendor/:vendorId", async (req, res) => {
282
+ try {
283
+ const { vendorId } = req.params;
284
+
285
+ const deletedVendor = await Vendor.findOneAndDelete({ vendor_id: vendorId }).lean();
286
+
287
+ if (!deletedVendor) {
288
+ return res.status(404).json({ error: "Vendor not found" });
289
+ }
290
+
291
+ res.json({
292
+ message: "Vendor deleted successfully",
293
+ vendor: deletedVendor,
294
+ });
295
+ } catch (error) {
296
+ console.error(error);
297
+ res.status(500).json({ error: "Internal server error" });
298
+ }
299
+ });
300
+
301
+
302
+
303
+
304
+
305
+ //INVENTORY STARTS HERE
306
+
307
+
308
+
309
+ // Add a single inventory item to a vendor
310
+ router.post("/vendor/:vendorId/inventory", async (req, res) => {
311
+ try {
312
+ const vendorId = req.params.vendorId;
313
+ const newItem = req.body; // e.g., { item_id, name, category, price, ... }
314
+
315
+ const updatedVendor = await Vendor.findOneAndUpdate(
316
+ { vendor_id: vendorId },
317
+ { $push: { inventory: newItem } },
318
+ { new: true, projection: { inventory: 1, vendor_info: 1 } }
319
+ ).lean();
320
+
321
+ if (!updatedVendor) {
322
+ return res.status(404).json({ error: "Vendor not found" });
323
+ }
324
+
325
+ res.json({
326
+ message: "Inventory item added successfully",
327
+ vendor: updatedVendor.vendor_info,
328
+ item: newItem,
329
+ });
330
+ } catch (error) {
331
+ console.error(error);
332
+ res.status(500).json({ error: "Internal server error" });
333
+ }
334
+ });
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+ // Add multiple inventory items to a vendor
343
+ router.post("/vendor/:vendorId/inventory/bulk", async (req, res) => {
344
+ try {
345
+ const vendorId = req.params.vendorId;
346
+ const newItems = req.body.items; // e.g., [{ item_id, name, ... }, {...}]
347
+
348
+ if (!Array.isArray(newItems) || newItems.length === 0) {
349
+ return res.status(400).json({ error: "Items array is required" });
350
+ }
351
+
352
+ const updatedVendor = await Vendor.findOneAndUpdate(
353
+ { vendor_id: vendorId },
354
+ { $push: { inventory: { $each: newItems } } },
355
+ { new: true, projection: { inventory: 1, vendor_info: 1 } }
356
+ ).lean();
357
+
358
+ if (!updatedVendor) {
359
+ return res.status(404).json({ error: "Vendor not found" });
360
+ }
361
+
362
+ res.json({
363
+ message: `${newItems.length} inventory items added successfully`,
364
+ vendor: updatedVendor.vendor_info,
365
+ items: newItems,
366
+ });
367
+ } catch (error) {
368
+ console.error(error);
369
+ res.status(500).json({ error: "Internal server error" });
370
+ }
371
+ });
372
+
373
+
374
+
375
+
376
+
377
+
378
+ router.put("/inventory/:itemId", async (req, res) => {
379
+ try {
380
+ const itemId = req.params.itemId;
381
+ const updateData = req.body; // e.g., { price: 300, quantity_available: 50 }
382
+
383
+ // Find the vendor containing the item and update the matching inventory element
384
+ const result = await Vendor.findOneAndUpdate(
385
+ { "inventory.item_id": itemId },
386
+ { $set: Object.fromEntries(
387
+ Object.entries(updateData).map(([key, value]) => [`inventory.$.${key}`, value])
388
+ )
389
+ },
390
+ { new: true, projection: { "inventory.$": 1, vendor_info: 1 } } // return updated item
391
+ ).lean();
392
+
393
+ if (!result || !result.inventory || result.inventory.length === 0) {
394
+ return res.status(404).json({ error: "Inventory item not found" });
395
+ }
396
+
397
+ res.json({
398
+ message: "Inventory item updated successfully",
399
+ vendor: result.vendor_info,
400
+ item: result.inventory[0],
401
+ });
402
+ } catch (error) {
403
+ console.error(error);
404
+ res.status(500).json({ error: "Internal server error" });
405
+ }
406
+ });
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+ router.put("/inventory/:vendorId/bulk", async (req, res) => {
415
+ try {
416
+ const { vendorId } = req.params;
417
+ const itemsToUpdate = req.body;
418
+
419
+ if (!Array.isArray(itemsToUpdate) || itemsToUpdate.length === 0) {
420
+ return res.status(400).json({ error: "Items array is required" });
421
+ }
422
+
423
+ // Validate the vendor first
424
+ const vendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId });
425
+ if (!vendor) {
426
+ return res.status(404).json({ error: `Vendor ${vendorId} not found` });
427
+ }
428
+
429
+ // Perform updates in parallel
430
+ const updateResults = await Promise.all(
431
+ itemsToUpdate.map(async (item) => {
432
+ const { item_id, ...updates } = item;
433
+ const updateQuery = {};
434
+
435
+ // Dynamically build the update fields
436
+ for (const [key, value] of Object.entries(updates)) {
437
+ updateQuery[`inventory.$.${key}`] = value;
438
+ }
439
+
440
+ const result = await Vendor.updateOne(
441
+ { "vendor_info.vendor_id": vendorId, "inventory.item_id": item_id },
442
+ { $set: updateQuery }
443
+ );
444
+
445
+ return { item_id, matched: result.matchedCount, modified: result.modifiedCount };
446
+ })
447
+ );
448
+
449
+ // Fetch the fresh vendor document after updates
450
+ const updatedVendor = await Vendor.findOne({ "vendor_info.vendor_id": vendorId }).lean();
451
+
452
+
453
+ console.log(updateResults)
454
+
455
+ res.json({
456
+ message: `${updateResults.filter(r => r.modified > 0).length} of ${itemsToUpdate.length} items updated successfully`,
457
+ updateResults,
458
+ vendor: updatedVendor?.vendor_info,
459
+ inventory: updatedVendor?.inventory,
460
+ });
461
+
462
+ } catch (error) {
463
+ console.error("Bulk update error:", error);
464
+ res.status(500).json({ error: "Internal server error" });
465
+ }
466
+ });
467
+
468
+
469
+
470
+
471
+
472
+
473
+ // DELETE /inventory/:itemId
474
+ router.delete("/inventory/:itemId", async (req, res) => {
475
+ try {
476
+ const { itemId } = req.params;
477
+
478
+ const result = await Vendor.findOneAndUpdate(
479
+ { "inventory.item_id": itemId },
480
+ { $pull: { inventory: { item_id: itemId } } },
481
+ { new: true, projection: { vendor_info: 1 } }
482
+ ).lean();
483
+
484
+ if (!result) {
485
+ return res.status(404).json({ error: "Inventory item not found" });
486
+ }
487
+
488
+ res.json({
489
+ message: "Inventory item deleted successfully",
490
+ vendor: result.vendor_info,
491
+ });
492
+ } catch (error) {
493
+ console.error(error);
494
+ res.status(500).json({ error: "Internal server error" });
495
+ }
496
+ });
497
+
498
+
499
+
500
+
501
+
502
+
503
+ // DELETE /inventory
504
+ // body: { itemIds: ["INV001", "INV002", ...] }
505
+ router.delete("/inventory", async (req, res) => {
506
+ try {
507
+ const { itemIds } = req.body;
508
+
509
+ if (!Array.isArray(itemIds) || itemIds.length === 0) {
510
+ return res.status(400).json({ error: "itemIds array is required" });
511
+ }
512
+
513
+ const result = await Vendor.updateMany(
514
+ { "inventory.item_id": { $in: itemIds } },
515
+ { $pull: { inventory: { item_id: { $in: itemIds } } } }
516
+ );
517
+
518
+ res.json({
519
+ message: "Inventory items deleted successfully",
520
+ deletedCount: result.modifiedCount,
521
+ });
522
+ } catch (error) {
523
+ console.error(error);
524
+ res.status(500).json({ error: "Internal server error" });
525
+ }
526
+ });
527
+
528
+
529
+
530
+ */
531
+
532
+ const router = express.Router();
533
+ router.proximityCache = {};
534
+
535
+ export default router;
536
+
537
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shyntech-proximity/inventories",
3
- "version": "1.0.2",
4
- "main": "src/index.js",
3
+ "version": "1.0.3",
4
+ "main": "index.js",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"