nexusbert commited on
Commit
bafd3be
·
1 Parent(s): cb429c5

push all other accessories

Browse files
package-lock.json CHANGED
@@ -16,6 +16,7 @@
16
  "dotenv": "^17.2.3",
17
  "express": "^5.1.0",
18
  "jsonwebtoken": "^9.0.2",
 
19
  "pg": "^8.16.3",
20
  "reflect-metadata": "^0.2.2",
21
  "swagger-jsdoc": "^6.2.8",
@@ -27,6 +28,7 @@
27
  "@types/cors": "^2.8.19",
28
  "@types/express": "^5.0.5",
29
  "@types/jsonwebtoken": "^9.0.10",
 
30
  "@types/node": "^24.9.2",
31
  "@types/swagger-jsdoc": "^6.0.4",
32
  "@types/swagger-ui-express": "^4.1.6",
@@ -289,6 +291,15 @@
289
  "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
290
  "dev": true
291
  },
 
 
 
 
 
 
 
 
 
292
  "node_modules/@types/node": {
293
  "version": "24.9.2",
294
  "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
@@ -459,6 +470,11 @@
459
  "node": ">= 6.0.0"
460
  }
461
  },
 
 
 
 
 
462
  "node_modules/aproba": {
463
  "version": "2.1.0",
464
  "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
@@ -633,6 +649,22 @@
633
  "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
634
  "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
635
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  "node_modules/bytes": {
637
  "version": "3.1.2",
638
  "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -862,6 +894,52 @@
862
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
863
  "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
864
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
865
  "node_modules/console-control-strings": {
866
  "version": "1.1.0",
867
  "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -902,6 +980,11 @@
902
  "node": ">=6.6.0"
903
  }
904
  },
 
 
 
 
 
905
  "node_modules/cors": {
906
  "version": "2.8.5",
907
  "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -1980,6 +2063,14 @@
1980
  "url": "https://github.com/sponsors/isaacs"
1981
  }
1982
  },
 
 
 
 
 
 
 
 
1983
  "node_modules/minipass": {
1984
  "version": "7.1.2",
1985
  "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -2027,6 +2118,74 @@
2027
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2028
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
2029
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2030
  "node_modules/negotiator": {
2031
  "version": "1.0.0",
2032
  "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
@@ -2377,6 +2536,11 @@
2377
  "node": ">=0.10.0"
2378
  }
2379
  },
 
 
 
 
 
2380
  "node_modules/proxy-addr": {
2381
  "version": "2.0.7",
2382
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -2825,6 +2989,14 @@
2825
  "node": ">= 0.8"
2826
  }
2827
  },
 
 
 
 
 
 
 
 
2828
  "node_modules/string_decoder": {
2829
  "version": "1.3.0",
2830
  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -3170,6 +3342,11 @@
3170
  "node": ">= 0.4"
3171
  }
3172
  },
 
 
 
 
 
3173
  "node_modules/typeorm": {
3174
  "version": "0.3.27",
3175
  "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.27.tgz",
 
16
  "dotenv": "^17.2.3",
17
  "express": "^5.1.0",
18
  "jsonwebtoken": "^9.0.2",
19
+ "multer": "^1.4.5-lts.1",
20
  "pg": "^8.16.3",
21
  "reflect-metadata": "^0.2.2",
22
  "swagger-jsdoc": "^6.2.8",
 
28
  "@types/cors": "^2.8.19",
29
  "@types/express": "^5.0.5",
30
  "@types/jsonwebtoken": "^9.0.10",
31
+ "@types/multer": "^1.4.12",
32
  "@types/node": "^24.9.2",
33
  "@types/swagger-jsdoc": "^6.0.4",
34
  "@types/swagger-ui-express": "^4.1.6",
 
291
  "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
292
  "dev": true
293
  },
294
+ "node_modules/@types/multer": {
295
+ "version": "1.4.13",
296
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
297
+ "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",
298
+ "dev": true,
299
+ "dependencies": {
300
+ "@types/express": "*"
301
+ }
302
+ },
303
  "node_modules/@types/node": {
304
  "version": "24.9.2",
305
  "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
 
470
  "node": ">= 6.0.0"
471
  }
