├── Procfile ├── readmeImg ├── Create_Product.PNG ├── Remove_Product.PNG ├── Update_Product.PNG └── User_List_Ban.PNG ├── dist ├── util │ ├── squareNumber.js.map │ ├── squareNumber.js │ ├── logger.js.map │ ├── logger.js │ ├── secrets.js.map │ └── secrets.js ├── routers │ ├── auth.js.map │ ├── user.js.map │ ├── admin.js.map │ ├── product.js.map │ ├── user.js │ ├── auth.js │ ├── product.js │ └── admin.js ├── models │ ├── Product.js.map │ ├── User.js.map │ ├── Product.js │ └── User.js ├── config │ ├── passport.js.map │ └── passport.js ├── app.js.map ├── server.js.map ├── helpers │ ├── apiError.js.map │ └── apiError.js ├── services │ ├── product.js.map │ ├── product.js │ ├── user.js.map │ └── user.js ├── app.js ├── controllers │ ├── product.js.map │ ├── user.js.map │ ├── product.js │ ├── auth.js.map │ ├── user.js │ └── auth.js ├── server.js └── data │ ├── products.js.map │ ├── ImportData.js.map │ ├── products.js │ └── ImportData.js ├── jest.config.js ├── request.rest ├── src ├── routers │ ├── auth.ts │ ├── user.ts │ ├── product.ts │ └── admin.ts ├── models │ ├── Product.ts │ └── User.ts ├── app.ts ├── config │ └── passport.ts ├── server.ts ├── helpers │ └── apiError.ts ├── services │ ├── product.ts │ └── user.ts ├── controllers │ ├── product.ts │ ├── user.ts │ └── auth.ts └── data │ └── ImportData.ts ├── .env.example ├── .gitignore ├── .env ├── tsconfig.json ├── test ├── db-helper.ts ├── controllers │ ├── user.test.ts │ └── product.test.ts └── services │ ├── product.test.ts │ └── user.test.ts ├── README.md └── package.json /Procfile: -------------------------------------------------------------------------------- 1 | web: yarn start -------------------------------------------------------------------------------- /readmeImg/Create_Product.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DwcQuocXa/BeauClothing-E-Commerce-Backend/HEAD/readmeImg/Create_Product.PNG -------------------------------------------------------------------------------- /readmeImg/Remove_Product.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DwcQuocXa/BeauClothing-E-Commerce-Backend/HEAD/readmeImg/Remove_Product.PNG -------------------------------------------------------------------------------- /readmeImg/Update_Product.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DwcQuocXa/BeauClothing-E-Commerce-Backend/HEAD/readmeImg/Update_Product.PNG -------------------------------------------------------------------------------- /readmeImg/User_List_Ban.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DwcQuocXa/BeauClothing-E-Commerce-Backend/HEAD/readmeImg/User_List_Ban.PNG -------------------------------------------------------------------------------- /dist/util/squareNumber.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"squareNumber.js","sourceRoot":"","sources":["../../src/util/squareNumber.ts"],"names":[],"mappings":";;AAAA,SAAwB,YAAY,CAAC,CAAS;IAC5C,OAAO,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAFD,+BAEC"} -------------------------------------------------------------------------------- /dist/util/squareNumber.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function squareNumber(n) { 4 | return n * n; 5 | } 6 | exports.default = squareNumber; 7 | //# sourceMappingURL=squareNumber.js.map -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | "ts-jest": { 4 | tsconfig: "tsconfig.json", 5 | }, 6 | }, 7 | moduleFileExtensions: ["ts", "js"], 8 | transform: { 9 | "^.+\\.(ts|tsx)$": "ts-jest", 10 | }, 11 | testMatch: ["**/test/**/*.test.(ts|js)"], 12 | testEnvironment: "node", 13 | setupFiles: ["dotenv/config"], 14 | }; 15 | -------------------------------------------------------------------------------- /request.rest: -------------------------------------------------------------------------------- 1 | GET http://localhost:5000/api/v1/users/google-authenticate 2 | 3 | ### 4 | POST http://localhost:5000/api/v1/users/google-authenticate 5 | Content-Type: application/json 6 | 7 | { 8 | "token_Id": "fadjskhfsjak" 9 | } 10 | 11 | ### 12 | 13 | POST http://localhost:5000/api/v1/admin/products 14 | Content-Type: application/json 15 | 16 | { 17 | "token_Id": "fadjskhfsjak" 18 | } -------------------------------------------------------------------------------- /dist/routers/auth.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routers/auth.ts"],"names":[],"mappings":";;;;;AAAA,sDAAqD;AAGrD,8CAAgF;AAEhF,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;AAEhC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,aAAM,CAAC,CAAC;AAC/B,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,aAAM,CAAC,CAAC;AAE/B,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,mBAAY,CAAC,CAAC;AAClD,MAAM,CAAC,GAAG,CAAC,6BAA6B,EAAE,kBAAW,CAAC,CAAC;AAEvD,kBAAe,MAAM,CAAC"} -------------------------------------------------------------------------------- /dist/routers/user.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/routers/user.ts"],"names":[],"mappings":";;;;;AAAA,sDAAqD;AAGrD,8CAK6B;AAE7B,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;AAEhC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,cAAO,CAAC,CAAC;AAEzB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,cAAO,CAAC,CAAC;AAC7B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,0BAAmB,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,0BAAmB,CAAC,CAAC;AAE5C,kBAAe,MAAM,CAAC"} -------------------------------------------------------------------------------- /dist/routers/admin.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/routers/admin.ts"],"names":[],"mappings":";;;;;AAAA,sDAA0C;AAC1C,8CAAqD;AAErD,oDAIgC;AAEhC,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;AAEhC,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,qBAAc,CAAC,CAAC;AAEzC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,uBAAa,CAAC,CAAC;AACxC,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,uBAAa,CAAC,CAAC;AAClD,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE,uBAAa,CAAC,CAAC;AAErD,kBAAe,MAAM,CAAC"} -------------------------------------------------------------------------------- /src/routers/auth.ts: -------------------------------------------------------------------------------- 1 | import express, { Response, Request } from "express"; 2 | import passport from "passport"; 3 | 4 | import { authenticate, findByEmail, signIn, signUp } from "../controllers/auth"; 5 | 6 | const router = express.Router(); 7 | 8 | router.post("/signin", signIn); 9 | router.post("/signup", signUp); 10 | 11 | router.post("/google-authenticate", authenticate); 12 | router.get("/google-authenticate/:email", findByEmail); 13 | 14 | export default router; 15 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Get this from mongodb atlas after you've logged in and created a database 4 | MONGODB_URI=mongodb+srv://luongkill:luong123@cluster0.5qozh.mongodb.net/myFirstDatabase?retryWrites=true&w=majority&ssl=true 5 | 6 | # Application Port - express server listens on this port (default 5000). 7 | 8 | 9 | # jwt_secret 10 | JWT_SECRET=luong123 11 | 12 | # google_client 13 | GOOGLE_CLIENT_ID=634958478111-32garfk7ttd62rvjpp3tqmp0f58ijdsn.apps.googleusercontent.com 14 | -------------------------------------------------------------------------------- /dist/routers/product.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/routers/product.ts"],"names":[],"mappings":";;;;;AAAA,sDAA0C;AAE1C,oDAMgC;AAEhC,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;AAEhC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,iBAAO,CAAC,CAAC;AACzB,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,kBAAQ,CAAC,CAAC;AACpC,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,uBAAa,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,uBAAa,CAAC,CAAC;AAC5C,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAa,CAAC,CAAC;AAEhC,kBAAe,MAAM,CAAC"} -------------------------------------------------------------------------------- /src/routers/user.ts: -------------------------------------------------------------------------------- 1 | import express, { Response, Request } from "express"; 2 | import passport from "passport"; 3 | 4 | import { 5 | findAll, 6 | getCart, 7 | manageProductInCart, 8 | removeProductInCart, 9 | } from "../controllers/user"; 10 | 11 | const router = express.Router(); 12 | 13 | router.get("/", findAll); 14 | 15 | router.get("/cart", getCart); 16 | router.put("/cart", manageProductInCart); 17 | router.delete("/cart", removeProductInCart); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /src/routers/product.ts: -------------------------------------------------------------------------------- 1 | import express, { Router } from "express"; 2 | 3 | import { 4 | createProduct, 5 | findById, 6 | deleteProduct, 7 | findAll, 8 | updateProduct, 9 | } from "../controllers/product"; 10 | 11 | const router = express.Router(); 12 | 13 | router.get("/", findAll); 14 | router.get("/:productId", findById); 15 | router.put("/:productId", updateProduct); 16 | router.delete("/:productId", deleteProduct); 17 | router.post("/", createProduct); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | 11 | pids 12 | logs 13 | results 14 | tmp 15 | 16 | # Build 17 | public/css/main.css 18 | 19 | # Coverage reports 20 | coverage 21 | 22 | # API keys and secrets 23 | .env 24 | 25 | # Dependency directory 26 | node_modules 27 | bower_components 28 | 29 | # Editors 30 | .idea 31 | *.iml 32 | 33 | # OS metadata 34 | .DS_Store 35 | Thumbs.db 36 | 37 | # Ignore built ts files 38 | dist/**/* 39 | 40 | *.swo 41 | *.swp 42 | -------------------------------------------------------------------------------- /src/routers/admin.ts: -------------------------------------------------------------------------------- 1 | import express, { Router } from "express"; 2 | import { banOrUnbanUser } from "../controllers/user"; 3 | 4 | import { 5 | createProduct, 6 | deleteProduct, 7 | updateProduct, 8 | } from "../controllers/product"; 9 | 10 | const router = express.Router(); 11 | 12 | router.put("/users/ban", banOrUnbanUser); 13 | 14 | router.post("/products", createProduct); 15 | router.put("/products/:productId", updateProduct); 16 | router.delete("/products/:productId", deleteProduct); 17 | 18 | export default router; 19 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Get this from mongodb atlas after you've logged in and created a database 4 | MONGODB_URI=mongodb+srv://luongkill:luong123@cluster0.5qozh.mongodb.net/myFirstDatabase?retryWrites=true&w=majority&ssl=true 5 | 6 | # Application Port - express server listens on this port (default 5000). 7 | 8 | 9 | # jwt_secret 10 | JWT_SECRET=luong123 11 | 12 | # google_client 13 | GOOGLE_CLIENT_ID=634958478111-32garfk7ttd62rvjpp3tqmp0f58ijdsn.apps.googleusercontent.com 14 | 15 | ## "build": "npm run build-ts && npm run lint && npm run copy-static-assets", -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "skipLibCheck": true, 8 | "target": "es6", 9 | "noImplicitAny": true, 10 | "types": ["@types/jest", "@types/node"], 11 | //"types": ["jest", "node"], 12 | "moduleResolution": "node", 13 | "sourceMap": true, 14 | "outDir": "dist", 15 | "baseUrl": ".", 16 | "paths": { 17 | "*": ["node_modules/*", "src/types/*"] 18 | } 19 | }, 20 | "include": ["src/**/*"] 21 | } 22 | -------------------------------------------------------------------------------- /dist/util/logger.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":";;;;;AAAA,sDAA6B;AAE7B,MAAM,OAAO,GAA0B;IACrC,UAAU,EAAE;QACV,IAAI,iBAAO,CAAC,UAAU,CAAC,OAAO,CAAC;YAC7B,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;SACjE,CAAC;QACF,IAAI,iBAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;KACvE;CACF,CAAA;AAED,MAAM,MAAM,GAAG,iBAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;AAE5C,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;IACzC,MAAM,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAA;CACnD;AAED,kBAAe,MAAM,CAAA"} -------------------------------------------------------------------------------- /dist/models/Product.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Product.js","sourceRoot":"","sources":["../../src/models/Product.ts"],"names":[],"mappings":";;;;;AAAA,wDAA8C;AAa9C,MAAM,aAAa,GAAG,IAAI,kBAAQ,CAAC,MAAM,CAAC;IACxC,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;KAChB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;KACf;IACD,UAAU,EAAE,MAAM;IAClB,KAAK,EAAE;QACL,IAAI,EAAE,KAAK;QACX,uCAAuC;KACxC;IACD,KAAK,EAAE;QACL,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;KACf;IACD,GAAG,EAAE;QACH,IAAI,EAAE,CAAC,MAAM,CAAC;QACd,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC,CAAC;AAEH,kBAAe,kBAAQ,CAAC,KAAK,CAAkB,SAAS,EAAE,aAAa,CAAC,CAAC"} -------------------------------------------------------------------------------- /dist/routers/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const express_1 = __importDefault(require("express")); 7 | const user_1 = require("../controllers/user"); 8 | const router = express_1.default.Router(); 9 | router.get("/", user_1.findAll); 10 | router.get("/cart", user_1.getCart); 11 | router.put("/cart", user_1.manageProductInCart); 12 | router.delete("/cart", user_1.removeProductInCart); 13 | exports.default = router; 14 | //# sourceMappingURL=user.js.map -------------------------------------------------------------------------------- /dist/routers/auth.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const express_1 = __importDefault(require("express")); 7 | const auth_1 = require("../controllers/auth"); 8 | const router = express_1.default.Router(); 9 | router.post("/signin", auth_1.signIn); 10 | router.post("/signup", auth_1.signUp); 11 | router.post("/google-authenticate", auth_1.authenticate); 12 | router.get("/google-authenticate/:email", auth_1.findByEmail); 13 | exports.default = router; 14 | //# sourceMappingURL=auth.js.map -------------------------------------------------------------------------------- /dist/routers/product.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const express_1 = __importDefault(require("express")); 7 | const product_1 = require("../controllers/product"); 8 | const router = express_1.default.Router(); 9 | router.get("/", product_1.findAll); 10 | router.get("/:productId", product_1.findById); 11 | router.put("/:productId", product_1.updateProduct); 12 | router.delete("/:productId", product_1.deleteProduct); 13 | router.post("/", product_1.createProduct); 14 | exports.default = router; 15 | //# sourceMappingURL=product.js.map -------------------------------------------------------------------------------- /dist/routers/admin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const express_1 = __importDefault(require("express")); 7 | const user_1 = require("../controllers/user"); 8 | const product_1 = require("../controllers/product"); 9 | const router = express_1.default.Router(); 10 | router.put("/users/ban", user_1.banOrUnbanUser); 11 | router.post("/products", product_1.createProduct); 12 | router.put("/products/:productId", product_1.updateProduct); 13 | router.delete("/products/:productId", product_1.deleteProduct); 14 | exports.default = router; 15 | //# sourceMappingURL=admin.js.map -------------------------------------------------------------------------------- /dist/models/User.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"User.js","sourceRoot":"","sources":["../../src/models/User.ts"],"names":[],"mappings":";;;;;;AAAA,wDAA8C;AAYjC,QAAA,UAAU,GAAG,IAAI,kBAAQ,CAAC,MAAM,CAAC;IAC5C,OAAO,EAAE;QACP,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,KAAK;KACf;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,KAAK;KACf;IACD,SAAS,EAAE;QACT,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,KAAK;KAChB;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,KAAK;KAChB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,IAAI;KACb;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,KAAK;KAChB;IACD,IAAI,EAAE;QACJ;YACE,OAAO,EAAE,EAAE,IAAI,EAAE,kBAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE;YACjE,QAAQ,EAAE,MAAM;SACjB;KACF;CACF,CAAC,CAAC;AAEH,kBAAe,kBAAQ,CAAC,KAAK,CAAe,MAAM,EAAE,kBAAU,CAAC,CAAC"} -------------------------------------------------------------------------------- /dist/util/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const winston_1 = __importDefault(require("winston")); 7 | const options = { 8 | transports: [ 9 | new winston_1.default.transports.Console({ 10 | level: process.env.NODE_ENV === 'production' ? 'error' : 'debug', 11 | }), 12 | new winston_1.default.transports.File({ filename: 'debug.log', level: 'debug' }), 13 | ], 14 | }; 15 | const logger = winston_1.default.createLogger(options); 16 | if (process.env.NODE_ENV !== 'production') { 17 | logger.debug('Logging initialized at debug level'); 18 | } 19 | exports.default = logger; 20 | //# sourceMappingURL=logger.js.map -------------------------------------------------------------------------------- /dist/config/passport.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"passport.js","sourceRoot":"","sources":["../../src/config/passport.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,4DAA2C;AAG3C,MAAM,mBAAmB,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;AAEhE,MAAM,gBAAgB,GACpB,0EAA0E,CAAC;AAE7E,kBAAe,IAAI,mBAAmB,CACpC;IACE,QAAQ,EAAE,gBAAgB;CAC3B,EACD,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,EACxC,UAAgB,WAAgB,EAAE,QAAgB,EAAE,IAAc;;;QAChE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzB,MAAM,WAAW,GAAG;YAClB,KAAK,EAAE,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,OAAO,0CAAE,KAAK;YAClC,SAAS,EAAE,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,OAAO,0CAAE,SAAS;YAC1C,QAAQ,EAAE,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,OAAO,0CAAE,QAAQ;SACzC,CAAC;QAEF,IAAI;YACF,MAAM,IAAI,GAAG,MAAM,cAAW,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YACzD,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;SAC/C;QAAC,OAAO,KAAK,EAAE;YACd,IAAI,CAAC,KAAK,CAAC,CAAC;SACb;;CACF,CACF,CAAC"} -------------------------------------------------------------------------------- /src/models/Product.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from "mongoose"; 2 | 3 | export type Categories = "T-Shirts" | "Pants" | "Shoes" | "Jackets"; 4 | 5 | export type ProductDocument = Document & { 6 | name: string; 7 | description: string; 8 | categories: Categories; 9 | sizes: string[]; 10 | price: number; 11 | img: string[]; 12 | }; 13 | 14 | const productSchema = new mongoose.Schema({ 15 | name: { 16 | type: String, 17 | index: true, 18 | uppercase: true, 19 | }, 20 | description: { 21 | type: String, 22 | required: true, 23 | }, 24 | categories: String, 25 | sizes: { 26 | type: Array, 27 | //default: ["XS", "S", "M", "L", "XL"], 28 | }, 29 | price: { 30 | type: Number, 31 | required: true, 32 | }, 33 | img: { 34 | type: [String], 35 | required: false, 36 | }, 37 | }); 38 | 39 | export default mongoose.model("Product", productSchema); 40 | -------------------------------------------------------------------------------- /dist/app.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":";;;;;AAAA,sDAA8B;AAC9B,gDAAwB;AAExB,2CAA2C;AAE3C,gEAA8C;AAC9C,0DAAwC;AACxC,0DAAwC;AACxC,4DAA0C;AAC1C,gEAAwC;AAExC,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;AAEtB,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAClC,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AACxB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAChD,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,GAAE,CAAC,CAAC;AAEhB,iCAAiC;AACjC,yBAAyB;AAEzB,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,iBAAa,CAAC,CAAC;AAC3C,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,cAAU,CAAC,CAAC;AACpC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAU,CAAC,CAAC;AACrC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,eAAW,CAAC,CAAC;AAEtC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACxB,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,GAAG,CAAC,IAAA,sBAAY,GAAE,CAAC,CAAC;AAExB,kBAAe,GAAG,CAAC"} -------------------------------------------------------------------------------- /dist/server.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,gEAAwC;AACxC,wDAAgC;AAChC,oDAA4B;AAE5B,gDAAwB;AACxB,+CAA+C;AAC/C,gBAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AAEhC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;AAC9B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,WAAqB,CAAC;AAE9C,MAAM,KAAK,GAAG,GAAS,EAAE;IAC9B,MAAM,kBAAQ,CAAC,UAAU,EAAE,CAAC;IAC5B,MAAM,kBAAQ;SACX,OAAO,CAAC,UAAU,EAAE;QACnB,eAAe,EAAE,IAAI;QACrB,kBAAkB,EAAE,IAAI;QACxB,gBAAgB,EAAE,KAAK;QACvB,cAAc,EAAE,IAAI;KACrB,CAAC;SACD,IAAI,CAAC,GAAG,EAAE;QACT,uBAAuB;QACvB,aAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;QACtB,OAAO,CAAC,GAAG,CACT,iEAAiE;YAC/D,KAAK,CACR,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC,CAAA,CAAC;AApBW,QAAA,KAAK,SAoBhB;AAEF,IAAA,aAAK,GAAE,CAAC;AACR;;GAEG;AACH,aAAG,CAAC,GAAG,CAAC,IAAA,sBAAY,GAAE,CAAC,CAAC"} -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import cors from "cors"; 3 | import passport from "passport"; 4 | //import Strategy from "./config/passport"; 5 | 6 | import productRouter from "./routers/product"; 7 | import userRouter from "./routers/user"; 8 | import authRouter from "./routers/auth"; 9 | import adminRouter from "./routers/admin"; 10 | import errorHandler from "errorhandler"; 11 | 12 | const app = express(); 13 | 14 | app.set("port", process.env.PORT); 15 | app.use(express.json()); 16 | app.use(express.urlencoded({ extended: true })); 17 | app.use(cors()); 18 | 19 | //app.use(passport.initialize()); 20 | //passport.use(Strategy); 21 | 22 | app.use("/api/v1/products", productRouter); 23 | app.use("/api/v1/auth", authRouter); 24 | app.use("/api/v1/users", userRouter); 25 | app.use("/api/v1/admin", adminRouter); 26 | 27 | app.get("/", (req, res) => { 28 | res.send("Deploy successfully"); 29 | }); 30 | 31 | app.use(errorHandler()); 32 | 33 | export default app; 34 | -------------------------------------------------------------------------------- /dist/models/Product.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const mongoose_1 = __importDefault(require("mongoose")); 7 | const productSchema = new mongoose_1.default.Schema({ 8 | name: { 9 | type: String, 10 | index: true, 11 | uppercase: true, 12 | }, 13 | description: { 14 | type: String, 15 | required: true, 16 | }, 17 | categories: String, 18 | sizes: { 19 | type: Array, 20 | //default: ["XS", "S", "M", "L", "XL"], 21 | }, 22 | price: { 23 | type: Number, 24 | required: true, 25 | }, 26 | img: { 27 | type: [String], 28 | required: false, 29 | }, 30 | }); 31 | exports.default = mongoose_1.default.model("Product", productSchema); 32 | //# sourceMappingURL=Product.js.map -------------------------------------------------------------------------------- /src/config/passport.ts: -------------------------------------------------------------------------------- 1 | import userService from "../services/user"; 2 | import passport from "passport"; 3 | 4 | const GoogleTokenStrategy = require("passport-google-id-token"); 5 | 6 | const GOOGLE_CLIENT_ID = 7 | "634958478111-32garfk7ttd62rvjpp3tqmp0f58ijdsn.apps.googleusercontent.com"; 8 | 9 | export default new GoogleTokenStrategy( 10 | { 11 | clientID: GOOGLE_CLIENT_ID, 12 | }, 13 | console.log("backend connect to google"), 14 | async function (parsedToken: any, googleId: string, done: Function) { 15 | console.log(parsedToken); 16 | const userPayload = { 17 | email: parsedToken?.payload?.email, 18 | firstName: parsedToken?.payload?.firstName, 19 | lastName: parsedToken?.payload?.lastName, 20 | }; 21 | 22 | try { 23 | const user = await userService.findOrCreate(userPayload); 24 | done(undefined, user); 25 | console.log("To the authenticate controller"); 26 | } catch (error) { 27 | done(error); 28 | } 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /dist/util/secrets.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/util/secrets.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,4CAAoB;AAEpB,sDAA8B;AAE9B,IAAI,YAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;IACzB,gBAAM,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACvE,gBAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;CACjC;KAAM;IACL,gBAAM,CAAC,KAAK,CACV,gEAAgE,CACjE,CAAC;IACF,gBAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,2DAA2D;CACrG;AAEY,QAAA,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;AAChD,MAAM,IAAI,GAAG,mBAAW,KAAK,YAAY,CAAC,CAAC,oCAAoC;AAElE,QAAA,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAW,CAAC;AACjD,QAAA,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;AACxB,QAAA,WAAW,GAAG,CACzB,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAC3D,CAAC;AAEZ,IAAI,CAAC,kBAAU,EAAE;IACf,gBAAM,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;CACjB;AAED,IAAI,CAAC,mBAAW,EAAE;IAChB,IAAI,IAAI,EAAE;QACR,gBAAM,CAAC,KAAK,CACV,mEAAmE,CACpE,CAAC;KACH;SAAM;QACL,gBAAM,CAAC,KAAK,CACV,yEAAyE,CAC1E,CAAC;KACH;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;CACjB"} -------------------------------------------------------------------------------- /src/models/User.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from "mongoose"; 2 | 3 | export type UserDocument = Document & { 4 | isAdmin?: boolean; 5 | isBanned?: boolean; 6 | firstName: string; 7 | lastName: string; 8 | email: string; 9 | password: string; 10 | cart: { product: mongoose.Types.ObjectId; quantity: number }[]; 11 | }; 12 | 13 | export const userSchema = new mongoose.Schema({ 14 | isAdmin: { 15 | type: Boolean, 16 | default: false, 17 | }, 18 | isBanned: { 19 | type: Boolean, 20 | default: false, 21 | }, 22 | firstName: { 23 | type: String, 24 | required: false, 25 | }, 26 | lastName: { 27 | type: String, 28 | required: false, 29 | }, 30 | email: { 31 | type: String, 32 | required: false, 33 | unique: true, 34 | }, 35 | password: { 36 | type: String, 37 | required: false, 38 | }, 39 | cart: [ 40 | { 41 | product: { type: mongoose.Schema.Types.ObjectId, ref: "Product" }, 42 | quantity: Number, 43 | }, 44 | ], 45 | }); 46 | 47 | export default mongoose.model("User", userSchema); 48 | -------------------------------------------------------------------------------- /dist/helpers/apiError.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"apiError.js","sourceRoot":"","sources":["../../src/helpers/apiError.ts"],"names":[],"mappings":";;;AAAA,MAAqB,QAAS,SAAQ,KAAK;IACzC,YACW,UAAkB,EAClB,OAAe,EACf,MAAc;QAEvB,KAAK,EAAE,CAAC;QAJC,eAAU,GAAV,UAAU,CAAQ;QAClB,YAAO,GAAP,OAAO,CAAQ;QACf,WAAM,GAAN,MAAM,CAAQ;IAGzB,CAAC;CACF;AARD,2BAQC;AAED,MAAa,aAAc,SAAQ,QAAQ;IACzC,YAAqB,UAAkB,WAAW,EAAE,MAAoB;QACtE,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QADT,YAAO,GAAP,OAAO,CAAsB;IAElD,CAAC;CACF;AAJD,sCAIC;AAED,MAAa,cAAe,SAAQ,QAAQ;IAC1C,YAAqB,UAAkB,WAAW,EAAE,MAAoB;QACtE,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QADT,YAAO,GAAP,OAAO,CAAsB;IAElD,CAAC;CACF;AAJD,wCAIC;AAED,MAAa,mBAAoB,SAAQ,QAAQ;IAC/C,YACW,UAAkB,uBAAuB,EAClD,MAAoB;QAEpB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAHnB,YAAO,GAAP,OAAO,CAAkC;IAIpD,CAAC;CACF;AAPD,kDAOC;AAED,MAAa,iBAAkB,SAAQ,QAAQ;IAC7C,YACW,UAAkB,sBAAsB,EACjD,MAAoB;QAEpB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAHnB,YAAO,GAAP,OAAO,CAAiC;IAInD,CAAC;CACF;AAPD,8CAOC;AAED,MAAa,eAAgB,SAAQ,QAAQ;IAC3C,YAAqB,UAAkB,aAAa,EAAE,MAAoB;QACxE,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QADT,YAAO,GAAP,OAAO,CAAwB;IAEpD,CAAC;CACF;AAJD,0CAIC"} -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import errorHandler from "errorhandler"; 2 | import mongoose from "mongoose"; 3 | import dotenv from "dotenv"; 4 | 5 | import app from "./app"; 6 | //import { MONGODB_URI } from "./util/secrets"; 7 | dotenv.config({ path: ".env" }); 8 | 9 | const PORT = process.env.PORT; 10 | const mongodbUrl = process.env.MONGODB_URI as string; 11 | 12 | export const mongo = async () => { 13 | await mongoose.disconnect(); 14 | await mongoose 15 | .connect(mongodbUrl, { 16 | useNewUrlParser: true, 17 | useUnifiedTopology: true, 18 | useFindAndModify: false, 19 | useCreateIndex: true, 20 | }) 21 | .then(() => { 22 | // Start Express server 23 | app.listen(PORT, () => console.log(`Server running on port: ${PORT}`)); 24 | }) 25 | .catch((error: Error) => { 26 | console.log( 27 | "MongoDB connection error. Please make sure MongoDB is running. " + 28 | error 29 | ); 30 | process.exit(1); 31 | }); 32 | }; 33 | 34 | mongo(); 35 | /** 36 | * Error Handler. Provides full stack - remove for production 37 | */ 38 | app.use(errorHandler()); 39 | -------------------------------------------------------------------------------- /test/db-helper.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { ConnectOptions } from "mongoose"; 2 | import { MongoMemoryServer } from "mongodb-memory-server"; 3 | 4 | const connect = async () => { 5 | const mongod = await MongoMemoryServer.create(); 6 | const uri = mongod.getUri(); 7 | 8 | const mongooseOpts: ConnectOptions = { 9 | useNewUrlParser: true, 10 | useUnifiedTopology: true, 11 | useFindAndModify: false, 12 | useCreateIndex: true, 13 | }; 14 | 15 | await mongoose.disconnect(); 16 | await mongoose.connect(uri, mongooseOpts); 17 | 18 | return { 19 | closeDatabase: async () => { 20 | await mongoose.connection.dropDatabase(); 21 | await mongoose.connection.close(); 22 | await mongod.stop(); 23 | }, 24 | clearDatabase: async () => { 25 | const collections = mongoose.connection.collections; 26 | for (const key in collections) { 27 | const collection = collections[key]; 28 | await collection.deleteMany({}); 29 | } 30 | }, 31 | }; 32 | }; 33 | 34 | export default connect; 35 | export type MongodHelper = { 36 | closeDatabase: () => Promise; 37 | clearDatabase: () => Promise; 38 | }; 39 | -------------------------------------------------------------------------------- /dist/services/product.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/services/product.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,gEAA6D;AAC7D,kDAAoD;AAEpD,MAAM,MAAM,GAAG,CAAO,OAAwB,EAA4B,EAAE;IAC1E,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC,CAAA,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAO,SAAiB,EAA4B,EAAE;IACrE,MAAM,YAAY,GAAG,MAAM,iBAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAEvD,IAAI,CAAC,YAAY,EAAE;QACjB,MAAM,IAAI,wBAAa,CAAC,WAAW,SAAS,eAAe,CAAC,CAAC;KAC9D;IAED,OAAO,YAAY,CAAC;AACtB,CAAC,CAAA,CAAC;AAEF,MAAM,OAAO,GAAG,GAAqC,EAAE;IACrD,OAAO,iBAAO,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;AAC1C,CAAC,CAAA,CAAC;AAEF,MAAM,MAAM,GAAG,CACb,SAAiB,EACjB,MAAgC,EACC,EAAE;IACnC,MAAM,YAAY,GAAG,MAAM,iBAAO,CAAC,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE;QACtE,GAAG,EAAE,IAAI;KACV,CAAC,CAAC;IAEH,IAAI,CAAC,YAAY,EAAE;QACjB,MAAM,IAAI,wBAAa,CAAC,WAAW,SAAS,eAAe,CAAC,CAAC;KAC9D;IAED,OAAO,YAAY,CAAC;AACtB,CAAC,CAAA,CAAC;AAEF,MAAM,aAAa,GAAG,CACpB,SAAiB,EACgB,EAAE;IACnC,MAAM,YAAY,GAAG,iBAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAE1D,IAAI,CAAC,YAAY,EAAE;QACjB,MAAM,IAAI,wBAAa,CAAC,WAAW,SAAS,eAAe,CAAC,CAAC;KAC9D;IAED,OAAO,YAAY,CAAC;AACtB,CAAC,CAAA,CAAC;AAEF,kBAAe;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,aAAa;CACd,CAAC"} -------------------------------------------------------------------------------- /dist/models/User.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.userSchema = void 0; 7 | const mongoose_1 = __importDefault(require("mongoose")); 8 | exports.userSchema = new mongoose_1.default.Schema({ 9 | isAdmin: { 10 | type: Boolean, 11 | default: false, 12 | }, 13 | isBanned: { 14 | type: Boolean, 15 | default: false, 16 | }, 17 | firstName: { 18 | type: String, 19 | required: false, 20 | }, 21 | lastName: { 22 | type: String, 23 | required: false, 24 | }, 25 | email: { 26 | type: String, 27 | required: false, 28 | unique: true, 29 | }, 30 | password: { 31 | type: String, 32 | required: false, 33 | }, 34 | cart: [ 35 | { 36 | product: { type: mongoose_1.default.Schema.Types.ObjectId, ref: "Product" }, 37 | quantity: Number, 38 | }, 39 | ], 40 | }); 41 | exports.default = mongoose_1.default.model("User", exports.userSchema); 42 | //# sourceMappingURL=User.js.map -------------------------------------------------------------------------------- /src/helpers/apiError.ts: -------------------------------------------------------------------------------- 1 | export default class ApiError extends Error { 2 | constructor( 3 | readonly statusCode: number, 4 | readonly message: string, 5 | readonly source?: Error 6 | ) { 7 | super(); 8 | } 9 | } 10 | 11 | export class NotFoundError extends ApiError { 12 | constructor(readonly message: string = "Not Found", source?: Error | any) { 13 | super(404, message, source); 14 | } 15 | } 16 | 17 | export class ForbiddenError extends ApiError { 18 | constructor(readonly message: string = "Forbidden", source?: Error | any) { 19 | super(403, message, source); 20 | } 21 | } 22 | 23 | export class InternalServerError extends ApiError { 24 | constructor( 25 | readonly message: string = "Internal Server Error", 26 | source?: Error | any 27 | ) { 28 | super(500, message, source); 29 | } 30 | } 31 | 32 | export class UnauthorizedError extends ApiError { 33 | constructor( 34 | readonly message: string = "Unauthorized Request", 35 | source?: Error | any 36 | ) { 37 | super(401, message, source); 38 | } 39 | } 40 | 41 | export class BadRequestError extends ApiError { 42 | constructor(readonly message: string = "Bad Request", source?: Error | any) { 43 | super(400, message, source); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dist/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const express_1 = __importDefault(require("express")); 7 | const cors_1 = __importDefault(require("cors")); 8 | //import Strategy from "./config/passport"; 9 | const product_1 = __importDefault(require("./routers/product")); 10 | const user_1 = __importDefault(require("./routers/user")); 11 | const auth_1 = __importDefault(require("./routers/auth")); 12 | const admin_1 = __importDefault(require("./routers/admin")); 13 | const errorhandler_1 = __importDefault(require("errorhandler")); 14 | const app = (0, express_1.default)(); 15 | app.set("port", process.env.PORT); 16 | app.use(express_1.default.json()); 17 | app.use(express_1.default.urlencoded({ extended: true })); 18 | app.use((0, cors_1.default)()); 19 | //app.use(passport.initialize()); 20 | //passport.use(Strategy); 21 | app.use("/api/v1/products", product_1.default); 22 | app.use("/api/v1/auth", auth_1.default); 23 | app.use("/api/v1/users", user_1.default); 24 | app.use("/api/v1/admin", admin_1.default); 25 | app.get("/", (req, res) => { 26 | res.send("Deploy successfully"); 27 | }); 28 | app.use((0, errorhandler_1.default)()); 29 | exports.default = app; 30 | //# sourceMappingURL=app.js.map -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeauClothing-E-Commerce-Backend# WEAR E-Commerce Clothing Site 2 | 3 | - A well-designed MERN(MongoDB, Express.js, React, Node.js) Stack Application E-Commerce website in clothing fashion inspired by H&M. 4 | - Demo link: https://beau-ecommerce-project-duc-ngo.netlify.app/ 5 | - Link to Frontend source code: https://github.com/DwcQuocXa/BeauClothing-E-Commerce-Frontend 6 | 7 | # Features 8 | 9 | - User Sign Up and Sign In 10 | - User Login System with Google 11 | - Protected Admin/User routes in both front-end and back-end 12 | - Remain logged-in while refresh with JWT token and Local Storage 13 | - User can add items to Cart 14 | - User can increase/decrease the quantity of an item in Cart 15 | - User can delete items in the Cart 16 | - Admin can create new items 17 | - Admin can ban an user, make that user can no longer log in to the system 18 | 19 | # Technologies 20 | 21 | - TypeScript 22 | - React 23 | - Redux, Redux-thunk, Redux-Saga 24 | - Material-UI 25 | - Formik 26 | - React-router-dom 27 | - Node.js 28 | - Express.js 29 | - REST API 30 | - MongoDB 31 | - Mongoose 32 | - jsonwebtoken 33 | - Passport 34 | - Well-tested unit tests with Jest 35 | 36 | # Images from Admin Features 37 | 38 | ![Admin Create Product](./readmeImg/Create_Product.PNG) 39 | 40 | ![Admin Remove Product](./readmeImg/Remove_Product.PNG) 41 | 42 | ![Admin Upadte Product](./readmeImg/Update_Product.PNG) 43 | 44 | ![Admin Ban User](./readmeImg/User_List_Ban.PNG) 45 | -------------------------------------------------------------------------------- /src/services/product.ts: -------------------------------------------------------------------------------- 1 | import Product, { ProductDocument } from "../models/Product"; 2 | import { NotFoundError } from "../helpers/apiError"; 3 | 4 | const create = async (product: ProductDocument): Promise => { 5 | return product.save(); 6 | }; 7 | 8 | const findById = async (productId: string): Promise => { 9 | const foundProduct = await Product.findById(productId); 10 | 11 | if (!foundProduct) { 12 | throw new NotFoundError(`Product ${productId} is not found`); 13 | } 14 | 15 | return foundProduct; 16 | }; 17 | 18 | const findAll = async (): Promise => { 19 | return Product.find().sort({ name: 1 }); 20 | }; 21 | 22 | const update = async ( 23 | productId: string, 24 | update: Partial 25 | ): Promise => { 26 | const foundProduct = await Product.findByIdAndUpdate(productId, update, { 27 | new: true, 28 | }); 29 | 30 | if (!foundProduct) { 31 | throw new NotFoundError(`Product ${productId} is not found`); 32 | } 33 | 34 | return foundProduct; 35 | }; 36 | 37 | const deleteProduct = async ( 38 | productId: string 39 | ): Promise => { 40 | const foundProduct = Product.findByIdAndDelete(productId); 41 | 42 | if (!foundProduct) { 43 | throw new NotFoundError(`Product ${productId} is not found`); 44 | } 45 | 46 | return foundProduct; 47 | }; 48 | 49 | export default { 50 | create, 51 | findById, 52 | findAll, 53 | update, 54 | deleteProduct, 55 | }; 56 | -------------------------------------------------------------------------------- /dist/util/secrets.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.MONGODB_URI = exports.PORT = exports.JWT_SECRET = exports.ENVIRONMENT = void 0; 7 | const dotenv_1 = __importDefault(require("dotenv")); 8 | const fs_1 = __importDefault(require("fs")); 9 | const logger_1 = __importDefault(require("./logger")); 10 | if (fs_1.default.existsSync(".env")) { 11 | logger_1.default.debug("Using .env file to supply config environment variables"); 12 | dotenv_1.default.config({ path: ".env" }); 13 | } 14 | else { 15 | logger_1.default.debug("Using .env.example file to supply config environment variables"); 16 | dotenv_1.default.config({ path: ".env.example" }); // you can delete this after you create your own .env file! 17 | } 18 | exports.ENVIRONMENT = process.env.NODE_ENV; 19 | const prod = exports.ENVIRONMENT === "production"; // Anything else is treated as 'dev' 20 | exports.JWT_SECRET = process.env["JWT_SECRET"]; 21 | exports.PORT = process.env.PORT; 22 | exports.MONGODB_URI = (prod ? process.env["MONGODB_URI"] : process.env["MONGODB_URI_LOCAL"]); 23 | if (!exports.JWT_SECRET) { 24 | logger_1.default.error("No client secret. Set JWT_SECRET environment variable."); 25 | process.exit(1); 26 | } 27 | if (!exports.MONGODB_URI) { 28 | if (prod) { 29 | logger_1.default.error("No mongo connection string. Set MONGODB_URI environment variable."); 30 | } 31 | else { 32 | logger_1.default.error("No mongo connection string. Set MONGODB_URI_LOCAL environment variable."); 33 | } 34 | process.exit(1); 35 | } 36 | //# sourceMappingURL=secrets.js.map -------------------------------------------------------------------------------- /dist/helpers/apiError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.BadRequestError = exports.UnauthorizedError = exports.InternalServerError = exports.ForbiddenError = exports.NotFoundError = void 0; 4 | class ApiError extends Error { 5 | constructor(statusCode, message, source) { 6 | super(); 7 | this.statusCode = statusCode; 8 | this.message = message; 9 | this.source = source; 10 | } 11 | } 12 | exports.default = ApiError; 13 | class NotFoundError extends ApiError { 14 | constructor(message = "Not Found", source) { 15 | super(404, message, source); 16 | this.message = message; 17 | } 18 | } 19 | exports.NotFoundError = NotFoundError; 20 | class ForbiddenError extends ApiError { 21 | constructor(message = "Forbidden", source) { 22 | super(403, message, source); 23 | this.message = message; 24 | } 25 | } 26 | exports.ForbiddenError = ForbiddenError; 27 | class InternalServerError extends ApiError { 28 | constructor(message = "Internal Server Error", source) { 29 | super(500, message, source); 30 | this.message = message; 31 | } 32 | } 33 | exports.InternalServerError = InternalServerError; 34 | class UnauthorizedError extends ApiError { 35 | constructor(message = "Unauthorized Request", source) { 36 | super(401, message, source); 37 | this.message = message; 38 | } 39 | } 40 | exports.UnauthorizedError = UnauthorizedError; 41 | class BadRequestError extends ApiError { 42 | constructor(message = "Bad Request", source) { 43 | super(400, message, source); 44 | this.message = message; 45 | } 46 | } 47 | exports.BadRequestError = BadRequestError; 48 | //# sourceMappingURL=apiError.js.map -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Server", 3 | "version": "1.0.0", 4 | "main": "server.ts", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@types/bcrypt": "^5.0.0", 8 | "@types/bcryptjs": "^2.4.2", 9 | "@types/errorhandler": "^1.5.0", 10 | "@types/jest": "^24.9.1", 11 | "@types/jsonwebtoken": "^8.5.6", 12 | "@types/mongodb-memory-server": "^2.3.0", 13 | "@types/passport": "^1.0.7", 14 | "@types/supertest": "^2.0.11", 15 | "bcryptjs": "^2.4.3", 16 | "cors": "^2.8.5", 17 | "dotenv": "^10.0.0", 18 | "errorhandler": "^1.5.1", 19 | "esm": "^3.2.25", 20 | "express": "^4.17.1", 21 | "jest": "^24.9.0", 22 | "jsonwebtoken": "^8.5.1", 23 | "mongodb-memory-server": "^8.0.4", 24 | "mongoose": "^5.13.13", 25 | "nodemon": "^2.0.15", 26 | "passport": "^0.4.1", 27 | "passport-google-verify-token": "^2.0.0", 28 | "supertest": "^6.1.6", 29 | "ts-jest": "^24.3.0", 30 | "winston": "^3.3.3" 31 | }, 32 | "scripts": { 33 | "start": "npm run serve", 34 | "serve": "nodemon ./dist/server.js", 35 | "data:import": "tsc -w node ./dist/data/ImportData.js", 36 | "test": "jest --forceExit --detectOpenHandles --coverage --verbose false", 37 | "build-ts": "tsc", 38 | "lint": "tsc --noEmit && eslint \"**/*.{js,ts}\" --quiet --fix" 39 | }, 40 | "devDependencies": { 41 | "@types/cors": "^2.8.12", 42 | "@types/express": "^4.17.13", 43 | "@types/mocha": "^9.0.0", 44 | "@types/mongoose": "^5.11.97", 45 | "@types/node": "^16.11.7", 46 | "ts-node": "^10.4.0", 47 | "ts-node-dev": "^1.1.8", 48 | "typescript": "^4.4.4" 49 | }, 50 | "husky": { 51 | "hooks": { 52 | "pre-commit": "lint-staged" 53 | } 54 | }, 55 | "lint-staged": { 56 | "./src/**/*.{js,jsx,ts,tsx}": [ 57 | "npx prettier --write", 58 | "eslint --fix" 59 | ] 60 | }, 61 | "jest": { 62 | "testEnvironment": "node" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /dist/controllers/product.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"product.js","sourceRoot":"","sources":["../../src/controllers/product.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.mongo = void 0; 16 | const errorhandler_1 = __importDefault(require("errorhandler")); 17 | const mongoose_1 = __importDefault(require("mongoose")); 18 | const dotenv_1 = __importDefault(require("dotenv")); 19 | const app_1 = __importDefault(require("./app")); 20 | //import { MONGODB_URI } from "./util/secrets"; 21 | dotenv_1.default.config({ path: ".env" }); 22 | const PORT = process.env.PORT; 23 | const mongodbUrl = process.env.MONGODB_URI; 24 | const mongo = () => __awaiter(void 0, void 0, void 0, function* () { 25 | yield mongoose_1.default.disconnect(); 26 | yield mongoose_1.default 27 | .connect(mongodbUrl, { 28 | useNewUrlParser: true, 29 | useUnifiedTopology: true, 30 | useFindAndModify: false, 31 | useCreateIndex: true, 32 | }) 33 | .then(() => { 34 | // Start Express server 35 | app_1.default.listen(PORT, () => console.log(`Server running on port: ${PORT}`)); 36 | }) 37 | .catch((error) => { 38 | console.log("MongoDB connection error. Please make sure MongoDB is running. " + 39 | error); 40 | process.exit(1); 41 | }); 42 | }); 43 | exports.mongo = mongo; 44 | (0, exports.mongo)(); 45 | /** 46 | * Error Handler. Provides full stack - remove for production 47 | */ 48 | app_1.default.use((0, errorhandler_1.default)()); 49 | //# sourceMappingURL=server.js.map -------------------------------------------------------------------------------- /dist/config/passport.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const user_1 = __importDefault(require("../services/user")); 16 | const GoogleTokenStrategy = require("passport-google-id-token"); 17 | const GOOGLE_CLIENT_ID = "634958478111-32garfk7ttd62rvjpp3tqmp0f58ijdsn.apps.googleusercontent.com"; 18 | exports.default = new GoogleTokenStrategy({ 19 | clientID: GOOGLE_CLIENT_ID, 20 | }, console.log("backend connect to google"), function (parsedToken, googleId, done) { 21 | var _a, _b, _c; 22 | return __awaiter(this, void 0, void 0, function* () { 23 | console.log(parsedToken); 24 | const userPayload = { 25 | email: (_a = parsedToken === null || parsedToken === void 0 ? void 0 : parsedToken.payload) === null || _a === void 0 ? void 0 : _a.email, 26 | firstName: (_b = parsedToken === null || parsedToken === void 0 ? void 0 : parsedToken.payload) === null || _b === void 0 ? void 0 : _b.firstName, 27 | lastName: (_c = parsedToken === null || parsedToken === void 0 ? void 0 : parsedToken.payload) === null || _c === void 0 ? void 0 : _c.lastName, 28 | }; 29 | try { 30 | const user = yield user_1.default.findOrCreate(userPayload); 31 | done(undefined, user); 32 | console.log("To the authenticate controller"); 33 | } 34 | catch (error) { 35 | done(error); 36 | } 37 | }); 38 | }); 39 | //# sourceMappingURL=passport.js.map -------------------------------------------------------------------------------- /dist/controllers/user.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/controllers/user.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/controllers/product.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | 3 | import Product, { ProductDocument } from "../models/Product"; 4 | import ProductService from "../services/product"; 5 | import { NotFoundError } from "../helpers/apiError"; 6 | 7 | // POST /products 8 | export const createProduct = async ( 9 | req: Request, 10 | res: Response, 11 | next: NextFunction 12 | ) => { 13 | console.log("admin router"); 14 | try { 15 | const { name, description, categories, sizes, price, img } = req.body; 16 | 17 | const newProduct: ProductDocument = new Product({ 18 | name, 19 | description, 20 | categories, 21 | sizes, 22 | price, 23 | img, 24 | }); 25 | 26 | console.log(newProduct); 27 | console.log("admin router"); 28 | 29 | await ProductService.create(newProduct); 30 | res.json(newProduct); 31 | } catch (error) { 32 | next(new NotFoundError("Product not found", error)); 33 | } 34 | }; 35 | 36 | // PUT /products/:productId 37 | export const updateProduct = async ( 38 | req: Request, 39 | res: Response, 40 | next: NextFunction 41 | ) => { 42 | try { 43 | const update = req.body; 44 | const productId = req.params.productId; 45 | const updateProduct = await ProductService.update(productId, update); 46 | res.json(updateProduct); 47 | } catch (error) { 48 | next(new NotFoundError("Product not found", error)); 49 | } 50 | }; 51 | 52 | // DELETE /products/:productId 53 | export const deleteProduct = async ( 54 | req: Request, 55 | res: Response, 56 | next: NextFunction 57 | ) => { 58 | try { 59 | await ProductService.deleteProduct(req.params.productId); 60 | res.status(204).end(); 61 | } catch (error) { 62 | next(new NotFoundError("Product not found", error)); 63 | } 64 | }; 65 | 66 | // GET /products/:productId 67 | export const findById = async ( 68 | req: Request, 69 | res: Response, 70 | next: NextFunction 71 | ) => { 72 | try { 73 | res.json(await ProductService.findById(req.params.productId)); 74 | } catch (error) { 75 | next(new NotFoundError("Product not found", error)); 76 | } 77 | }; 78 | 79 | // GET /products 80 | export const findAll = async ( 81 | req: Request, 82 | res: Response, 83 | next: NextFunction 84 | ) => { 85 | try { 86 | res.json(await ProductService.findAll()); 87 | } catch (error) { 88 | next(new NotFoundError("Product not found", error)); 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /dist/services/product.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const Product_1 = __importDefault(require("../models/Product")); 16 | const apiError_1 = require("../helpers/apiError"); 17 | const create = (product) => __awaiter(void 0, void 0, void 0, function* () { 18 | return product.save(); 19 | }); 20 | const findById = (productId) => __awaiter(void 0, void 0, void 0, function* () { 21 | const foundProduct = yield Product_1.default.findById(productId); 22 | if (!foundProduct) { 23 | throw new apiError_1.NotFoundError(`Product ${productId} is not found`); 24 | } 25 | return foundProduct; 26 | }); 27 | const findAll = () => __awaiter(void 0, void 0, void 0, function* () { 28 | return Product_1.default.find().sort({ name: 1 }); 29 | }); 30 | const update = (productId, update) => __awaiter(void 0, void 0, void 0, function* () { 31 | const foundProduct = yield Product_1.default.findByIdAndUpdate(productId, update, { 32 | new: true, 33 | }); 34 | if (!foundProduct) { 35 | throw new apiError_1.NotFoundError(`Product ${productId} is not found`); 36 | } 37 | return foundProduct; 38 | }); 39 | const deleteProduct = (productId) => __awaiter(void 0, void 0, void 0, function* () { 40 | const foundProduct = Product_1.default.findByIdAndDelete(productId); 41 | if (!foundProduct) { 42 | throw new apiError_1.NotFoundError(`Product ${productId} is not found`); 43 | } 44 | return foundProduct; 45 | }); 46 | exports.default = { 47 | create, 48 | findById, 49 | findAll, 50 | update, 51 | deleteProduct, 52 | }; 53 | //# sourceMappingURL=product.js.map -------------------------------------------------------------------------------- /src/controllers/user.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | 3 | import User, { UserDocument } from "../models/User"; 4 | import UserService from "../services/user"; 5 | import { NotFoundError, ForbiddenError } from "../helpers/apiError"; 6 | 7 | export const findAll = async ( 8 | req: Request, 9 | res: Response, 10 | next: NextFunction 11 | ) => { 12 | try { 13 | res.json(await UserService.findAll()); 14 | } catch (error: any) { 15 | if (error.statusCode === 403) next(new ForbiddenError(error.message)); 16 | next(new NotFoundError("User not found", error)); 17 | } 18 | }; 19 | 20 | //PUT /users 21 | export const banOrUnbanUser = async ( 22 | req: Request, 23 | res: Response, 24 | next: NextFunction 25 | ) => { 26 | try { 27 | res.json(await UserService.banOrUnbanUser(req.body.userId)); 28 | } catch (error: any) { 29 | if (error.statusCode === 403) next(new ForbiddenError(error.message)); 30 | next(new NotFoundError("User not found", error)); 31 | } 32 | }; 33 | 34 | // GET /users/cart 35 | export const getCart = async ( 36 | req: Request, 37 | res: Response, 38 | next: NextFunction 39 | ) => { 40 | try { 41 | const user: UserDocument = await UserService.getCart(req.body.userId); 42 | res.json(user.cart); 43 | } catch (error) { 44 | next(new NotFoundError("Cart not found", error)); 45 | } 46 | }; 47 | 48 | // PUT /users/cart 49 | export const manageProductInCart = async ( 50 | req: Request, 51 | res: Response, 52 | next: NextFunction 53 | ) => { 54 | try { 55 | const userId = req.body.userId; 56 | const productId = req.body.productId; 57 | const isIncreased = req.body.isIncreased; 58 | if (isIncreased) { 59 | const updatedUser: UserDocument = await UserService.addProductToCart( 60 | productId, 61 | userId 62 | ); 63 | res.json(updatedUser.cart); 64 | } else { 65 | const updatedUser: UserDocument = 66 | await UserService.decreaseQuantityOfProduct(productId, userId); 67 | res.json(updatedUser.cart); 68 | } 69 | } catch (error) { 70 | next(new NotFoundError("Product not found", error)); 71 | } 72 | }; 73 | 74 | // DELETE /users/cart 75 | export const removeProductInCart = async ( 76 | req: Request, 77 | res: Response, 78 | next: NextFunction 79 | ) => { 80 | try { 81 | const userId = req.body.userId; 82 | const productId = req.body.productId; 83 | const updatedUser: UserDocument = await UserService.removeProductInCart( 84 | productId, 85 | userId 86 | ); 87 | res.json(updatedUser.cart); 88 | } catch (error) { 89 | next(new NotFoundError("Product not found", error)); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /dist/services/user.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/services/user.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /test/controllers/user.test.ts: -------------------------------------------------------------------------------- 1 | // import request from "supertest"; 2 | 3 | // import User, { UserDocument } from "../../src/models/User"; 4 | // import Product from "../../src/models/Product"; 5 | // import UserService from "../../src/services/user"; 6 | // import ProductService from "../../src/services/product"; 7 | // import connect, { MongodHelper } from "../db-helper"; 8 | // import app from "../../src/app"; 9 | 10 | // const nonExistingProductId = "5e57b77b5744fa0b461c7906"; 11 | 12 | // async function createProduct() { 13 | // let product = { 14 | // name: "RELAXED FIT MOTIF-DETAIL HOODIE", 15 | // description: 16 | // "Smiley® x H&M. Hoodie in sweatshirt fabric with a motif front and back and a soft brushed inside. Relaxed fit with a lined, wrapover, drawstring hood, kangaroo pocket, dropped shoulders, long sleeves and ribbing at the cuffs and hem.", 17 | // categories: "T-Shirts", 18 | // sizes: ["XS", "S", "M", "L", "XL"], 19 | // price: 24.99, 20 | // img: [ 21 | // "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fc7%2F79%2Fc779f00ea678411e215159fe67ee9790b4effa36.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/fullscreen]", 22 | // ], 23 | // }; 24 | // return await request(app).post("/api/v1/products").send(product); 25 | // } 26 | 27 | // async function createUser(override?: Partial) { 28 | // let user = new User({ 29 | // firstName: "Duc", 30 | // lastName: "Ngo", 31 | // email: "random@gmail.com", 32 | // password: "randompassword", 33 | // cart: [], 34 | // }); 35 | 36 | // // if (override) { 37 | // // user = { ...user, ...override }; 38 | // // } 39 | 40 | // return await request(app).post("/api/v1/users").send(user); 41 | // } 42 | 43 | // describe("user service", () => { 44 | // let mongodHelper: MongodHelper; 45 | // beforeEach(async () => { 46 | // mongodHelper = await connect(); 47 | // }); 48 | 49 | // afterEach(async () => { 50 | // await mongodHelper.clearDatabase(); 51 | // }); 52 | 53 | // afterAll(async () => { 54 | // await mongodHelper.closeDatabase(); 55 | // }); 56 | 57 | // it("should get a list of all users", async () => { 58 | // // const res1 = await createUser({ 59 | // // firstName: "Nguyen", 60 | // // lastName: "Pham", 61 | // // email: "random1@gmail.com", 62 | // // }); 63 | // // const res2 = await createUser({ 64 | // // firstName: "Dat", 65 | // // lastName: "Nguyen", 66 | // // email: "random2@gmail.com", 67 | // // }); 68 | // const res1 = await createUser(); 69 | // const res3 = await request(app).get("/api/v1/users"); 70 | 71 | // expect(res3.body.length).toEqual(1); 72 | // //expect(res3.body[0]._id).toEqual(res2.body._id); 73 | // //expect(res3.body[1]._id).toEqual(res1.body._id); 74 | // }); 75 | 76 | // it("should manage products in the cart", async () => { 77 | // let res = await createProduct(); 78 | // expect(res.status).toBe(200); 79 | // const productId = res.body._id; 80 | 81 | // res = await createUser(); 82 | // expect(res.status).toBe(200); 83 | // const userId = res.body._id; 84 | 85 | // const body = { 86 | // productId: productId, 87 | // userId: userId, 88 | // isIncrease: true, 89 | // }; 90 | 91 | // res = await request(app).put(`/api/v1/users/cart`).send(body); 92 | // expect(res.status).toEqual(200); 93 | // expect(res.body.cart.length).toEqual(1); 94 | // expect(res.body.cart[0].product._id).toEqual(productId); 95 | // }); 96 | // }); 97 | -------------------------------------------------------------------------------- /dist/data/products.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"products.js","sourceRoot":"","sources":["../../src/data/products.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/controllers/product.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.findAll = exports.findById = exports.deleteProduct = exports.updateProduct = exports.createProduct = void 0; 16 | const Product_1 = __importDefault(require("../models/Product")); 17 | const product_1 = __importDefault(require("../services/product")); 18 | const apiError_1 = require("../helpers/apiError"); 19 | // POST /products 20 | const createProduct = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 21 | console.log("admin router"); 22 | try { 23 | const { name, description, categories, sizes, price, img } = req.body; 24 | const newProduct = new Product_1.default({ 25 | name, 26 | description, 27 | categories, 28 | sizes, 29 | price, 30 | img, 31 | }); 32 | console.log(newProduct); 33 | console.log("admin router"); 34 | yield product_1.default.create(newProduct); 35 | res.json(newProduct); 36 | } 37 | catch (error) { 38 | next(new apiError_1.NotFoundError("Product not found", error)); 39 | } 40 | }); 41 | exports.createProduct = createProduct; 42 | // PUT /products/:productId 43 | const updateProduct = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 44 | try { 45 | const update = req.body; 46 | const productId = req.params.productId; 47 | const updateProduct = yield product_1.default.update(productId, update); 48 | res.json(updateProduct); 49 | } 50 | catch (error) { 51 | next(new apiError_1.NotFoundError("Product not found", error)); 52 | } 53 | }); 54 | exports.updateProduct = updateProduct; 55 | // DELETE /products/:productId 56 | const deleteProduct = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 57 | try { 58 | yield product_1.default.deleteProduct(req.params.productId); 59 | res.status(204).end(); 60 | } 61 | catch (error) { 62 | next(new apiError_1.NotFoundError("Product not found", error)); 63 | } 64 | }); 65 | exports.deleteProduct = deleteProduct; 66 | // GET /products/:productId 67 | const findById = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 68 | try { 69 | res.json(yield product_1.default.findById(req.params.productId)); 70 | } 71 | catch (error) { 72 | next(new apiError_1.NotFoundError("Product not found", error)); 73 | } 74 | }); 75 | exports.findById = findById; 76 | // GET /products 77 | const findAll = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 78 | try { 79 | res.json(yield product_1.default.findAll()); 80 | } 81 | catch (error) { 82 | next(new apiError_1.NotFoundError("Product not found", error)); 83 | } 84 | }); 85 | exports.findAll = findAll; 86 | //# sourceMappingURL=product.js.map -------------------------------------------------------------------------------- /test/services/product.test.ts: -------------------------------------------------------------------------------- 1 | import Product from "../../src/models/Product"; 2 | import ProductService from "../../src/services/product"; 3 | import connect, { MongodHelper } from "../db-helper"; 4 | //import "@types/jest"; 5 | 6 | const nonExistingProductId = "5e57b77b5744fa0b461c7906"; 7 | 8 | async function createProduct() { 9 | const product = new Product({ 10 | name: "RELAXED FIT MOTIF-DETAIL HOODIE", 11 | description: 12 | "Smiley® x H&M. Hoodie in sweatshirt fabric with a motif front and back and a soft brushed inside. Relaxed fit with a lined, wrapover, drawstring hood, kangaroo pocket, dropped shoulders, long sleeves and ribbing at the cuffs and hem.", 13 | categories: "T-Shirts", 14 | sizes: ["XS", "S", "M", "L", "XL"], 15 | price: 24.99, 16 | img: "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fc7%2F79%2Fc779f00ea678411e215159fe67ee9790b4effa36.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/fullscreen]", 17 | }); 18 | return await ProductService.create(product); 19 | } 20 | describe("product service", () => { 21 | let mongodHelper: MongodHelper; 22 | beforeEach(async () => { 23 | mongodHelper = await connect(); 24 | }); 25 | 26 | afterEach(async () => { 27 | await mongodHelper.clearDatabase(); 28 | }); 29 | 30 | afterAll(async () => { 31 | await mongodHelper.closeDatabase(); 32 | }); 33 | 34 | it("should create a product", async () => { 35 | const product = await createProduct(); 36 | expect(product).toHaveProperty("_id"); 37 | expect(product).toHaveProperty("name", "RELAXED FIT MOTIF-DETAIL HOODIE"); 38 | expect(product).toHaveProperty("price", 24.99); 39 | }); 40 | 41 | it("should get a product with id", async () => { 42 | const product = await createProduct(); 43 | const found = await ProductService.findById(product._id); 44 | expect(found.name).toEqual(product.name); 45 | expect(found._id).toEqual(product._id); 46 | }); 47 | 48 | it("should get a list of all products", async () => { 49 | const product = await createProduct(); 50 | const found = await ProductService.findAll(); 51 | expect(found.length).toEqual(1); 52 | expect(found[0]._id).toEqual(product._id); 53 | }); 54 | 55 | it("should not get a non-existing product", async () => { 56 | expect.assertions(1); 57 | return ProductService.findById(nonExistingProductId).catch((e) => { 58 | expect(e.message).toMatch(`Product ${nonExistingProductId} is not found`); 59 | }); 60 | }); 61 | 62 | it("should update an existing product", async () => { 63 | const product = await createProduct(); 64 | const update = { 65 | name: "RELAXED FIT MOTIF-DETAIL JACKET", 66 | price: 110, 67 | }; 68 | const updated = await ProductService.update(product._id, update); 69 | expect(updated).toHaveProperty("_id", product._id); 70 | expect(updated).toHaveProperty("name", "RELAXED FIT MOTIF-DETAIL JACKET"); 71 | expect(updated).toHaveProperty("price", 110); 72 | }); 73 | 74 | it("should not update a non-existing movie", async () => { 75 | expect.assertions(1); 76 | const update = { 77 | name: "RELAXED FIT MOTIF-DETAIL JACKET", 78 | price: 110, 79 | }; 80 | return ProductService.update(nonExistingProductId, update).catch((e) => { 81 | expect(e.message).toMatch(`Product ${nonExistingProductId} is not found`); 82 | }); 83 | }); 84 | 85 | it("should delete an existing product", async () => { 86 | expect.assertions(1); 87 | const product = await createProduct(); 88 | await ProductService.deleteProduct(product._id); 89 | return ProductService.findById(product._id).catch((e) => { 90 | expect(e.message).toBe(`Product ${product._id} is not found`); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /dist/controllers/auth.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/controllers/auth.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/controllers/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.removeProductInCart = exports.manageProductInCart = exports.getCart = exports.banOrUnbanUser = exports.findAll = void 0; 16 | const user_1 = __importDefault(require("../services/user")); 17 | const apiError_1 = require("../helpers/apiError"); 18 | const findAll = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 19 | try { 20 | res.json(yield user_1.default.findAll()); 21 | } 22 | catch (error) { 23 | if (error.statusCode === 403) 24 | next(new apiError_1.ForbiddenError(error.message)); 25 | next(new apiError_1.NotFoundError("User not found", error)); 26 | } 27 | }); 28 | exports.findAll = findAll; 29 | //PUT /users 30 | const banOrUnbanUser = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 31 | try { 32 | res.json(yield user_1.default.banOrUnbanUser(req.body.userId)); 33 | } 34 | catch (error) { 35 | if (error.statusCode === 403) 36 | next(new apiError_1.ForbiddenError(error.message)); 37 | next(new apiError_1.NotFoundError("User not found", error)); 38 | } 39 | }); 40 | exports.banOrUnbanUser = banOrUnbanUser; 41 | // GET /users/cart 42 | const getCart = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 43 | try { 44 | const user = yield user_1.default.getCart(req.body.userId); 45 | res.json(user.cart); 46 | } 47 | catch (error) { 48 | next(new apiError_1.NotFoundError("Cart not found", error)); 49 | } 50 | }); 51 | exports.getCart = getCart; 52 | // PUT /users/cart 53 | const manageProductInCart = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 54 | try { 55 | const userId = req.body.userId; 56 | const productId = req.body.productId; 57 | const isIncreased = req.body.isIncreased; 58 | if (isIncreased) { 59 | const updatedUser = yield user_1.default.addProductToCart(productId, userId); 60 | res.json(updatedUser.cart); 61 | } 62 | else { 63 | const updatedUser = yield user_1.default.decreaseQuantityOfProduct(productId, userId); 64 | res.json(updatedUser.cart); 65 | } 66 | } 67 | catch (error) { 68 | next(new apiError_1.NotFoundError("Product not found", error)); 69 | } 70 | }); 71 | exports.manageProductInCart = manageProductInCart; 72 | // DELETE /users/cart 73 | const removeProductInCart = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 74 | try { 75 | const userId = req.body.userId; 76 | const productId = req.body.productId; 77 | const updatedUser = yield user_1.default.removeProductInCart(productId, userId); 78 | res.json(updatedUser.cart); 79 | } 80 | catch (error) { 81 | next(new apiError_1.NotFoundError("Product not found", error)); 82 | } 83 | }); 84 | exports.removeProductInCart = removeProductInCart; 85 | //# sourceMappingURL=user.js.map -------------------------------------------------------------------------------- /dist/data/ImportData.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ImportData.js","sourceRoot":"","sources":["../../src/data/ImportData.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /test/controllers/product.test.ts: -------------------------------------------------------------------------------- 1 | import request from "supertest"; 2 | import { Request, Response, NextFunction } from "express"; 3 | import { ProductDocument } from "../../src/models/Product"; 4 | import app from "../../src/app"; 5 | import connect, { MongodHelper } from "../db-helper"; 6 | 7 | const nonExistingProductId = "5e57b77b5744fa0b461c7906"; 8 | 9 | async function createProduct(override?: Partial) { 10 | let product = { 11 | name: "RELAXED FIT MOTIF-DETAIL HOODIE", 12 | description: 13 | "Smiley® x H&M. Hoodie in sweatshirt fabric with a motif front and back and a soft brushed inside. Relaxed fit with a lined, wrapover, drawstring hood, kangaroo pocket, dropped shoulders, long sleeves and ribbing at the cuffs and hem.", 14 | categories: "T-Shirts", 15 | sizes: ["XS", "S", "M", "L", "XL"], 16 | price: 24.99, 17 | img: [ 18 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fc7%2F79%2Fc779f00ea678411e215159fe67ee9790b4effa36.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/fullscreen]", 19 | ], 20 | }; 21 | 22 | if (override) { 23 | product = { ...product, ...override }; 24 | } 25 | 26 | return await request(app).post("/api/v1/products").send(product); 27 | } 28 | 29 | describe("product controller", () => { 30 | let mongodHelper: MongodHelper; 31 | beforeEach(async () => { 32 | mongodHelper = await connect(); 33 | }); 34 | 35 | afterEach(async () => { 36 | await mongodHelper.clearDatabase(); 37 | }); 38 | 39 | afterAll(async () => { 40 | await mongodHelper.closeDatabase(); 41 | }); 42 | 43 | it("should create a product", async () => { 44 | const res = await createProduct(); 45 | expect(res.status).toBe(200); 46 | expect(res.body).toHaveProperty("_id"); 47 | expect(res.body.name).toBe("RELAXED FIT MOTIF-DETAIL HOODIE"); 48 | }); 49 | 50 | it("should get back an existing product", async () => { 51 | let res = await createProduct(); 52 | expect(res.status).toBe(200); 53 | 54 | const productId = res.body._id; 55 | res = await request(app).get(`/api/v1/products/${productId}`); 56 | 57 | expect(res.body._id).toEqual(productId); 58 | }); 59 | 60 | it("should not get back a non-existing product", async () => { 61 | const res = await request(app).get( 62 | `/api/v1/products/${nonExistingProductId}` 63 | ); 64 | expect(res.status).toBe(404); 65 | }); 66 | 67 | it("should get back all product", async () => { 68 | const res1 = await createProduct({ 69 | name: "RELAXED JACKET", 70 | price: 110, 71 | }); 72 | const res2 = await createProduct({ 73 | name: "FIT PANTS", 74 | price: 32, 75 | }); 76 | 77 | const res3 = await request(app).get("/api/v1/products"); 78 | 79 | expect(res3.body.length).toEqual(2); 80 | expect(res3.body[0]._id).toEqual(res2.body._id); 81 | expect(res3.body[1]._id).toEqual(res1.body._id); 82 | }); 83 | 84 | it("should update an existing product", async () => { 85 | let res = await createProduct(); 86 | expect(res.status).toBe(200); 87 | 88 | const productId = res.body._id; 89 | const update = { 90 | name: "RELAXED JACKET", 91 | price: 110, 92 | }; 93 | 94 | res = await request(app).put(`/api/v1/products/${productId}`).send(update); 95 | 96 | expect(res.status).toEqual(200); 97 | expect(res.body.name).toEqual("RELAXED JACKET"); 98 | expect(res.body.price).toEqual(110); 99 | }); 100 | 101 | it("should delete an existing product", async () => { 102 | let res1 = await createProduct(); 103 | expect(res1.status).toBe(200); 104 | const productId = res1.body._id; 105 | 106 | let res2 = await request(app).delete(`/api/v1/products/${productId}`); 107 | 108 | expect(res2.status).toEqual(204); 109 | 110 | let res3 = await request(app).get(`/api/v1/products/${productId}`); 111 | expect(res3.status).toBe(404); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /src/services/user.ts: -------------------------------------------------------------------------------- 1 | import User, { UserDocument } from "../models/User"; 2 | import mongoose from "mongoose"; 3 | import { NotFoundError } from "../helpers/apiError"; 4 | 5 | const findAll = async (): Promise => { 6 | return User.find(); 7 | }; 8 | 9 | const create = async (user: UserDocument): Promise => { 10 | return user.save(); 11 | }; 12 | 13 | const findById = async (userId: string): Promise => { 14 | const foundUser = await User.findById(userId); 15 | 16 | if (!foundUser) { 17 | throw new NotFoundError(`User ${userId} is not found`); 18 | } 19 | 20 | return foundUser; 21 | }; 22 | 23 | const findOrCreate = async (payload: Partial) => { 24 | const user = await User.findOne(); 25 | 26 | if (!user) { 27 | const newUser = new User({ 28 | email: payload.email, 29 | firstName: payload.firstName, 30 | lastName: payload.lastName, 31 | }); 32 | newUser.save(); 33 | return newUser; 34 | } 35 | return user; 36 | }; 37 | 38 | const banOrUnbanUser = async (userId: string): Promise => { 39 | const user = await User.findById(userId); 40 | if (!user) { 41 | throw new Error(`User ${userId} not found`); 42 | } 43 | user.isBanned = !user.isBanned; 44 | return user.save(); 45 | }; 46 | 47 | const getCart = async (userId: string): Promise => { 48 | const user = await User.findById(userId).populate("cart.product"); 49 | 50 | if (!user) { 51 | throw new Error(`User ${userId} not found`); 52 | } 53 | return user; 54 | }; 55 | 56 | const addProductToCart = async ( 57 | productId: string, 58 | userId: string 59 | ): Promise => { 60 | const user = await User.findById(userId); 61 | if (!user) { 62 | throw new Error(`User ${userId} not found`); 63 | } 64 | // Check if exist 65 | const existedProduct = user.cart.find( 66 | (item) => item.product.toHexString() == productId 67 | ); 68 | if (existedProduct) { 69 | // Increase the quantity by one 70 | existedProduct.quantity++; 71 | } else { 72 | // Add new product to cart 73 | user.cart = [ 74 | ...user.cart, 75 | { product: mongoose.Types.ObjectId(productId), quantity: 1 }, 76 | ]; 77 | } 78 | 79 | return user 80 | .save() 81 | .then((user) => user.populate("cart.product").execPopulate()); 82 | }; 83 | 84 | const decreaseQuantityOfProduct = async ( 85 | productId: string, 86 | userId: string 87 | ): Promise => { 88 | const user = await User.findById(userId); 89 | if (!user) { 90 | throw new Error(`User ${userId} not found`); 91 | } 92 | //Check if exist 93 | const existedProduct = user.cart.find( 94 | (item) => item.product.toHexString() == productId 95 | ); 96 | if (existedProduct) { 97 | // Prevent user from decreasing the quantity if it is one 98 | if (existedProduct.quantity > 1) 99 | // Decrease the quantity by one 100 | existedProduct.quantity--; 101 | } else { 102 | throw new Error(`Product ${productId} not found`); 103 | } 104 | 105 | return user 106 | .save() 107 | .then((user) => user.populate("cart.product").execPopulate()); 108 | }; 109 | 110 | const removeProductInCart = async ( 111 | productId: string, 112 | userId: string 113 | ): Promise => { 114 | const user = await User.findById(userId); 115 | if (!user) { 116 | throw new Error(`User ${userId} not found`); 117 | } 118 | // Check if exist 119 | const existedIndex = user.cart.findIndex( 120 | (item) => item.product.toHexString() == productId 121 | ); 122 | if (existedIndex !== -1) { 123 | // If existed, remove that product 124 | user.cart.splice(existedIndex, 1); 125 | } 126 | 127 | return user 128 | .save() 129 | .then((user) => user.populate("cart.product").execPopulate()); 130 | }; 131 | 132 | export default { 133 | findAll, 134 | create, 135 | findById, 136 | findOrCreate, 137 | getCart, 138 | addProductToCart, 139 | decreaseQuantityOfProduct, 140 | removeProductInCart, 141 | banOrUnbanUser, 142 | }; 143 | -------------------------------------------------------------------------------- /src/controllers/auth.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | import dotenv from "dotenv"; 4 | import bcrypt from "bcryptjs"; 5 | 6 | import User, { UserDocument } from "../models/User"; 7 | import { BadRequestError, InternalServerError } from "../helpers/apiError"; 8 | 9 | dotenv.config({ path: ".env" }); 10 | 11 | const JWT_SECRET = process.env.JWT_SECRET as string; 12 | 13 | export const signIn = async ( 14 | req: Request, 15 | res: Response, 16 | next: NextFunction 17 | ) => { 18 | const { email, password } = req.body; 19 | 20 | try { 21 | const existingUser = await User.findOne({ email }); 22 | 23 | if (!existingUser) 24 | return res.status(404).json({ message: "User doesn't exist" }); 25 | 26 | const isPasswordCorrect = await bcrypt.compare( 27 | password, 28 | existingUser.password 29 | ); 30 | 31 | if (!isPasswordCorrect) 32 | return res.status(404).json({ message: "Invalid credentials" }); 33 | 34 | const token = jwt.sign( 35 | { email: existingUser.email, id: existingUser._id }, 36 | JWT_SECRET, 37 | { expiresIn: "1h" } 38 | ); 39 | 40 | res.status(200).json({ result: existingUser, token }); 41 | } catch (error: any) { 42 | if (error.name === "ValidationError") { 43 | next(new BadRequestError("Invalid Request", error)); 44 | } else { 45 | next(new InternalServerError("Internal Server Error", error)); 46 | } 47 | } 48 | }; 49 | 50 | export const signUp = async ( 51 | req: Request, 52 | res: Response, 53 | next: NextFunction 54 | ) => { 55 | const { email, password, confirmPassword, firstName, lastName } = req.body; 56 | 57 | try { 58 | const existingUser = await User.findOne({ email }); 59 | 60 | if (existingUser) 61 | return res.status(400).json({ message: "User already exist" }); 62 | 63 | if (password !== confirmPassword) 64 | return res.status(400).json({ message: "Passwords don't match" }); 65 | 66 | const hashedPassword = await bcrypt.hash(password, 12); 67 | 68 | //const isAdmin = email === "duc.ngogia2002@gmail.com" ? true : false; 69 | 70 | const result = await User.create({ 71 | isAdmin: email === "duc.ngogia2002@gmail.com", 72 | email: email, 73 | password: hashedPassword, 74 | firstName: firstName, 75 | lastName: lastName, 76 | }); 77 | 78 | const token = jwt.sign( 79 | { email: result.email, id: result._id }, 80 | JWT_SECRET, 81 | { expiresIn: "1h" } 82 | ); 83 | 84 | res.status(200).json({ result: result, token }); 85 | } catch (error: any) { 86 | if (error.name === "ValidationError") { 87 | next(new BadRequestError("Invalid Request", error)); 88 | } else { 89 | next(new InternalServerError("Internal Server Error", error)); 90 | } 91 | } 92 | }; 93 | 94 | export const authenticate = async ( 95 | req: Request, 96 | res: Response, 97 | next: NextFunction 98 | ) => { 99 | const { result } = req.body; 100 | 101 | try { 102 | const existingUser = await User.findOne({ email: result.email }); 103 | 104 | if (existingUser) { 105 | const token = jwt.sign( 106 | { email: existingUser.email, id: existingUser._id }, 107 | JWT_SECRET, 108 | { expiresIn: "1h" } 109 | ); 110 | res.status(200).json({ result: existingUser, token }); 111 | } 112 | 113 | const token = jwt.sign( 114 | { 115 | email: result.email, 116 | id: result._id, 117 | }, 118 | JWT_SECRET, 119 | { expiresIn: "1h" } 120 | ); 121 | 122 | const resultGoogle = await User.create({ 123 | firstName: result.givenName, 124 | lastName: result.familyName, 125 | email: result.email, 126 | }); 127 | 128 | res.status(200).json({ result: resultGoogle, token }); 129 | } catch (error) { 130 | return next(new InternalServerError()); 131 | } 132 | }; 133 | 134 | export const findByEmail = async ( 135 | req: Request, 136 | res: Response, 137 | next: NextFunction 138 | ) => { 139 | try { 140 | const { email } = req.params; 141 | res.status(200).json(await User.findOne({ email })); 142 | } catch (error) { 143 | return next(new InternalServerError()); 144 | } 145 | }; 146 | -------------------------------------------------------------------------------- /test/services/user.test.ts: -------------------------------------------------------------------------------- 1 | import User from "../../src/models/User"; 2 | import Product from "../../src/models/Product"; 3 | import UserService from "../../src/services/user"; 4 | import ProductService from "../../src/services/product"; 5 | import connect, { MongodHelper } from "../db-helper"; 6 | 7 | const nonExistingProductId = "5e57b77b5744fa0b461c7906"; 8 | 9 | async function createProduct() { 10 | const product = new Product({ 11 | name: "RELAXED FIT MOTIF-DETAIL HOODIE", 12 | description: 13 | "Smiley® x H&M. Hoodie in sweatshirt fabric with a motif front and back and a soft brushed inside. Relaxed fit with a lined, wrapover, drawstring hood, kangaroo pocket, dropped shoulders, long sleeves and ribbing at the cuffs and hem.", 14 | categories: "T-Shirts", 15 | sizes: ["XS", "S", "M", "L", "XL"], 16 | price: 24.99, 17 | img: "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fc7%2F79%2Fc779f00ea678411e215159fe67ee9790b4effa36.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/fullscreen]", 18 | }); 19 | return await ProductService.create(product); 20 | } 21 | 22 | async function createUser() { 23 | const user = new User({ 24 | firstName: "Duc", 25 | lastName: "Ngo", 26 | email: "random@gmail.com", 27 | password: "randompassword", 28 | cart: [], 29 | }); 30 | return await User.create(user); 31 | } 32 | 33 | describe("user service", () => { 34 | let mongodHelper: MongodHelper; 35 | beforeEach(async () => { 36 | mongodHelper = await connect(); 37 | }); 38 | 39 | afterEach(async () => { 40 | await mongodHelper.clearDatabase(); 41 | }); 42 | 43 | afterAll(async () => { 44 | await mongodHelper.closeDatabase(); 45 | }); 46 | 47 | it("should create an user", async () => { 48 | const user = await createUser(); 49 | expect(user).toHaveProperty("_id"); 50 | expect(user).toHaveProperty("email", "random@gmail.com"); 51 | expect(user).toHaveProperty("firstName", "Duc"); 52 | }); 53 | 54 | it("should get a list of all users", async () => { 55 | const user = await createUser(); 56 | const found = await UserService.findAll(); 57 | expect(found.length).toEqual(1); 58 | expect(found[0]._id).toEqual(user._id); 59 | }); 60 | 61 | it("should get the cart", async () => { 62 | const product = await createProduct(); 63 | expect(product).toHaveProperty("_id"); 64 | 65 | const user = await createUser(); 66 | const foundedUser = await UserService.getCart(user._id); 67 | expect(foundedUser).toHaveProperty("_id"); 68 | 69 | await UserService.addProductToCart(product.id, foundedUser.id); 70 | 71 | const updateUser = await UserService.getCart(user.id); 72 | expect(updateUser.cart.length).toEqual(1); 73 | expect(updateUser.cart[0].product).toHaveProperty("_id"); 74 | }); 75 | 76 | it("should increase the quantity of product in cart", async () => { 77 | const product = await createProduct(); 78 | expect(product).toHaveProperty("_id"); 79 | 80 | const user = await createUser(); 81 | const foundedUser = await UserService.getCart(user._id); 82 | expect(foundedUser).toHaveProperty("_id"); 83 | 84 | await UserService.addProductToCart(product.id, foundedUser.id); 85 | const updateUser2 = await UserService.addProductToCart( 86 | product.id, 87 | foundedUser.id 88 | ); 89 | expect(updateUser2.cart.length).toEqual(1); 90 | expect(updateUser2.cart[0].quantity).toEqual(2); 91 | }); 92 | 93 | it("should decrease the quantity of product in cart", async () => { 94 | const product = await createProduct(); 95 | expect(product).toHaveProperty("_id"); 96 | 97 | const user = await createUser(); 98 | const foundedUser = await UserService.getCart(user._id); 99 | expect(foundedUser).toHaveProperty("_id"); 100 | 101 | await UserService.addProductToCart(product.id, foundedUser.id); 102 | 103 | const updateUser3 = await UserService.decreaseQuantityOfProduct( 104 | product.id, 105 | foundedUser.id 106 | ); 107 | 108 | expect(updateUser3.cart.length).toEqual(1); 109 | expect(updateUser3.cart[0].quantity).toEqual(1); 110 | }); 111 | 112 | it("should remove product in cart", async () => { 113 | const product = await createProduct(); 114 | expect(product).toHaveProperty("_id"); 115 | 116 | const user = await createUser(); 117 | const foundedUser = await UserService.getCart(user._id); 118 | expect(foundedUser).toHaveProperty("_id"); 119 | 120 | await UserService.addProductToCart(product.id, foundedUser.id); 121 | 122 | const updateUser2 = await UserService.removeProductInCart( 123 | product.id, 124 | foundedUser.id 125 | ); 126 | expect(updateUser2.cart.length).toEqual(0); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /dist/services/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const User_1 = __importDefault(require("../models/User")); 16 | const mongoose_1 = __importDefault(require("mongoose")); 17 | const apiError_1 = require("../helpers/apiError"); 18 | const findAll = () => __awaiter(void 0, void 0, void 0, function* () { 19 | return User_1.default.find(); 20 | }); 21 | const create = (user) => __awaiter(void 0, void 0, void 0, function* () { 22 | return user.save(); 23 | }); 24 | const findById = (userId) => __awaiter(void 0, void 0, void 0, function* () { 25 | const foundUser = yield User_1.default.findById(userId); 26 | if (!foundUser) { 27 | throw new apiError_1.NotFoundError(`User ${userId} is not found`); 28 | } 29 | return foundUser; 30 | }); 31 | const findOrCreate = (payload) => __awaiter(void 0, void 0, void 0, function* () { 32 | const user = yield User_1.default.findOne(); 33 | if (!user) { 34 | const newUser = new User_1.default({ 35 | email: payload.email, 36 | firstName: payload.firstName, 37 | lastName: payload.lastName, 38 | }); 39 | newUser.save(); 40 | return newUser; 41 | } 42 | return user; 43 | }); 44 | const banOrUnbanUser = (userId) => __awaiter(void 0, void 0, void 0, function* () { 45 | const user = yield User_1.default.findById(userId); 46 | if (!user) { 47 | throw new Error(`User ${userId} not found`); 48 | } 49 | user.isBanned = !user.isBanned; 50 | return user.save(); 51 | }); 52 | const getCart = (userId) => __awaiter(void 0, void 0, void 0, function* () { 53 | const user = yield User_1.default.findById(userId).populate("cart.product"); 54 | if (!user) { 55 | throw new Error(`User ${userId} not found`); 56 | } 57 | return user; 58 | }); 59 | const addProductToCart = (productId, userId) => __awaiter(void 0, void 0, void 0, function* () { 60 | const user = yield User_1.default.findById(userId); 61 | if (!user) { 62 | throw new Error(`User ${userId} not found`); 63 | } 64 | // Check if exist 65 | const existedProduct = user.cart.find((item) => item.product.toHexString() == productId); 66 | if (existedProduct) { 67 | // Increase the quantity by one 68 | existedProduct.quantity++; 69 | } 70 | else { 71 | // Add new product to cart 72 | user.cart = [ 73 | ...user.cart, 74 | { product: mongoose_1.default.Types.ObjectId(productId), quantity: 1 }, 75 | ]; 76 | } 77 | return user 78 | .save() 79 | .then((user) => user.populate("cart.product").execPopulate()); 80 | }); 81 | const decreaseQuantityOfProduct = (productId, userId) => __awaiter(void 0, void 0, void 0, function* () { 82 | const user = yield User_1.default.findById(userId); 83 | if (!user) { 84 | throw new Error(`User ${userId} not found`); 85 | } 86 | //Check if exist 87 | const existedProduct = user.cart.find((item) => item.product.toHexString() == productId); 88 | if (existedProduct) { 89 | // Prevent user from decreasing the quantity if it is one 90 | if (existedProduct.quantity > 1) 91 | // Decrease the quantity by one 92 | existedProduct.quantity--; 93 | } 94 | else { 95 | throw new Error(`Product ${productId} not found`); 96 | } 97 | return user 98 | .save() 99 | .then((user) => user.populate("cart.product").execPopulate()); 100 | }); 101 | const removeProductInCart = (productId, userId) => __awaiter(void 0, void 0, void 0, function* () { 102 | const user = yield User_1.default.findById(userId); 103 | if (!user) { 104 | throw new Error(`User ${userId} not found`); 105 | } 106 | // Check if exist 107 | const existedIndex = user.cart.findIndex((item) => item.product.toHexString() == productId); 108 | if (existedIndex !== -1) { 109 | // If existed, remove that product 110 | user.cart.splice(existedIndex, 1); 111 | } 112 | return user 113 | .save() 114 | .then((user) => user.populate("cart.product").execPopulate()); 115 | }); 116 | exports.default = { 117 | findAll, 118 | create, 119 | findById, 120 | findOrCreate, 121 | getCart, 122 | addProductToCart, 123 | decreaseQuantityOfProduct, 124 | removeProductInCart, 125 | banOrUnbanUser, 126 | }; 127 | //# sourceMappingURL=user.js.map -------------------------------------------------------------------------------- /dist/controllers/auth.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.findByEmail = exports.authenticate = exports.signUp = exports.signIn = void 0; 16 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); 17 | const dotenv_1 = __importDefault(require("dotenv")); 18 | const bcryptjs_1 = __importDefault(require("bcryptjs")); 19 | const User_1 = __importDefault(require("../models/User")); 20 | const apiError_1 = require("../helpers/apiError"); 21 | dotenv_1.default.config({ path: ".env" }); 22 | const JWT_SECRET = process.env.JWT_SECRET; 23 | const signIn = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 24 | const { email, password } = req.body; 25 | try { 26 | const existingUser = yield User_1.default.findOne({ email }); 27 | if (!existingUser) 28 | return res.status(404).json({ message: "User doesn't exist" }); 29 | const isPasswordCorrect = yield bcryptjs_1.default.compare(password, existingUser.password); 30 | if (!isPasswordCorrect) 31 | return res.status(404).json({ message: "Invalid credentials" }); 32 | const token = jsonwebtoken_1.default.sign({ email: existingUser.email, id: existingUser._id }, JWT_SECRET, { expiresIn: "1h" }); 33 | res.status(200).json({ result: existingUser, token }); 34 | } 35 | catch (error) { 36 | if (error.name === "ValidationError") { 37 | next(new apiError_1.BadRequestError("Invalid Request", error)); 38 | } 39 | else { 40 | next(new apiError_1.InternalServerError("Internal Server Error", error)); 41 | } 42 | } 43 | }); 44 | exports.signIn = signIn; 45 | const signUp = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 46 | const { email, password, confirmPassword, firstName, lastName } = req.body; 47 | try { 48 | const existingUser = yield User_1.default.findOne({ email }); 49 | if (existingUser) 50 | return res.status(400).json({ message: "User already exist" }); 51 | if (password !== confirmPassword) 52 | return res.status(400).json({ message: "Passwords don't match" }); 53 | const hashedPassword = yield bcryptjs_1.default.hash(password, 12); 54 | //const isAdmin = email === "duc.ngogia2002@gmail.com" ? true : false; 55 | const result = yield User_1.default.create({ 56 | isAdmin: email === "duc.ngogia2002@gmail.com", 57 | email: email, 58 | password: hashedPassword, 59 | firstName: firstName, 60 | lastName: lastName, 61 | }); 62 | const token = jsonwebtoken_1.default.sign({ email: result.email, id: result._id }, JWT_SECRET, { expiresIn: "1h" }); 63 | res.status(200).json({ result: result, token }); 64 | } 65 | catch (error) { 66 | if (error.name === "ValidationError") { 67 | next(new apiError_1.BadRequestError("Invalid Request", error)); 68 | } 69 | else { 70 | next(new apiError_1.InternalServerError("Internal Server Error", error)); 71 | } 72 | } 73 | }); 74 | exports.signUp = signUp; 75 | const authenticate = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 76 | const { result } = req.body; 77 | try { 78 | const existingUser = yield User_1.default.findOne({ email: result.email }); 79 | if (existingUser) { 80 | const token = jsonwebtoken_1.default.sign({ email: existingUser.email, id: existingUser._id }, JWT_SECRET, { expiresIn: "1h" }); 81 | res.status(200).json({ result: existingUser, token }); 82 | } 83 | const token = jsonwebtoken_1.default.sign({ 84 | email: result.email, 85 | id: result._id, 86 | }, JWT_SECRET, { expiresIn: "1h" }); 87 | const resultGoogle = yield User_1.default.create({ 88 | firstName: result.givenName, 89 | lastName: result.familyName, 90 | email: result.email, 91 | }); 92 | res.status(200).json({ result: resultGoogle, token }); 93 | } 94 | catch (error) { 95 | return next(new apiError_1.InternalServerError()); 96 | } 97 | }); 98 | exports.authenticate = authenticate; 99 | const findByEmail = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 100 | try { 101 | const { email } = req.params; 102 | res.status(200).json(yield User_1.default.findOne({ email })); 103 | } 104 | catch (error) { 105 | return next(new apiError_1.InternalServerError()); 106 | } 107 | }); 108 | exports.findByEmail = findByEmail; 109 | //# sourceMappingURL=auth.js.map -------------------------------------------------------------------------------- /src/data/ImportData.ts: -------------------------------------------------------------------------------- 1 | import Product from "../models/Product"; 2 | import { mongo } from "../server"; 3 | 4 | let productsData = [ 5 | { 6 | name: "Wool-blend baseball jacket", 7 | description: 8 | "Edition by John Boyega. An uncompromising collection developed together with actor and innovator, John Boyega. This lightly padded baseball jacket in woven fabric made from a soft wool blend has a ribbed collar and press-studs down the front. It has a diagonal welt pocket with a press-stud at each side, an inner pocket and ribbing at the cuffs and hem. Quilted satin lining.", 9 | categories: "Jackets", 10 | sizes: ["XS", "S", "M", "L", "XL"], 11 | price: 139.99, 12 | img: [ 13 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F9d%2F7c%2F9d7cb0fdd7fc417cda5223c6e804db6f5840af33.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 14 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F02%2Fc1%2F02c188429ec47d689ba6b8842c4aafec036d4844.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 15 | ], 16 | }, 17 | { 18 | name: "Double-breasted wool-mix coat", 19 | description: 20 | "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This calf-length, double-breasted coat is made in a soft weave containing some recycled wool. In a straight cut, it has wide notch lapels, corozo nut buttons, flap front pockets, an inner pocket, and a single back vent. Lined.", 21 | categories: "Jackets", 22 | sizes: ["XS", "S", "M", "L", "XL"], 23 | price: 149.99, 24 | img: [ 25 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F1e%2Fdb%2F1edbc3769844f46e0b59079f88640d421ebfeb08.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 26 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fba%2F2d%2Fba2d20f86f00714e94743686fd11bf1bf4066ca4.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jacketscoats_coats%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 27 | ], 28 | }, 29 | { 30 | name: "Wool-blend coat", 31 | description: 32 | "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This calf-length, double-breasted coat is made in a soft weave containing some recycled wool. In a straight cut, it has wide notch lapels, corozo nut buttons, flap front pockets, an inner pocket, and a single back vent. Lined.", 33 | 34 | categories: "Jackets", 35 | sizes: ["XS", "S", "M", "L", "XL"], 36 | price: 69.99, 37 | img: [ 38 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fcd%2Ff6%2Fcdf675f0692a0295d1ff0b744da766e30105df32.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 39 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fd9%2F0e%2Fd90e1f4866251660553bfef9c5680f43440f9158.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 40 | ], 41 | }, 42 | { 43 | name: "Quilted grape leather puffer jacket", 44 | description: 45 | "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This quilted puffer jacket is in Vegea™ imitation leather, a material made from bio-waste produced during the winemaking process. The jacket has a stand-up collar, a zip and wind flap with press-studs down the front, fleece-lined front pockets with a press-stud and an inner pocket. Elasticated ribbing at the cuffs and a concealed elastic drawstring at the hem for an adjustable fit. The sleeves can be unzipped and detached to allow the jacket to be worn as a gilet. Lined.", 46 | 47 | categories: "Jackets", 48 | sizes: ["XS", "S", "M", "L", "XL"], 49 | price: 229.99, 50 | img: [ 51 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F33%2Ff9%2F33f9f296be008242bad9f14fa93299403f977bb8.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jacketscoats_jackets%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 52 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F31%2Fa2%2F31a2dc4991605605410e88df9eaa5a81f04fdecd.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 53 | ], 54 | }, 55 | { 56 | name: "Regular Fit embroidery-detail rugby shirt", 57 | description: 58 | "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This quilted puffer jacket is in Vegea™ imitation leather, a material made from bio-waste produced during the winemaking process. The jacket has a stand-up collar, a zip and wind flap with press-studs down the front, fleece-lined front pockets with a press-stud and an inner pocket. Elasticated ribbing at the cuffs and a concealed elastic drawstring at the hem for an adjustable fit. The sleeves can be unzipped and detached to allow the jacket to be worn as a gilet. Lined.", 59 | 60 | categories: "T-Shirts", 61 | sizes: ["XS", "S", "M", "L", "XL"], 62 | price: 19.99, 63 | img: [ 64 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F85%2F84%2F8584d4a91939bc882f2132b4427e580d32fbff54.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 65 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fac%2Fc6%2Facc6b299d6ab483ea2605585b909ef86ea22cc34.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 66 | ], 67 | }, 68 | { 69 | name: "Flower Cotton T-shirt", 70 | description: "T-shirt in soft cotton jersey.", 71 | categories: "T-Shirts", 72 | sizes: ["XS", "S", "M", "L", "XL"], 73 | price: 19.99, 74 | img: [ 75 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F07%2Fec%2F07ececdebb1804c10749bcc30569f9152d5b394e.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 76 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F82%2F32%2F8232534428522832315b5aa64633d7046e28c107.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 77 | ], 78 | }, 79 | { 80 | name: "Relaxed Fit Jersey top", 81 | description: 82 | "Top in soft cotton jersey with a round, rib-trimmed neckline. Relaxed fit with dropped shoulders, long sleeves and wide ribbing at the cuffs.", 83 | categories: "T-Shirts", 84 | sizes: ["XS", "S", "M", "L", "XL"], 85 | price: 27.99, 86 | img: [ 87 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F72%2F47%2F7247c7e2ed1049c270904da68d68f23e9fa684cc.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 88 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F83%2Fc3%2F83c3e0096a7369ea11b29e8b2aac0651a9784dc6.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 89 | ], 90 | }, 91 | { 92 | name: "Block-coloured T-shirt", 93 | description: 94 | "Top in soft cotton jersey with a round, rib-trimmed neckline. Relaxed fit with dropped shoulders, long sleeves and wide ribbing at the cuffs.", 95 | categories: "T-Shirts", 96 | sizes: ["XS", "S", "M", "L", "XL"], 97 | price: 15.99, 98 | img: [ 99 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fdb%2Fcf%2Fdbcfdfe143baeda32b73998d1076edc0951ac3b9.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_tshirtstanks_printed%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 100 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F0e%2F4d%2F0e4d19eb891e5fd74f874fbfcb16b108c65efa23.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 101 | ], 102 | }, 103 | { 104 | name: "Cotton denim Loose Jeans", 105 | description: 106 | "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. These 5-pocket jeans in sturdy denim made from recycled cotton have a regular waist, zip fly and button and loose-fitting legs.", 107 | categories: "Pants", 108 | sizes: ["XS", "S", "M", "L", "XL"], 109 | price: 39.99, 110 | img: [ 111 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F36%2F79%2F36796b7a4998e613a7da9f002a2fd699994cb3dc.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_trousers%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 112 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F73%2F09%2F7309a16586ff6c67ae18cb8ff27464a3dea50457.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans_loose%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 113 | ], 114 | }, 115 | { 116 | name: "Regular Jeans", 117 | description: 118 | "5-pocket jeans in stretch denim with a regular waist, zip fly and button and straight legs with good room for movement over the thighs and knees.", 119 | categories: "Pants", 120 | sizes: ["XS", "S", "M", "L", "XL"], 121 | price: 24.99, 122 | img: [ 123 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffc%2Fba%2Ffcba25c2a546ed633a5eca1b14f46219bb196513.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 124 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffe%2Fab%2Ffeab9ceb516cc017a59b739eb686eee831d020f1.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans_regular%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 125 | ], 126 | }, 127 | { 128 | name: "Relaxed Jeans", 129 | description: 130 | "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. These 5-pocket jeans in sturdy denim made from recycled cotton have a regular waist, zip fly and button and loose-fitting legs.", 131 | categories: "Pants", 132 | sizes: ["XS", "S", "M", "L", "XL"], 133 | price: 24.99, 134 | img: [ 135 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F26%2Fe8%2F26e8d1798edc59f3cdfb84ca9068094ba6ca070d.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 136 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F81%2F2d%2F812db6d977430047149d5f4f442f53cfdef38e5b.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 137 | ], 138 | }, 139 | { 140 | name: "Skinny Jeans", 141 | description: 142 | "5-pocket jeans in stretch cotton denim with a regular waist, zip fly and button, and skinny legs.", 143 | categories: "Pants", 144 | sizes: ["XS", "S", "M", "L", "XL"], 145 | price: 17.99, 146 | img: [ 147 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F3b%2F96%2F3b9675cd1f97ce69d2787406457e984944b77c7c.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 148 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fb3%2Ff6%2Fb3f6559fabc023d978477b2a437ff78e1e7691d8.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 149 | ], 150 | }, 151 | { 152 | name: "Loose Jeans", 153 | description: 154 | "5-pocket jeans in sturdy cotton denim. Loose fit with a regular waist, zip fly and button and wide legs.", 155 | categories: "Pants", 156 | sizes: ["XS", "S", "M", "L", "XL"], 157 | price: 24.99, 158 | img: [ 159 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F7f%2F09%2F7f09ec485a69eb97c01cf85cff1a3ac7687a2a0f.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 160 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fa1%2F13%2Fa1136aed48e7b6a9b306fc4d97c4f79818058ca3.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 161 | ], 162 | }, 163 | { 164 | name: "Leather brogues", 165 | description: 166 | "Toga Archives x H&M. A collection of classic, iconic pieces from Tokyo-based TOGA. Wardrobe classics characterised by innovative details and functions, defined by a gender-neutral, individual approach to fashion. A pair of brogues in leather with perforated details and an extended shaft in scuba fabric. Open lacing at the front and a chunky zip with a metal ring pull at the back. Leather linings and insoles and rubber soles.", 167 | categories: "Shoes", 168 | sizes: ["XS", "S", "M", "L", "XL"], 169 | price: 199.99, 170 | img: [ 171 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffe%2Ffd%2Ffefd4e6faac9a91db4065a7a6bbdc21f6c0c9b50.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 172 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F1d%2F90%2F1d906b0434731018f989dac5fbf32ec685d03fca.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_shoes_dressed%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 173 | ], 174 | }, 175 | { 176 | name: "TEDDY MONKSTRAPS IN PATENT LEATHER", 177 | description: 178 | "DERBIES WITH AN ALMOND TOE AND A SINGLE MONK STRAP WITH METAL BUCKLE.", 179 | categories: "Shoes", 180 | sizes: ["XS", "S", "M", "L", "XL"], 181 | price: 394.99, 182 | img: [ 183 | "https://saint-laurent.dam.kering.com/m/7a3626ef06c85277/Medium-6688941TV001000_A.jpg?v=6", 184 | "https://saint-laurent.dam.kering.com/m/a433aa64bd1452c/Medium-6688941TV001000_B.jpg?v=5", 185 | ], 186 | }, 187 | { 188 | name: "Bryson Suede Chelsea Boot", 189 | description: 190 | "The Bryson combines the refined styling of a Chelsea boot with the durability of a rubber lug sole. This sophisticated shoe features an oiled suede upper accented with a woven logo pull tab at the heel.", 191 | categories: "Shoes", 192 | sizes: ["XS", "S", "M", "L", "XL"], 193 | price: 199.99, 194 | img: [ 195 | "https://www.rlmedia.io/is/image/PoloGSI/s7-1339647_lifestyle?$rl_df_pdp_5_7_lif$", 196 | "https://www.rlmedia.io/is/image/PoloGSI/s7-1339647_alternate1?$rl_df_pdp_5_7$", 197 | ], 198 | }, 199 | { 200 | name: "Logo Print Vintage Check Cotton Sneakers", 201 | description: 202 | "Cotton sneakers in Vintage check, printed with our refreshed logo in contrasting tones.", 203 | categories: "Shoes", 204 | sizes: ["XS", "S", "M", "L", "XL"], 205 | price: 249.99, 206 | img: [ 207 | "https://assets.burberry.com/is/image/Burberryltd/15284bd2a24bd2430615f5f73067b3d6504e7ba9.jpg?$BBY_V2_SL_1x1$&wid=1251&hei=1251", 208 | "https://assets.burberry.com/is/image/Burberryltd/8c8f999797a19d9fcba56bf99bde1fea8d4dde12.jpg?$BBY_V2_ML_1x1$&wid=1251&hei=1251", 209 | ], 210 | }, 211 | ]; 212 | 213 | export const importData = async () => { 214 | try { 215 | await Product.insertMany(productsData); 216 | 217 | console.log("Data Import Success"); 218 | 219 | process.exit(); 220 | } catch (error) { 221 | console.error("Error with data import", error); 222 | process.exit(1); 223 | } 224 | }; 225 | 226 | importData(); 227 | -------------------------------------------------------------------------------- /dist/data/products.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | let products = [ 4 | { 5 | name: "Wool-blend baseball jacket", 6 | description: "Edition by John Boyega. An uncompromising collection developed together with actor and innovator, John Boyega. This lightly padded baseball jacket in woven fabric made from a soft wool blend has a ribbed collar and press-studs down the front. It has a diagonal welt pocket with a press-stud at each side, an inner pocket and ribbing at the cuffs and hem. Quilted satin lining.", 7 | categories: "Jackets", 8 | sizes: ["XS", "S", "M", "L", "XL"], 9 | price: 139.99, 10 | img: [ 11 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F9d%2F7c%2F9d7cb0fdd7fc417cda5223c6e804db6f5840af33.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 12 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F02%2Fc1%2F02c188429ec47d689ba6b8842c4aafec036d4844.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 13 | ], 14 | }, 15 | { 16 | name: "Double-breasted wool-mix coat", 17 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This calf-length, double-breasted coat is made in a soft weave containing some recycled wool. In a straight cut, it has wide notch lapels, corozo nut buttons, flap front pockets, an inner pocket, and a single back vent. Lined.", 18 | categories: "Jackets", 19 | sizes: ["XS", "S", "M", "L", "XL"], 20 | price: 149.99, 21 | img: [ 22 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F1e%2Fdb%2F1edbc3769844f46e0b59079f88640d421ebfeb08.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 23 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fba%2F2d%2Fba2d20f86f00714e94743686fd11bf1bf4066ca4.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jacketscoats_coats%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 24 | ], 25 | }, 26 | { 27 | name: "Wool-blend coat", 28 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This calf-length, double-breasted coat is made in a soft weave containing some recycled wool. In a straight cut, it has wide notch lapels, corozo nut buttons, flap front pockets, an inner pocket, and a single back vent. Lined.", 29 | categories: "Jackets", 30 | sizes: ["XS", "S", "M", "L", "XL"], 31 | price: 69.99, 32 | img: [ 33 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fcd%2Ff6%2Fcdf675f0692a0295d1ff0b744da766e30105df32.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 34 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fd9%2F0e%2Fd90e1f4866251660553bfef9c5680f43440f9158.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 35 | ], 36 | }, 37 | { 38 | name: "Quilted grape leather puffer jacket", 39 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This quilted puffer jacket is in Vegea™ imitation leather, a material made from bio-waste produced during the winemaking process. The jacket has a stand-up collar, a zip and wind flap with press-studs down the front, fleece-lined front pockets with a press-stud and an inner pocket. Elasticated ribbing at the cuffs and a concealed elastic drawstring at the hem for an adjustable fit. The sleeves can be unzipped and detached to allow the jacket to be worn as a gilet. Lined.", 40 | categories: "Jackets", 41 | sizes: ["XS", "S", "M", "L", "XL"], 42 | price: 229.99, 43 | img: [ 44 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F33%2Ff9%2F33f9f296be008242bad9f14fa93299403f977bb8.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jacketscoats_jackets%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 45 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F31%2Fa2%2F31a2dc4991605605410e88df9eaa5a81f04fdecd.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 46 | ], 47 | }, 48 | { 49 | name: "Regular Fit embroidery-detail rugby shirt", 50 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This quilted puffer jacket is in Vegea™ imitation leather, a material made from bio-waste produced during the winemaking process. The jacket has a stand-up collar, a zip and wind flap with press-studs down the front, fleece-lined front pockets with a press-stud and an inner pocket. Elasticated ribbing at the cuffs and a concealed elastic drawstring at the hem for an adjustable fit. The sleeves can be unzipped and detached to allow the jacket to be worn as a gilet. Lined.", 51 | categories: "T-Shirts", 52 | sizes: ["XS", "S", "M", "L", "XL"], 53 | price: 19.99, 54 | img: [ 55 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F85%2F84%2F8584d4a91939bc882f2132b4427e580d32fbff54.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 56 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fac%2Fc6%2Facc6b299d6ab483ea2605585b909ef86ea22cc34.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 57 | ], 58 | }, 59 | { 60 | name: "Flower Cotton T-shirt", 61 | description: "T-shirt in soft cotton jersey.", 62 | categories: "T-Shirts", 63 | sizes: ["XS", "S", "M", "L", "XL"], 64 | price: 19.99, 65 | img: [ 66 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F1f%2F56%2F1f56c8a454701eedf3a3c384c2d07c8e06ba11f7.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVEDETAIL%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 67 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F82%2F32%2F8232534428522832315b5aa64633d7046e28c107.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 68 | ], 69 | }, 70 | { 71 | name: "Relaxed Fit Jersey top", 72 | description: "Top in soft cotton jersey with a round, rib-trimmed neckline. Relaxed fit with dropped shoulders, long sleeves and wide ribbing at the cuffs.", 73 | categories: "T-Shirts", 74 | sizes: ["XS", "S", "M", "L", "XL"], 75 | price: 27.99, 76 | img: [ 77 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F72%2F47%2F7247c7e2ed1049c270904da68d68f23e9fa684cc.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 78 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F83%2Fc3%2F83c3e0096a7369ea11b29e8b2aac0651a9784dc6.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 79 | ], 80 | }, 81 | { 82 | name: "Block-coloured T-shirt", 83 | description: "Top in soft cotton jersey with a round, rib-trimmed neckline. Relaxed fit with dropped shoulders, long sleeves and wide ribbing at the cuffs.", 84 | categories: "T-Shirts", 85 | sizes: ["XS", "S", "M", "L", "XL"], 86 | price: 15.99, 87 | img: [ 88 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fdb%2Fcf%2Fdbcfdfe143baeda32b73998d1076edc0951ac3b9.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_tshirtstanks_printed%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 89 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F0e%2F4d%2F0e4d19eb891e5fd74f874fbfcb16b108c65efa23.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 90 | ], 91 | }, 92 | { 93 | name: "Cotton denim Loose Jeans", 94 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. These 5-pocket jeans in sturdy denim made from recycled cotton have a regular waist, zip fly and button and loose-fitting legs.", 95 | categories: "Pants", 96 | sizes: ["XS", "S", "M", "L", "XL"], 97 | price: 39.99, 98 | img: [ 99 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F36%2F79%2F36796b7a4998e613a7da9f002a2fd699994cb3dc.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_trousers%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 100 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F73%2F09%2F7309a16586ff6c67ae18cb8ff27464a3dea50457.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans_loose%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 101 | ], 102 | }, 103 | { 104 | name: "Regular Jeans", 105 | description: "5-pocket jeans in stretch denim with a regular waist, zip fly and button and straight legs with good room for movement over the thighs and knees.", 106 | categories: "Pants", 107 | sizes: ["XS", "S", "M", "L", "XL"], 108 | price: 24.99, 109 | img: [ 110 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffc%2Fba%2Ffcba25c2a546ed633a5eca1b14f46219bb196513.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 111 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffe%2Fab%2Ffeab9ceb516cc017a59b739eb686eee831d020f1.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans_regular%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 112 | ], 113 | }, 114 | { 115 | name: "Relaxed Jeans", 116 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. These 5-pocket jeans in sturdy denim made from recycled cotton have a regular waist, zip fly and button and loose-fitting legs.", 117 | categories: "Pants", 118 | sizes: ["XS", "S", "M", "L", "XL"], 119 | price: 24.99, 120 | img: [ 121 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F26%2Fe8%2F26e8d1798edc59f3cdfb84ca9068094ba6ca070d.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 122 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F81%2F2d%2F812db6d977430047149d5f4f442f53cfdef38e5b.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 123 | ], 124 | }, 125 | { 126 | name: "Skinny Jeans", 127 | description: "5-pocket jeans in stretch cotton denim with a regular waist, zip fly and button, and skinny legs.", 128 | categories: "Pants", 129 | sizes: ["XS", "S", "M", "L", "XL"], 130 | price: 17.99, 131 | img: [ 132 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F3b%2F96%2F3b9675cd1f97ce69d2787406457e984944b77c7c.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 133 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fb3%2Ff6%2Fb3f6559fabc023d978477b2a437ff78e1e7691d8.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 134 | ], 135 | }, 136 | { 137 | name: "Loose Jeans", 138 | description: "5-pocket jeans in sturdy cotton denim. Loose fit with a regular waist, zip fly and button and wide legs.", 139 | categories: "Pants", 140 | sizes: ["XS", "S", "M", "L", "XL"], 141 | price: 24.99, 142 | img: [ 143 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F7f%2F09%2F7f09ec485a69eb97c01cf85cff1a3ac7687a2a0f.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 144 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fa1%2F13%2Fa1136aed48e7b6a9b306fc4d97c4f79818058ca3.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 145 | ], 146 | }, 147 | { 148 | name: "Leather brogues", 149 | description: "Toga Archives x H&M. A collection of classic, iconic pieces from Tokyo-based TOGA. Wardrobe classics characterised by innovative details and functions, defined by a gender-neutral, individual approach to fashion. A pair of brogues in leather with perforated details and an extended shaft in scuba fabric. Open lacing at the front and a chunky zip with a metal ring pull at the back. Leather linings and insoles and rubber soles.", 150 | categories: "Shoes", 151 | sizes: ["XS", "S", "M", "L", "XL"], 152 | price: 199.99, 153 | img: [ 154 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffe%2Ffd%2Ffefd4e6faac9a91db4065a7a6bbdc21f6c0c9b50.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 155 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F1d%2F90%2F1d906b0434731018f989dac5fbf32ec685d03fca.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_shoes_dressed%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 156 | ], 157 | }, 158 | { 159 | name: "TEDDY MONKSTRAPS IN PATENT LEATHER", 160 | description: "DERBIES WITH AN ALMOND TOE AND A SINGLE MONK STRAP WITH METAL BUCKLE.", 161 | categories: "Shoes", 162 | sizes: ["XS", "S", "M", "L", "XL"], 163 | price: 394.99, 164 | img: [ 165 | "https://saint-laurent.dam.kering.com/m/7a3626ef06c85277/Medium-6688941TV001000_A.jpg?v=6", 166 | "https://saint-laurent.dam.kering.com/m/a433aa64bd1452c/Medium-6688941TV001000_B.jpg?v=5", 167 | ], 168 | }, 169 | { 170 | name: "Bryson Suede Chelsea Boot", 171 | description: "The Bryson combines the refined styling of a Chelsea boot with the durability of a rubber lug sole. This sophisticated shoe features an oiled suede upper accented with a woven logo pull tab at the heel.", 172 | categories: "Shoes", 173 | sizes: ["XS", "S", "M", "L", "XL"], 174 | price: 199.99, 175 | img: [ 176 | "https://www.rlmedia.io/is/image/PoloGSI/s7-1339647_lifestyle?$rl_df_pdp_5_7_lif$", 177 | "https://www.rlmedia.io/is/image/PoloGSI/s7-1339647_alternate1?$rl_df_pdp_5_7$", 178 | ], 179 | }, 180 | { 181 | name: "Logo Print Vintage Check Cotton Sneakers", 182 | description: "Cotton sneakers in Vintage check, printed with our refreshed logo in contrasting tones.", 183 | categories: "Shoes", 184 | sizes: ["XS", "S", "M", "L", "XL"], 185 | price: 249.99, 186 | img: [ 187 | "https://assets.burberry.com/is/image/Burberryltd/15284bd2a24bd2430615f5f73067b3d6504e7ba9.jpg?$BBY_V2_SL_1x1$&wid=1251&hei=1251", 188 | "https://assets.burberry.com/is/image/Burberryltd/8c8f999797a19d9fcba56bf99bde1fea8d4dde12.jpg?$BBY_V2_ML_1x1$&wid=1251&hei=1251", 189 | ], 190 | }, 191 | ]; 192 | exports.default = products; 193 | //# sourceMappingURL=products.js.map -------------------------------------------------------------------------------- /dist/data/ImportData.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.importData = void 0; 16 | const Product_1 = __importDefault(require("../models/Product")); 17 | let productsData = [ 18 | { 19 | name: "Wool-blend baseball jacket", 20 | description: "Edition by John Boyega. An uncompromising collection developed together with actor and innovator, John Boyega. This lightly padded baseball jacket in woven fabric made from a soft wool blend has a ribbed collar and press-studs down the front. It has a diagonal welt pocket with a press-stud at each side, an inner pocket and ribbing at the cuffs and hem. Quilted satin lining.", 21 | categories: "Jackets", 22 | sizes: ["XS", "S", "M", "L", "XL"], 23 | price: 139.99, 24 | img: [ 25 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F9d%2F7c%2F9d7cb0fdd7fc417cda5223c6e804db6f5840af33.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 26 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F02%2Fc1%2F02c188429ec47d689ba6b8842c4aafec036d4844.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 27 | ], 28 | }, 29 | { 30 | name: "Double-breasted wool-mix coat", 31 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This calf-length, double-breasted coat is made in a soft weave containing some recycled wool. In a straight cut, it has wide notch lapels, corozo nut buttons, flap front pockets, an inner pocket, and a single back vent. Lined.", 32 | categories: "Jackets", 33 | sizes: ["XS", "S", "M", "L", "XL"], 34 | price: 149.99, 35 | img: [ 36 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F1e%2Fdb%2F1edbc3769844f46e0b59079f88640d421ebfeb08.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 37 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fba%2F2d%2Fba2d20f86f00714e94743686fd11bf1bf4066ca4.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jacketscoats_coats%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 38 | ], 39 | }, 40 | { 41 | name: "Wool-blend coat", 42 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This calf-length, double-breasted coat is made in a soft weave containing some recycled wool. In a straight cut, it has wide notch lapels, corozo nut buttons, flap front pockets, an inner pocket, and a single back vent. Lined.", 43 | categories: "Jackets", 44 | sizes: ["XS", "S", "M", "L", "XL"], 45 | price: 69.99, 46 | img: [ 47 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fcd%2Ff6%2Fcdf675f0692a0295d1ff0b744da766e30105df32.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 48 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fd9%2F0e%2Fd90e1f4866251660553bfef9c5680f43440f9158.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 49 | ], 50 | }, 51 | { 52 | name: "Quilted grape leather puffer jacket", 53 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This quilted puffer jacket is in Vegea™ imitation leather, a material made from bio-waste produced during the winemaking process. The jacket has a stand-up collar, a zip and wind flap with press-studs down the front, fleece-lined front pockets with a press-stud and an inner pocket. Elasticated ribbing at the cuffs and a concealed elastic drawstring at the hem for an adjustable fit. The sleeves can be unzipped and detached to allow the jacket to be worn as a gilet. Lined.", 54 | categories: "Jackets", 55 | sizes: ["XS", "S", "M", "L", "XL"], 56 | price: 229.99, 57 | img: [ 58 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F33%2Ff9%2F33f9f296be008242bad9f14fa93299403f977bb8.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jacketscoats_jackets%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 59 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F31%2Fa2%2F31a2dc4991605605410e88df9eaa5a81f04fdecd.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 60 | ], 61 | }, 62 | { 63 | name: "Regular Fit embroidery-detail rugby shirt", 64 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. This quilted puffer jacket is in Vegea™ imitation leather, a material made from bio-waste produced during the winemaking process. The jacket has a stand-up collar, a zip and wind flap with press-studs down the front, fleece-lined front pockets with a press-stud and an inner pocket. Elasticated ribbing at the cuffs and a concealed elastic drawstring at the hem for an adjustable fit. The sleeves can be unzipped and detached to allow the jacket to be worn as a gilet. Lined.", 65 | categories: "T-Shirts", 66 | sizes: ["XS", "S", "M", "L", "XL"], 67 | price: 19.99, 68 | img: [ 69 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F85%2F84%2F8584d4a91939bc882f2132b4427e580d32fbff54.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 70 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fac%2Fc6%2Facc6b299d6ab483ea2605585b909ef86ea22cc34.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 71 | ], 72 | }, 73 | { 74 | name: "Flower Cotton T-shirt", 75 | description: "T-shirt in soft cotton jersey.", 76 | categories: "T-Shirts", 77 | sizes: ["XS", "S", "M", "L", "XL"], 78 | price: 19.99, 79 | img: [ 80 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F07%2Fec%2F07ececdebb1804c10749bcc30569f9152d5b394e.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 81 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F82%2F32%2F8232534428522832315b5aa64633d7046e28c107.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 82 | ], 83 | }, 84 | { 85 | name: "Relaxed Fit Jersey top", 86 | description: "Top in soft cotton jersey with a round, rib-trimmed neckline. Relaxed fit with dropped shoulders, long sleeves and wide ribbing at the cuffs.", 87 | categories: "T-Shirts", 88 | sizes: ["XS", "S", "M", "L", "XL"], 89 | price: 27.99, 90 | img: [ 91 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F72%2F47%2F7247c7e2ed1049c270904da68d68f23e9fa684cc.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 92 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F83%2Fc3%2F83c3e0096a7369ea11b29e8b2aac0651a9784dc6.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 93 | ], 94 | }, 95 | { 96 | name: "Block-coloured T-shirt", 97 | description: "Top in soft cotton jersey with a round, rib-trimmed neckline. Relaxed fit with dropped shoulders, long sleeves and wide ribbing at the cuffs.", 98 | categories: "T-Shirts", 99 | sizes: ["XS", "S", "M", "L", "XL"], 100 | price: 15.99, 101 | img: [ 102 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fdb%2Fcf%2Fdbcfdfe143baeda32b73998d1076edc0951ac3b9.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_tshirtstanks_printed%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 103 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F0e%2F4d%2F0e4d19eb891e5fd74f874fbfcb16b108c65efa23.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 104 | ], 105 | }, 106 | { 107 | name: "Cotton denim Loose Jeans", 108 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. These 5-pocket jeans in sturdy denim made from recycled cotton have a regular waist, zip fly and button and loose-fitting legs.", 109 | categories: "Pants", 110 | sizes: ["XS", "S", "M", "L", "XL"], 111 | price: 39.99, 112 | img: [ 113 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F36%2F79%2F36796b7a4998e613a7da9f002a2fd699994cb3dc.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_trousers%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 114 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F73%2F09%2F7309a16586ff6c67ae18cb8ff27464a3dea50457.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans_loose%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 115 | ], 116 | }, 117 | { 118 | name: "Regular Jeans", 119 | description: "5-pocket jeans in stretch denim with a regular waist, zip fly and button and straight legs with good room for movement over the thighs and knees.", 120 | categories: "Pants", 121 | sizes: ["XS", "S", "M", "L", "XL"], 122 | price: 24.99, 123 | img: [ 124 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffc%2Fba%2Ffcba25c2a546ed633a5eca1b14f46219bb196513.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 125 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffe%2Fab%2Ffeab9ceb516cc017a59b739eb686eee831d020f1.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_jeans_regular%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 126 | ], 127 | }, 128 | { 129 | name: "Relaxed Jeans", 130 | description: "Edition by John Boyega. An uncompromising collection made from more sustainable materials developed together with actor and innovator, John Boyega. These 5-pocket jeans in sturdy denim made from recycled cotton have a regular waist, zip fly and button and loose-fitting legs.", 131 | categories: "Pants", 132 | sizes: ["XS", "S", "M", "L", "XL"], 133 | price: 24.99, 134 | img: [ 135 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F26%2Fe8%2F26e8d1798edc59f3cdfb84ca9068094ba6ca070d.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 136 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F81%2F2d%2F812db6d977430047149d5f4f442f53cfdef38e5b.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 137 | ], 138 | }, 139 | { 140 | name: "Skinny Jeans", 141 | description: "5-pocket jeans in stretch cotton denim with a regular waist, zip fly and button, and skinny legs.", 142 | categories: "Pants", 143 | sizes: ["XS", "S", "M", "L", "XL"], 144 | price: 17.99, 145 | img: [ 146 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F3b%2F96%2F3b9675cd1f97ce69d2787406457e984944b77c7c.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 147 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fb3%2Ff6%2Fb3f6559fabc023d978477b2a437ff78e1e7691d8.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 148 | ], 149 | }, 150 | { 151 | name: "Loose Jeans", 152 | description: "5-pocket jeans in sturdy cotton denim. Loose fit with a regular waist, zip fly and button and wide legs.", 153 | categories: "Pants", 154 | sizes: ["XS", "S", "M", "L", "XL"], 155 | price: 24.99, 156 | img: [ 157 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F7f%2F09%2F7f09ec485a69eb97c01cf85cff1a3ac7687a2a0f.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 158 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Fa1%2F13%2Fa1136aed48e7b6a9b306fc4d97c4f79818058ca3.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 159 | ], 160 | }, 161 | { 162 | name: "Leather brogues", 163 | description: "Toga Archives x H&M. A collection of classic, iconic pieces from Tokyo-based TOGA. Wardrobe classics characterised by innovative details and functions, defined by a gender-neutral, individual approach to fashion. A pair of brogues in leather with perforated details and an extended shaft in scuba fabric. Open lacing at the front and a chunky zip with a metal ring pull at the back. Leather linings and insoles and rubber soles.", 164 | categories: "Shoes", 165 | sizes: ["XS", "S", "M", "L", "XL"], 166 | price: 199.99, 167 | img: [ 168 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2Ffe%2Ffd%2Ffefd4e6faac9a91db4065a7a6bbdc21f6c0c9b50.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5B%5D%2Ctype%5BDESCRIPTIVESTILLLIFE%5D%2Cres%5Bm%5D%2Chmver%5B2%5D&call=url[file:/product/main]", 169 | "https://lp2.hm.com/hmgoepprod?set=quality%5B79%5D%2Csource%5B%2F1d%2F90%2F1d906b0434731018f989dac5fbf32ec685d03fca.jpg%5D%2Corigin%5Bdam%5D%2Ccategory%5Bmen_shoes_dressed%5D%2Ctype%5BLOOKBOOK%5D%2Cres%5Bm%5D%2Chmver%5B1%5D&call=url[file:/product/main]", 170 | ], 171 | }, 172 | { 173 | name: "TEDDY MONKSTRAPS IN PATENT LEATHER", 174 | description: "DERBIES WITH AN ALMOND TOE AND A SINGLE MONK STRAP WITH METAL BUCKLE.", 175 | categories: "Shoes", 176 | sizes: ["XS", "S", "M", "L", "XL"], 177 | price: 394.99, 178 | img: [ 179 | "https://saint-laurent.dam.kering.com/m/7a3626ef06c85277/Medium-6688941TV001000_A.jpg?v=6", 180 | "https://saint-laurent.dam.kering.com/m/a433aa64bd1452c/Medium-6688941TV001000_B.jpg?v=5", 181 | ], 182 | }, 183 | { 184 | name: "Bryson Suede Chelsea Boot", 185 | description: "The Bryson combines the refined styling of a Chelsea boot with the durability of a rubber lug sole. This sophisticated shoe features an oiled suede upper accented with a woven logo pull tab at the heel.", 186 | categories: "Shoes", 187 | sizes: ["XS", "S", "M", "L", "XL"], 188 | price: 199.99, 189 | img: [ 190 | "https://www.rlmedia.io/is/image/PoloGSI/s7-1339647_lifestyle?$rl_df_pdp_5_7_lif$", 191 | "https://www.rlmedia.io/is/image/PoloGSI/s7-1339647_alternate1?$rl_df_pdp_5_7$", 192 | ], 193 | }, 194 | { 195 | name: "Logo Print Vintage Check Cotton Sneakers", 196 | description: "Cotton sneakers in Vintage check, printed with our refreshed logo in contrasting tones.", 197 | categories: "Shoes", 198 | sizes: ["XS", "S", "M", "L", "XL"], 199 | price: 249.99, 200 | img: [ 201 | "https://assets.burberry.com/is/image/Burberryltd/15284bd2a24bd2430615f5f73067b3d6504e7ba9.jpg?$BBY_V2_SL_1x1$&wid=1251&hei=1251", 202 | "https://assets.burberry.com/is/image/Burberryltd/8c8f999797a19d9fcba56bf99bde1fea8d4dde12.jpg?$BBY_V2_ML_1x1$&wid=1251&hei=1251", 203 | ], 204 | }, 205 | ]; 206 | const importData = () => __awaiter(void 0, void 0, void 0, function* () { 207 | try { 208 | yield Product_1.default.insertMany(productsData); 209 | console.log("Data Import Success"); 210 | process.exit(); 211 | } 212 | catch (error) { 213 | console.error("Error with data import", error); 214 | process.exit(1); 215 | } 216 | }); 217 | exports.importData = importData; 218 | (0, exports.importData)(); 219 | //# sourceMappingURL=ImportData.js.map --------------------------------------------------------------------------------