openhands commited on
Commit
6d6fcca
·
1 Parent(s): d0e6aee

feat: AMK POS Backend API

Browse files
package-lock.json ADDED
@@ -0,0 +1,1039 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "amk-pos-backend",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "amk-pos-backend",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "bcryptjs": "^2.4.3",
12
+ "cors": "^2.8.5",
13
+ "express": "^4.18.2",
14
+ "express-validator": "^7.0.1",
15
+ "jsonwebtoken": "^9.0.2",
16
+ "uuid": "^9.0.1"
17
+ }
18
+ },
19
+ "node_modules/accepts": {
20
+ "version": "1.3.8",
21
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
22
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "mime-types": "~2.1.34",
26
+ "negotiator": "0.6.3"
27
+ },
28
+ "engines": {
29
+ "node": ">= 0.6"
30
+ }
31
+ },
32
+ "node_modules/array-flatten": {
33
+ "version": "1.1.1",
34
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
35
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
36
+ "license": "MIT"
37
+ },
38
+ "node_modules/bcryptjs": {
39
+ "version": "2.4.3",
40
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
41
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
42
+ "license": "MIT"
43
+ },
44
+ "node_modules/body-parser": {
45
+ "version": "1.20.5",
46
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
47
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
48
+ "license": "MIT",
49
+ "dependencies": {
50
+ "bytes": "~3.1.2",
51
+ "content-type": "~1.0.5",
52
+ "debug": "2.6.9",
53
+ "depd": "2.0.0",
54
+ "destroy": "~1.2.0",
55
+ "http-errors": "~2.0.1",
56
+ "iconv-lite": "~0.4.24",
57
+ "on-finished": "~2.4.1",
58
+ "qs": "~6.15.1",
59
+ "raw-body": "~2.5.3",
60
+ "type-is": "~1.6.18",
61
+ "unpipe": "~1.0.0"
62
+ },
63
+ "engines": {
64
+ "node": ">= 0.8",
65
+ "npm": "1.2.8000 || >= 1.4.16"
66
+ }
67
+ },
68
+ "node_modules/body-parser/node_modules/qs": {
69
+ "version": "6.15.1",
70
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
71
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
72
+ "license": "BSD-3-Clause",
73
+ "dependencies": {
74
+ "side-channel": "^1.1.0"
75
+ },
76
+ "engines": {
77
+ "node": ">=0.6"
78
+ },
79
+ "funding": {
80
+ "url": "https://github.com/sponsors/ljharb"
81
+ }
82
+ },
83
+ "node_modules/buffer-equal-constant-time": {
84
+ "version": "1.0.1",
85
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
86
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
87
+ "license": "BSD-3-Clause"
88
+ },
89
+ "node_modules/bytes": {
90
+ "version": "3.1.2",
91
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
92
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
93
+ "license": "MIT",
94
+ "engines": {
95
+ "node": ">= 0.8"
96
+ }
97
+ },
98
+ "node_modules/call-bind-apply-helpers": {
99
+ "version": "1.0.2",
100
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
101
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
102
+ "license": "MIT",
103
+ "dependencies": {
104
+ "es-errors": "^1.3.0",
105
+ "function-bind": "^1.1.2"
106
+ },
107
+ "engines": {
108
+ "node": ">= 0.4"
109
+ }
110
+ },
111
+ "node_modules/call-bound": {
112
+ "version": "1.0.4",
113
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
114
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
115
+ "license": "MIT",
116
+ "dependencies": {
117
+ "call-bind-apply-helpers": "^1.0.2",
118
+ "get-intrinsic": "^1.3.0"
119
+ },
120
+ "engines": {
121
+ "node": ">= 0.4"
122
+ },
123
+ "funding": {
124
+ "url": "https://github.com/sponsors/ljharb"
125
+ }
126
+ },
127
+ "node_modules/content-disposition": {
128
+ "version": "0.5.4",
129
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
130
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
131
+ "license": "MIT",
132
+ "dependencies": {
133
+ "safe-buffer": "5.2.1"
134
+ },
135
+ "engines": {
136
+ "node": ">= 0.6"
137
+ }
138
+ },
139
+ "node_modules/content-type": {
140
+ "version": "1.0.5",
141
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
142
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
143
+ "license": "MIT",
144
+ "engines": {
145
+ "node": ">= 0.6"
146
+ }
147
+ },
148
+ "node_modules/cookie": {
149
+ "version": "0.7.2",
150
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
151
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
152
+ "license": "MIT",
153
+ "engines": {
154
+ "node": ">= 0.6"
155
+ }
156
+ },
157
+ "node_modules/cookie-signature": {
158
+ "version": "1.0.7",
159
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
160
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
161
+ "license": "MIT"
162
+ },
163
+ "node_modules/cors": {
164
+ "version": "2.8.6",
165
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
166
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
167
+ "license": "MIT",
168
+ "dependencies": {
169
+ "object-assign": "^4",
170
+ "vary": "^1"
171
+ },
172
+ "engines": {
173
+ "node": ">= 0.10"
174
+ },
175
+ "funding": {
176
+ "type": "opencollective",
177
+ "url": "https://opencollective.com/express"
178
+ }
179
+ },
180
+ "node_modules/debug": {
181
+ "version": "2.6.9",
182
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
183
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
184
+ "license": "MIT",
185
+ "dependencies": {
186
+ "ms": "2.0.0"
187
+ }
188
+ },
189
+ "node_modules/depd": {
190
+ "version": "2.0.0",
191
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
192
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
193
+ "license": "MIT",
194
+ "engines": {
195
+ "node": ">= 0.8"
196
+ }
197
+ },
198
+ "node_modules/destroy": {
199
+ "version": "1.2.0",
200
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
201
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
202
+ "license": "MIT",
203
+ "engines": {
204
+ "node": ">= 0.8",
205
+ "npm": "1.2.8000 || >= 1.4.16"
206
+ }
207
+ },
208
+ "node_modules/dunder-proto": {
209
+ "version": "1.0.1",
210
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
211
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
212
+ "license": "MIT",
213
+ "dependencies": {
214
+ "call-bind-apply-helpers": "^1.0.1",
215
+ "es-errors": "^1.3.0",
216
+ "gopd": "^1.2.0"
217
+ },
218
+ "engines": {
219
+ "node": ">= 0.4"
220
+ }
221
+ },
222
+ "node_modules/ecdsa-sig-formatter": {
223
+ "version": "1.0.11",
224
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
225
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
226
+ "license": "Apache-2.0",
227
+ "dependencies": {
228
+ "safe-buffer": "^5.0.1"
229
+ }
230
+ },
231
+ "node_modules/ee-first": {
232
+ "version": "1.1.1",
233
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
234
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
235
+ "license": "MIT"
236
+ },
237
+ "node_modules/encodeurl": {
238
+ "version": "2.0.0",
239
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
240
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
241
+ "license": "MIT",
242
+ "engines": {
243
+ "node": ">= 0.8"
244
+ }
245
+ },
246
+ "node_modules/es-define-property": {
247
+ "version": "1.0.1",
248
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
249
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
250
+ "license": "MIT",
251
+ "engines": {
252
+ "node": ">= 0.4"
253
+ }
254
+ },
255
+ "node_modules/es-errors": {
256
+ "version": "1.3.0",
257
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
258
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
259
+ "license": "MIT",
260
+ "engines": {
261
+ "node": ">= 0.4"
262
+ }
263
+ },
264
+ "node_modules/es-object-atoms": {
265
+ "version": "1.1.1",
266
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
267
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
268
+ "license": "MIT",
269
+ "dependencies": {
270
+ "es-errors": "^1.3.0"
271
+ },
272
+ "engines": {
273
+ "node": ">= 0.4"
274
+ }
275
+ },
276
+ "node_modules/escape-html": {
277
+ "version": "1.0.3",
278
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
279
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
280
+ "license": "MIT"
281
+ },
282
+ "node_modules/etag": {
283
+ "version": "1.8.1",
284
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
285
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
286
+ "license": "MIT",
287
+ "engines": {
288
+ "node": ">= 0.6"
289
+ }
290
+ },
291
+ "node_modules/express": {
292
+ "version": "4.22.1",
293
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
294
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
295
+ "license": "MIT",
296
+ "dependencies": {
297
+ "accepts": "~1.3.8",
298
+ "array-flatten": "1.1.1",
299
+ "body-parser": "~1.20.3",
300
+ "content-disposition": "~0.5.4",
301
+ "content-type": "~1.0.4",
302
+ "cookie": "~0.7.1",
303
+ "cookie-signature": "~1.0.6",
304
+ "debug": "2.6.9",
305
+ "depd": "2.0.0",
306
+ "encodeurl": "~2.0.0",
307
+ "escape-html": "~1.0.3",
308
+ "etag": "~1.8.1",
309
+ "finalhandler": "~1.3.1",
310
+ "fresh": "~0.5.2",
311
+ "http-errors": "~2.0.0",
312
+ "merge-descriptors": "1.0.3",
313
+ "methods": "~1.1.2",
314
+ "on-finished": "~2.4.1",
315
+ "parseurl": "~1.3.3",
316
+ "path-to-regexp": "~0.1.12",
317
+ "proxy-addr": "~2.0.7",
318
+ "qs": "~6.14.0",
319
+ "range-parser": "~1.2.1",
320
+ "safe-buffer": "5.2.1",
321
+ "send": "~0.19.0",
322
+ "serve-static": "~1.16.2",
323
+ "setprototypeof": "1.2.0",
324
+ "statuses": "~2.0.1",
325
+ "type-is": "~1.6.18",
326
+ "utils-merge": "1.0.1",
327
+ "vary": "~1.1.2"
328
+ },
329
+ "engines": {
330
+ "node": ">= 0.10.0"
331
+ },
332
+ "funding": {
333
+ "type": "opencollective",
334
+ "url": "https://opencollective.com/express"
335
+ }
336
+ },
337
+ "node_modules/express-validator": {
338
+ "version": "7.3.2",
339
+ "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.2.tgz",
340
+ "integrity": "sha512-ctLw1Vl6dXVH62dIQMDdTAQkrh480mkFuG6/SGXOaVlwPNukhRAe7EgJIMJ2TSAni8iwHBRp530zAZE5ZPF2IA==",
341
+ "license": "MIT",
342
+ "dependencies": {
343
+ "lodash": "^4.18.1",
344
+ "validator": "~13.15.23"
345
+ },
346
+ "engines": {
347
+ "node": ">= 8.0.0"
348
+ }
349
+ },
350
+ "node_modules/finalhandler": {
351
+ "version": "1.3.2",
352
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
353
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
354
+ "license": "MIT",
355
+ "dependencies": {
356
+ "debug": "2.6.9",
357
+ "encodeurl": "~2.0.0",
358
+ "escape-html": "~1.0.3",
359
+ "on-finished": "~2.4.1",
360
+ "parseurl": "~1.3.3",
361
+ "statuses": "~2.0.2",
362
+ "unpipe": "~1.0.0"
363
+ },
364
+ "engines": {
365
+ "node": ">= 0.8"
366
+ }
367
+ },
368
+ "node_modules/forwarded": {
369
+ "version": "0.2.0",
370
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
371
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
372
+ "license": "MIT",
373
+ "engines": {
374
+ "node": ">= 0.6"
375
+ }
376
+ },
377
+ "node_modules/fresh": {
378
+ "version": "0.5.2",
379
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
380
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
381
+ "license": "MIT",
382
+ "engines": {
383
+ "node": ">= 0.6"
384
+ }
385
+ },
386
+ "node_modules/function-bind": {
387
+ "version": "1.1.2",
388
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
389
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
390
+ "license": "MIT",
391
+ "funding": {
392
+ "url": "https://github.com/sponsors/ljharb"
393
+ }
394
+ },
395
+ "node_modules/get-intrinsic": {
396
+ "version": "1.3.0",
397
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
398
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
399
+ "license": "MIT",
400
+ "dependencies": {
401
+ "call-bind-apply-helpers": "^1.0.2",
402
+ "es-define-property": "^1.0.1",
403
+ "es-errors": "^1.3.0",
404
+ "es-object-atoms": "^1.1.1",
405
+ "function-bind": "^1.1.2",
406
+ "get-proto": "^1.0.1",
407
+ "gopd": "^1.2.0",
408
+ "has-symbols": "^1.1.0",
409
+ "hasown": "^2.0.2",
410
+ "math-intrinsics": "^1.1.0"
411
+ },
412
+ "engines": {
413
+ "node": ">= 0.4"
414
+ },
415
+ "funding": {
416
+ "url": "https://github.com/sponsors/ljharb"
417
+ }
418
+ },
419
+ "node_modules/get-proto": {
420
+ "version": "1.0.1",
421
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
422
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
423
+ "license": "MIT",
424
+ "dependencies": {
425
+ "dunder-proto": "^1.0.1",
426
+ "es-object-atoms": "^1.0.0"
427
+ },
428
+ "engines": {
429
+ "node": ">= 0.4"
430
+ }
431
+ },
432
+ "node_modules/gopd": {
433
+ "version": "1.2.0",
434
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
435
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
436
+ "license": "MIT",
437
+ "engines": {
438
+ "node": ">= 0.4"
439
+ },
440
+ "funding": {
441
+ "url": "https://github.com/sponsors/ljharb"
442
+ }
443
+ },
444
+ "node_modules/has-symbols": {
445
+ "version": "1.1.0",
446
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
447
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
448
+ "license": "MIT",
449
+ "engines": {
450
+ "node": ">= 0.4"
451
+ },
452
+ "funding": {
453
+ "url": "https://github.com/sponsors/ljharb"
454
+ }
455
+ },
456
+ "node_modules/hasown": {
457
+ "version": "2.0.3",
458
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
459
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
460
+ "license": "MIT",
461
+ "dependencies": {
462
+ "function-bind": "^1.1.2"
463
+ },
464
+ "engines": {
465
+ "node": ">= 0.4"
466
+ }
467
+ },
468
+ "node_modules/http-errors": {
469
+ "version": "2.0.1",
470
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
471
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
472
+ "license": "MIT",
473
+ "dependencies": {
474
+ "depd": "~2.0.0",
475
+ "inherits": "~2.0.4",
476
+ "setprototypeof": "~1.2.0",
477
+ "statuses": "~2.0.2",
478
+ "toidentifier": "~1.0.1"
479
+ },
480
+ "engines": {
481
+ "node": ">= 0.8"
482
+ },
483
+ "funding": {
484
+ "type": "opencollective",
485
+ "url": "https://opencollective.com/express"
486
+ }
487
+ },
488
+ "node_modules/iconv-lite": {
489
+ "version": "0.4.24",
490
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
491
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
492
+ "license": "MIT",
493
+ "dependencies": {
494
+ "safer-buffer": ">= 2.1.2 < 3"
495
+ },
496
+ "engines": {
497
+ "node": ">=0.10.0"
498
+ }
499
+ },
500
+ "node_modules/inherits": {
501
+ "version": "2.0.4",
502
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
503
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
504
+ "license": "ISC"
505
+ },
506
+ "node_modules/ipaddr.js": {
507
+ "version": "1.9.1",
508
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
509
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
510
+ "license": "MIT",
511
+ "engines": {
512
+ "node": ">= 0.10"
513
+ }
514
+ },
515
+ "node_modules/jsonwebtoken": {
516
+ "version": "9.0.3",
517
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
518
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
519
+ "license": "MIT",
520
+ "dependencies": {
521
+ "jws": "^4.0.1",
522
+ "lodash.includes": "^4.3.0",
523
+ "lodash.isboolean": "^3.0.3",
524
+ "lodash.isinteger": "^4.0.4",
525
+ "lodash.isnumber": "^3.0.3",
526
+ "lodash.isplainobject": "^4.0.6",
527
+ "lodash.isstring": "^4.0.1",
528
+ "lodash.once": "^4.0.0",
529
+ "ms": "^2.1.1",
530
+ "semver": "^7.5.4"
531
+ },
532
+ "engines": {
533
+ "node": ">=12",
534
+ "npm": ">=6"
535
+ }
536
+ },
537
+ "node_modules/jsonwebtoken/node_modules/ms": {
538
+ "version": "2.1.3",
539
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
540
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
541
+ "license": "MIT"
542
+ },
543
+ "node_modules/jwa": {
544
+ "version": "2.0.1",
545
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
546
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
547
+ "license": "MIT",
548
+ "dependencies": {
549
+ "buffer-equal-constant-time": "^1.0.1",
550
+ "ecdsa-sig-formatter": "1.0.11",
551
+ "safe-buffer": "^5.0.1"
552
+ }
553
+ },
554
+ "node_modules/jws": {
555
+ "version": "4.0.1",
556
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
557
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
558
+ "license": "MIT",
559
+ "dependencies": {
560
+ "jwa": "^2.0.1",
561
+ "safe-buffer": "^5.0.1"
562
+ }
563
+ },
564
+ "node_modules/lodash": {
565
+ "version": "4.18.1",
566
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
567
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
568
+ "license": "MIT"
569
+ },
570
+ "node_modules/lodash.includes": {
571
+ "version": "4.3.0",
572
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
573
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
574
+ "license": "MIT"
575
+ },
576
+ "node_modules/lodash.isboolean": {
577
+ "version": "3.0.3",
578
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
579
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
580
+ "license": "MIT"
581
+ },
582
+ "node_modules/lodash.isinteger": {
583
+ "version": "4.0.4",
584
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
585
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
586
+ "license": "MIT"
587
+ },
588
+ "node_modules/lodash.isnumber": {
589
+ "version": "3.0.3",
590
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
591
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
592
+ "license": "MIT"
593
+ },
594
+ "node_modules/lodash.isplainobject": {
595
+ "version": "4.0.6",
596
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
597
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
598
+ "license": "MIT"
599
+ },
600
+ "node_modules/lodash.isstring": {
601
+ "version": "4.0.1",
602
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
603
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
604
+ "license": "MIT"
605
+ },
606
+ "node_modules/lodash.once": {
607
+ "version": "4.1.1",
608
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
609
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
610
+ "license": "MIT"
611
+ },
612
+ "node_modules/math-intrinsics": {
613
+ "version": "1.1.0",
614
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
615
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
616
+ "license": "MIT",
617
+ "engines": {
618
+ "node": ">= 0.4"
619
+ }
620
+ },
621
+ "node_modules/media-typer": {
622
+ "version": "0.3.0",
623
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
624
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
625
+ "license": "MIT",
626
+ "engines": {
627
+ "node": ">= 0.6"
628
+ }
629
+ },
630
+ "node_modules/merge-descriptors": {
631
+ "version": "1.0.3",
632
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
633
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
634
+ "license": "MIT",
635
+ "funding": {
636
+ "url": "https://github.com/sponsors/sindresorhus"
637
+ }
638
+ },
639
+ "node_modules/methods": {
640
+ "version": "1.1.2",
641
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
642
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
643
+ "license": "MIT",
644
+ "engines": {
645
+ "node": ">= 0.6"
646
+ }
647
+ },
648
+ "node_modules/mime": {
649
+ "version": "1.6.0",
650
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
651
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
652
+ "license": "MIT",
653
+ "bin": {
654
+ "mime": "cli.js"
655
+ },
656
+ "engines": {
657
+ "node": ">=4"
658
+ }
659
+ },
660
+ "node_modules/mime-db": {
661
+ "version": "1.52.0",
662
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
663
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
664
+ "license": "MIT",
665
+ "engines": {
666
+ "node": ">= 0.6"
667
+ }
668
+ },
669
+ "node_modules/mime-types": {
670
+ "version": "2.1.35",
671
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
672
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
673
+ "license": "MIT",
674
+ "dependencies": {
675
+ "mime-db": "1.52.0"
676
+ },
677
+ "engines": {
678
+ "node": ">= 0.6"
679
+ }
680
+ },
681
+ "node_modules/ms": {
682
+ "version": "2.0.0",
683
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
684
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
685
+ "license": "MIT"
686
+ },
687
+ "node_modules/negotiator": {
688
+ "version": "0.6.3",
689
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
690
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
691
+ "license": "MIT",
692
+ "engines": {
693
+ "node": ">= 0.6"
694
+ }
695
+ },
696
+ "node_modules/object-assign": {
697
+ "version": "4.1.1",
698
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
699
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
700
+ "license": "MIT",
701
+ "engines": {
702
+ "node": ">=0.10.0"
703
+ }
704
+ },
705
+ "node_modules/object-inspect": {
706
+ "version": "1.13.4",
707
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
708
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
709
+ "license": "MIT",
710
+ "engines": {
711
+ "node": ">= 0.4"
712
+ },
713
+ "funding": {
714
+ "url": "https://github.com/sponsors/ljharb"
715
+ }
716
+ },
717
+ "node_modules/on-finished": {
718
+ "version": "2.4.1",
719
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
720
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
721
+ "license": "MIT",
722
+ "dependencies": {
723
+ "ee-first": "1.1.1"
724
+ },
725
+ "engines": {
726
+ "node": ">= 0.8"
727
+ }
728
+ },
729
+ "node_modules/parseurl": {
730
+ "version": "1.3.3",
731
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
732
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
733
+ "license": "MIT",
734
+ "engines": {
735
+ "node": ">= 0.8"
736
+ }
737
+ },
738
+ "node_modules/path-to-regexp": {
739
+ "version": "0.1.13",
740
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
741
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
742
+ "license": "MIT"
743
+ },
744
+ "node_modules/proxy-addr": {
745
+ "version": "2.0.7",
746
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
747
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
748
+ "license": "MIT",
749
+ "dependencies": {
750
+ "forwarded": "0.2.0",
751
+ "ipaddr.js": "1.9.1"
752
+ },
753
+ "engines": {
754
+ "node": ">= 0.10"
755
+ }
756
+ },
757
+ "node_modules/qs": {
758
+ "version": "6.14.2",
759
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
760
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
761
+ "license": "BSD-3-Clause",
762
+ "dependencies": {
763
+ "side-channel": "^1.1.0"
764
+ },
765
+ "engines": {
766
+ "node": ">=0.6"
767
+ },
768
+ "funding": {
769
+ "url": "https://github.com/sponsors/ljharb"
770
+ }
771
+ },
772
+ "node_modules/range-parser": {
773
+ "version": "1.2.1",
774
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
775
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
776
+ "license": "MIT",
777
+ "engines": {
778
+ "node": ">= 0.6"
779
+ }
780
+ },
781
+ "node_modules/raw-body": {
782
+ "version": "2.5.3",
783
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
784
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
785
+ "license": "MIT",
786
+ "dependencies": {
787
+ "bytes": "~3.1.2",
788
+ "http-errors": "~2.0.1",
789
+ "iconv-lite": "~0.4.24",
790
+ "unpipe": "~1.0.0"
791
+ },
792
+ "engines": {
793
+ "node": ">= 0.8"
794
+ }
795
+ },
796
+ "node_modules/safe-buffer": {
797
+ "version": "5.2.1",
798
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
799
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
800
+ "funding": [
801
+ {
802
+ "type": "github",
803
+ "url": "https://github.com/sponsors/feross"
804
+ },
805
+ {
806
+ "type": "patreon",
807
+ "url": "https://www.patreon.com/feross"
808
+ },
809
+ {
810
+ "type": "consulting",
811
+ "url": "https://feross.org/support"
812
+ }
813
+ ],
814
+ "license": "MIT"
815
+ },
816
+ "node_modules/safer-buffer": {
817
+ "version": "2.1.2",
818
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
819
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
820
+ "license": "MIT"
821
+ },
822
+ "node_modules/semver": {
823
+ "version": "7.7.4",
824
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
825
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
826
+ "license": "ISC",
827
+ "bin": {
828
+ "semver": "bin/semver.js"
829
+ },
830
+ "engines": {
831
+ "node": ">=10"
832
+ }
833
+ },
834
+ "node_modules/send": {
835
+ "version": "0.19.2",
836
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
837
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
838
+ "license": "MIT",
839
+ "dependencies": {
840
+ "debug": "2.6.9",
841
+ "depd": "2.0.0",
842
+ "destroy": "1.2.0",
843
+ "encodeurl": "~2.0.0",
844
+ "escape-html": "~1.0.3",
845
+ "etag": "~1.8.1",
846
+ "fresh": "~0.5.2",
847
+ "http-errors": "~2.0.1",
848
+ "mime": "1.6.0",
849
+ "ms": "2.1.3",
850
+ "on-finished": "~2.4.1",
851
+ "range-parser": "~1.2.1",
852
+ "statuses": "~2.0.2"
853
+ },
854
+ "engines": {
855
+ "node": ">= 0.8.0"
856
+ }
857
+ },
858
+ "node_modules/send/node_modules/ms": {
859
+ "version": "2.1.3",
860
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
861
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
862
+ "license": "MIT"
863
+ },
864
+ "node_modules/serve-static": {
865
+ "version": "1.16.3",
866
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
867
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
868
+ "license": "MIT",
869
+ "dependencies": {
870
+ "encodeurl": "~2.0.0",
871
+ "escape-html": "~1.0.3",
872
+ "parseurl": "~1.3.3",
873
+ "send": "~0.19.1"
874
+ },
875
+ "engines": {
876
+ "node": ">= 0.8.0"
877
+ }
878
+ },
879
+ "node_modules/setprototypeof": {
880
+ "version": "1.2.0",
881
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
882
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
883
+ "license": "ISC"
884
+ },
885
+ "node_modules/side-channel": {
886
+ "version": "1.1.0",
887
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
888
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
889
+ "license": "MIT",
890
+ "dependencies": {
891
+ "es-errors": "^1.3.0",
892
+ "object-inspect": "^1.13.3",
893
+ "side-channel-list": "^1.0.0",
894
+ "side-channel-map": "^1.0.1",
895
+ "side-channel-weakmap": "^1.0.2"
896
+ },
897
+ "engines": {
898
+ "node": ">= 0.4"
899
+ },
900
+ "funding": {
901
+ "url": "https://github.com/sponsors/ljharb"
902
+ }
903
+ },
904
+ "node_modules/side-channel-list": {
905
+ "version": "1.0.1",
906
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
907
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
908
+ "license": "MIT",
909
+ "dependencies": {
910
+ "es-errors": "^1.3.0",
911
+ "object-inspect": "^1.13.4"
912
+ },
913
+ "engines": {
914
+ "node": ">= 0.4"
915
+ },
916
+ "funding": {
917
+ "url": "https://github.com/sponsors/ljharb"
918
+ }
919
+ },
920
+ "node_modules/side-channel-map": {
921
+ "version": "1.0.1",
922
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
923
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
924
+ "license": "MIT",
925
+ "dependencies": {
926
+ "call-bound": "^1.0.2",
927
+ "es-errors": "^1.3.0",
928
+ "get-intrinsic": "^1.2.5",
929
+ "object-inspect": "^1.13.3"
930
+ },
931
+ "engines": {
932
+ "node": ">= 0.4"
933
+ },
934
+ "funding": {
935
+ "url": "https://github.com/sponsors/ljharb"
936
+ }
937
+ },
938
+ "node_modules/side-channel-weakmap": {
939
+ "version": "1.0.2",
940
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
941
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
942
+ "license": "MIT",
943
+ "dependencies": {
944
+ "call-bound": "^1.0.2",
945
+ "es-errors": "^1.3.0",
946
+ "get-intrinsic": "^1.2.5",
947
+ "object-inspect": "^1.13.3",
948
+ "side-channel-map": "^1.0.1"
949
+ },
950
+ "engines": {
951
+ "node": ">= 0.4"
952
+ },
953
+ "funding": {
954
+ "url": "https://github.com/sponsors/ljharb"
955
+ }
956
+ },
957
+ "node_modules/statuses": {
958
+ "version": "2.0.2",
959
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
960
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
961
+ "license": "MIT",
962
+ "engines": {
963
+ "node": ">= 0.8"
964
+ }
965
+ },
966
+ "node_modules/toidentifier": {
967
+ "version": "1.0.1",
968
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
969
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
970
+ "license": "MIT",
971
+ "engines": {
972
+ "node": ">=0.6"
973
+ }
974
+ },
975
+ "node_modules/type-is": {
976
+ "version": "1.6.18",
977
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
978
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
979
+ "license": "MIT",
980
+ "dependencies": {
981
+ "media-typer": "0.3.0",
982
+ "mime-types": "~2.1.24"
983
+ },
984
+ "engines": {
985
+ "node": ">= 0.6"
986
+ }
987
+ },
988
+ "node_modules/unpipe": {
989
+ "version": "1.0.0",
990
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
991
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
992
+ "license": "MIT",
993
+ "engines": {
994
+ "node": ">= 0.8"
995
+ }
996
+ },
997
+ "node_modules/utils-merge": {
998
+ "version": "1.0.1",
999
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1000
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1001
+ "license": "MIT",
1002
+ "engines": {
1003
+ "node": ">= 0.4.0"
1004
+ }
1005
+ },
1006
+ "node_modules/uuid": {
1007
+ "version": "9.0.1",
1008
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1009
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1010
+ "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
1011
+ "funding": [
1012
+ "https://github.com/sponsors/broofa",
1013
+ "https://github.com/sponsors/ctavan"
1014
+ ],
1015
+ "license": "MIT",
1016
+ "bin": {
1017
+ "uuid": "dist/bin/uuid"
1018
+ }
1019
+ },
1020
+ "node_modules/validator": {
1021
+ "version": "13.15.35",
1022
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
1023
+ "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
1024
+ "license": "MIT",
1025
+ "engines": {
1026
+ "node": ">= 0.10"
1027
+ }
1028
+ },
1029
+ "node_modules/vary": {
1030
+ "version": "1.1.2",
1031
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1032
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1033
+ "license": "MIT",
1034
+ "engines": {
1035
+ "node": ">= 0.8"
1036
+ }
1037
+ }
1038
+ }
1039
+ }
package.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "amk-pos-backend",
3
+ "version": "1.0.0",
4
+ "description": "AMK POS Backend API",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "start": "node src/index.js",
8
+ "dev": "node src/index.js"
9
+ },
10
+ "dependencies": {
11
+ "bcryptjs": "^2.4.3",
12
+ "cors": "^2.8.5",
13
+ "express": "^4.18.2",
14
+ "express-validator": "^7.0.1",
15
+ "jsonwebtoken": "^9.0.2",
16
+ "uuid": "^9.0.1"
17
+ }
18
+ }
src/index.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const path = require('path');
4
+
5
+ // Import routes
6
+ const authRoutes = require('./routes/auth');
7
+ const productsRoutes = require('./routes/products');
8
+ const salesRoutes = require('./routes/sales');
9
+ const inventoryRoutes = require('./routes/inventory');
10
+ const customersRoutes = require('./routes/customers');
11
+ const employeesRoutes = require('./routes/employees');
12
+ const reportsRoutes = require('./routes/reports');
13
+ const settingsRoutes = require('./routes/settings');
14
+
15
+ const app = express();
16
+ const PORT = process.env.PORT || 3001;
17
+
18
+ // Middleware
19
+ app.use(cors());
20
+ app.use(express.json());
21
+
22
+ // Initialize data files
23
+ const { initializeDataFiles } = require('../store');
24
+ initializeDataFiles();
25
+
26
+ // Routes
27
+ app.use('/api/auth', authRoutes);
28
+ app.use('/api/products', productsRoutes);
29
+ app.use('/api/sales', salesRoutes);
30
+ app.use('/api/inventory', inventoryRoutes);
31
+ app.use('/api/customers', customersRoutes);
32
+ app.use('/api/employees', employeesRoutes);
33
+ app.use('/api/reports', reportsRoutes);
34
+ app.use('/api/settings', settingsRoutes);
35
+
36
+ // Health check
37
+ app.get('/api/health', (req, res) => {
38
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
39
+ });
40
+
41
+ // Serve static files from data directory for receipts
42
+ app.use('/receipts', express.static(path.join(__dirname, '../data')));
43
+
44
+ app.listen(PORT, () => {
45
+ console.log(`AMK POS Backend running on port ${PORT}`);
46
+ });
47
+
48
+ module.exports = app;
src/middleware/auth.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const jwt = require('jsonwebtoken');
2
+
3
+ const JWT_SECRET = process.env.JWT_SECRET || 'amk-pos-secret-key-2024';
4
+
5
+ // Verify token middleware
6
+ function verifyToken(req, res, next) {
7
+ const token = req.headers['authorization']?.split(' ')[1];
8
+
9
+ if (!token) {
10
+ return res.status(401).json({ error: 'No token provided' });
11
+ }
12
+
13
+ try {
14
+ const decoded = jwt.verify(token, JWT_SECRET);
15
+ req.user = decoded;
16
+ next();
17
+ } catch (error) {
18
+ return res.status(401).json({ error: 'Invalid token' });
19
+ }
20
+ }
21
+
22
+ // Admin only middleware
23
+ function adminOnly(req, res, next) {
24
+ if (req.user.role !== 'admin') {
25
+ return res.status(403).json({ error: 'Admin access required' });
26
+ }
27
+ next();
28
+ }
29
+
30
+ // Generate token
31
+ function generateToken(user) {
32
+ return jwt.sign(
33
+ { id: user.id, username: user.username, role: user.role },
34
+ JWT_SECRET,
35
+ { expiresIn: '24h' }
36
+ );
37
+ }
38
+
39
+ module.exports = {
40
+ JWT_SECRET,
41
+ verifyToken,
42
+ adminOnly,
43
+ generateToken
44
+ };
src/routes/auth.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const bcrypt = require('bcryptjs');
3
+ const { body, validationResult } = require('express-validator');
4
+ const { readData, writeData, findByField, addItem, updateItem, deleteItem, findById } = require('../../store');
5
+ const { generateToken, verifyToken, adminOnly } = require('../middleware/auth');
6
+
7
+ const router = express.Router();
8
+
9
+ // Login
10
+ router.post('/login', [
11
+ body('username').trim().notEmpty(),
12
+ body('password').notEmpty()
13
+ ], async (req, res) => {
14
+ try {
15
+ const errors = validationResult(req);
16
+ if (!errors.isEmpty()) {
17
+ return res.status(400).json({ errors: errors.array() });
18
+ }
19
+
20
+ const { username, password } = req.body;
21
+ const users = readData('users');
22
+ const user = users.find(u => u.username === username);
23
+
24
+ if (!user || !bcrypt.compareSync(password, user.password)) {
25
+ return res.status(401).json({ error: 'Invalid credentials' });
26
+ }
27
+
28
+ const token = generateToken(user);
29
+ const { password: _, ...userWithoutPassword } = user;
30
+
31
+ res.json({ token, user: userWithoutPassword });
32
+ } catch (error) {
33
+ res.status(500).json({ error: error.message });
34
+ }
35
+ });
36
+
37
+ // Register (admin only)
38
+ router.post('/register', verifyToken, adminOnly, [
39
+ body('username').trim().notEmpty(),
40
+ body('password').notEmpty(),
41
+ body('name').trim().notEmpty(),
42
+ body('role').isIn(['admin', 'manager', 'staff'])
43
+ ], async (req, res) => {
44
+ try {
45
+ const errors = validationResult(req);
46
+ if (!errors.isEmpty()) {
47
+ return res.status(400).json({ errors: errors.array() });
48
+ }
49
+
50
+ const { username, password, name, email, role } = req.body;
51
+ const users = readData('users');
52
+
53
+ if (users.find(u => u.username === username)) {
54
+ return res.status(400).json({ error: 'Username already exists' });
55
+ }
56
+
57
+ const hashedPassword = bcrypt.hashSync(password, 10);
58
+ const newUser = {
59
+ username,
60
+ password: hashedPassword,
61
+ name,
62
+ email,
63
+ role
64
+ };
65
+
66
+ const user = addItem('users', newUser);
67
+ const { password: _, ...userWithoutPassword } = user;
68
+
69
+ res.status(201).json(userWithoutPassword);
70
+ } catch (error) {
71
+ res.status(500).json({ error: error.message });
72
+ }
73
+ });
74
+
75
+ // Get current user
76
+ router.get('/me', verifyToken, (req, res) => {
77
+ const user = findById('users', req.user.id);
78
+ if (!user) {
79
+ return res.status(404).json({ error: 'User not found' });
80
+ }
81
+ const { password: _, ...userWithoutPassword } = user;
82
+ res.json(userWithoutPassword);
83
+ });
84
+
85
+ // Get all users (admin only)
86
+ router.get('/users', verifyToken, adminOnly, (req, res) => {
87
+ const users = readData('users');
88
+ const usersWithoutPassword = users.map(({ password, ...user }) => user);
89
+ res.json(usersWithoutPassword);
90
+ });
91
+
92
+ // Update user
93
+ router.put('/users/:id', verifyToken, adminOnly, async (req, res) => {
94
+ try {
95
+ const { id } = req.params;
96
+ const { password, ...updates } = req.body;
97
+
98
+ if (password) {
99
+ updates.password = bcrypt.hashSync(password, 10);
100
+ }
101
+
102
+ const user = updateItem('users', id, updates);
103
+ if (!user) {
104
+ return res.status(404).json({ error: 'User not found' });
105
+ }
106
+
107
+ const { password: _, ...userWithoutPassword } = user;
108
+ res.json(userWithoutPassword);
109
+ } catch (error) {
110
+ res.status(500).json({ error: error.message });
111
+ }
112
+ });
113
+
114
+ // Delete user (admin only)
115
+ router.delete('/users/:id', verifyToken, adminOnly, (req, res) => {
116
+ const { id } = req.params;
117
+ if (deleteItem('users', id)) {
118
+ res.json({ message: 'User deleted' });
119
+ } else {
120
+ res.status(404).json({ error: 'User not found' });
121
+ }
122
+ });
123
+
124
+ module.exports = router;
src/routes/customers.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { body, validationResult } = require('express-validator');
3
+ const { readData, addItem, updateItem, deleteItem, findById } = require('../../store');
4
+ const { verifyToken } = require('../middleware/auth');
5
+
6
+ const router = express.Router();
7
+
8
+ // Get all customers
9
+ router.get('/', (req, res) => {
10
+ const customers = readData('customers');
11
+ res.json(customers);
12
+ });
13
+
14
+ // Get customer by ID
15
+ router.get('/:id', (req, res) => {
16
+ const customer = findById('customers', req.params.id);
17
+ if (!customer) {
18
+ return res.status(404).json({ error: 'Customer not found' });
19
+ }
20
+ res.json(customer);
21
+ });
22
+
23
+ // Add customer
24
+ router.post('/', [
25
+ body('name').trim().notEmpty(),
26
+ body('email').isEmail()
27
+ ], (req, res) => {
28
+ try {
29
+ const errors = validationResult(req);
30
+ if (!errors.isEmpty()) {
31
+ return res.status(400).json({ errors: errors.array() });
32
+ }
33
+
34
+ const customer = addItem('customers', req.body);
35
+ res.status(201).json(customer);
36
+ } catch (error) {
37
+ res.status(500).json({ error: error.message });
38
+ }
39
+ });
40
+
41
+ // Update customer
42
+ router.put('/:id', verifyToken, (req, res) => {
43
+ const customer = updateItem('customers', req.params.id, req.body);
44
+ if (!customer) {
45
+ return res.status(404).json({ error: 'Customer not found' });
46
+ }
47
+ res.json(customer);
48
+ });
49
+
50
+ // Delete customer
51
+ router.delete('/:id', verifyToken, (req, res) => {
52
+ if (deleteItem('customers', req.params.id)) {
53
+ res.json({ message: 'Customer deleted' });
54
+ } else {
55
+ res.status(404).json({ error: 'Customer not found' });
56
+ }
57
+ });
58
+
59
+ // Get customer purchase history
60
+ router.get('/:id/history', (req, res) => {
61
+ const sales = readData('sales');
62
+ const customerSales = sales.filter(s => s.customerId === req.params.id);
63
+ res.json(customerSales);
64
+ });
65
+
66
+ module.exports = router;
src/routes/employees.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { body, validationResult } = require('express-validator');
3
+ const { readData, addItem, updateItem, deleteItem, findById } = require('../../store');
4
+ const { verifyToken, adminOnly } = require('../middleware/auth');
5
+
6
+ const router = express.Router();
7
+
8
+ // Get all employees
9
+ router.get('/', (req, res) => {
10
+ const employees = readData('employees');
11
+ res.json(employees);
12
+ });
13
+
14
+ // Get employee by ID
15
+ router.get('/:id', (req, res) => {
16
+ const employee = findById('employees', req.params.id);
17
+ if (!employee) {
18
+ return res.status(404).json({ error: 'Employee not found' });
19
+ }
20
+ res.json(employee);
21
+ });
22
+
23
+ // Add employee
24
+ router.post('/', verifyToken, adminOnly, [
25
+ body('name').trim().notEmpty(),
26
+ body('email').isEmail()
27
+ ], (req, res) => {
28
+ try {
29
+ const errors = validationResult(req);
30
+ if (!errors.isEmpty()) {
31
+ return res.status(400).json({ errors: errors.array() });
32
+ }
33
+
34
+ const employee = addItem('employees', req.body);
35
+ res.status(201).json(employee);
36
+ } catch (error) {
37
+ res.status(500).json({ error: error.message });
38
+ }
39
+ });
40
+
41
+ // Update employee
42
+ router.put('/:id', verifyToken, adminOnly, (req, res) => {
43
+ const employee = updateItem('employees', req.params.id, req.body);
44
+ if (!employee) {
45
+ return res.status(404).json({ error: 'Employee not found' });
46
+ }
47
+ res.json(employee);
48
+ });
49
+
50
+ // Delete employee
51
+ router.delete('/:id', verifyToken, adminOnly, (req, res) => {
52
+ if (deleteItem('employees', req.params.id)) {
53
+ res.json({ message: 'Employee deleted' });
54
+ } else {
55
+ res.status(404).json({ error: 'Employee not found' });
56
+ }
57
+ });
58
+
59
+ // Get employee sales
60
+ router.get('/:id/sales', (req, res) => {
61
+ const sales = readData('sales');
62
+ const employeeSales = sales.filter(s => s.cashierId === req.params.id);
63
+ res.json(employeeSales);
64
+ });
65
+
66
+ module.exports = router;
src/routes/inventory.js ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { readData, writeData, findById, addItem, updateItem } = require('../../store');
3
+ const { verifyToken } = require('../middleware/auth');
4
+
5
+ const router = express.Router();
6
+
7
+ // Get inventory overview
8
+ router.get('/', (req, res) => {
9
+ const products = readData('products');
10
+ const inventory = readData('inventory');
11
+ const settings = readData('settings');
12
+
13
+ const inventoryWithProducts = products.map(product => {
14
+ const inv = inventory.find(i => i.productId === product.sku) || { quantity: 0, lowStockThreshold: settings.lowStockThreshold || 15 };
15
+ return {
16
+ ...product,
17
+ quantity: inv.quantity,
18
+ lowStockThreshold: inv.lowStockThreshold || settings.lowStockThreshold || 15,
19
+ isLowStock: inv.quantity <= (inv.lowStockThreshold || settings.lowStockThreshold || 15)
20
+ };
21
+ });
22
+
23
+ res.json(inventoryWithProducts);
24
+ });
25
+
26
+ // Get low stock alerts
27
+ router.get('/alerts', (req, res) => {
28
+ const products = readData('products');
29
+ const inventory = readData('inventory');
30
+ const settings = readData('settings');
31
+
32
+ const alerts = [];
33
+
34
+ products.forEach(product => {
35
+ const inv = inventory.find(i => i.productId === product.sku);
36
+ const threshold = inv?.lowStockThreshold || settings.lowStockThreshold || 15;
37
+ if (inv && inv.quantity <= threshold) {
38
+ alerts.push({
39
+ ...product,
40
+ currentStock: inv.quantity,
41
+ threshold
42
+ });
43
+ }
44
+ });
45
+
46
+ res.json(alerts);
47
+ });
48
+
49
+ // Update stock quantity
50
+ router.put('/:productId', verifyToken, (req, res) => {
51
+ const { productId } = req.params;
52
+ const { quantity, lowStockThreshold, reason } = req.body;
53
+
54
+ const inventory = readData('inventory');
55
+ const index = inventory.findIndex(i => i.productId === productId);
56
+
57
+ if (index !== -1) {
58
+ if (quantity !== undefined) inventory[index].quantity = quantity;
59
+ if (lowStockThreshold !== undefined) inventory[index].lowStockThreshold = lowStockThreshold;
60
+ } else {
61
+ inventory.push({
62
+ productId,
63
+ quantity: quantity || 0,
64
+ lowStockThreshold: lowStockThreshold || 15
65
+ });
66
+ }
67
+
68
+ writeData('inventory', inventory);
69
+
70
+ // Log adjustment if reason provided
71
+ if (reason) {
72
+ const adjustments = readData('sales');
73
+ adjustments.push({
74
+ type: 'adjustment',
75
+ productId,
76
+ quantity: quantity,
77
+ reason,
78
+ adjustedBy: req.user.name,
79
+ createdAt: new Date().toISOString()
80
+ });
81
+ // Don't overwrite sales, just log to a separate mechanism if needed
82
+ }
83
+
84
+ res.json({ productId, quantity, lowStockThreshold });
85
+ });
86
+
87
+ // Stock adjustment history
88
+ router.get('/history', (req, res) => {
89
+ // This would need a separate adjustments file
90
+ res.json([]);
91
+ });
92
+
93
+ module.exports = router;
src/routes/products.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { body, validationResult } = require('express-validator');
3
+ const { readData, addItem, updateItem, deleteItem, findById } = require('../../store');
4
+ const { verifyToken } = require('../middleware/auth');
5
+
6
+ const router = express.Router();
7
+
8
+ // Get all products
9
+ router.get('/', (req, res) => {
10
+ const products = readData('products');
11
+ res.json(products);
12
+ });
13
+
14
+ // Get product by ID
15
+ router.get('/:id', (req, res) => {
16
+ const product = findById('products', req.params.id);
17
+ if (!product) {
18
+ return res.status(404).json({ error: 'Product not found' });
19
+ }
20
+ res.json(product);
21
+ });
22
+
23
+ // Add product
24
+ router.post('/', verifyToken, [
25
+ body('name').trim().notEmpty(),
26
+ body('price').isFloat({ min: 0 }),
27
+ body('category').trim().notEmpty()
28
+ ], (req, res) => {
29
+ try {
30
+ const errors = validationResult(req);
31
+ if (!errors.isEmpty()) {
32
+ return res.status(400).json({ errors: errors.array() });
33
+ }
34
+
35
+ const product = addItem('products', req.body);
36
+ res.status(201).json(product);
37
+ } catch (error) {
38
+ res.status(500).json({ error: error.message });
39
+ }
40
+ });
41
+
42
+ // Update product
43
+ router.put('/:id', verifyToken, (req, res) => {
44
+ const product = updateItem('products', req.params.id, req.body);
45
+ if (!product) {
46
+ return res.status(404).json({ error: 'Product not found' });
47
+ }
48
+ res.json(product);
49
+ });
50
+
51
+ // Delete product
52
+ router.delete('/:id', verifyToken, (req, res) => {
53
+ if (deleteItem('products', req.params.id)) {
54
+ res.json({ message: 'Product deleted' });
55
+ } else {
56
+ res.status(404).json({ error: 'Product not found' });
57
+ }
58
+ });
59
+
60
+ // Get categories
61
+ router.get('/meta/categories', (req, res) => {
62
+ const products = readData('products');
63
+ const categories = [...new Set(products.map(p => p.category))];
64
+ res.json(categories);
65
+ });
66
+
67
+ module.exports = router;
src/routes/reports.js ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { readData } = require('../../store');
3
+
4
+ const router = express.Router();
5
+
6
+ // Helper: Get date range for filtering
7
+ function getDateRange(period) {
8
+ const now = new Date();
9
+ let start, end;
10
+
11
+ if (period === 'daily') {
12
+ start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
13
+ end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
14
+ } else if (period === 'weekly') {
15
+ start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
16
+ end = now;
17
+ } else if (period === 'monthly') {
18
+ start = new Date(now.getFullYear(), now.getMonth(), 1);
19
+ end = now;
20
+ } else {
21
+ start = new Date(0);
22
+ end = now;
23
+ }
24
+
25
+ return { start, end };
26
+ }
27
+
28
+ // Get daily report
29
+ router.get('/daily', (req, res) => {
30
+ const { start, end } = getDateRange('daily');
31
+ const sales = readData('sales');
32
+ const settings = readData('settings');
33
+
34
+ const periodSales = sales.filter(s => {
35
+ const saleDate = new Date(s.createdAt);
36
+ return saleDate >= start && saleDate < end && s.status === 'completed';
37
+ });
38
+
39
+ const revenue = periodSales.reduce((sum, s) => sum + s.total, 0);
40
+ const orderCount = periodSales.length;
41
+ const avgOrderValue = orderCount > 0 ? revenue / orderCount : 0;
42
+ const taxCollected = periodSales.reduce((sum, s) => sum + (s.tax || 0), 0);
43
+
44
+ // Product breakdown
45
+ const productSales = {};
46
+ periodSales.forEach(sale => {
47
+ sale.items.forEach(item => {
48
+ if (!productSales[item.name]) {
49
+ productSales[item.name] = { quantity: 0, revenue: 0 };
50
+ }
51
+ productSales[item.name].quantity += item.quantity;
52
+ productSales[item.name].revenue += item.quantity * item.price;
53
+ });
54
+ });
55
+
56
+ res.json({
57
+ period: 'daily',
58
+ date: start.toISOString().split('T')[0],
59
+ revenue,
60
+ orderCount,
61
+ avgOrderValue,
62
+ taxCollected,
63
+ productSales,
64
+ settings
65
+ });
66
+ });
67
+
68
+ // Get weekly report
69
+ router.get('/weekly', (req, res) => {
70
+ const { start, end } = getDateRange('weekly');
71
+ const sales = readData('sales');
72
+ const settings = readData('settings');
73
+
74
+ const periodSales = sales.filter(s => {
75
+ const saleDate = new Date(s.createdAt);
76
+ return saleDate >= start && saleDate < end && s.status === 'completed';
77
+ });
78
+
79
+ const revenue = periodSales.reduce((sum, s) => sum + s.total, 0);
80
+ const orderCount = periodSales.length;
81
+ const avgOrderValue = orderCount > 0 ? revenue / orderCount : 0;
82
+ const taxCollected = periodSales.reduce((sum, s) => sum + (s.tax || 0), 0);
83
+
84
+ // Daily breakdown for the week
85
+ const dailySales = {};
86
+ for (let i = 0; i < 7; i++) {
87
+ const d = new Date(start);
88
+ d.setDate(d.getDate() + i);
89
+ const dateKey = d.toISOString().split('T')[0];
90
+ dailySales[dateKey] = 0;
91
+ }
92
+
93
+ periodSales.forEach(sale => {
94
+ const dateKey = new Date(sale.createdAt).toISOString().split('T')[0];
95
+ if (dailySales[dateKey] !== undefined) {
96
+ dailySales[dateKey] += sale.total;
97
+ }
98
+ });
99
+
100
+ res.json({
101
+ period: 'weekly',
102
+ startDate: start.toISOString().split('T')[0],
103
+ endDate: end.toISOString().split('T')[0],
104
+ revenue,
105
+ orderCount,
106
+ avgOrderValue,
107
+ taxCollected,
108
+ dailySales,
109
+ settings
110
+ });
111
+ });
112
+
113
+ // Get monthly report
114
+ router.get('/monthly', (req, res) => {
115
+ const { start, end } = getDateRange('monthly');
116
+ const sales = readData('sales');
117
+ const settings = readData('settings');
118
+
119
+ const periodSales = sales.filter(s => {
120
+ const saleDate = new Date(s.createdAt);
121
+ return saleDate >= start && saleDate < end && s.status === 'completed';
122
+ });
123
+
124
+ const revenue = periodSales.reduce((sum, s) => sum + s.total, 0);
125
+ const orderCount = periodSales.length;
126
+ const avgOrderValue = orderCount > 0 ? revenue / orderCount : 0;
127
+ const taxCollected = periodSales.reduce((sum, s) => sum + (s.tax || 0), 0);
128
+
129
+ // Daily breakdown for the month
130
+ const dailySales = {};
131
+ const daysInMonth = new Date(end.getFullYear(), end.getMonth() + 1, 0).getDate();
132
+ for (let i = 1; i <= daysInMonth; i++) {
133
+ const d = new Date(end.getFullYear(), end.getMonth(), i);
134
+ const dateKey = d.toISOString().split('T')[0];
135
+ dailySales[dateKey] = 0;
136
+ }
137
+
138
+ periodSales.forEach(sale => {
139
+ const dateKey = new Date(sale.createdAt).toISOString().split('T')[0];
140
+ if (dailySales[dateKey] !== undefined) {
141
+ dailySales[dateKey] += sale.total;
142
+ }
143
+ });
144
+
145
+ res.json({
146
+ period: 'monthly',
147
+ startDate: start.toISOString().split('T')[0],
148
+ endDate: end.toISOString().split('T')[0],
149
+ revenue,
150
+ orderCount,
151
+ avgOrderValue,
152
+ taxCollected,
153
+ dailySales,
154
+ settings
155
+ });
156
+ });
157
+
158
+ // Export data
159
+ router.get('/export', (req, res) => {
160
+ const { type } = req.query;
161
+ const sales = readData('sales');
162
+ const products = readData('products');
163
+ const customers = readData('customers');
164
+
165
+ if (type === 'sales') {
166
+ res.setHeader('Content-Type', 'text/csv');
167
+ res.setHeader('Content-Disposition', 'attachment; filename=sales.csv');
168
+ res.csv('Sales Export', sales);
169
+ } else if (type === 'products') {
170
+ res.json(products);
171
+ } else if (type === 'customers') {
172
+ res.json(customers);
173
+ } else {
174
+ res.json({ sales, products, customers });
175
+ }
176
+ });
177
+
178
+ // Analytics for dashboard
179
+ router.get('/analytics', (req, res) => {
180
+ const sales = readData('sales');
181
+ const settings = readData('settings');
182
+ const products = readData('products');
183
+ const inventory = readData('inventory');
184
+
185
+ // Today's revenue
186
+ const today = new Date();
187
+ today.setHours(0, 0, 0, 0);
188
+ const todaySales = sales.filter(s => {
189
+ const saleDate = new Date(s.createdAt);
190
+ return saleDate >= today && s.status === 'completed';
191
+ });
192
+ const todayRevenue = todaySales.reduce((sum, s) => sum + s.total, 0);
193
+
194
+ // This week's revenue
195
+ const weekStart = new Date(today);
196
+ weekStart.setDate(weekStart.getDate() - 7);
197
+ const weekSales = sales.filter(s => {
198
+ const saleDate = new Date(s.createdAt);
199
+ return saleDate >= weekStart && s.status === 'completed';
200
+ });
201
+ const weekRevenue = weekSales.reduce((sum, s) => sum + s.total, 0);
202
+
203
+ // This month's revenue
204
+ const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
205
+ const monthSales = sales.filter(s => {
206
+ const saleDate = new Date(s.createdAt);
207
+ return saleDate >= monthStart && s.status === 'completed';
208
+ });
209
+ const monthRevenue = monthSales.reduce((sum, s) => sum + s.total, 0);
210
+
211
+ // Last 7 days sales data for chart
212
+ const last7Days = [];
213
+ for (let i = 6; i >= 0; i--) {
214
+ const d = new Date(today);
215
+ d.setDate(d.getDate() - i);
216
+ const dateKey = d.toISOString().split('T')[0];
217
+ const dayStart = new Date(d);
218
+ dayStart.setHours(0, 0, 0, 0);
219
+ const dayEnd = new Date(d);
220
+ dayEnd.setHours(23, 59, 59, 999);
221
+
222
+ const dayTotal = sales
223
+ .filter(s => {
224
+ const saleDate = new Date(s.createdAt);
225
+ return saleDate >= dayStart && saleDate <= dayEnd && s.status === 'completed';
226
+ })
227
+ .reduce((sum, s) => sum + s.total, 0);
228
+
229
+ last7Days.push({ date: dateKey, revenue: dayTotal });
230
+ }
231
+
232
+ // Top products
233
+ const productCounts = {};
234
+ sales.forEach(sale => {
235
+ if (sale.status === 'completed') {
236
+ sale.items.forEach(item => {
237
+ if (!productCounts[item.productId]) {
238
+ productCounts[item.productId] = { quantity: 0, revenue: 0 };
239
+ }
240
+ productCounts[item.productId].quantity += item.quantity;
241
+ productCounts[item.productId].revenue += item.quantity * item.price;
242
+ });
243
+ }
244
+ });
245
+
246
+ const topProducts = Object.entries(productCounts)
247
+ .map(([productId, data]) => {
248
+ const product = products.find(p => p.id === productId);
249
+ return { productId, name: product?.name || 'Unknown', quantity: data.quantity, revenue: data.revenue };
250
+ })
251
+ .sort((a, b) => b.revenue - a.revenue)
252
+ .slice(0, 5);
253
+
254
+ // Low stock alert count
255
+ const lowStockCount = inventory.filter(i => {
256
+ const threshold = i.lowStockThreshold || settings.lowStockThreshold || 15;
257
+ return i.quantity <= threshold;
258
+ }).length;
259
+
260
+ res.json({
261
+ todayRevenue,
262
+ weekRevenue,
263
+ monthRevenue,
264
+ todayOrders: todaySales.length,
265
+ weekOrders: weekSales.length,
266
+ monthOrders: monthSales.length,
267
+ last7Days,
268
+ topProducts,
269
+ lowStockCount,
270
+ settings
271
+ });
272
+ });
273
+
274
+ module.exports = router;
src/routes/sales.js ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { body, validationResult } = require('express-validator');
3
+ const { readData, writeData, findById, addItem, updateItem } = require('../../store');
4
+ const { verifyToken } = require('../middleware/auth');
5
+
6
+ const router = express.Router();
7
+
8
+ // Get all sales
9
+ router.get('/', (req, res) => {
10
+ const sales = readData('sales');
11
+ res.json(sales.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)));
12
+ });
13
+
14
+ // Get sale by ID
15
+ router.get('/:id', (req, res) => {
16
+ const sale = findById('sales', req.params.id);
17
+ if (!sale) {
18
+ return res.status(404).json({ error: 'Sale not found' });
19
+ }
20
+ res.json(sale);
21
+ });
22
+
23
+ // Create new sale
24
+ router.post('/', verifyToken, [
25
+ body('items').isArray({ min: 1 }),
26
+ body('total').isFloat({ min: 0 })
27
+ ], (req, res) => {
28
+ try {
29
+ const errors = validationResult(req);
30
+ if (!errors.isEmpty()) {
31
+ return res.status(400).json({ errors: errors.array() });
32
+ }
33
+
34
+ const { items, subtotal, tax, discount, total, paymentMethod, customerId, notes } = req.body;
35
+ const products = readData('products');
36
+ const inventory = readData('inventory');
37
+
38
+ // Process each item - update inventory
39
+ const updatedInventory = [...inventory];
40
+
41
+ items.forEach(item => {
42
+ const product = products.find(p => p.id === item.productId);
43
+ if (product) {
44
+ const invIndex = updatedInventory.findIndex(i => i.productId === product.sku);
45
+ if (invIndex !== -1) {
46
+ updatedInventory[invIndex].quantity = Math.max(0, updatedInventory[invIndex].quantity - item.quantity);
47
+ }
48
+ }
49
+ });
50
+
51
+ writeData('inventory', updatedInventory);
52
+
53
+ // Create sale record
54
+ const sale = addItem('sales', {
55
+ items,
56
+ subtotal,
57
+ tax,
58
+ discount,
59
+ total,
60
+ paymentMethod,
61
+ customerId,
62
+ notes,
63
+ status: 'completed',
64
+ cashierId: req.user.id,
65
+ cashierName: req.user.name
66
+ });
67
+
68
+ res.status(201).json(sale);
69
+ } catch (error) {
70
+ res.status(500).json({ error: error.message });
71
+ }
72
+ });
73
+
74
+ // Process refund
75
+ router.post('/:id/refund', verifyToken, [
76
+ body('reason').trim().notEmpty()
77
+ ], (req, res) => {
78
+ try {
79
+ const { id } = req.params;
80
+ const { reason } = req.body;
81
+
82
+ const sale = findById('sales', id);
83
+ if (!sale) {
84
+ return res.status(404).json({ error: 'Sale not found' });
85
+ }
86
+
87
+ if (sale.status === 'refunded') {
88
+ return res.status(400).json({ error: 'Sale already refunded' });
89
+ }
90
+
91
+ // Restore inventory
92
+ const products = readData('products');
93
+ const inventory = readData('inventory');
94
+ const updatedInventory = [...inventory];
95
+
96
+ sale.items.forEach(item => {
97
+ const invIndex = updatedInventory.findIndex(i => i.productId === item.sku);
98
+ if (invIndex !== -1) {
99
+ updatedInventory[invIndex].quantity += item.quantity;
100
+ }
101
+ });
102
+
103
+ writeData('inventory', updatedInventory);
104
+
105
+ // Update sale status
106
+ const updatedSale = updateItem('sales', id, {
107
+ status: 'refunded',
108
+ refundReason: reason,
109
+ refundedBy: req.user.name,
110
+ refundedAt: new Date().toISOString()
111
+ });
112
+
113
+ res.json(updatedSale);
114
+ } catch (error) {
115
+ res.status(500).json({ error: error.message });
116
+ }
117
+ });
118
+
119
+ // Get receipt
120
+ router.get('/receipt/:id', (req, res) => {
121
+ const sale = findById('sales', req.params.id);
122
+ if (!sale) {
123
+ return res.status(404).json({ error: 'Sale not found' });
124
+ }
125
+
126
+ const settings = readData('settings');
127
+
128
+ const receipt = {
129
+ ...sale,
130
+ storeName: settings.storeName,
131
+ storeAddress: settings.storeAddress,
132
+ storePhone: settings.storePhone,
133
+ receiptHeader: settings.receiptHeader,
134
+ receiptFooter: settings.receiptFooter
135
+ };
136
+
137
+ res.json(receipt);
138
+ });
139
+
140
+ module.exports = router;
src/routes/settings.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { readData, writeData } = require('../../store');
3
+ const { verifyToken, adminOnly } = require('../middleware/auth');
4
+
5
+ const router = express.Router();
6
+
7
+ // Get settings
8
+ router.get('/', (req, res) => {
9
+ const settings = readData('settings');
10
+ res.json(settings);
11
+ });
12
+
13
+ // Update settings (admin only)
14
+ router.put('/', verifyToken, adminOnly, (req, res) => {
15
+ const settings = readData('settings');
16
+ const updatedSettings = { ...settings, ...req.body };
17
+ writeData('settings', updatedSettings);
18
+ res.json(updatedSettings);
19
+ });
20
+
21
+ module.exports = router;
store.js ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const bcrypt = require('bcryptjs');
4
+ const { v4: uuidv4 } = require('uuid');
5
+
6
+ const DATA_DIR = path.join(__dirname, '../data');
7
+
8
+ // Ensure data directory exists
9
+ if (!fs.existsSync(DATA_DIR)) {
10
+ fs.mkdirSync(DATA_DIR, { recursive: true });
11
+ }
12
+
13
+ // File paths
14
+ const files = {
15
+ users: path.join(DATA_DIR, 'users.json'),
16
+ products: path.join(DATA_DIR, 'products.json'),
17
+ inventory: path.join(DATA_DIR, 'inventory.json'),
18
+ sales: path.join(DATA_DIR, 'sales.json'),
19
+ customers: path.join(DATA_DIR, 'customers.json'),
20
+ employees: path.join(DATA_DIR, 'employees.json'),
21
+ settings: path.join(DATA_DIR, 'settings.json')
22
+ };
23
+
24
+ // Initialize default admin user
25
+ const hashedPassword = bcrypt.hashSync('admin123', 10);
26
+
27
+ // Default data - products only
28
+ const defaultData = {
29
+ users: [
30
+ {
31
+ id: uuidv4(),
32
+ username: 'admin',
33
+ password: hashedPassword,
34
+ role: 'admin',
35
+ name: 'System Administrator',
36
+ email: 'admin@amkpos.com',
37
+ createdAt: new Date().toISOString()
38
+ }
39
+ ],
40
+ products: [
41
+ {
42
+ id: uuidv4(),
43
+ name: 'Coffee Latte',
44
+ description: 'Premium smooth latte with espresso',
45
+ category: 'Beverages',
46
+ price: 4.50,
47
+ salePrice: null,
48
+ sku: 'COF-LAT-001',
49
+ barcode: '1234567890123',
50
+ image: 'https://images.unsplash.com/photo-1572442388796-11668a67e53d?w=200',
51
+ inStock: true,
52
+ createdAt: new Date().toISOString()
53
+ },
54
+ {
55
+ id: uuidv4(),
56
+ name: 'Espresso Shot',
57
+ description: 'Rich and bold espresso shot',
58
+ category: 'Beverages',
59
+ price: 3.00,
60
+ salePrice: null,
61
+ sku: 'COF-ESP-001',
62
+ barcode: '1234567890124',
63
+ image: 'https://images.unsplash.com/photo-1510707577719-ae7c14805e3a?w=200',
64
+ inStock: true,
65
+ createdAt: new Date().toISOString()
66
+ },
67
+ {
68
+ id: uuidv4(),
69
+ name: 'Turkey Sandwich',
70
+ description: 'Freshly baked bread with turkey',
71
+ category: 'Food',
72
+ price: 8.00,
73
+ salePrice: null,
74
+ sku: 'SAN-TUR-001',
75
+ barcode: '1234567890125',
76
+ image: 'https://images.unsplash.com/photo-1528735602780-2552fd46c7af?w=200',
77
+ inStock: true,
78
+ createdAt: new Date().toISOString()
79
+ },
80
+ {
81
+ id: uuidv4(),
82
+ name: 'Garden Salad',
83
+ description: 'Fresh mixed greens with vinaigrette',
84
+ category: 'Food',
85
+ price: 7.50,
86
+ salePrice: 6.50,
87
+ sku: 'SAL-GAR-001',
88
+ barcode: '1234567890126',
89
+ image: 'https://images.unsplash.com/photo-1512621776951-a57141f2eefd?w=200',
90
+ inStock: true,
91
+ createdAt: new Date().toISOString()
92
+ },
93
+ {
94
+ id: uuidv4(),
95
+ name: 'Blueberry Muffin',
96
+ description: 'Freshly baked blueberry muffin',
97
+ category: 'Bakery',
98
+ price: 3.50,
99
+ salePrice: null,
100
+ sku: 'MUF-BLU-001',
101
+ barcode: '1234567890127',
102
+ image: 'https://images.unsplash.com/photo-1607958996333-41aef7caefaa?w=200',
103
+ inStock: true,
104
+ createdAt: new Date().toISOString()
105
+ },
106
+ {
107
+ id: uuidv4(),
108
+ name: 'Orange Juice',
109
+ description: 'Freshly squeezed orange juice',
110
+ category: 'Beverages',
111
+ price: 4.00,
112
+ salePrice: null,
113
+ sku: 'JUI-ORA-001',
114
+ barcode: '1234567890128',
115
+ image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=200',
116
+ inStock: true,
117
+ createdAt: new Date().toISOString()
118
+ }
119
+ ],
120
+ inventory: [],
121
+ sales: [],
122
+ customers: [],
123
+ employees: [
124
+ {
125
+ id: uuidv4(),
126
+ name: 'John Smith',
127
+ email: 'john@amkpos.com',
128
+ phone: '555-0101',
129
+ role: 'Manager',
130
+ hireDate: '2024-01-15',
131
+ active: true
132
+ },
133
+ {
134
+ id: uuidv4(),
135
+ name: 'Sarah Johnson',
136
+ email: 'sarah@amkpos.com',
137
+ phone: '555-0102',
138
+ role: 'Staff',
139
+ hireDate: '2024-02-01',
140
+ active: true
141
+ }
142
+ ],
143
+ settings: {
144
+ storeName: 'AMK POS',
145
+ storeEmail: 'info@amkpos.com',
146
+ storePhone: '555-0100',
147
+ storeAddress: '123 Main Street, City, State 12345',
148
+ taxRate: 8.5,
149
+ currency: 'USD',
150
+ lowStockThreshold: 15,
151
+ receiptHeader: 'Thank you for visiting AMK POS!',
152
+ receiptFooter: 'Please come again!'
153
+ }
154
+ };
155
+
156
+ // Initialize data files
157
+ function initializeDataFiles() {
158
+ Object.keys(files).forEach(function(key) {
159
+ if (!fs.existsSync(files[key])) {
160
+ var data = defaultData[key];
161
+ fs.writeFileSync(files[key], JSON.stringify(data, null, 2));
162
+ console.log('Initialized ' + key + '.json');
163
+ }
164
+ });
165
+ }
166
+
167
+ // Generic read function
168
+ function readData(file) {
169
+ try {
170
+ var data = fs.readFileSync(files[file], 'utf8');
171
+ return JSON.parse(data);
172
+ } catch (error) {
173
+ console.error('Error reading ' + file + ':', error.message);
174
+ return [];
175
+ }
176
+ }
177
+
178
+ // Generic write function
179
+ function writeData(file, data) {
180
+ try {
181
+ fs.writeFileSync(files[file], JSON.stringify(data, null, 2));
182
+ return true;
183
+ } catch (error) {
184
+ console.error('Error writing ' + file + ':', error.message);
185
+ return false;
186
+ }
187
+ }
188
+
189
+ // Find by ID
190
+ function findById(file, id) {
191
+ var data = readData(file);
192
+ return data.find(function(item) { return item.id === id; });
193
+ }
194
+
195
+ // Add item
196
+ function addItem(file, item) {
197
+ var data = readData(file);
198
+ item.id = uuidv4();
199
+ item.createdAt = new Date().toISOString();
200
+ data.push(item);
201
+ writeData(file, data);
202
+ return item;
203
+ }
204
+
205
+ // Update item
206
+ function updateItem(file, id, updates) {
207
+ var data = readData(file);
208
+ var index = data.findIndex(function(item) { return item.id === id; });
209
+ if (index !== -1) {
210
+ data[index] = Object.assign({}, data[index], updates, { updatedAt: new Date().toISOString() });
211
+ writeData(file, data);
212
+ return data[index];
213
+ }
214
+ return null;
215
+ }
216
+
217
+ // Delete item
218
+ function deleteItem(file, id) {
219
+ var data = readData(file);
220
+ var index = data.findIndex(function(item) { return item.id === id; });
221
+ if (index !== -1) {
222
+ data.splice(index, 1);
223
+ writeData(file, data);
224
+ return true;
225
+ }
226
+ return false;
227
+ }
228
+
229
+ // Find by field
230
+ function findByField(file, field, value) {
231
+ var data = readData(file);
232
+ return data.find(function(item) { return item[field] === value; });
233
+ }
234
+
235
+ module.exports = {
236
+ DATA_DIR: DATA_DIR,
237
+ files: files,
238
+ readData: readData,
239
+ writeData: writeData,
240
+ findById: findById,
241
+ addItem: addItem,
242
+ updateItem: updateItem,
243
+ deleteItem: deleteItem,
244
+ findByField: findByField,
245
+ initializeDataFiles: initializeDataFiles
246
+ };