472
  },
473
+ "node_modules/append-field": {
474
+ "version": "1.0.0",
475
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
476
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
477
+ },
478
  "node_modules/aproba": {
479
  "version": "2.1.0",
480
  "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
 
649
  "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
650
  "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
651
  },
652
+ "node_modules/buffer-from": {
653
+ "version": "1.1.2",
654
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
655
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
656
+ },
657
+ "node_modules/busboy": {
658
+ "version": "1.6.0",
659
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
660
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
661
+ "dependencies": {
662
+ "streamsearch": "^1.1.0"
663
+ },
664
+ "engines": {
665
+ "node": ">=10.16.0"
666
+ }
667
+ },
668
  "node_modules/bytes": {
669
  "version": "3.1.2",
670
  "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
 
894
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
895
  "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
896
  },
897
+ "node_modules/concat-stream": {
898
+ "version": "1.6.2",
899
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
900
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
901
+ "engines": [
902
+ "node >= 0.8"
903
+ ],
904
+ "dependencies": {
905
+ "buffer-from": "^1.0.0",
906
+ "inherits": "^2.0.3",
907
+ "readable-stream": "^2.2.2",
908
+ "typedarray": "^0.0.6"
909
+ }
910
+ },
911
+ "node_modules/concat-stream/node_modules/isarray": {
912
+ "version": "1.0.0",
913
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
914
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
915
+ },
916
+ "node_modules/concat-stream/node_modules/readable-stream": {
917
+ "version": "2.3.8",
918
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
919
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
920
+ "dependencies": {
921
+ "core-util-is": "~1.0.0",
922
+ "inherits": "~2.0.3",
923
+ "isarray": "~1.0.0",
924
+ "process-nextick-args": "~2.0.0",
925
+ "safe-buffer": "~5.1.1",
926
+ "string_decoder": "~1.1.1",
927
+ "util-deprecate": "~1.0.1"
928
+ }
929
+ },
930
+ "node_modules/concat-stream/node_modules/safe-buffer": {
931
+ "version": "5.1.2",
932
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
933
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
934
+ },
935
+ "node_modules/concat-stream/node_modules/string_decoder": {
936
+ "version": "1.1.1",
937
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
938
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
939
+ "dependencies": {
940
+ "safe-buffer": "~5.1.0"
941
+ }
942
+ },
943
  "node_modules/console-control-strings": {
944
  "version": "1.1.0",
945
  "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
 
980
  "node": ">=6.6.0"
981
  }
982
  },
983
+ "node_modules/core-util-is": {
984
+ "version": "1.0.3",
985
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
986
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
987
+ },
988
  "node_modules/cors": {
989
  "version": "2.8.5",
990
  "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
 
2063
  "url": "https://github.com/sponsors/isaacs"
2064
  }
2065
  },
2066
+ "node_modules/minimist": {
2067
+ "version": "1.2.8",
2068
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
2069
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
2070
+ "funding": {
2071
+ "url": "https://github.com/sponsors/ljharb"
2072
+ }
2073
+ },
2074
  "node_modules/minipass": {
2075
  "version": "7.1.2",
2076
  "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
 
2118
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2119
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
2120
  },
