nexusbert commited on
Commit
2fe81ea
Β·
1 Parent(s): b5e5eac

add features

Browse files
package-lock.json CHANGED
@@ -22,6 +22,8 @@
22
  "pg": "*",
23
  "pinata": "*",
24
  "reflect-metadata": "*",
 
 
25
  "typeorm": "*",
26
  "uuid": "*"
27
  },
@@ -34,6 +36,7 @@
34
  "@types/multer": "*",
35
  "@types/node": "*",
36
  "@types/supertest": "*",
 
37
  "@types/uuid": "*",
38
  "jest": "*",
39
  "supertest": "*",
@@ -42,6 +45,62 @@
42
  "typescript": "*"
43
  }
44
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  "node_modules/@babel/code-frame": {
46
  "version": "7.27.1",
47
  "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -966,6 +1025,11 @@
966
  "@jridgewell/sourcemap-codec": "^1.4.14"
967
  }
968
  },
 
 
 
 
 
969
  "node_modules/@napi-rs/wasm-runtime": {
970
  "version": "0.2.12",
971
  "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@@ -1020,6 +1084,12 @@
1020
  "url": "https://opencollective.com/pkgr"
1021
  }
1022
  },
 
 
 
 
 
 
1023
  "node_modules/@sinclair/typebox": {
1024
  "version": "0.34.41",
1025
  "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
@@ -1231,6 +1301,11 @@
1231
  "pretty-format": "^30.0.0"
1232
  }
1233
  },
 
 
 
 
 
1234
  "node_modules/@types/jsonwebtoken": {
1235
  "version": "9.0.10",
1236
  "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
@@ -1359,6 +1434,16 @@
1359
  "@types/superagent": "^8.1.0"
1360
  }
1361
  },
 
 
 
 
 
 
 
 
 
 
1362
  "node_modules/@types/uuid": {
1363
  "version": "11.0.0",
1364
  "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz",
@@ -2133,6 +2218,11 @@
2133
  "url": "https://github.com/sponsors/ljharb"
2134
  }
2135
  },
 
 
 
 
 
2136
  "node_modules/callsites": {
2137
  "version": "3.1.0",
2138
  "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -2350,6 +2440,14 @@
2350
  "node": ">= 0.8"
2351
  }
2352
  },
 
 
 
 
 
 
 
 
2353
  "node_modules/component-emitter": {
2354
  "version": "1.3.1",
2355
  "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@@ -2362,8 +2460,7 @@
2362
  "node_modules/concat-map": {
2363
  "version": "0.0.1",
2364
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
2365
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
2366
- "dev": true
2367
  },
2368
  "node_modules/concat-stream": {
2369
  "version": "2.0.0",
@@ -2560,6 +2657,17 @@
2560
  "node": ">=0.3.1"
2561
  }
2562
  },
 
 
 
 
 
 
 
 
 
 
 
2563
  "node_modules/dotenv": {
2564
  "version": "17.2.3",
2565
  "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
@@ -2727,6 +2835,14 @@
2727
  "node": ">=4"
2728
  }
2729
  },
 
 
 
 
 
 
 
 
2730
  "node_modules/etag": {
2731
  "version": "1.8.1",
2732
  "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -3028,8 +3144,7 @@
3028
  "node_modules/fs.realpath": {
3029
  "version": "1.0.0",
3030
  "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
3031
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
3032
- "dev": true
3033
  },
3034
  "node_modules/fsevents": {
3035
  "version": "2.3.3",
@@ -3360,7 +3475,6 @@
3360
  "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
3361
  "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
3362
  "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
3363
- "dev": true,
3364
  "dependencies": {
3365
  "once": "^1.3.0",
3366
  "wrappy": "1"
@@ -4315,6 +4429,12 @@
4315
  "node": ">=8"
4316
  }
4317
  },
 
 
 
 
 
 
4318
  "node_modules/lodash.includes": {
4319
  "version": "4.3.0",
4320
  "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -4325,6 +4445,12 @@
4325
  "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
4326
  "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
4327
  },
 
 
 
 
 
 
4328
  "node_modules/lodash.isinteger": {
4329
  "version": "4.0.4",
4330
  "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
@@ -4351,6 +4477,11 @@
4351
  "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
4352
  "dev": true
4353
  },
 
 
 
 
 
4354
  "node_modules/lodash.once": {
4355
  "version": "4.1.1",
4356
  "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@@ -4737,6 +4868,12 @@
4737
  "url": "https://github.com/sponsors/sindresorhus"
4738
  }
4739
  },
 
 
 
 
 
 
4740
  "node_modules/p-limit": {
4741
  "version": "3.1.0",
4742
  "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -4832,7 +4969,6 @@
4832
  "version": "1.0.1",
4833
  "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
4834
  "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
4835
- "dev": true,
4836
  "engines": {
4837
  "node": ">=0.10.0"
4838
  }
@@ -5837,6 +5973,98 @@
5837
  "url": "https://github.com/sponsors/ljharb"
5838
  }
5839
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5840
  "node_modules/synckit": {
5841
  "version": "0.11.11",
5842
  "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
@@ -6462,6 +6690,14 @@
6462
  "node": ">=10.12.0"
6463
  }
6464
  },
 
 
 
 
 
 
 
 
6465
  "node_modules/vary": {
6466
  "version": "1.1.2",
6467
  "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -6640,6 +6876,14 @@
6640
  "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
6641
  "dev": true
6642
  },
 
 
 
 
 
 
 
 
6643
  "node_modules/yargs": {
6644
  "version": "17.7.2",
6645
  "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -6722,6 +6966,34 @@
6722
  "funding": {
6723
  "url": "https://github.com/sponsors/sindresorhus"
6724
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6725
  }
6726
  }
6727
  }
 
22
  "pg": "*",
23
  "pinata": "*",
24
  "reflect-metadata": "*",
25
+ "swagger-jsdoc": "*",
26
+ "swagger-ui-express": "*",
27
  "typeorm": "*",
28
  "uuid": "*"
29
  },
 
36
  "@types/multer": "*",
37
  "@types/node": "*",
38
  "@types/supertest": "*",
39
+ "@types/swagger-ui-express": "*",
40
  "@types/uuid": "*",
41
  "jest": "*",
42
  "supertest": "*",
 
45
  "typescript": "*"
46
  }
47
  },
