@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 +215 -0
- package/docs/SRS.md +311 -0
- package/docs/openapi.yaml +344 -0
- package/index.js +537 -0
- package/package.json +7 -3
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.
|
|
4
|
-
"main": "
|
|
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'",
|