2121
+ "node_modules/multer": {
2122
+ "version": "1.4.5-lts.2",
2123
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
2124
+ "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
2125
+ "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
2126
+ "dependencies": {
2127
+ "append-field": "^1.0.0",
2128
+ "busboy": "^1.0.0",
2129
+ "concat-stream": "^1.5.2",
2130
+ "mkdirp": "^0.5.4",
2131
+ "object-assign": "^4.1.1",
2132
+ "type-is": "^1.6.4",
2133
+ "xtend": "^4.0.0"
2134
+ },
2135
+ "engines": {
2136
+ "node": ">= 6.0.0"
2137
+ }
2138
+ },
2139
+ "node_modules/multer/node_modules/media-typer": {
2140
+ "version": "0.3.0",
2141
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
2142
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
2143
+ "engines": {
2144
+ "node": ">= 0.6"
2145
+ }
2146
+ },
2147
+ "node_modules/multer/node_modules/mime-db": {
2148
+ "version": "1.52.0",
2149
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
2150
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
2151
+ "engines": {
2152
+ "node": ">= 0.6"
2153
+ }
2154
+ },
2155
+ "node_modules/multer/node_modules/mime-types": {
2156
+ "version": "2.1.35",
2157
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
2158
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
2159
+ "dependencies": {
2160
+ "mime-db": "1.52.0"
2161
+ },
2162
+ "engines": {
2163
+ "node": ">= 0.6"
2164
+ }
2165
+ },
2166
+ "node_modules/multer/node_modules/mkdirp": {
2167
+ "version": "0.5.6",
2168
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
2169
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
2170
+ "dependencies": {
2171
+ "minimist": "^1.2.6"
2172
+ },
2173
+ "bin": {
2174
+ "mkdirp": "bin/cmd.js"
2175
+ }
2176
+ },
2177
+ "node_modules/multer/node_modules/type-is": {
2178
+ "version": "1.6.18",
2179
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
2180
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
2181
+ "dependencies": {
2182
+ "media-typer": "0.3.0",
2183
+ "mime-types": "~2.1.24"
2184
+ },
2185
+ "engines": {
2186
+ "node": ">= 0.6"
2187
+ }
2188
+ },
2189
  "node_modules/negotiator": {
2190
  "version": "1.0.0",
2191
  "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
 
2536
  "node": ">=0.10.0"
2537
  }
2538
  },
2539
+ "node_modules/process-nextick-args": {
2540
+ "version": "2.0.1",
2541
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
2542
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
2543
+ },
2544
  "node_modules/proxy-addr": {
2545
  "version": "2.0.7",
2546
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
 
2989
  "node": ">= 0.8"
2990
  }
2991
  },
2992
+ "node_modules/streamsearch": {
2993
+ "version": "1.1.0",
2994
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
2995
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
2996
+ "engines": {
2997
+ "node": ">=10.0.0"
2998
+ }
2999
+ },
3000
  "node_modules/string_decoder": {
3001
  "version": "1.3.0",
3002
  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
 
3342
  "node": ">= 0.4"
3343
  }
3344
  },