48
+ "node_modules/@apidevtools/json-schema-ref-parser": {
49
+ "version": "9.1.2",
50
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz",
51
+ "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==",
52
+ "dependencies": {
53
+ "@jsdevtools/ono": "^7.1.3",
54
+ "@types/json-schema": "^7.0.6",
55
+ "call-me-maybe": "^1.0.1",
56
+ "js-yaml": "^4.1.0"
57
+ }
58
+ },
59
+ "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": {
60
+ "version": "2.0.1",
61
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
62
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
63
+ },
64
+ "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": {
65
+ "version": "4.1.0",
66
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
67
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
68
+ "dependencies": {
69
+ "argparse": "^2.0.1"
70
+ },
71
+ "bin": {
72
+ "js-yaml": "bin/js-yaml.js"
73
+ }
74
+ },
75
+ "node_modules/@apidevtools/openapi-schemas": {
76
+ "version": "2.1.0",
77
+ "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
78
+ "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
79
+ "engines": {
80
+ "node": ">=10"
81
+ }
82
+ },
83
+ "node_modules/@apidevtools/swagger-methods": {
84
+ "version": "3.0.2",
85
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
86
+ "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="
87
+ },
88
+ "node_modules/@apidevtools/swagger-parser": {
89
+ "version": "10.0.3",
90
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz",
91
+ "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==",
92
+ "dependencies": {
93
+ "@apidevtools/json-schema-ref-parser": "^9.0.6",
94
+ "@apidevtools/openapi-schemas": "^2.0.4",
95
+ "@apidevtools/swagger-methods": "^3.0.2",
96
+ "@jsdevtools/ono": "^7.1.3",
97
+ "call-me-maybe": "^1.0.1",
98
+ "z-schema": "^5.0.1"
99
+ },
100
+ "peerDependencies": {
101
+ "openapi-types": ">=7"
102
+ }
103
+ },
104
  "node_modules/@babel/code-frame": {
105
  "version": "7.27.1",
106
  "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
 
1025
  "@jridgewell/sourcemap-codec": "^1.4.14"
1026
  }
1027
  },
1028
+ "node_modules/@jsdevtools/ono": {
1029
+ "version": "7.1.3",
1030
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
1031
+ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
1032
+ },
1033
  "node_modules/@napi-rs/wasm-runtime": {
1034
  "version": "0.2.12",
1035
  "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
 
1084
  "url": "https://opencollective.com/pkgr"
1085
  }
1086
  },
1087
+ "node_modules/@scarf/scarf": {
1088
+ "version": "1.4.0",
1089
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
1090
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
1091
+ "hasInstallScript": true
1092
+ },
1093
  "node_modules/@sinclair/typebox": {
1094
  "version": "0.34.41",
1095
  "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
 
1301
  "pretty-format": "^30.0.0"
1302
  }
1303
  },
1304
+ "node_modules/@types/json-schema": {
1305
+ "version": "7.0.15",
1306
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
1307
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
1308
+ },
1309
  "node_modules/@types/jsonwebtoken": {
1310
  "version": "9.0.10",
1311
  "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
 
1434
  "@types/superagent": "^8.1.0"
1435
  }
1436
  },
1437
+ "node_modules/@types/swagger-ui-express": {
1438
+ "version": "4.1.8",
1439
+ "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
1440
+ "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
1441
+ "dev": true,
1442
+ "dependencies": {
1443
+ "@types/express": "*",
1444
+ "@types/serve-static": "*"
1445
+ }
1446
+ },
1447
  "node_modules/@types/uuid": {
1448
  "version": "11.0.0",
1449
  "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz",
 
2218
  "url": "https://github.com/sponsors/ljharb"
2219
  }
2220
  },
2221
+ "node_modules/call-me-maybe": {
2222
+ "version": "1.0.2",
2223
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
2224
+ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
2225
+ },
2226
  "node_modules/callsites": {
2227
  "version": "3.1.0",
2228
  "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
 
2440
  "node": ">= 0.8"
2441
  }
2442
  },
2443
+ "node_modules/commander": {
2444
+ "version": "6.2.0",
2445
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
2446
+ "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
2447
+ "engines": {
2448
+ "node": ">= 6"
2449
+ }
2450
+ },
2451
  "node_modules/component-emitter": {
2452
  "version": "1.3.1",
2453
  "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
 
2460
  "node_modules/concat-map": {
2461
  "version": "0.0.1",
2462
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
2463
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
 
2464
  },
2465
  "node_modules/concat-stream": {
2466
  "version": "2.0.0",
 
2657
  "node": ">=0.3.1"
2658
  }
2659
  },
2660
+ "node_modules/doctrine": {
2661
+ "version": "3.0.0",
2662
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
2663
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
2664
+ "dependencies": {
2665
+ "esutils": "^2.0.2"
2666
+ },
2667
+ "engines": {
2668
+ "node": ">=6.0.0"
2669
+ }
2670
+ },
2671
  "node_modules/dotenv": {
2672
  "version": "17.2.3",
2673
  "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
 
2835
  "node": ">=4"
2836
  }
2837
  },
2838
+ "node_modules/esutils": {
2839
+ "version": "2.0.3",
2840
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
2841
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
2842
+ "engines": {
2843
+ "node": ">=0.10.0"
2844
+ }
2845
+ },
2846
  "node_modules/etag": {
2847
  "version": "1.8.1",
2848
  "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
 
3144
  "node_modules/fs.realpath": {
3145
  "version": "1.0.0",
3146
  "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
3147
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
 
3148
  },
3149
  "node_modules/fsevents": {
3150
  "version": "2.3.3",
 
3475
  "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
3476
  "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
3477
  "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
 
3478
  "dependencies": {
3479
  "once": "^1.3.0",
3480
  "wrappy": "1"
 
4429
  "node": ">=8"
4430
  }
4431
  },
4432
+ "node_modules/lodash.get": {
4433
+ "version": "4.4.2",
4434
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
4435
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
4436
+ "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead."
4437
+ },
4438
  "node_modules/lodash.includes": {
4439
  "version": "4.3.0",
4440
  "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
 
4445
  "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
4446
  "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
4447
  },
4448
+ "node_modules/lodash.isequal": {
4449
+ "version": "4.5.0",
4450
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
4451
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
4452
+ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead."
4453
+ },
4454
  "node_modules/lodash.isinteger": {
4455
  "version": "4.0.4",
4456
  "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
 
4477
  "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
4478
  "dev": true
4479
  },
4480
+ "node_modules/lodash.mergewith": {
4481
+ "version": "4.6.2",
4482
+ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
4483
+ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="
4484
+ },
4485
  "node_modules/lodash.once": {
4486
  "version": "4.1.1",
4487
  "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
 
4868
  "url": "https://github.com/sponsors/sindresorhus"
4869
  }
4870
  },
