@shyntech-proximity/inventories 1.0.2 โ†’ 1.0.4

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.
package/docs/QA.md ADDED
@@ -0,0 +1,215 @@
1
+ # ๐Ÿงช QA Test Matrix
2
+
3
+ ## Proximity Inventory Backend Service (Inventory Domain)
4
+
5
+ ---
6
+
7
+ ## 1. Test Coverage Overview
8
+
9
+ | Area | Covered |
10
+ | ----------------------------- | ------- |
11
+ | Proximity inventory discovery | โœ… |
12
+ | Inventory lookup | โœ… |
13
+ | Inventory search | โœ… |
14
+ | Vendor inventory retrieval | โœ… |
15
+ | Inventory CRUD | โœ… |
16
+ | Bulk inventory operations | โœ… |
17
+ | Error handling | โœ… |
18
+ | Non-functional constraints | โœ… |
19
+
20
+ ---
21
+
22
+ ## 2. Proximity Inventory Discovery Tests
23
+
24
+ ### Endpoint: `GET /inventory`
25
+
26
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
27
+ | ------- | -------------------------- | ---------------------------- | -------------------- | ------------------------------------ |
28
+ | P-01 | Valid proximity match | DeviceScan exists with SSIDs | Valid device headers | 200, list of vendors |
29
+ | P-02 | No proximity data | No DeviceScan for device | Valid headers | 404, error message |
30
+ | P-03 | Multiple SSID matches | Scan overlaps >1 vendor | Valid headers | 200, multiple vendors |
31
+ | P-04 | Header priority resolution | All headers present | All headers | Device resolved using priority order |
32
+ | P-05 | Fallback to socket IP | No headers present | None | Uses socket IP |
33
+ | P-06 | Empty SSID list | Scan exists but no SSIDs | Valid headers | 200, empty vendor list |
34
+ | P-07 | Server failure | DB unavailable | Valid headers | 500 |
35
+
36
+ ---
37
+
38
+ ## 3. Inventory Item Lookup Tests
39
+
40
+ ### Endpoint: `GET /inventory/:itemId`
41
+
42
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
43
+ | ------- | ------------------ | -------------------------- | -------------- | ----------------------------- |
44
+ | I-01 | Valid item lookup | Item exists | Valid itemId | 200, vendor + item |
45
+ | I-02 | Item not found | Item absent | Invalid itemId | 404 |
46
+ | I-03 | Duplicate item IDs | Same itemId across vendors | Valid itemId | First matched vendor returned |
47
+ | I-04 | Malformed itemId | Invalid format | Malformed ID | 404 |
48
+ | I-05 | DB error | DB failure | Valid itemId | 500 |
49
+
50
+ ---
51
+
52
+ ## 4. Inventory Search Tests
53
+
54
+ ### Endpoint: `GET /search?query=...`
55
+
56
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
57
+ | ------- | -------------------- | -------------------- | ---------------- | ------------------ |
58
+ | S-01 | Match inventory name | Matching item exists | `query=name` | 200, matched items |
59
+ | S-02 | Match category | Category exists | `query=category` | 200 |
60
+ | S-03 | Match tags | Tag exists | `query=tag` | 200 |
61
+ | S-04 | Match vendor name | Vendor matches | `query=vendor` | Vendor returned |
62
+ | S-05 | Match vendor address | Address matches | `query=address` | Vendor returned |
63
+ | S-06 | Case-insensitive | Mixed case | `query=MiXeD` | Match returned |
64
+ | S-07 | No matches | No matching fields | `query=zzz` | 200, empty results |
65
+ | S-08 | Empty query | โ€” | `query=` | 400 |
66
+ | S-09 | Missing query | โ€” | No query param | 400 |
67
+ | S-10 | Special characters | Regex-safe input | `query=@#$` | 200 or empty |
68
+
69
+ ---
70
+
71
+ ## 5. Vendor Inventory Retrieval Tests
72
+
73
+ ### Endpoint: `GET /vendor/:vendorId/inventory`
74
+
75
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
76
+ | ------- | ------------------------ | --------------- | ---------------- | -------------------- |
77
+ | V-01 | Valid vendor inventory | Vendor exists | Valid vendorId | 200, inventory array |
78
+ | V-02 | Vendor with no inventory | Empty inventory | Valid vendorId | 200, empty array |
79
+ | V-03 | Vendor not found | Vendor missing | Invalid vendorId | 404 |
80
+ | V-04 | Malformed vendorId | Invalid format | Malformed ID | 404 |
81
+ | V-05 | DB failure | DB unavailable | Valid vendorId | 500 |
82
+
83
+ ---
84
+
85
+ ## 6. Single Inventory CRUD Tests
86
+
87
+ ### 6.1 Add Inventory Item
88
+
89
+ **POST `/vendor/:vendorId/inventory`**
90
+
91
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
92
+ | ------- | ----------------- | ---------------- | --------------- | -------------------- |
93
+ | C-01 | Add valid item | Vendor exists | Valid item body | 200, item added |
94
+ | C-02 | Vendor not found | Vendor absent | Valid body | 404 |
95
+ | C-03 | Duplicate item_id | Same item exists | Same item_id | 200 (no enforcement) |
96
+ | C-04 | Missing item_id | โ€” | Invalid body | 200 or schema error |
97
+ | C-05 | DB failure | DB unavailable | Valid body | 500 |
98
+
99
+ ---
100
+
101
+ ### 6.2 Update Inventory Item
102
+
103
+ **PUT `/inventory/:itemId`**
104
+
105
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
106
+ | ------- | ----------------- | -------------- | --------------------- | --------------------------- |
107
+ | C-06 | Partial update | Item exists | `{ price }` | 200, price updated |
108
+ | C-07 | Multiple fields | Item exists | `{ price, quantity }` | 200 |
109
+ | C-08 | Item not found | Item absent | Valid body | 404 |
110
+ | C-09 | Empty update body | Item exists | `{}` | 200, no change |
111
+ | C-10 | Invalid field | Item exists | `{ foo: 1 }` | Field added (no validation) |
112
+ | C-11 | DB failure | DB unavailable | Valid body | 500 |
113
+
114
+ ---
115
+
116
+ ### 6.3 Delete Inventory Item
117
+
118
+ **DELETE `/inventory/:itemId`**
119
+
120
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
121
+ | ------- | ----------------- | --------------- | -------------- | ------------------- |
122
+ | C-12 | Delete valid item | Item exists | Valid itemId | 200 |
123
+ | C-13 | Item not found | Item missing | Invalid itemId | 404 |
124
+ | C-14 | Multiple vendors | Item duplicated | Valid itemId | First match deleted |
125
+ | C-15 | DB failure | DB unavailable | Valid itemId | 500 |
126
+
127
+ ---
128
+
129
+ ## 7. Bulk Inventory Operations Tests
130
+
131
+ ### 7.1 Bulk Add
132
+
133
+ **POST `/vendor/:vendorId/inventory/bulk`**
134
+
135
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
136
+ | ------- | ------------------ | -------------- | ------------- | --------------- |
137
+ | B-01 | Add multiple items | Vendor exists | Valid items[] | 200 |
138
+ | B-02 | Empty items array | โ€” | `items: []` | 400 |
139
+ | B-03 | Invalid items type | โ€” | `items: {}` | 400 |
140
+ | B-04 | Vendor not found | Vendor missing | Valid items | 404 |
141
+ | B-05 | DB failure | DB unavailable | Valid items | 500 |
142
+
143
+ ---
144
+
145
+ ### 7.2 Bulk Update
146
+
147
+ **PUT `/inventory/:vendorId/bulk`**
148
+
149
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
150
+ | ------- | ----------------- | ---------------- | -------------- | -------------------- |
151
+ | B-06 | All items updated | Items exist | Valid updates | 200 |
152
+ | B-07 | Partial match | Some items exist | Mixed item_ids | 200, partial success |
153
+ | B-08 | No items array | โ€” | `{}` | 400 |
154
+ | B-09 | Empty array | โ€” | `[]` | 400 |
155
+ | B-10 | Vendor not found | Vendor absent | Valid array | 404 |
156
+ | B-11 | Invalid fields | Item exists | `{ foo }` | Field added |
157
+ | B-12 | DB failure | DB unavailable | Valid array | 500 |
158
+
159
+ ---
160
+
161
+ ### 7.3 Bulk Delete
162
+
163
+ **DELETE `/inventory`**
164
+
165
+ | Test ID | Scenario | Preconditions | Input | Expected Result |
166
+ | ------- | --------------------- | ---------------- | --------------- | --------------- |
167
+ | B-13 | Delete multiple items | Items exist | Valid itemIds[] | 200 |
168
+ | B-14 | Partial delete | Some items exist | Mixed itemIds | 200 |
169
+ | B-15 | Empty array | โ€” | `itemIds: []` | 400 |
170
+ | B-16 | Invalid type | โ€” | `itemIds: {}` | 400 |
171
+ | B-17 | DB failure | DB unavailable | Valid itemIds | 500 |
172
+
173
+ ---
174
+
175
+ ## 8. Non-Functional Test Matrix
176
+
177
+ ### Performance
178
+
179
+ | Test ID | Scenario | Metric | Expected |
180
+ | ------- | ------------------------ | ------------- | -------- |
181
+ | N-01 | Inventory lookup | Response time | < 500ms |
182
+ | N-02 | Search with many vendors | Response time | < 500ms |
183
+ | N-03 | Bulk update (100 items) | Completion | < 2s |
184
+
185
+ ---
186
+
187
+ ### Scalability & Integrity
188
+
189
+ | Test ID | Scenario | Expected Result |
190
+ | ------- | -------------------- | ------------------------ |
191
+ | N-04 | Concurrent updates | No document corruption |
192
+ | N-05 | Partial bulk failure | Successful items persist |
193
+ | N-06 | Large inventory | No DB write errors |
194
+
195
+ ---
196
+
197
+ ## 9. Known Risk Areas for QA
198
+
199
+ | Risk | Why It Matters |
200
+ | ------------------------- | ---------------------------------- |
201
+ | No uniqueness enforcement | Duplicate item IDs possible |
202
+ | Regex search | Performance degradation |
203
+ | Embedded inventory | MongoDB document size limits |
204
+ | No auth | Cross-vendor modification possible |
205
+
206
+ ---
207
+
208
+ ## 10. QA Execution Guide
209
+
210
+ | Role | Focus Areas |
211
+ | ------------ | ------------------------------ |
212
+ | Manual QA | Search, proximity, error flows |
213
+ | Automation | CRUD, bulk ops, regression |
214
+ | Load Testing | Search & bulk updates |
215
+
package/docs/SRS.md ADDED
@@ -0,0 +1,311 @@
1
+ # Software Requirements Specification (SRS)
2
+
3
+ ## Proximity Inventory Backend Service
4
+
5
+ ### **Inventory Domain Only**
6
+
7
+ ---
8
+
9
+ ## 1. Introduction
10
+
11
+ ### 1.1 Purpose
12
+
13
+ This document specifies the functional and non-functional requirements for the **Inventory domain** of the Proximity Inventory Backend Service.
14
+
15
+ The Inventory module enables:
16
+
17
+ * Vendor-linked inventory storage
18
+ * Proximity-based inventory discovery
19
+ * Text-based inventory search
20
+ * Single and bulk inventory management operations
21
+
22
+ ---
23
+
24
+ ### 1.2 Intended Audience
25
+
26
+ | Audience | Usage |
27
+ | --------------------------- | ---------------------------------------- |
28
+ | Product & Business | Understand inventory capabilities |
29
+ | Backend Engineers | Implement & maintain inventory APIs |
30
+ | Frontend / Mobile Engineers | Integrate inventory views & flows |
31
+ | QA / Test Engineers | Validate inventory behavior & edge cases |
32
+
33
+ ---
34
+
35
+ ### 1.3 Scope
36
+
37
+ | In Scope | Out of Scope |
38
+ | ----------------------------------- | ------------------------------ |
39
+ | Inventory CRUD (single & bulk) | Payments & orders |
40
+ | Inventory search | Analytics & reporting |
41
+ | Proximity-based inventory discovery | Device scan collection |
42
+ | Vendor-scoped inventory retrieval | Authentication & authorization |
43
+
44
+ ---
45
+
46
+ ### 1.4 Definitions
47
+
48
+ | Term | Description |
49
+ | -------------- | ----------------------------------------------------- |
50
+ | Inventory Item | A product or service offered by a vendor |
51
+ | Vendor | A business entity owning inventory |
52
+ | Proximity | Physical closeness inferred from detected Wi-Fi SSIDs |
53
+ | Bulk Operation | An API request affecting multiple inventory items |
54
+
55
+ ---
56
+
57
+ ## 2. System Overview
58
+
59
+ ### 2.1 Inventory Architecture Context
60
+
61
+ | Component | Responsibility |
62
+ | ------------------- | ---------------------------------------- |
63
+ | Client (Web/Mobile) | Requests inventory data |
64
+ | Inventory API | Inventory CRUD, search, proximity logic |
65
+ | MongoDB | Vendor documents with embedded inventory |
66
+ | DeviceScan Service | Supplies proximity data (read-only) |
67
+
68
+ ---
69
+
70
+ ### 2.2 Inventory Business Flow (Proximity)
71
+
72
+ | Step | Description |
73
+ | ---- | ------------------------------------- |
74
+ | 1 | Device performs Wi-Fi scan |
75
+ | 2 | Latest scan stored in `DeviceScan` |
76
+ | 3 | Client requests `/inventory` |
77
+ | 4 | Device identity resolved from headers |
78
+ | 5 | SSIDs matched against vendor SSIDs |
79
+ | 6 | Vendors with inventories returned |
80
+
81
+ ---
82
+
83
+ ## 3. Inventory Data Model Requirements
84
+
85
+ ### 3.1 Inventory Item Schema (Embedded)
86
+
87
+ | Field | Type | Required | Description |
88
+ | ------------------ | -------- | -------- | --------------------------- |
89
+ | item_id | string | Yes | Unique inventory identifier |
90
+ | name | string | Yes | Item display name |
91
+ | category | string | No | Item category |
92
+ | tags | string[] | No | Searchable keywords |
93
+ | price | number | No | Item price |
94
+ | quantity_available | number | No | Available stock count |
95
+
96
+ ---
97
+
98
+ ### 3.2 Inventory Storage Model
99
+
100
+ | Aspect | Design Choice |
101
+ | --------------- | --------------------------------- |
102
+ | Storage | Embedded inside Vendor document |
103
+ | Cardinality | One vendor โ†’ many inventory items |
104
+ | Lookup method | Positional `$` operator |
105
+ | Deletion method | `$pull` on inventory array |
106
+
107
+ ---
108
+
109
+ ## 4. Proximity Resolution (Inventory Context)
110
+
111
+ ### 4.1 Device Identification Priority
112
+
113
+ | Priority | Source |
114
+ | -------- | --------------------- |
115
+ | 1 | `x-device-sig` header |
116
+ | 2 | `public_ip` header |
117
+ | 3 | `x-ws-token` header |
118
+ | 4 | Socket remote address |
119
+
120
+ ---
121
+
122
+ ### 4.2 Inventory Proximity Matching Rule
123
+
124
+ | Rule | Description |
125
+ | ------------------ | -------------------------------------- |
126
+ | Matching condition | Vendor SSID โˆฉ detected Wi-Fi SSIDs โ‰  โˆ… |
127
+ | Data source | Latest `DeviceScan` per device |
128
+ | Result | Vendor inventory considered nearby |
129
+
130
+ ---
131
+
132
+ ## 5. Functional Requirements (Inventory APIs)
133
+
134
+ ---
135
+
136
+ ### 5.1 Proximity Inventory Discovery
137
+
138
+ | Attribute | Value |
139
+ | --------- | ---------------------------------- |
140
+ | Endpoint | `GET /inventory` |
141
+ | Purpose | Retrieve nearby vendor inventories |
142
+ | Input | Device-identifying headers |
143
+ | Output | List of vendor objects |
144
+
145
+ **Responses**
146
+
147
+ | Code | Condition |
148
+ | ---- | ---------------------------- |
149
+ | 200 | Vendors with inventory found |
150
+ | 404 | No proximity data available |
151
+ | 500 | Internal server error |
152
+
153
+ ---
154
+
155
+ ### 5.2 Inventory Item Lookup
156
+
157
+ | Attribute | Value |
158
+ | --------- | ------------------------- |
159
+ | Endpoint | `GET /inventory/:itemId` |
160
+ | Purpose | Retrieve item with vendor |
161
+ | Input | `itemId` |
162
+ | Output | Vendor info + item |
163
+
164
+ ---
165
+
166
+ ### 5.3 Inventory Search
167
+
168
+ | Attribute | Value |
169
+ | ------------ | ------------------------- |
170
+ | Endpoint | `GET /search` |
171
+ | Query Param | `query` |
172
+ | Search Scope | Vendor + inventory fields |
173
+ | Matching | Case-insensitive regex |
174
+
175
+ **Searchable Fields**
176
+
177
+ | Entity | Fields |
178
+ | --------- | -------------------- |
179
+ | Vendor | name, address |
180
+ | Inventory | name, category, tags |
181
+
182
+ ---
183
+
184
+ ### 5.4 Inventory Retrieval by Vendor
185
+
186
+ | Attribute | Value |
187
+ | --------- | --------------------------------- |
188
+ | Endpoint | `GET /vendor/:vendorId/inventory` |
189
+ | Purpose | Retrieve inventory for a vendor |
190
+ | Output | Inventory array only |
191
+
192
+ ---
193
+
194
+ ## 6. Inventory Management Operations
195
+
196
+ ---
197
+
198
+ ### 6.1 Single Inventory Item Operations
199
+
200
+ | Action | Method | Endpoint |
201
+ | -----: | ------ | ----------------------------- |
202
+ | Add | POST | `/vendor/:vendorId/inventory` |
203
+ | Update | PUT | `/inventory/:itemId` |
204
+ | Delete | DELETE | `/inventory/:itemId` |
205
+
206
+ **Update Rules**
207
+
208
+ | Rule | Description |
209
+ | -------------- | --------------------------------- |
210
+ | Partial update | Only supplied fields updated |
211
+ | Targeting | First matched `inventory.item_id` |
212
+
213
+ ---
214
+
215
+ ### 6.2 Bulk Inventory Operations
216
+
217
+ | Action | Method | Endpoint |
218
+ | ----------- | ------ | ---------------------------------- |
219
+ | Bulk add | POST | `/vendor/:vendorId/inventory/bulk` |
220
+ | Bulk update | PUT | `/inventory/:vendorId/bulk` |
221
+ | Bulk delete | DELETE | `/inventory` |
222
+
223
+ **Bulk Behavior Guarantees**
224
+
225
+ | Guarantee | Description |
226
+ | --------------- | ------------------------------------ |
227
+ | Execution | Best-effort |
228
+ | Partial success | Allowed |
229
+ | Isolation | Per-item atomic updates |
230
+ | Feedback | Per-item match & modification counts |
231
+
232
+ ---
233
+
234
+ ## 7. Error Handling
235
+
236
+ | HTTP Code | Meaning |
237
+ | --------- | --------------------------- |
238
+ | 400 | Invalid input |
239
+ | 404 | Inventory or vendor missing |
240
+ | 500 | Internal server error |
241
+
242
+ ---
243
+
244
+ ## 8. Non-Functional Requirements
245
+
246
+ ### 8.1 Performance
247
+
248
+ | Metric | Requirement |
249
+ | ---------------- | ----------- |
250
+ | Typical response | < 500 ms |
251
+ | Bulk update size | โ‰ฅ 100 items |
252
+
253
+ ---
254
+
255
+ ### 8.2 Scalability
256
+
257
+ | Aspect | Requirement |
258
+ | ---------------- | ---------------------------------------------------- |
259
+ | API | Stateless |
260
+ | Scaling | Horizontal |
261
+ | Required Indexes | `inventory.item_id`, `vendor_info.vendor_id`, `ssid` |
262
+
263
+ ---
264
+
265
+ ### 8.3 Reliability & Data Integrity
266
+
267
+ | Requirement | Description |
268
+ | ----------- | ------------------------------------------- |
269
+ | Atomicity | Per-item inventory update |
270
+ | Consistency | Inventory updates do not overwrite siblings |
271
+
272
+ ---
273
+
274
+ ### 8.4 Security Assumptions
275
+
276
+ | Area | Assumption |
277
+ | -------------- | ----------------------------------- |
278
+ | Authentication | Handled upstream |
279
+ | Authorization | Vendor ownership validated upstream |
280
+ | Validation | Required for all inventory inputs |
281
+
282
+ ---
283
+
284
+ ## 9. Known Limitations
285
+
286
+ | Limitation | Impact |
287
+ | ------------------ | ------------------------- |
288
+ | Embedded inventory | Document size growth risk |
289
+ | Regex search | Not index-optimized |
290
+ | No pagination | Large payload responses |
291
+ | No inventory locks | Concurrent edits possible |
292
+
293
+ ---
294
+
295
+ ## 10. Future Enhancements
296
+
297
+ | Feature | Description |
298
+ | -------------------- | ------------------------------ |
299
+ | Pagination | Inventory & search results |
300
+ | Soft deletes | Inventory lifecycle management |
301
+ | Stock thresholds | Low-stock alerts |
302
+ | Inventory versioning | Audit & rollback support |
303
+
304
+ ---
305
+
306
+ ## 11. Reading Guide
307
+
308
+ | Role | Sections |
309
+ | ----------------- | --------- |
310
+ | Stakeholders | 1โ€“4, 8โ€“10 |
311
+ | Backend Engineers | 3โ€“10 |
@@ -0,0 +1,344 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: Proximity Inventory Backend API
4
+ description: |
5
+ Inventory domain APIs for the Proximity platform.
6
+ Supports proximity-based discovery, inventory search,
7
+ and single/bulk inventory management.
8
+ version: 1.0.0
9
+
10
+ servers:
11
+ - url: https://www.proximityapp.ai
12
+ description: Production
13
+ - url: http://localhost:5000
14
+ description: Local development
15
+
16
+ tags:
17
+ - name: Inventory
18
+ description: Inventory discovery and management
19
+ - name: Search
20
+ description: Text-based inventory search
21
+ - name: Proximity
22
+ description: Proximity-based inventory discovery
23
+
24
+ paths:
25
+
26
+ /inventory:
27
+ get:
28
+ tags: [Proximity, Inventory]
29
+ summary: Get nearby vendor inventories
30
+ description: |
31
+ Returns vendors whose SSIDs match the latest
32
+ DeviceScan associated with the requesting device.
33
+ parameters:
34
+ - name: x-device-sig
35
+ in: header
36
+ schema: { type: string }
37
+ - name: public_ip
38
+ in: header
39
+ schema: { type: string }
40
+ - name: x-ws-token
41
+ in: header
42
+ schema: { type: string }
43
+ responses:
44
+ "200":
45
+ description: Vendors found
46
+ content:
47
+ application/json:
48
+ schema:
49
+ type: array
50
+ items:
51
+ $ref: "#/components/schemas/Vendor"
52
+ "404":
53
+ description: No proximity data found
54
+ "500":
55
+ description: Internal server error
56
+
57
+ delete:
58
+ tags: [Inventory]
59
+ summary: Bulk delete inventory items
60
+ description: Deletes multiple inventory items across vendors.
61
+ requestBody:
62
+ required: true
63
+ content:
64
+ application/json:
65
+ schema:
66
+ type: object
67
+ required: [itemIds]
68
+ properties:
69
+ itemIds:
70
+ type: array
71
+ items:
72
+ type: string
73
+ responses:
74
+ "200":
75
+ description: Inventory items deleted
76
+ content:
77
+ application/json:
78
+ schema:
79
+ type: object
80
+ properties:
81
+ deletedCount:
82
+ type: number
83
+ "400":
84
+ description: Invalid request
85
+ "500":
86
+ description: Internal server error
87
+
88
+ /inventory/{itemId}:
89
+ get:
90
+ tags: [Inventory]
91
+ summary: Get inventory item by ID
92
+ parameters:
93
+ - name: itemId
94
+ in: path
95
+ required: true
96
+ schema: { type: string }
97
+ responses:
98
+ "200":
99
+ description: Inventory item found
100
+ content:
101
+ application/json:
102
+ schema:
103
+ type: object
104
+ properties:
105
+ vendor:
106
+ $ref: "#/components/schemas/VendorInfo"
107
+ item:
108
+ $ref: "#/components/schemas/InventoryItem"
109
+ "404":
110
+ description: Inventory item not found
111
+ "500":
112
+ description: Internal server error
113
+
114
+ put:
115
+ tags: [Inventory]
116
+ summary: Update inventory item
117
+ parameters:
118
+ - name: itemId
119
+ in: path
120
+ required: true
121
+ schema: { type: string }
122
+ requestBody:
123
+ required: true
124
+ content:
125
+ application/json:
126
+ schema:
127
+ $ref: "#/components/schemas/InventoryItemUpdate"
128
+ responses:
129
+ "200":
130
+ description: Inventory item updated
131
+ "404":
132
+ description: Inventory item not found
133
+ "500":
134
+ description: Internal server error
135
+
136
+ delete:
137
+ tags: [Inventory]
138
+ summary: Delete inventory item
139
+ parameters:
140
+ - name: itemId
141
+ in: path
142
+ required: true
143
+ schema: { type: string }
144
+ responses:
145
+ "200":
146
+ description: Inventory item deleted
147
+ "404":
148
+ description: Inventory item not found
149
+ "500":
150
+ description: Internal server error
151
+
152
+ /inventory/{vendorId}/bulk:
153
+ put:
154
+ tags: [Inventory]
155
+ summary: Bulk update inventory items for a vendor
156
+ parameters:
157
+ - name: vendorId
158
+ in: path
159
+ required: true
160
+ schema: { type: string }
161
+ requestBody:
162
+ required: true
163
+ content:
164
+ application/json:
165
+ schema:
166
+ type: array
167
+ items:
168
+ allOf:
169
+ - $ref: "#/components/schemas/InventoryItemUpdate"
170
+ - type: object
171
+ required: [item_id]
172
+ properties:
173
+ item_id:
174
+ type: string
175
+ responses:
176
+ "200":
177
+ description: Bulk update completed
178
+ "400":
179
+ description: Invalid request
180
+ "404":
181
+ description: Vendor not found
182
+ "500":
183
+ description: Internal server error
184
+
185
+ /vendor/{vendorId}/inventory:
186
+ get:
187
+ tags: [Inventory]
188
+ summary: Get inventory for a vendor
189
+ parameters:
190
+ - name: vendorId
191
+ in: path
192
+ required: true
193
+ schema: { type: string }
194
+ responses:
195
+ "200":
196
+ description: Inventory list
197
+ content:
198
+ application/json:
199
+ schema:
200
+ type: array
201
+ items:
202
+ $ref: "#/components/schemas/InventoryItem"
203
+ "404":
204
+ description: Vendor not found
205
+ "500":
206
+ description: Internal server error
207
+
208
+ post:
209
+ tags: [Inventory]
210
+ summary: Add inventory item to vendor
211
+ parameters:
212
+ - name: vendorId
213
+ in: path
214
+ required: true
215
+ schema: { type: string }
216
+ requestBody:
217
+ required: true
218
+ content:
219
+ application/json:
220
+ schema:
221
+ $ref: "#/components/schemas/InventoryItem"
222
+ responses:
223
+ "200":
224
+ description: Inventory item added
225
+ "404":
226
+ description: Vendor not found
227
+ "500":
228
+ description: Internal server error
229
+
230
+ /vendor/{vendorId}/inventory/bulk:
231
+ post:
232
+ tags: [Inventory]
233
+ summary: Bulk add inventory items to vendor
234
+ parameters:
235
+ - name: vendorId
236
+ in: path
237
+ required: true
238
+ schema: { type: string }
239
+ requestBody:
240
+ required: true
241
+ content:
242
+ application/json:
243
+ schema:
244
+ type: object
245
+ required: [items]
246
+ properties:
247
+ items:
248
+ type: array
249
+ items:
250
+ $ref: "#/components/schemas/InventoryItem"
251
+ responses:
252
+ "200":
253
+ description: Inventory items added
254
+ "400":
255
+ description: Invalid request
256
+ "404":
257
+ description: Vendor not found
258
+ "500":
259
+ description: Internal server error
260
+
261
+ /search:
262
+ get:
263
+ tags: [Search]
264
+ summary: Search vendors and inventory
265
+ parameters:
266
+ - name: query
267
+ in: query
268
+ required: true
269
+ schema: { type: string }
270
+ responses:
271
+ "200":
272
+ description: Search results
273
+ content:
274
+ application/json:
275
+ schema:
276
+ type: object
277
+ properties:
278
+ query:
279
+ type: string
280
+ results:
281
+ type: array
282
+ items:
283
+ type: object
284
+ properties:
285
+ vendor_id:
286
+ type: string
287
+ vendor_name:
288
+ type: string
289
+ address:
290
+ type: string
291
+ inventory:
292
+ type: array
293
+ items:
294
+ $ref: "#/components/schemas/InventoryItem"
295
+ "400":
296
+ description: Query missing
297
+ "500":
298
+ description: Internal server error
299
+
300
+ components:
301
+ schemas:
302
+
303
+ Vendor:
304
+ type: object
305
+ properties:
306
+ vendor_info:
307
+ $ref: "#/components/schemas/VendorInfo"
308
+ inventory:
309
+ type: array
310
+ items:
311
+ $ref: "#/components/schemas/InventoryItem"
312
+
313
+ VendorInfo:
314
+ type: object
315
+ properties:
316
+ vendor_id:
317
+ type: string
318
+ name:
319
+ type: string
320
+ address:
321
+ type: string
322
+
323
+ InventoryItem:
324
+ type: object
325
+ required: [item_id, name]
326
+ properties:
327
+ item_id:
328
+ type: string
329
+ name:
330
+ type: string
331
+ category:
332
+ type: string
333
+ tags:
334
+ type: array
335
+ items:
336
+ type: string
337
+ price:
338
+ type: number
339
+ quantity_available:
340
+ type: number
341
+
342
+ InventoryItemUpdate:
343
+ type: object
344
+ additionalProperties: true
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,10 +1,14 @@
1
1
  {
2
2
  "name": "@shyntech-proximity/inventories",
3
- "version": "1.0.2",
4
- "main": "src/index.js",
3
+ "version": "1.0.4",
4
+ "main": "index.js",
5
5
  "files": [
6
6
  "dist",
7
- "src"
7
+ "src",
8
+ "index.js",
9
+ "models/",
10
+ "docs/",
11
+ "README.md"
8
12
  ],
9
13
  "scripts": {
10
14
  "build": "echo 'no build'",