3345
+ "node_modules/typedarray": {
3346
+ "version": "0.0.6",
3347
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
3348
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
3349
+ },
3350
  "node_modules/typeorm": {
3351
  "version": "0.3.27",
3352
  "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.27.tgz",
package.json CHANGED
@@ -25,6 +25,7 @@
25
  "dotenv": "^17.2.3",
26
  "express": "^5.1.0",
27
  "jsonwebtoken": "^9.0.2",
 
28
  "pg": "^8.16.3",
29
  "reflect-metadata": "^0.2.2",
30
  "swagger-jsdoc": "^6.2.8",
@@ -36,6 +37,7 @@
36
  "@types/cors": "^2.8.19",
37
  "@types/express": "^5.0.5",
38
  "@types/jsonwebtoken": "^9.0.10",
 
39
  "@types/node": "^24.9.2",
40
  "@types/swagger-jsdoc": "^6.0.4",
41
  "@types/swagger-ui-express": "^4.1.6",
 
25
  "dotenv": "^17.2.3",
26
  "express": "^5.1.0",
27
  "jsonwebtoken": "^9.0.2",
28
+ "multer": "^1.4.5-lts.1",
29
  "pg": "^8.16.3",
30
  "reflect-metadata": "^0.2.2",
31
  "swagger-jsdoc": "^6.2.8",
 
37
  "@types/cors": "^2.8.19",
38
  "@types/express": "^5.0.5",
39
  "@types/jsonwebtoken": "^9.0.10",
40
+ "@types/multer": "^1.4.12",
41
  "@types/node": "^24.9.2",
42
  "@types/swagger-jsdoc": "^6.0.4",
43
  "@types/swagger-ui-express": "^4.1.6",
src/index.ts CHANGED
@@ -96,8 +96,15 @@ const swaggerOptions: swaggerJsdoc.Options = {
96
  properties: {
97
  id: { type: "integer" },
98
  imageUrl: { type: "string", format: "uri" },
99
- category: { type: "string" },
100
- style: { type: "string", enum: ["formal", "casual", "streetwear"] },
 
 
 
 
 
 
 
101
  userId: { type: "integer" },
102
  createdAt: { type: "string", format: "date-time" }
103
  }
@@ -130,9 +137,13 @@ const swaggerOptions: swaggerJsdoc.Options = {
130
  },
131
  UploadRequest: {
132
  type: "object",
133
- required: ["imageUrl"],
134
  properties: {
135
- imageUrl: { type: "string", format: "uri" }
 
 
 
 
136
  }
137
  },
138
  SuggestRequest: {
 
96
  properties: {
97
  id: { type: "integer" },
98
  imageUrl: { type: "string", format: "uri" },
99
+ category: {
100
+ type: "string",
101
+ description: "Classification: shirt, pants, dress, jacket, shoes, sneakers, boots, watch, glasses, bag, hat, jewelry, accessories, etc."
102
+ },
103
+ style: {
104
+ type: "string",
105
+ enum: ["formal", "casual", "streetwear", "sportswear"],
106
+ description: "Style classification"
107
+ },
108
  userId: { type: "integer" },
109
  createdAt: { type: "string", format: "date-time" }
110
  }
 
137
  },
138
  UploadRequest: {
139
  type: "object",
140
+ required: ["image"],
141
  properties: {
142
+ image: {
143
+ type: "string",
144
+ format: "binary",
145
+ description: "Image file (JPEG, PNG, etc.)"
146
+ }
147
  }
148
  },
149
  SuggestRequest: {
src/middleware/auth.ts CHANGED
@@ -6,6 +6,8 @@ dotenv.config();
6
  export interface AuthRequest extends Request {
7
  userId?: number;
8
  userEmail?: string;
 
 
9
  }
10
 
11
  export const authenticateToken = (
 
6
  export interface AuthRequest extends Request {
7
  userId?: number;
8
  userEmail?: string;
9
+ file?: Express.Multer.File;
10
+ files?: Express.Multer.File[] | { [fieldname: string]: Express.Multer.File[] };
11
  }
12
 
13
  export const authenticateToken = (
src/routes/upload.ts CHANGED
@@ -1,4 +1,5 @@
1
  import express from "express";
 
2
  import cloudinary from "../utils/cloudinary";
3
  import { classifyFashionImage } from "../utils/hfClient";
4
  import { AppDataSource } from "../utils/dataSource";
@@ -7,23 +8,49 @@ import { authenticateToken, AuthRequest } from "../middleware/auth";
7
 
8
  const router = express.Router();
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  /**
11
  * @openapi
12
  * /api/upload:
13
  * post:
14
- * summary: Upload and classify a wardrobe item image
15
  * tags: [Upload]
16
  * security:
17
  * - bearerAuth: []
18
  * requestBody:
19
  * required: true
20
  * content:
21
- * application/json:
22
  * schema:
23
- * $ref: "#/components/schemas/UploadRequest"
 
 
 
 
 
 
 
 
 
 
24
  * responses:
25
  * 200:
26
- * description: Item uploaded and classified successfully
27
  * content:
28
  * application/json:
29
  * schema:
@@ -31,47 +58,120 @@ const router = express.Router();
31
  * properties:
32
  * success:
33
  * type: boolean
34
- * item:
35
- * $ref: "#/components/schemas/WardrobeItem"
 
 
 
 
 
36
  * 400:
37
- * description: Bad request (missing imageUrl)
38
  * 401:
39
  * description: Unauthorized
40
  * 500:
41
  * description: Server error
42
  */
43
- router.post("/", authenticateToken, async (req: AuthRequest, res) => {
44
  try {
45
- const { imageUrl } = req.body;
46
  const userId = req.userId!;
 
47
 
48
- if (!imageUrl) {
49
- return res.status(400).json({ success: false, error: "imageUrl is required" });
 
 
 
50
  }
51
 
52
- // Upload image to Cloudinary
53
- const uploadResult = await cloudinary.uploader.upload(imageUrl, {
54
- folder: "wardrobe",
55
- });
56
-
57
- // Classify using Fashion-CLIP
58
- const fashionResult = await classifyFashionImage(uploadResult.secure_url);
59
- const bestLabel = fashionResult[0]?.label || "unknown";
60
 
61
- // Save in DB with user association
62
  const itemRepo = AppDataSource.getRepository(WardrobeItem);
63
- const newItem = itemRepo.create({
64
- imageUrl: uploadResult.secure_url,
65
- category: bestLabel,
66
- style: bestLabel.includes("formal") ? "formal" : "casual",
67
- userId: userId,
68
- });
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- await itemRepo.save(newItem);
71
- res.json({ success: true, item: newItem });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  } catch (error: any) {
73
  console.error("Upload error:", error);
74
- res.status(500).json({ success: false, error: error.message || "Upload failed" });
 
 
 
75
  }
76
  });
77
 
 
1
  import express from "express";
2
+ import multer from "multer";
3
  import cloudinary from "../utils/cloudinary";
4
  import { classifyFashionImage } from "../utils/hfClient";
5
  import { AppDataSource } from "../utils/dataSource";
 
8
 
9
  const router = express.Router();
10
 
11
+ // Configure multer for memory storage (no disk writes needed)
12
+ const upload = multer({
13
+ storage: multer.memoryStorage(),
14
+ limits: {
15
+ fileSize: 10 * 1024 * 1024, // 10MB limit
16
+ },
17
+ fileFilter: (req, file, cb) => {
18
+ // Accept only image files
19
+ if (file.mimetype.startsWith("image/")) {
20
+ cb(null, true);
21
+ } else {
22
+ cb(new Error("Only image files are allowed"));
23
+ }
24
+ },
25
+ });
26
+
27
  /**
28
  * @openapi
29
  * /api/upload:
30
  * post:
31
+ * summary: Upload and classify wardrobe items including clothing, footwear, watches, and accessories (1-20 images, processed sequentially)
32
  * tags: [Upload]
33
  * security:
34
  * - bearerAuth: []
35
  * requestBody:
36
  * required: true
37
  * content:
38
+ * multipart/form-data:
39
  * schema:
40
+ * type: object
41
+ * required:
42
+ * - image
43
+ * properties:
44
+ * image:
45
+ * type: array
46
+ * items:
47
+ * type: string
48
+ * format: binary
49
+ * description: Image files to upload (clothing, footwear, watches, glasses, accessories, etc.). You can upload 1-20 images at once. Images are processed one by one.
50
+ * maxItems: 20
51
  * responses:
52
  * 200:
53
+ * description: Items uploaded and classified successfully
54
  * content:
55
  * application/json:
56
  * schema:
 
58
  * properties:
59
  * success:
60
  * type: boolean
61
+ * items:
62
+ * type: array
63
+ * items:
64
+ * $ref: "#/components/schemas/WardrobeItem"
65
+ * count:
66
+ * type: integer
67
+ * description: Number of items successfully uploaded
68
  * 400:
69
+ * description: Bad request (missing files, invalid file type, or too many files)
70
  * 401:
71
  * description: Unauthorized
72
  * 500:
73
  * description: Server error
74
  */
75
+ router.post("/", authenticateToken, upload.array("image", 20), async (req: AuthRequest, res) => {
76
  try {
 
77
  const userId = req.userId!;
78
+ const files = req.files as Express.Multer.File[];
79
 
80
+ if (!files || files.length === 0) {
81
+ return res.status(400).json({
82
+ success: false,
83
+ error: "At least one image file is required. Please upload 1-20 image files."
84
+ });
85
  }
86
 
87
+ if (files.length > 20) {
88
+ return res.status(400).json({
89
+ success: false,
90
+ error: "Maximum 20 images allowed per upload."
91
+ });
92
+ }
 
 
93
 
 
94
  const itemRepo = AppDataSource.getRepository(WardrobeItem);
95
+ const uploadedItems: WardrobeItem[] = [];
96
+ const failedFiles: string[] = [];
97
+
98
+ // Process files one by one (sequentially)
99
+ for (let i = 0; i < files.length; i++) {
100
+ const file = files[i];
101
+ try {
102
+ console.log(`Processing image ${i + 1}/${files.length}: ${file.originalname}`);
103
+
104
+ // Convert buffer to data URI for Cloudinary
105
+ const base64Image = `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
106
+
107
+ // Upload image to Cloudinary
108
+ const uploadResult = await cloudinary.uploader.upload(base64Image, {
109
+ folder: "wardrobe",
110
+ resource_type: "image",
111
+ });
112
 
113
+ // Classify using Fashion-CLIP (one by one)
114
+ const fashionResult = await classifyFashionImage(uploadResult.secure_url);
115
+ const bestLabel = fashionResult[0]?.label || "unknown";
116
+
117
+ // Determine style based on category and label
118
+ let style = "casual";
119
+ const labelLower = bestLabel.toLowerCase();
120
+
121
+ if (labelLower.includes("formal") || labelLower.includes("blazer") ||
122
+ labelLower.includes("heels") || labelLower.includes("loafers") ||
123
+ labelLower.includes("suit") || labelLower.includes("business") ||
124
+ labelLower.includes("elegant") || labelLower.includes("tie") ||
125
+ labelLower.includes("bow tie")) {
126
+ style = "formal";
127
+ } else if (labelLower.includes("streetwear") || labelLower.includes("sneakers") ||
128
+ labelLower.includes("hoodie") || labelLower.includes("cap") ||
129
+ labelLower.includes("beanie")) {
130
+ style = "streetwear";
131
+ } else if (labelLower.includes("sportswear") || labelLower.includes("backpack") ||
132
+ labelLower.includes("shorts")) {
133
+ style = "sportswear";
134
+ }
135
+ // Accessories like watches, glasses, jewelry default to casual but can be styled
136
+
137
+ // Save in DB with user association
138
+ const newItem = itemRepo.create({
139
+ imageUrl: uploadResult.secure_url,
140
+ category: bestLabel,
141
+ style: style,
142
+ userId: userId,
143
+ });
144
+
145
+ const savedItem = await itemRepo.save(newItem);
146
+ uploadedItems.push(savedItem);
147
+ console.log(`✅ Successfully processed: ${file.originalname} -> ${bestLabel}`);
148
+ } catch (error: any) {
149
+ console.error(`❌ Error processing file ${file.originalname}:`, error);
150
+ failedFiles.push(file.originalname);
151
+ // Continue processing other files even if one fails
152
+ }
153
+ }
154
+
155
+ if (uploadedItems.length === 0) {
156
+ return res.status(500).json({
157
+ success: false,
158
+ error: "Failed to upload any images. Please try again."
159
+ });
160
+ }
161
+
162
+ res.json({
163
+ success: true,
164
+ items: uploadedItems,
165
+ count: uploadedItems.length,
166
+ failed: failedFiles.length,
167
+ failedFiles: failedFiles.length > 0 ? failedFiles : undefined
168
+ });
169
  } catch (error: any) {
170
  console.error("Upload error:", error);
171
+ res.status(500).json({
172
+ success: false,
173
+ error: error.message || "Upload failed"
174
+ });
175
  }
176
  });
177
 
src/utils/hfClient.ts CHANGED
@@ -10,7 +10,20 @@ export async function classifyFashionImage(imageUrl: string) {
10
  const payload = {
11
  inputs: imageUrl,
12
  parameters: {
13
- candidate_labels: ["shirt", "pants", "dress", "jacket", "formal", "casual", "streetwear"],
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  },
15
  };
16
 
 
10
  const payload = {
11
  inputs: imageUrl,
12
  parameters: {
13
+ candidate_labels: [
14
+ // Clothing
15
+ "shirt", "pants", "dress", "jacket", "blazer", "t-shirt", "hoodie", "sweater",
16
+ "jeans", "shorts", "skirt", "suit", "coat", "cardigan", "vest",
17
+ // Footwear
18
+ "shoes", "sneakers", "boots", "sandals", "heels", "flats", "loafers", "slippers",
19
+ // Accessories
20
+ "watch", "wristwatch", "glasses", "sunglasses", "eyeglasses",
21
+ "belt", "bag", "handbag", "backpack", "purse", "wallet",
22
+ "hat", "cap", "beanie", "scarf", "tie", "bow tie",
23
+ "jewelry", "necklace", "bracelet", "earrings", "ring",
24
+ // Styles
25
+ "formal", "casual", "streetwear", "sportswear", "business", "elegant"
26
+ ],
27
  },
28
  };
29