4871
+ "node_modules/openapi-types": {
4872
+ "version": "12.1.3",
4873
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
4874
+ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
4875
+ "peer": true
4876
+ },
4877
  "node_modules/p-limit": {
4878
  "version": "3.1.0",
4879
  "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
 
4969
  "version": "1.0.1",
4970
  "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
4971
  "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
 
4972
  "engines": {
4973
  "node": ">=0.10.0"
4974
  }
 
5973
  "url": "https://github.com/sponsors/ljharb"
5974
  }
5975
  },
5976
+ "node_modules/swagger-jsdoc": {
5977
+ "version": "6.2.8",
5978
+ "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz",
5979
+ "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==",
5980
+ "dependencies": {
5981
+ "commander": "6.2.0",
5982
+ "doctrine": "3.0.0",
5983
+ "glob": "7.1.6",
5984
+ "lodash.mergewith": "^4.6.2",
5985
+ "swagger-parser": "^10.0.3",
5986
+ "yaml": "2.0.0-1"
5987
+ },
5988
+ "bin": {
5989
+ "swagger-jsdoc": "bin/swagger-jsdoc.js"
5990
+ },
5991
+ "engines": {
5992
+ "node": ">=12.0.0"
5993
+ }
5994
+ },
5995
+ "node_modules/swagger-jsdoc/node_modules/brace-expansion": {
5996
+ "version": "1.1.12",
5997
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
5998
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
5999
+ "dependencies": {
6000
+ "balanced-match": "^1.0.0",
6001
+ "concat-map": "0.0.1"
6002
+ }
6003
+ },
6004
+ "node_modules/swagger-jsdoc/node_modules/glob": {
6005
+ "version": "7.1.6",
6006
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
6007
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
6008
+ "deprecated": "Glob versions prior to v9 are no longer supported",
6009
+ "dependencies": {
6010
+ "fs.realpath": "^1.0.0",
6011
+ "inflight": "^1.0.4",
6012
+ "inherits": "2",
6013
+ "minimatch": "^3.0.4",
6014
+ "once": "^1.3.0",
6015
+ "path-is-absolute": "^1.0.0"
6016
+ },
6017
+ "engines": {
6018
+ "node": "*"
6019
+ },
6020
+ "funding": {
6021
+ "url": "https://github.com/sponsors/isaacs"
6022
+ }
6023
+ },
6024
+ "node_modules/swagger-jsdoc/node_modules/minimatch": {
6025
+ "version": "3.1.2",
6026
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
6027
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
6028
+ "dependencies": {
6029
+ "brace-expansion": "^1.1.7"
6030
+ },
6031
+ "engines": {
6032
+ "node": "*"
6033
+ }
6034
+ },
6035
+ "node_modules/swagger-parser": {
6036
+ "version": "10.0.3",
6037
+ "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz",
6038
+ "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==",
6039
+ "dependencies": {
6040
+ "@apidevtools/swagger-parser": "10.0.3"
6041
+ },
6042
+ "engines": {
6043
+ "node": ">=10"
6044
+ }
6045
+ },
6046
+ "node_modules/swagger-ui-dist": {
6047
+ "version": "5.30.1",
6048
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.1.tgz",
6049
+ "integrity": "sha512-4mNAUM31sr52K3JcK9qiGbfsFKNh/dm3PkEe+F9FAM31YY/NoRYUgsR/L6d7LLFn6PgZXtBG2ygp8+7UnpUIPg==",
6050
+ "dependencies": {
6051
+ "@scarf/scarf": "=1.4.0"
6052
+ }
6053
+ },
6054
+ "node_modules/swagger-ui-express": {
6055
+ "version": "5.0.1",
6056
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
6057
+ "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
6058
+ "dependencies": {
6059
+ "swagger-ui-dist": ">=5.0.0"
6060
+ },
6061
+ "engines": {
6062
+ "node": ">= v0.10.32"
6063
+ },
6064
+ "peerDependencies": {
6065
+ "express": ">=4.0.0 || >=5.0.0-beta"
6066
+ }
6067
+ },
6068
  "node_modules/synckit": {
6069
  "version": "0.11.11",
6070
  "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
 
6690
  "node": ">=10.12.0"
6691
  }
6692
  },
6693
+ "node_modules/validator": {
6694
+ "version": "13.15.20",
6695
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz",
6696
+ "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==",
6697
+ "engines": {
6698
+ "node": ">= 0.10"
6699
+ }
6700
+ },
6701
  "node_modules/vary": {
6702
  "version": "1.1.2",
6703
  "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
 
6876
  "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
6877
  "dev": true
6878
  },
6879
+ "node_modules/yaml": {
6880
+ "version": "2.0.0-1",
6881
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
6882
+ "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
6883
+ "engines": {
6884
+ "node": ">= 6"
6885
+ }
6886
+ },
6887
  "node_modules/yargs": {
6888
  "version": "17.7.2",
6889
  "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
 
6966
  "funding": {
6967
  "url": "https://github.com/sponsors/sindresorhus"
6968
  }
6969
+ },
6970
+ "node_modules/z-schema": {
6971
+ "version": "5.0.5",
6972
+ "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
6973
+ "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
6974
+ "dependencies": {
6975
+ "lodash.get": "^4.4.2",
6976
+ "lodash.isequal": "^4.5.0",
6977
+ "validator": "^13.7.0"
6978
+ },
6979
+ "bin": {
6980
+ "z-schema": "bin/z-schema"
6981
+ },
6982
+ "engines": {
6983
+ "node": ">=8.0.0"
6984
+ },
6985
+ "optionalDependencies": {
6986
+ "commander": "^9.4.1"
6987
+ }
6988
+ },
6989
+ "node_modules/z-schema/node_modules/commander": {
6990
+ "version": "9.5.0",
6991
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
6992
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
6993
+ "optional": true,
6994
+ "engines": {
6995
+ "node": "^12.20.0 || >=14"
6996
+ }
6997
  }
6998
  }
6999
  }
package.json CHANGED
@@ -32,7 +32,9 @@
32
  "bcryptjs": "*",
33
  "pinata": "*",
34
  "multer": "*",
35
- "form-data": "*"
 
 
36
  },
37
  "devDependencies": {
38
  "@types/express": "*",
@@ -48,6 +50,7 @@
48
  "jest": "*",
49
  "ts-jest": "*",
50
  "supertest": "*",
51
- "@types/supertest": "*"
 
52
  }
53
  }
 
32
  "bcryptjs": "*",
33
  "pinata": "*",
34
  "multer": "*",
35
+ "form-data": "*",
36
+ "swagger-ui-express": "*",
37
+ "swagger-jsdoc": "*"
38
  },
39
  "devDependencies": {
40
  "@types/express": "*",
 
50
  "jest": "*",
51
  "ts-jest": "*",
52
  "supertest": "*",
53
+ "@types/supertest": "*",
54
+ "@types/swagger-ui-express": "*"
55
  }
56
  }
src/app.ts CHANGED
@@ -2,13 +2,19 @@ import express, { Application } from 'express';
2
  import cors from 'cors';
3
  import helmet from 'helmet';
4
  import rateLimit from 'express-rate-limit';
 
 
5
 
6
  // Routes
7
  import authRoutes from './routes/authRoutes';
 
8
  import agentRoutes from './routes/agentRoutes';
9
  import chatRoutes from './routes/chatRoutes';
10
  import subscriptionRoutes from './routes/subscriptionRoutes';
11
  import walletRoutes from './routes/walletRoutes';
 
 
 
12
 
13
  const app: Application = express();
14
 
@@ -35,8 +41,15 @@ app.get('/health', (req, res) => {
35
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
36
  });
37
 
 
 
 
38
  // API routes
39
  app.use('/api/auth', authRoutes);
 
 
 
 
40
  app.use('/api/agents', agentRoutes);
41
  app.use('/api/chat', chatRoutes);
42
  app.use('/api/subscriptions', subscriptionRoutes);
 
2
  import cors from 'cors';
3
  import helmet from 'helmet';
4
  import rateLimit from 'express-rate-limit';
5
+ import swaggerUi from 'swagger-ui-express';
6
+ import { swaggerSpec } from './docs/swagger';
7
 
8
  // Routes
9
  import authRoutes from './routes/authRoutes';
10
+ import creatorAuthRoutes from './routes/creatorAuthRoutes';
11
  import agentRoutes from './routes/agentRoutes';
12
  import chatRoutes from './routes/chatRoutes';
13
  import subscriptionRoutes from './routes/subscriptionRoutes';
14
  import walletRoutes from './routes/walletRoutes';
15
+ import userRoutes from './routes/userRoutes';
16
+ import creatorRoutes from './routes/creatorRoutes';
17
+ import adminRoutes from './routes/adminRoutes';
18
 
19
  const app: Application = express();
20
 
 
41
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
42
  });
43
 
44
+ // Docs
45
+ app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
46
+
47
  // API routes
48
  app.use('/api/auth', authRoutes);
49
+ app.use('/api/creator-auth', creatorAuthRoutes);
50
+ app.use('/api/users', userRoutes);
51
+ app.use('/api/creators', creatorRoutes);
52
+ app.use('/api/admin', adminRoutes);
53
  app.use('/api/agents', agentRoutes);
54
  app.use('/api/chat', chatRoutes);
55
  app.use('/api/subscriptions', subscriptionRoutes);
src/config/database.ts CHANGED
@@ -6,11 +6,12 @@ import { ApiKey } from '../entities/ApiKey';
6
  import { ChatMessage } from '../entities/ChatMessage';
7
  import { Wallet } from '../entities/Wallet';
8
  import { Transaction } from '../entities/Transaction';
 
9
 
10
  export const AppDataSource = new DataSource({
11
  type: 'postgres',
12
  url: process.env.DATABASE_URL,
13
- entities: [Agent, User, Subscription, ApiKey, ChatMessage, Wallet, Transaction],
14
  synchronize: process.env.NODE_ENV !== 'production', // Auto-sync in dev only
15
  logging: process.env.NODE_ENV === 'development',
16
  });
 
6
  import { ChatMessage } from '../entities/ChatMessage';
7
  import { Wallet } from '../entities/Wallet';
8
  import { Transaction } from '../entities/Transaction';
9
+ import { CreatorProfile } from '../entities/CreatorProfile';
10
 
11
  export const AppDataSource = new DataSource({
12
  type: 'postgres',
13
  url: process.env.DATABASE_URL,
14
+ entities: [Agent, User, Subscription, ApiKey, ChatMessage, Wallet, Transaction, CreatorProfile],
15
  synchronize: process.env.NODE_ENV !== 'production', // Auto-sync in dev only
16
  logging: process.env.NODE_ENV === 'development',
17
  });
src/controllers/adminController.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from 'express';
2
+ import { AppDataSource } from '../config/database';
3
+ import { User } from '../entities/User';
4
+ import { Agent, AgentStatus } from '../entities/Agent';
5
+ import { ChatMessage } from '../entities/ChatMessage';
6
+ import { Transaction } from '../entities/Transaction';
7
+
8
+ const userRepository = AppDataSource.getRepository(User);
9
+ const agentRepository = AppDataSource.getRepository(Agent);
10
+ const messageRepository = AppDataSource.getRepository(ChatMessage);
11
+ const txRepository = AppDataSource.getRepository(Transaction);
12
+
13
+ export class AdminController {
14
+ async overview(req: Request, res: Response) {
15
+ try {
16
+ const { from, to } = req.query as any;
17
+
18
+ const [users, agents, messages] = await Promise.all([
19
+ userRepository.count(),
20
+ agentRepository.count(),
21
+ messageRepository.count(),
22
+ ]);
23
+
24
+ const pendingAgents = await agentRepository.count({ where: { status: AgentStatus.PENDING } });
25
+
26
+ let pvQb = txRepository
27
+ .createQueryBuilder('t')
28
+ .select('SUM(ABS(t.amount))', 'sum');
29
+
30
+ if (from) pvQb = pvQb.where('t.createdAt >= :from', { from });
31
+ if (to) pvQb = pvQb.andWhere('t.createdAt <= :to', { to });
32
+
33
+ const totalPointsVolumeRow = await pvQb.getRawOne();
34
+
35
+ res.json({
36
+ totals: {
37
+ users,
38
+ agents,
39
+ messages,
40
+ pendingAgents,
41
+ pointsVolume: Number(totalPointsVolumeRow?.sum || 0),
42
+ },
43
+ });
44
+ } catch (error) {
45
+ console.error('Admin overview error:', error);
46
+ res.status(500).json({ error: 'Failed to get overview' });
47
+ }
48
+ }
49
+ }
src/controllers/creatorAuthController.ts ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from 'express';
2
+ import bcrypt from 'bcryptjs';
3
+ import jwt from 'jsonwebtoken';
4
+ import { AppDataSource } from '../config/database';
5
+ import { User } from '../entities/User';
6
+ import { CreatorProfile } from '../entities/CreatorProfile';
7
+
8
+ const userRepository = AppDataSource.getRepository(User);
9
+ const profileRepository = AppDataSource.getRepository(CreatorProfile);
10
+
11
+ export class CreatorAuthController {
12
+ async register(req: Request, res: Response) {
13
+ try {
14
+ const {
15
+ fullName,
16
+ username,
17
+ email,
18
+ password,
19
+ profileImage,
20
+ bio,
21
+ organization,
22
+ role,
23
+ website,
24
+ linkedinUrl,
25
+ githubUrl,
26
+ huggingfaceUrl,
27
+ primaryLanguages,
28
+ frameworks,
29
+ agentSpecialties,
30
+ preferredCompute,
31
+ endpointHosting,
32
+ walletAddress,
33
+ bankAccount,
34
+ preferredCurrency,
35
+ country,
36
+ taxId,
37
+ idDocumentUrl,
38
+ } = req.body;
39
+
40
+ if (!fullName || !username || !email || !password) {
41
+ return res.status(400).json({ error: 'fullName, username, email and password are required' });
42
+ }
43
+
44
+ // Username stored without leading @
45
+ const cleanUsername = String(username).replace(/^@+/, '').toLowerCase();
46
+
47
+ // Uniqueness checks
48
+ const existingUser = await userRepository.findOne({ where: { email } });
49
+ if (existingUser) return res.status(400).json({ error: 'Email already in use' });
50
+ const existingProfile = await profileRepository.findOne({ where: { username: cleanUsername } });
51
+ if (existingProfile) return res.status(400).json({ error: 'Username already in use' });
52
+
53
+ const hashedPassword = await bcrypt.hash(password, 10);
54
+
55
+ // Create user
56
+ const user = userRepository.create({ email, password: hashedPassword, name: fullName, isCreator: true, isAdmin: false });
57
+ const savedUser = await userRepository.save(user);
58
+
59
+ // Normalize arrays
60
+ const normArray = (v: any) => {
61
+ if (!v) return [] as string[];
62
+ if (Array.isArray(v)) return v;
63
+ try { return JSON.parse(v); } catch { return [String(v)]; }
64
+ };
65
+
66
+ // Create profile
67
+ const profile = profileRepository.create({
68
+ userId: savedUser.id,
69
+ fullName,
70
+ username: cleanUsername,
71
+ profileImage,
72
+ bio,
73
+ organization,
74
+ role,
75
+ website,
76
+ linkedinUrl,
77
+ githubUrl,
78
+ huggingfaceUrl,
79
+ primaryLanguages: normArray(primaryLanguages),
80
+ frameworks: normArray(frameworks),
81
+ agentSpecialties: normArray(agentSpecialties),
82
+ preferredCompute,
83
+ endpointHosting,
84
+ walletAddress,
85
+ bankAccount: bankAccount ? (typeof bankAccount === 'string' ? JSON.parse(bankAccount) : bankAccount) : undefined,
86
+ preferredCurrency,
87
+ country,
88
+ taxId,
89
+ idDocumentUrl,
90
+ });
91
+ await profileRepository.save(profile);
92
+
93
+ // Token
94
+ const token = jwt.sign(
95
+ { id: savedUser.id, email: savedUser.email, isAdmin: savedUser.isAdmin },
96
+ process.env.JWT_SECRET!,
97
+ { expiresIn: '7d' }
98
+ );
99
+
100
+ res.status(201).json({ token, user: { id: savedUser.id, email: savedUser.email, name: savedUser.name, isCreator: true } });
101
+ } catch (error: any) {
102
+ console.error('Creator register error:', error);
103
+ res.status(500).json({ error: error.message || 'Failed to register creator' });
104
+ }
105
+ }
106
+
107
+ async login(req: Request, res: Response) {
108
+ try {
109
+ const { email, password } = req.body;
110
+ if (!email || !password) return res.status(400).json({ error: 'Email and password are required' });
111
+
112
+ const user = await userRepository.findOne({ where: { email } });
113
+ if (!user || !user.isCreator) return res.status(401).json({ error: 'Invalid credentials' });
114
+
115
+ const isValid = await bcrypt.compare(password, user.password);
116
+ if (!isValid) return res.status(401).json({ error: 'Invalid credentials' });
117
+
118
+ const token = jwt.sign(
119
+ { id: user.id, email: user.email, isAdmin: user.isAdmin },
120
+ process.env.JWT_SECRET!,
121
+ { expiresIn: '7d' }
122
+ );
123
+
124
+ // Fetch profile brief
125
+ const profile = await profileRepository.findOne({ where: { userId: user.id } });
126
+
127
+ res.json({
128
+ token,
129
+ user: {
130
+ id: user.id,
131
+ email: user.email,
132
+ name: user.name,
133
+ isCreator: user.isCreator,
134
+ profile: profile ? { username: profile.username, fullName: profile.fullName } : null,
135
+ },
136
+ });
137
+ } catch (error: any) {
138
+ console.error('Creator login error:', error);
139
+ res.status(500).json({ error: error.message || 'Failed to login creator' });
140
+ }
141
+ }
142
+ }
src/controllers/creatorController.ts ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from 'express';
2
+ import { AppDataSource } from '../config/database';
3
+ import { Agent } from '../entities/Agent';
4
+ import { Transaction, TransactionType } from '../entities/Transaction';
5
+
6
+ const agentRepository = AppDataSource.getRepository(Agent);
7
+ const txRepository = AppDataSource.getRepository(Transaction);
8
+
9
+ export class CreatorController {
10
+ async overview(req: Request, res: Response) {
11
+ try {
12
+ const userId = (req as any).user.id;
13
+ const { agentId, from, to } = req.query as any;
14
+
15
+ const agents = await agentRepository.find({ where: { creatorId: userId } });
16
+ const agentIds = agents.map((a) => a.id);
17
+
18
+ let countQb = txRepository
19
+ .createQueryBuilder('t')
20
+ .where('t.type = :type', { type: TransactionType.CHARGE })
21
+ .andWhere('t.agentId IN (:...agentIds)', { agentIds: agentIds.length ? agentIds : [''] });
22
+
23
+ if (agentId) countQb = countQb.andWhere('t.agentId = :agentId', { agentId });
24
+ if (from) countQb = countQb.andWhere('t.createdAt >= :from', { from });
25
+ if (to) countQb = countQb.andWhere('t.createdAt <= :to', { to });
26
+
27
+ const totalTasks = await countQb.getCount();
28
+
29
+ let sumQb = txRepository
30
+ .createQueryBuilder('t')
31
+ .where('t.type = :type', { type: TransactionType.CHARGE })
32
+ .andWhere('t.agentId IN (:...agentIds)', { agentIds: agentIds.length ? agentIds : [''] })
33
+ .select('SUM(ABS(t.amount))', 'sum');
34
+
35
+ if (agentId) sumQb = sumQb.andWhere('t.agentId = :agentId', { agentId });
36
+ if (from) sumQb = sumQb.andWhere('t.createdAt >= :from', { from });
37
+ if (to) sumQb = sumQb.andWhere('t.createdAt <= :to', { to });
38
+
39
+ const totalPoints = await sumQb.getRawOne();
40
+
41
+ res.json({
42
+ agents: agents.map((a) => ({ id: a.id, name: a.name, pointsPerTask: Number(a.pointsPerTask || 0) })),
43
+ totals: {
44
+ agents: agents.length,
45
+ tasks: totalTasks,
46
+ pointsEarned: Number(totalPoints?.sum || 0),
47
+ },
48
+ });
49
+ } catch (error) {
50
+ console.error('Creator overview error:', error);
51
+ res.status(500).json({ error: 'Failed to get overview' });
52
+ }
53
+ }
54
+
55
+ async earnings(req: Request, res: Response) {
56
+ try {
57
+ const userId = (req as any).user.id;
58
+ const { from, to, agentId } = req.query as any;
59
+
60
+ const agents = await agentRepository.find({ where: { creatorId: userId } });
61
+ const agentIds = agents.map((a) => a.id);
62
+
63
+ let qb = txRepository
64
+ .createQueryBuilder('t')
65
+ .where('t.agentId IN (:...agentIds)', { agentIds: agentIds.length ? agentIds : [''] })
66
+ .andWhere('t.type = :type', { type: TransactionType.CHARGE })
67
+ .select("DATE_TRUNC('day', t.createdAt)", 'day')
68
+ .addSelect('SUM(ABS(t.amount))', 'points')
69
+ .groupBy("DATE_TRUNC('day', t.createdAt)")
70
+ .orderBy('day', 'ASC');
71
+
72
+ if (agentId) qb = qb.andWhere('t.agentId = :agentId', { agentId });
73
+ if (from) qb = qb.andWhere('t.createdAt >= :from', { from });
74
+ if (to) qb = qb.andWhere('t.createdAt <= :to', { to });
75
+
76
+ const rows = await qb.getRawMany();
77
+
78
+ res.json({
79
+ series: rows.map((r) => ({ day: r.day, points: Number(r.points) })),
80
+ });
81
+ } catch (error) {
82
+ console.error('Creator earnings error:', error);
83
+ res.status(500).json({ error: 'Failed to get earnings' });
84
+ }
85
+ }
86
+ }
src/controllers/userController.ts ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from 'express';
2
+ import { AppDataSource } from '../config/database';
3
+ import { ChatMessage } from '../entities/ChatMessage';
4
+ import { Agent } from '../entities/Agent';
5
+
6
+ const messageRepository = AppDataSource.getRepository(ChatMessage);
7
+ const agentRepository = AppDataSource.getRepository(Agent);
8
+
9
+ export class UserController {
10
+ /**
11
+ * Get aggregated chat history across agents for the current user
12
+ */
13
+ async getMyHistory(req: Request, res: Response) {
14
+ try {
15
+ const userId = (req as any).user.id;
16
+ const { limit = 50, offset = 0, agentId, role, from, to, search } = req.query as any;
17
+
18
+ // Build query with filters
19
+ let qb = messageRepository
20
+ .createQueryBuilder('m')
21
+ .where('m.userId = :userId', { userId })
22
+ .orderBy('m.createdAt', 'DESC')
23
+ .limit(Number(limit))
24
+ .offset(Number(offset));
25
+
26
+ if (agentId) {
27
+ qb = qb.andWhere('m.agentId = :agentId', { agentId });
28
+ }
29
+ if (role) {
30
+ qb = qb.andWhere('m.role = :role', { role });
31
+ }
32
+ if (from) {
33
+ qb = qb.andWhere('m.createdAt >= :from', { from });
34
+ }
35
+ if (to) {
36
+ qb = qb.andWhere('m.createdAt <= :to', { to });
37
+ }
38
+ if (search) {
39
+ qb = qb.andWhere('m.content ILIKE :search', { search: `%${search}%` });
40
+ }
41
+
42
+ const messages = await qb.getMany();
43
+
44
+ // Attach minimal agent info
45
+ const agentIds = Array.from(new Set(messages.map((m) => m.agentId)));
46
+ const agents = agentIds.length ? await agentRepository.findByIds(agentIds) : [];
47
+ const agentMap = new Map(agents.map((a) => [a.id, { id: a.id, name: a.name, avatar: a.avatar, category: a.category }]));
48
+
49
+ res.json({
50
+ messages: messages.map((m) => ({
51
+ id: m.id,
52
+ agentId: m.agentId,
53
+ agent: agentMap.get(m.agentId) || null,
54
+ role: m.role,
55
+ content: m.content,
56
+ metadata: m.metadata,
57
+ createdAt: m.createdAt,
58
+ })),
59
+ pagination: {
60
+ limit: Number(limit),
61
+ offset: Number(offset),
62
+ count: messages.length,
63
+ },
64
+ });
65
+ } catch (error) {
66
+ console.error('Get user history error:', error);
67
+ res.status(500).json({ error: 'Failed to get history' });
68
+ }
69
+ }
70
+ }
src/controllers/walletController.ts CHANGED
@@ -67,7 +67,7 @@ export class WalletController {
67
  */
68
  async verifyPayment(req: Request, res: Response) {
69
  try {
70
- const secret = process.env.PAYSTACK_SECRET;
71
  if (!secret) {
72
  throw new Error('Paystack secret not configured');
73
  }
@@ -84,27 +84,37 @@ export class WalletController {
84
  const event = req.body;
85
 
86
  if (event.event === 'charge.success') {
87
- const { reference, amount, metadata } = event.data;
88
 
89
- // Extract userId from reference or metadata
90
- const userId = metadata?.userId || reference.split('_')[1];
 
91
 
92
- if (!userId) {
93
- console.error('Could not extract userId from payment reference');
94
- return res.status(400).json({ error: 'Invalid payment reference' });
 
95
  }
96
 
97
- // Calculate points from amount (amount is in kobo for NGN, or smallest unit)
98
- // For NGN: amount / 100 = Naira, then convert to dollars and points
 
 
 
 
 
99
  const amountInNaira = amount / 100;
100
- const amountInDollars = amountInNaira / 750; // Adjust exchange rate as needed
101
- const points = walletService.dollarsToPoints(amountInDollars);
 
102
 
103
  // Add points to wallet
104
- await walletService.addPoints(userId, points, reference, {
105
  paystackEvent: event.event,
106
  amountInNaira,
107
  amountInDollars,
 
 
108
  });
109
  }
110
 
 
67
  */
68
  async verifyPayment(req: Request, res: Response) {
69
  try {
70
+ const secret = process.env.PAYSTACK_SECRET_KEY;
71
  if (!secret) {
72
  throw new Error('Paystack secret not configured');
73
  }
 
84
  const event = req.body;
85
 
86
  if (event.event === 'charge.success') {
87
+ const { reference, amount, metadata, customer } = event.data;
88
 
89
+ // Prefer userId in metadata, else try by email
90
+ const userId = metadata?.userId;
91
+ const email = customer?.email;
92
 
93
+ let resolvedUserId = userId;
94
+ if (!resolvedUserId && email) {
95
+ // For now, require userId in metadata in production flows
96
+ console.warn('No userId in metadata; include userId in reference metadata for reliability');
97
  }
98
 
99
+ if (!resolvedUserId) {
100
+ return res.status(400).json({ error: 'Missing userId in metadata' });
101
+ }
102
+
103
+ // Calculate points from amount
104
+ // amount is typically in kobo (NGN) β†’ naira = amount/100 β†’ dollars via FX β†’ points via POINT_VALUE_USD
105
+ const pointValueUsd = parseFloat(process.env.POINT_VALUE_USD || '0.05');
106
  const amountInNaira = amount / 100;
107
+ const fx = parseFloat(process.env.NGN_PER_USD || '750');
108
+ const amountInDollars = amountInNaira / fx;
109
+ const points = Math.round(amountInDollars / pointValueUsd);
110
 
111
  // Add points to wallet
112
+ await walletService.addPoints(resolvedUserId, points, reference, {
113
  paystackEvent: event.event,
114
  amountInNaira,
115
  amountInDollars,
116
+ fx,
117
+ pointValueUsd,
118
  });
119
  }
120
 
src/docs/swagger.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import swaggerJSDoc from 'swagger-jsdoc';
2
+
3
+ export const swaggerSpec = swaggerJSDoc({
4
+ definition: {
5
+ openapi: '3.0.0',
6
+ info: {
7
+ title: 'Zurri API',
8
+ version: '1.0.0',
9
+ description: 'Zurri Agents Marketplace API with Wallet Point System',
10
+ },
11
+ servers: [{ url: '/api' }],
12
+ components: {
13
+ securitySchemes: {
14
+ bearerAuth: {
15
+ type: 'http',
16
+ scheme: 'bearer',
17
+ bearerFormat: 'JWT',
18
+ },
19
+ },
20
+ },
21
+ security: [{ bearerAuth: [] }],
22
+ },
23
+ apis: [
24
+ './src/routes/*.ts',
25
+ ],
26
+ });
src/entities/CreatorProfile.ts ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
2
+ import { User } from './User';
3
+
4
+ export enum VerificationStatus {
5
+ UNVERIFIED = 'UNVERIFIED',
6
+ PENDING = 'PENDING',
7
+ }
8
+
9
+ @Entity('creator_profiles')
10
+ @Index(['username'], { unique: true })
11
+ export class CreatorProfile {
12
+ @PrimaryGeneratedColumn('uuid')
13
+ id: string;
14
+
15
+ @OneToOne(() => User)
16
+ @JoinColumn({ name: 'userId' })
17
+ user: User;
18
+
19
+ @Column({ unique: true })
20
+ userId: string;
21
+
22
+ // Basic Information
23
+ @Column()
24
+ fullName: string;
25
+
26
+ @Column({ unique: true })
27
+ username: string; // e.g. @neuralnex (store without @)
28
+
29
+ @Column({ nullable: true })
30
+ profileImage?: string; // URL/IPFS
31
+
32
+ @Column({ length: 200, nullable: true })
33
+ bio?: string;
34
+
35
+ // Professional Information
36
+ @Column({ nullable: true })
37
+ organization?: string;
38
+
39
+ @Column({ nullable: true })
40
+ role?: string; // e.g., AI Engineer
41
+
42
+ @Column({ nullable: true })
43
+ website?: string;
44
+
45
+ @Column({ nullable: true })
46
+ linkedinUrl?: string;
47
+
48
+ @Column({ nullable: true })
49
+ githubUrl?: string;
50
+
51
+ @Column({ nullable: true })
52
+ huggingfaceUrl?: string;
53
+
54
+ // Technical Stack & Focus
55
+ @Column('text', { array: true, default: '{}' })
56
+ primaryLanguages: string[];
57
+
58
+ @Column('text', { array: true, default: '{}' })
59
+ frameworks: string[];
60
+
61
+ @Column('text', { array: true, default: '{}' })
62
+ agentSpecialties: string[];
63
+
64
+ @Column({ nullable: true })
65
+ preferredCompute?: string;
66
+
67
+ @Column({ nullable: true })
68
+ endpointHosting?: string;
69
+
70
+ // Payout & Monetization
71
+ @Column({ nullable: true })
72
+ walletAddress?: string;
73
+
74
+ @Column('jsonb', { nullable: true })
75
+ bankAccount?: { accountName?: string; accountNumber?: string; bankCode?: string };
76
+
77
+ @Column({ nullable: true })
78
+ preferredCurrency?: string;
79
+
80
+ @Column({ nullable: true })
81
+ country?: string;
82
+
83
+ @Column({ nullable: true })
84
+ taxId?: string;
85
+
86
+ // Verification
87
+ @Column({ nullable: true })
88
+ idDocumentUrl?: string;
89
+
90
+ @Column({ type: 'enum', enum: VerificationStatus, default: VerificationStatus.UNVERIFIED })
91
+ verificationStatus: VerificationStatus;
92
+
93
+ @Column({ default: false })
94
+ portfolioApproved: boolean;
95
+
96
+ @CreateDateColumn()
97
+ createdAt: Date;
98
+
99
+ @UpdateDateColumn()
100
+ updatedAt: Date;
101
+ }
src/entities/User.ts CHANGED
@@ -29,6 +29,9 @@ export class User {
29
  @Column({ default: false })
30
  isAdmin: boolean;
31
 
 
 
 
32
  @Column({ default: true })
33
  isActive: boolean;
34
 
 
29
  @Column({ default: false })
30
  isAdmin: boolean;
31
 
32
+ @Column({ default: false })
33
+ isCreator: boolean;
34
+
35
  @Column({ default: true })
36
  isActive: boolean;
37
 
src/routes/adminRoutes.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Router } from 'express';
2
+ import { authenticate, requireAdmin } from '../middlewares/auth';
3
+ import { AdminController } from '../controllers/adminController';
4
+
5
+ const router = Router();
6
+ const adminController = new AdminController();
7
+
8
+ router.get('/overview', authenticate, requireAdmin, adminController.overview.bind(adminController));
9
+
10
+ export default router;
src/routes/authRoutes.ts CHANGED
@@ -4,9 +4,15 @@ import jwt from 'jsonwebtoken';
4
  import { AppDataSource } from '../config/database';
5
  import { User } from '../entities/User';
6
  import { authenticate } from '../middlewares/auth';
 
 
 
7
 
8
  const router = Router();
9
  const userRepository = AppDataSource.getRepository(User);
 
 
 
10
 
11
  /**
12
  * Register new user
@@ -110,7 +116,7 @@ router.post('/login', async (req: Request, res: Response) => {
110
  });
111
 
112
  /**
113
- * Get current user
114
  */
115
  router.get('/me', authenticate, async (req: Request, res: Response) => {
116
  try {
@@ -124,7 +130,29 @@ router.get('/me', authenticate, async (req: Request, res: Response) => {
124
  return res.status(404).json({ error: 'User not found' });
125
  }
126
 
127
- res.json(user);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  } catch (error) {
129
  console.error('Get me error:', error);
130
  res.status(500).json({ error: 'Failed to get user' });
 
4
  import { AppDataSource } from '../config/database';
5
  import { User } from '../entities/User';
6
  import { authenticate } from '../middlewares/auth';
7
+ import { WalletService } from '../services/walletService';
8
+ import { Agent } from '../entities/Agent';
9
+ import { ChatMessage } from '../entities/ChatMessage';
10
 
11
  const router = Router();
12
  const userRepository = AppDataSource.getRepository(User);
13
+ const agentRepository = AppDataSource.getRepository(Agent);
14
+ const messageRepository = AppDataSource.getRepository(ChatMessage);
15
+ const walletService = new WalletService();
16
 
17
  /**
18
  * Register new user
 
116
  });
117
 
118
  /**
119
+ * Get current user (profile + wallet + counts)
120
  */
121
  router.get('/me', authenticate, async (req: Request, res: Response) => {
122
  try {
 
130
  return res.status(404).json({ error: 'User not found' });
131
  }
132
 
133
+ // Wallet summary
134
+ const wallet = await walletService.getOrCreateWallet(userId);
135
+
136
+ // Counts
137
+ const agentsCreated = await agentRepository.count({ where: { creatorId: userId } });
138
+ const totalMessages = await messageRepository.count({ where: { userId } });
139
+
140
+ res.json({
141
+ id: user.id,
142
+ email: user.email,
143
+ name: user.name,
144
+ isAdmin: user.isAdmin,
145
+ createdAt: user.createdAt,
146
+ wallet: {
147
+ balance: Number(wallet.balance),
148
+ balanceInDollars: walletService.pointsToDollars(Number(wallet.balance)),
149
+ freeTasksRemaining: await walletService.getFreeTasksRemaining(userId),
150
+ },
151
+ counts: {
152
+ agentsCreated,
153
+ totalMessages,
154
+ },
155
+ });
156
  } catch (error) {
157
  console.error('Get me error:', error);
158
  res.status(500).json({ error: 'Failed to get user' });
src/routes/creatorAuthRoutes.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Router } from 'express';
2
+ import { CreatorAuthController } from '../controllers/creatorAuthController';
3
+
4
+ const router = Router();
5
+ const controller = new CreatorAuthController();
6
+
7
+ router.post('/register', controller.register.bind(controller));
8
+ router.post('/login', controller.login.bind(controller));
9
+
10
+ export default router;
src/routes/creatorRoutes.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Router } from 'express';
2
+ import { authenticate } from '../middlewares/auth';
3
+ import { CreatorController } from '../controllers/creatorController';
4
+
5
+ const router = Router();
6
+ const creatorController = new CreatorController();
7
+
8
+ router.get('/me/overview', authenticate, creatorController.overview.bind(creatorController));
9
+ router.get('/me/earnings', authenticate, creatorController.earnings.bind(creatorController));
10
+
11
+ export default router;
src/routes/userRoutes.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Router } from 'express';
2
+ import { authenticate } from '../middlewares/auth';
3
+ import { UserController } from '../controllers/userController';
4
+
5
+ const router = Router();
6
+ const userController = new UserController();
7
+
8
+ router.get('/me/history', authenticate, userController.getMyHistory.bind(userController));
9
+
10
+ export default router;