├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .github ├── logo.png ├── orders-db-model.png ├── student-classes-db-model.png └── typeorm_relations.json ├── .gitignore ├── .huskyrc.json ├── .lintstagedrc.json ├── CONTRIBUTING.md ├── README.md ├── commitlint.config.js ├── docker-compose.yml ├── jest.config.js ├── ormconfig.json ├── package.json ├── prettier.config.js ├── src ├── __tests__ │ └── App.spec.ts ├── modules │ ├── classes │ │ ├── dtos │ │ │ └── ICreateClassDTO.ts │ │ ├── infra │ │ │ ├── http │ │ │ │ ├── controllers │ │ │ │ │ └── ClassesController.ts │ │ │ │ └── routes │ │ │ │ │ └── classes.routes.ts │ │ │ ├── typeorm │ │ │ │ ├── entities │ │ │ │ │ └── Class.ts │ │ │ │ └── repositories │ │ │ │ │ └── ClassesRepository.ts │ │ │ └── validators │ │ │ │ └── createClass.ts │ │ ├── repositories │ │ │ └── IClassesRepository.ts │ │ └── services │ │ │ └── CreateClassService.ts │ ├── customers │ │ ├── dtos │ │ │ └── ICreateCustomerDTO.ts │ │ ├── infra │ │ │ ├── http │ │ │ │ ├── controller │ │ │ │ │ └── CustomersController.ts │ │ │ │ └── routes │ │ │ │ │ └── customers.routes.ts │ │ │ ├── typeorm │ │ │ │ ├── entities │ │ │ │ │ └── Customer.ts │ │ │ │ └── repositories │ │ │ │ │ └── CustomersRepository.ts │ │ │ └── validators │ │ │ │ └── createCustomer.ts │ │ ├── repositories │ │ │ └── ICustomersRepository.ts │ │ └── services │ │ │ └── CreateCustomerService.ts │ ├── orders │ │ ├── dtos │ │ │ └── ICreateOrderDTO.ts │ │ ├── infra │ │ │ ├── http │ │ │ │ ├── controller │ │ │ │ │ └── OrdersController.ts │ │ │ │ └── routes │ │ │ │ │ └── orders.routes.ts │ │ │ ├── typeorm │ │ │ │ ├── entities │ │ │ │ │ ├── Order.ts │ │ │ │ │ └── OrdersProducts.ts │ │ │ │ └── repositories │ │ │ │ │ └── OrdersRepository.ts │ │ │ └── validators │ │ │ │ ├── createOrder.ts │ │ │ │ └── showOrder.ts │ │ ├── repositories │ │ │ └── IOrdersRepository.ts │ │ └── services │ │ │ ├── CreateOrderService.ts │ │ │ └── FindOrderService.ts │ ├── products │ │ ├── dtos │ │ │ ├── ICreateProductDTO.ts │ │ │ └── IUpdateProductsQuantityDTO.ts │ │ ├── infra │ │ │ ├── http │ │ │ │ ├── controller │ │ │ │ │ └── ProductsController.ts │ │ │ │ └── routes │ │ │ │ │ └── products.routes.ts │ │ │ ├── typeorm │ │ │ │ ├── entities │ │ │ │ │ └── Product.ts │ │ │ │ └── repositories │ │ │ │ │ └── ProductsRepository.ts │ │ │ └── validators │ │ │ │ └── createProduct.ts │ │ ├── repositories │ │ │ └── IProductsRepository.ts │ │ └── services │ │ │ └── CreateProductService.ts │ ├── students │ │ ├── dtos │ │ │ └── ICreateStudentDTO.ts │ │ ├── infra │ │ │ ├── http │ │ │ │ ├── controllers │ │ │ │ │ └── StudentsController.ts │ │ │ │ └── routes │ │ │ │ │ └── students.routes.ts │ │ │ └── typeorm │ │ │ │ ├── entities │ │ │ │ ├── Student.ts │ │ │ │ └── StudentClasses.ts │ │ │ │ └── repositories │ │ │ │ └── StudentsRepository.ts │ │ ├── repositories │ │ │ └── IStudentsRepository.ts │ │ ├── services │ │ │ └── CreateStudentService.ts │ │ └── validators │ │ │ └── createStudent.ts │ └── teachers │ │ ├── dtos │ │ └── ICreateTeacherDTO.ts │ │ ├── infra │ │ ├── http │ │ │ ├── controllers │ │ │ │ └── TeachersController.ts │ │ │ └── routes │ │ │ │ └── teachers.routes.ts │ │ └── typeorm │ │ │ ├── entities │ │ │ └── Teacher.ts │ │ │ └── repositories │ │ │ └── TeachersRepository.ts │ │ ├── repositories │ │ └── ITeachersRepository.ts │ │ ├── services │ │ └── CreateTeacherService.ts │ │ └── validators │ │ └── createTeacher.ts └── shared │ ├── container │ └── index.ts │ ├── errors │ └── AppError.ts │ ├── handlers │ └── errorsHandler.ts │ └── infra │ ├── http │ ├── app.ts │ ├── routes │ │ └── index.ts │ └── server.ts │ └── typeorm │ ├── index.ts │ └── migrations │ ├── 1598266682467-create_customers_table.ts │ ├── 1598269989216-CreateProductTable.ts │ ├── 1598279750914-CreateOrderTable.ts │ ├── 1598353275953-CreateOrderProductsTable.ts │ ├── 1598359381489-UpdateOrdersProductsIdColumn.ts │ ├── 1598364937210-UpdatePricePrecisions.ts │ ├── 1598871336598-Student.ts │ ├── 1598874390567-Teacher.ts │ ├── 1598879822720-Classes.ts │ ├── 1598883848517-AddNowDefaultToTimestampColumns.ts │ └── 1598885739547-StudentClasses.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Database 2 | POSTGRES_USER= 3 | POSTGRES_PASSWORD= 4 | POSTGRES_DATABASE= 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /*.js 2 | node_modules 3 | dist -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "airbnb-base", 9 | "plugin:@typescript-eslint/recommended", 10 | "prettier/@typescript-eslint", 11 | "plugin:prettier/recommended" 12 | ], 13 | "globals": { 14 | "Atomics": "readonly", 15 | "SharedArrayBuffer": "readonly" 16 | }, 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaVersion": 2018, 20 | "sourceType": "module" 21 | }, 22 | "plugins": ["@typescript-eslint", "prettier"], 23 | "rules": { 24 | "@typescript-eslint/no-unused-vars": [ 25 | "error", 26 | { 27 | "argsIgnorePattern": "_" 28 | } 29 | ], 30 | "no-useless-constructor": "off", 31 | "@typescript-eslint/interface-name-prefix": [ 32 | "error", 33 | { 34 | "prefixWithI": "always" 35 | } 36 | ], 37 | "@typescript-eslint/camelcase": "off", 38 | "prettier/prettier": "error", 39 | "class-methods-use-this": "off", 40 | "import/extensions": [ 41 | "error", 42 | "ignorePackages", 43 | { 44 | "ts": "never" 45 | } 46 | ] 47 | }, 48 | "settings": { 49 | "import/resolver": { 50 | "typescript": {} 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LauraBeatris/typeorm-relations/4a180848f4912565040673a0e2571a152eb59f30/.github/logo.png -------------------------------------------------------------------------------- /.github/orders-db-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LauraBeatris/typeorm-relations/4a180848f4912565040673a0e2571a152eb59f30/.github/orders-db-model.png -------------------------------------------------------------------------------- /.github/student-classes-db-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LauraBeatris/typeorm-relations/4a180848f4912565040673a0e2571a152eb59f30/.github/student-classes-db-model.png -------------------------------------------------------------------------------- /.github/typeorm_relations.json: -------------------------------------------------------------------------------- 1 | {"_type":"export","__export_format":4,"__export_date":"2020-09-01T09:59:12.641Z","__export_source":"insomnia.desktop.app:v2020.3.3","resources":[{"_id":"req_c40f21fd833e41af9e6e7b55abb798e4","authentication":{},"body":{},"created":1590028776677,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1590028776677,"method":"GET","modified":1590354161030,"name":"List","parameters":[],"parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/pokemons","_type":"request"},{"_id":"wrk_59ee7513e0524a20b7d12c2e771000a0","created":1590028718790,"description":"","modified":1590028718790,"name":"Deno Pokemons","parentId":null,"scope":null,"_type":"workspace"},{"_id":"req_4ab060866eb148758a3079ff5c4aa1e8","authentication":{},"body":{},"created":1590030184342,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1589888223169,"method":"GET","modified":1590405256595,"name":"Show","parameters":[],"parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/pokemons/ss","_type":"request"},{"_id":"req_f4eae23412ee4f6aa653b97406c31bf7","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Piplup\",\n\t\"age\": 5, \n\t\"abilities\": [\"Water Canon\"]\n}"},"created":1590031301331,"description":"","headers":[{"id":"pair_23259700e097462f819daaa5421812cb","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1589817946415,"method":"POST","modified":1590061336895,"name":"Create","parameters":[],"parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/pokemons","_type":"request"},{"_id":"req_fffa830eb27d4a558289bc2e9555523e","authentication":{},"body":{},"created":1590060742690,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1589853084792,"method":"DELETE","modified":1590060750555,"name":"Delete","parameters":[],"parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/pokemons/pikachu","_type":"request"},{"_id":"req_3444eeb90193473189c5fd31f0832ff5","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"votes\": 7\n}"},"created":1590060846976,"description":"","headers":[{"id":"pair_23259700e097462f819daaa5421812cb","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1589782808038,"method":"PATCH","modified":1590089168275,"name":"Update","parameters":[],"parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/projects/skylab","_type":"request"},{"_id":"req_e933513929ac42e8a56b6e6c9f8f6ecc","authentication":{},"body":{},"created":1590062056784,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1590062056784,"method":"GET","modified":1590062503901,"name":"List games","parameters":[],"parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/pokemons/generations","_type":"request"},{"_id":"req_a0d8dc9ecc004126aca03c6f7fe62a4d","authentication":{"disabled":false,"token":"{{ provider_token }}","type":"bearer"},"body":{},"created":1587820160688,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1587820160688,"method":"GET","modified":1598117386938,"name":"List","parameters":[],"parentId":"fld_022c75daecbe497e8bffe850a6959d1a","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/appointments","_type":"request"},{"_id":"fld_022c75daecbe497e8bffe850a6959d1a","created":1587819040123,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1587819040123,"modified":1587819040123,"name":"Appointments","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"request_group"},{"_id":"wrk_113da797c6ae4cbd98178de7cb975733","created":1587818789248,"description":"","modified":1587818789248,"name":"Hotseat","parentId":null,"scope":null,"_type":"workspace"},{"_id":"req_c6a637c219b9439caa156f86091d6765","authentication":{"disabled":false,"token":"{{ customer_token }}","type":"bearer"},"body":{"mimeType":"application/json","text":"{\n\t\"provider_id\": \"{{ provider_id }}\",\n\t\"date\": \"2020-08-22 15:00\",\n\t\"type\": \"HAIR_CARE\"\n}"},"created":1587820478406,"description":"","headers":[{"id":"pair_3a329dbf05a34d4089be18e851030e10","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1587820160638,"method":"POST","modified":1598118356550,"name":"Create","parameters":[],"parentId":"fld_022c75daecbe497e8bffe850a6959d1a","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/appointments","_type":"request"},{"_id":"req_1299437331d0431bbdd0e2edc19030e2","authentication":{"token":"{{ provider_token }}","type":"bearer"},"body":{},"created":1596629769063,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1587820160738,"method":"GET","modified":1598118083962,"name":"List Provider Appointments","parameters":[{"description":"","id":"pair_330730d4440e4e658e4f43989cee837e","name":"day","value":"22"},{"description":"","id":"pair_73bdc4aa8d0a45bf96a9881e5a282501","name":"month","value":"8"},{"description":"","id":"pair_fb0d62fe1fb84391841ded24ac1fab9c","name":"year","value":"2020"}],"parentId":"fld_022c75daecbe497e8bffe850a6959d1a","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/appointments/me","_type":"request"},{"_id":"req_4a12cb990e494bc182432a49411d6d1d","authentication":{"token":"{{ token }}","type":"bearer"},"body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Prestador\", \n\t\"email\": \"prestador4@gmail.com\", \n\t\"password\": \"12345\",\n\t\"is_provider\": true\n}"},"created":1589648617598,"description":"","headers":[{"id":"pair_ed84ece388ea4d62b13bd105975ef2dd","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1589648617598,"method":"POST","modified":1597928019246,"name":"Create","parameters":[],"parentId":"fld_bd5425e38b3648a4ab9ae07b12f1de3f","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/users","_type":"request"},{"_id":"fld_bd5425e38b3648a4ab9ae07b12f1de3f","created":1589648614573,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1589648614573,"modified":1589648614573,"name":"Users","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"request_group"},{"_id":"req_bc323c4164a7414da3678a61ff5aa2b8","authentication":{"disabled":false,"token":"{{ customer_token }}","type":"bearer"},"body":{"mimeType":"multipart/form-data","params":[{"description":"","fileName":"/Users/lauraserafim/Downloads/share.png","id":"pair_a90ed8670890425f9ffa3f529141b7fe","name":"avatar","type":"file","value":""}]},"created":1589683104277,"description":"","headers":[{"description":"","id":"pair_5f6661ccfbea473a9f6932cf776922fb","name":"authentication","value":"{{ customer_token }}"},{"description":"","id":"pair_fc16dda8cc3a476e90e37e51fb5bc110","name":"Content-Type","value":"multipart/form-data"}],"isPrivate":false,"metaSortKey":-1589683104277,"method":"PATCH","modified":1598219948246,"name":"Update Avatar","parameters":[],"parentId":"fld_bd5425e38b3648a4ab9ae07b12f1de3f","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/users/avatar","_type":"request"},{"_id":"req_8540b9e829e344f6a3ddf29b89015797","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"email\": \"provedor@gmail.com\", \n\t\"password\": \"12345\"\n}"},"created":1589677530264,"description":"","headers":[{"id":"pair_0be95894caab4125a1ceddc0919ab650","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1589677530264,"method":"POST","modified":1596998822516,"name":"Create","parameters":[],"parentId":"fld_346bf953ec0b4c04a08739108bba32ef","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/sessions","_type":"request"},{"_id":"fld_346bf953ec0b4c04a08739108bba32ef","created":1589677525446,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1589677525446,"modified":1589677525446,"name":"Sessions","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"request_group"},{"_id":"req_afc90efda4f84366b47c34beee2725ad","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"token\": \"8b3f6c48-b49f-49a9-ab46-08b77d0935d0\",\n\t\"password\": \"123123\"\n}"},"created":1593912907107,"description":"","headers":[{"id":"pair_e35f381696da41c9af9d75da547124cc","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1593912907107,"method":"PATCH","modified":1596998348536,"name":"Reset","parameters":[],"parentId":"fld_b349c9476031471290f215c039787af9","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/password/reset","_type":"request"},{"_id":"fld_b349c9476031471290f215c039787af9","created":1593912899685,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1593912899685,"modified":1593912899685,"name":"Password ","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"request_group"},{"_id":"req_0bc924075209458fb3908da6ab63124d","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"email\": \"laurabeatriserafim@gmail.com\"\n}"},"created":1593912925140,"description":"","headers":[{"id":"pair_d06fd324ba7a43de94c1c4c1e3f769ea","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1593912925140,"method":"POST","modified":1597661584645,"name":"Send Recover Mail","parameters":[],"parentId":"fld_b349c9476031471290f215c039787af9","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/password/recover-request","_type":"request"},{"_id":"req_2bdf4618323a403d9b4d0036bb278213","authentication":{"token":"{{ customer_token }}","type":"bearer"},"body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Batatinha\", \t\n\t\"email\": \"batatinhaassada3@gmail.com\", \n\t\"password\": \"123456\",\n\t\"old_password\": \"123456\",\n\t\"password_confirmation\": \"123456\"\n}"},"created":1594478198117,"description":"","headers":[{"id":"pair_eaa1f0be1d5c4a45872192840f774f96","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1594478198117,"method":"PUT","modified":1596997670998,"name":"Update","parameters":[],"parentId":"fld_ce054115ae1148b683e28d367271dd75","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/profile ","_type":"request"},{"_id":"fld_ce054115ae1148b683e28d367271dd75","created":1594478193192,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1594478193192,"modified":1594478193192,"name":"Profile","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"request_group"},{"_id":"req_3a9141b2dabb45d7ab885fb5cfae5cd3","authentication":{"token":"{{ customer_token }}","type":"bearer"},"body":{},"created":1594480430816,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1594195561628.5,"method":"GET","modified":1596991795218,"name":"Show ","parameters":[],"parentId":"fld_ce054115ae1148b683e28d367271dd75","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/profile ","_type":"request"},{"_id":"req_2d825046ee4344e486f2c26ba21c9080","authentication":{"token":"{{ customer_token }}","type":"bearer"},"body":{},"created":1594501363288,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1594501363288,"method":"GET","modified":1597923051468,"name":"List Providers","parameters":[],"parentId":"fld_9653fbef6dc34ef894ee99aa8e69d0ef","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/providers","_type":"request"},{"_id":"fld_9653fbef6dc34ef894ee99aa8e69d0ef","created":1594501343256,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1594501343256,"modified":1594501343256,"name":"Providers","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"request_group"},{"_id":"req_bae73635c2c4435fae223f9582370008","authentication":{"token":"{{ customer_token }}","type":"bearer"},"body":{},"created":1594575717790,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1594489780702.5,"method":"GET","modified":1596996712045,"name":"Month Availability","parameters":[{"description":"","id":"pair_2df0bce30a46481d9bd6ece9ffa5f516","name":"month","value":"7"},{"description":"","id":"pair_598ffd6d17e245f6a01b4965439e0094","name":"year","value":"2020"}],"parentId":"fld_9653fbef6dc34ef894ee99aa8e69d0ef","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/providers/{{ provider_id }}/month-availability","_type":"request"},{"_id":"req_76022bac001b4266b7fb830303104300","authentication":{"token":"{{ customer_token }}","type":"bearer"},"body":{},"created":1594575719993,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1594483989409.75,"method":"GET","modified":1598118324237,"name":"Day Availability","parameters":[{"description":"","id":"pair_7699c51149a4410e872b0d074329720b","name":"month","value":"8"},{"description":"","id":"pair_3866b956f53042e6a19824191022a154","name":"year","value":"2020"},{"description":"","id":"pair_bcbeb46c08de4ab0847552f0a9d2fb7a","name":"day","value":"22"}],"parentId":"fld_9653fbef6dc34ef894ee99aa8e69d0ef","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/providers/{{ provider_id }}/day-availability","_type":"request"},{"_id":"req_46bd61688e654c4692e9e522a35d41de","authentication":{},"body":{},"created":1589282680911,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1589747669661,"method":"GET","modified":1590853347725,"name":"List","parameters":[{"description":"","id":"pair_472d3c1b357b4b3f9c65396e207341bd","name":"name","value":"test"}],"parentId":"fld_5e4cc5fff675449f940846f6d46d7a79","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}","_type":"request"},{"_id":"fld_5e4cc5fff675449f940846f6d46d7a79","created":1589744770099,"description":"","environment":{"resource":"transactions"},"environmentPropertyOrder":{"&":["resource"]},"metaSortKey":-1589744770099,"modified":1590850460129,"name":"Transactions","parentId":"wrk_2bf4e9f997334a6b90b9943333a3c6d0","_type":"request_group"},{"_id":"wrk_2bf4e9f997334a6b90b9943333a3c6d0","created":1589282633836,"description":"","modified":1589734201881,"name":"GoFinances","parentId":null,"scope":null,"_type":"workspace"},{"_id":"req_1bdd39fd61184621bce47185cc307edb","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"title\": \"Spotify\",\n\t\"type\": \"{% prompt 'Type', 'What is the transaction type?', 'income', 'type', false, true %}\",\n\t\"value\": {% prompt 'value', 'What is the transaction value?', '0', 'value', false, true %},\n\t\"category\": \"{% prompt 'What is the transaction category?', 'Category', '\"general\"', 'category', false, true %}\"\n}"},"created":1589282730719,"description":"","headers":[{"id":"pair_a5d5b26794224aeb997e8382b3292741","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1589282680961,"method":"POST","modified":1590855372789,"name":"Create","parameters":[],"parentId":"fld_5e4cc5fff675449f940846f6d46d7a79","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}","_type":"request"},{"_id":"req_1f3cf9cb979c43ada4cfa81f1568c133","authentication":{},"body":{},"created":1589744789396,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1589282680936,"method":"DELETE","modified":1590852911044,"name":"Delete","parameters":[],"parentId":"fld_5e4cc5fff675449f940846f6d46d7a79","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}/{% response 'body', 'req_1bdd39fd61184621bce47185cc307edb', 'b64::JC5pZA==::46b', 'always' %}","_type":"request"},{"_id":"req_e027e259a8284d59b4183dcd922d8c0a","authentication":{},"body":{"mimeType":"multipart/form-data","params":[{"description":"","fileName":"/Users/lauraserafim/Desktop/file.csv","id":"pair_6256c002ee0a4ec783ac76d8874217e5","name":"file","type":"file","value":""},{"description":"","id":"pair_cb50f32f0b2843bf83de1e548d2716ef","name":"","value":""},{"description":"","id":"pair_079068a4ef884cb395ff0dbdf4143497","name":"","value":""}]},"created":1589747669611,"description":"","headers":[{"id":"pair_88fe46261893473d9d84d0c3bc37e1f0","name":"Content-Type","value":"multipart/form-data"}],"isPrivate":false,"metaSortKey":-1589747669611,"method":"POST","modified":1590850439877,"name":"Import CSV","parameters":[],"parentId":"fld_5e4cc5fff675449f940846f6d46d7a79","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}/import","_type":"request"},{"_id":"req_2b017a251c91456fa62be682d8f86d05","authentication":{"token":"","type":"bearer"},"body":{"mimeType":"application/json","text":"{\n\t\"title\": \"Spotify\",\n\t\"type\": {% prompt 'Type', 'What is the transaction type?', 'income', 'type', false, true %},\n\t\"value\": {% prompt 'value', 'What is the transaction value?', '0', 'value', false, true %},\n\t\"category\": {% prompt 'What is the transaction category?', 'Category', 'general', 'category', false, true %}\n}"},"created":1590268752652,"description":"","headers":[{"id":"pair_674cc5494356430cb9c29b847a76960b","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1589282680948.5,"method":"PUT","modified":1590854799097,"name":"Update","parameters":[],"parentId":"fld_5e4cc5fff675449f940846f6d46d7a79","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}/{% response 'body', 'req_1bdd39fd61184621bce47185cc307edb', 'b64::JC5pZA==::46b', 'no-history' %}","_type":"request"},{"_id":"req_d707e2de016047b2b0ddd18821211596","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Laura Beatris Teste\", \n\t\"email\": \"laura@gmail.com\"\n}"},"created":1598267903572,"description":"","headers":[{"id":"pair_9b155ed840e740419078f51030055de1","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1598267903572,"method":"POST","modified":1598882434651,"name":"Create","parameters":[],"parentId":"fld_0312f014b46b4251913f2943487eef94","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/customers","_type":"request"},{"_id":"fld_0312f014b46b4251913f2943487eef94","created":1598267856876,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1598267856876,"modified":1598267856876,"name":"Customers","parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"request_group"},{"_id":"wrk_32b1ce3069e24641a06fa58895c46cc9","created":1598267850619,"description":"","modified":1598267850619,"name":"Orders ","parentId":null,"scope":null,"_type":"workspace"},{"_id":"req_6995ad2e665b48318ce515e402cf507f","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"customer_id\": \"c1d07a99-77e9-4aa5-aa1a-94127602150e\", \n\t\"products\": [\n\t\t{\n\t\t\t\"id\": \"d996708d-1ac5-4f1f-a335-fa0ecf2db9f2\",\n\t\t\t\"quantity\": 5\n\t\t}\n\t]\n}"},"created":1598279402844,"description":"","headers":[{"id":"pair_9b155ed840e740419078f51030055de1","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1598276841979,"method":"POST","modified":1598368467367,"name":"Create","parameters":[],"parentId":"fld_17600845b53a4e818ad11c98fc9415f3","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/orders","_type":"request"},{"_id":"fld_17600845b53a4e818ad11c98fc9415f3","created":1598279394883,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1598279394883,"modified":1598279394883,"name":"Orders","parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"request_group"},{"_id":"req_d49a5dda4ddc4c23a8ba6819dc2d5e5a","authentication":{},"body":{},"created":1598356567800,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1598276842029,"method":"GET","modified":1598368524885,"name":"Show","parameters":[],"parentId":"fld_17600845b53a4e818ad11c98fc9415f3","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/orders/5543edc2-691d-4332-bc8d-b976b80ad844","_type":"request"},{"_id":"req_795a5532d8504bc3bf66b26ffd400ffd","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Junior\",\n\t\"age\": 23, \n\t\"email\": \"juniorstudenteste2@gmail.com\"\n}"},"created":1598882333116,"description":"","headers":[{"id":"pair_db3d44aa0a7f4696b013dc5edfe6d222","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1598882333116,"method":"POST","modified":1598882743648,"name":"Create","parameters":[],"parentId":"fld_1dc771dd640e431193aff441e76f4773","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/students","_type":"request"},{"_id":"fld_1dc771dd640e431193aff441e76f4773","created":1598882025172,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1598882025172,"modified":1598882025172,"name":"Students","parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"request_group"},{"_id":"req_9777cf02c06d40e19cae75fd88b43fba","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"subject\": \"Chemistry 23\", \n\t\"teacher_id\": \"8b0d5858-31b2-4f00-8a48-3efcc7a06bdb\",\n\t\"students\": [\n\t\t{\n\t\t\t\"id\": \"458df191-c698-4509-9c34-6d42394bb573\"\n\t\t}\n\t]\n}"},"created":1598882986479,"description":"","headers":[{"id":"pair_9aec37949a67451eaabebf6bf3778f04","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1598882986480,"method":"POST","modified":1598953393812,"name":"Create","parameters":[],"parentId":"fld_847ec48eed47477da6f5158f0b7ea345","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/classes","_type":"request"},{"_id":"fld_847ec48eed47477da6f5158f0b7ea345","created":1598882033318,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1598882033318,"modified":1598882033318,"name":"Classes","parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"request_group"},{"_id":"req_b59c3d0f6fbc4066a28d5f8b08693adb","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Laura\",\n\t\"age\": 23,\n\t\"email\": \"laurateacher1@gmail.com\"\n}"},"created":1598882052474,"description":"","headers":[{"id":"pair_0c060fa668d048f99ee2e6477f113d60","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1598882052474,"method":"POST","modified":1598882835562,"name":"Create","parameters":[],"parentId":"fld_807d235cda3b42918285c8eb19d61a64","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/teachers","_type":"request"},{"_id":"fld_807d235cda3b42918285c8eb19d61a64","created":1598882045345,"description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1598882045345,"modified":1598882045345,"name":"Teachers","parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"request_group"},{"_id":"env_a721eb55ccae012fcdcadc657e50d0228b5006be","color":null,"created":1590028718917,"data":{"base_url":"http://localhost:8000"},"dataPropertyOrder":{"&":["base_url"]},"isPrivate":false,"metaSortKey":1590028718917,"modified":1590028766612,"name":"Base Environment","parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","_type":"environment"},{"_id":"jar_a721eb55ccae012fcdcadc657e50d0228b5006be","cookies":[],"created":1590028718920,"modified":1590028718920,"name":"Default Jar","parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","_type":"cookie_jar"},{"_id":"spc_fa1143109074414b95de99a290b7ee66","contentType":"yaml","contents":"","created":1592789716864,"fileName":"Deno Pokemons","modified":1592789716864,"parentId":"wrk_59ee7513e0524a20b7d12c2e771000a0","_type":"api_spec"},{"_id":"env_7fa9a86d555a45c1ac07753ab0038765","color":"#04cb08","created":1590028756386,"data":{"base_url":"http://localhost:3333"},"dataPropertyOrder":{"&":["base_url"]},"isPrivate":false,"metaSortKey":1590028756386,"modified":1590354143532,"name":"dev","parentId":"env_a721eb55ccae012fcdcadc657e50d0228b5006be","_type":"environment"},{"_id":"env_ea5e663447ea0717cf24bdc6c49ca818e0679418","color":null,"created":1587818789278,"data":{},"dataPropertyOrder":null,"isPrivate":false,"metaSortKey":1587818789279,"modified":1587818789278,"name":"Base Environment","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"environment"},{"_id":"jar_ea5e663447ea0717cf24bdc6c49ca818e0679418","cookies":[],"created":1587818789284,"modified":1587818789284,"name":"Default Jar","parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"cookie_jar"},{"_id":"spc_63a1b3140c7947a6a002748a1b4ffe49","contentType":"yaml","contents":"","created":1592789716848,"fileName":"Hotseat","modified":1592789716848,"parentId":"wrk_113da797c6ae4cbd98178de7cb975733","_type":"api_spec"},{"_id":"env_3fdc0a190d644208aa1e3c2fbfeee160","color":"#5543cb","created":1587819246046,"data":{"base_url":"http://localhost:8080","customer_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwM2JiYTY0Zi01MDk5LTQyOWQtYjA1ZS1hMjViMDg3OTJlNzQiLCJleHBpcmVzSW4iOiI3ZCIsImlhdCI6MTU5MzI3OTI1NH0.0Opx8jRc5PHhby3aAlJQl8rvUL6g3QGcmkoxY2EoN4Y","provider_id":"77f00f9a-3f75-41f2-a59e-1d0812f8e25b","provider_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3N2YwMGY5YS0zZjc1LTQxZjItYTU5ZS0xZDA4MTJmOGUyNWIiLCJpYXQiOjE1OTgxMTc2MjQsImV4cCI6MTU5ODIyNTYyNH0.nzKiUWFS6pH7FFh7ibr16gccFRhVUjgMQc_oT_BZt4g","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4MmIxOTQzMy1lNTUxLTQxOTgtOWI5Yi03ZjNlZjA4Zjg4NGIiLCJleHBpcmVzSW4iOiI3ZCIsImlhdCI6MTU5MjgyMTM1Mn0.IYHAu61Ku4Qy5E6u4Y-IesKpIgbvGsapRpdPCpkFpbM"},"dataPropertyOrder":{"&":["base_url","token","customer_token","provider_token","provider_id"]},"isPrivate":false,"metaSortKey":1587819246046,"modified":1598117834718,"name":"dev","parentId":"env_ea5e663447ea0717cf24bdc6c49ca818e0679418","_type":"environment"},{"_id":"env_22ac5389de44a248fc8a4b8e0e8904e16a284b45","color":null,"created":1589282633898,"data":{"base_url":"http://localhost:3333"},"dataPropertyOrder":{"&":["base_url"]},"isPrivate":false,"metaSortKey":1589282633898,"modified":1589282669220,"name":"Base Environment","parentId":"wrk_2bf4e9f997334a6b90b9943333a3c6d0","_type":"environment"},{"_id":"jar_22ac5389de44a248fc8a4b8e0e8904e16a284b45","cookies":[],"created":1589282633901,"modified":1589282633901,"name":"Default Jar","parentId":"wrk_2bf4e9f997334a6b90b9943333a3c6d0","_type":"cookie_jar"},{"_id":"spc_d973430a191a483380f40d1dcd6e05cb","contentType":"yaml","contents":"","created":1592789716851,"fileName":"GoFinances","modified":1592789716851,"parentId":"wrk_2bf4e9f997334a6b90b9943333a3c6d0","_type":"api_spec"},{"_id":"env_ddca97f6a9764b3db5241cc42f95ecab","color":"#c561cb","created":1590850305119,"data":{"base_url":"http://localhost:3333"},"dataPropertyOrder":{"&":["base_url"]},"isPrivate":false,"metaSortKey":1590850305119,"modified":1590850318408,"name":"dev","parentId":"env_22ac5389de44a248fc8a4b8e0e8904e16a284b45","_type":"environment"},{"_id":"env_996bcb51d207aea01293d4a0a5af1a7038a88cb3","color":null,"created":1598267850887,"data":{},"dataPropertyOrder":null,"isPrivate":false,"metaSortKey":1598267850888,"modified":1598267850887,"name":"Base Environment","parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"environment"},{"_id":"jar_996bcb51d207aea01293d4a0a5af1a7038a88cb3","cookies":[],"created":1598267850891,"modified":1598267850891,"name":"Default Jar","parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"cookie_jar"},{"_id":"spc_280120db049545c6ab4f5ab08f534268","contentType":"yaml","contents":"","created":1598267850640,"fileName":"Orders ","modified":1598267850640,"parentId":"wrk_32b1ce3069e24641a06fa58895c46cc9","_type":"api_spec"},{"_id":"env_64f1e2ef012b4416af2b66ca4c64ad23","color":"#cb2500","created":1598267862050,"data":{"base_url":"http://localhost:3333","customer_id":"6e9eca43-8ea0-4a64-9d00-71c62632b8d1","teacher_id":""},"dataPropertyOrder":{"&":["base_url","customer_id","teacher_id"]},"isPrivate":false,"metaSortKey":1598267862050,"modified":1598882065630,"name":"dev","parentId":"env_996bcb51d207aea01293d4a0a5af1a7038a88cb3","_type":"environment"}]} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | build/ 5 | tmp/ 6 | temp/ 7 | -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 4 | "pre-commit": "lint-staged", 5 | "pre-push": "yarn lint" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.js": [ 3 | "yarn lint", 4 | "git add" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thanks you so much for your interest in contributing to this project! 4 | 5 | ## About our deal 6 | 7 | Hi! I'm Laura and i'm the creator and maintainer of this project. 8 | 9 | If you encounter bugs, please **do** open an issue describing the bug and including steps to easily reproduce it (bonus points for a CodeSandbox that demonstrates the problem). 10 | 11 | If you have an idea for an enhancement, go ahead and share it via an issue, but please don't expect a timely response. 12 | 13 | This project is MIT-licensed, and this means that you can implement and use whatever enhancements you'd like. 14 | 15 | ## Commits and Code Standardization 16 | 17 | This project follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification. [Husky](https://github.com/typicode/husky) execute scrips according to git hooks in order to test if a developer is following the [Eslint Lint Rules](https://github.com/LauraBeatris/gofinances-api/blob/master/.eslintrc.js) and also the commits convention. 18 | 19 | ## Bug reports 20 | 21 | If you encounter a problem with this project, please open an issue. Be sure to include: 22 | 23 | - Package version 24 | - Node and Express versions 25 | - Brief but thorough description of the issue 26 | - Link to a CodeSandbox (or similar) demonstrating the problem (optional, but highly recommended, especially for complex problems) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # TypeORM Relationships 6 | 7 | > Learn how to perform relationships in TypeORM 8 | 9 | > [![Run in Insomnia}](https://insomnia.rest/images/run.svg)](https://insomnia.rest/run/?label=Hotseat%20API&uri=https%3A%2F%2Fraw.githubusercontent.com%2FLauraBeatris%2Ftypeorm-relations%2Fmaster%2F.github%2Ftypeorm_relations.json) 10 | 11 | [![Author](https://img.shields.io/badge/author-LauraBeatris-ED341F?style=flat-square)](https://github.com/LauraBeatris) 12 | [![Languages](https://img.shields.io/github/languages/count/LauraBeatris/typeorm-relations?color=%23ED341F&style=flat-square)](#) 13 | [![Stars](https://img.shields.io/github/stars/LauraBeatris/typeorm-relations?color=ED341F&style=flat-square)](https://github.com/LauraBeatris/typeorm-relations/stargazers) 14 | [![Forks](https://img.shields.io/github/forks/LauraBeatris/typeorm-relations?color=%23ED341F&style=flat-square)](https://github.com/LauraBeatris/typeorm-relations/network/members) 15 | [![Contributors](https://img.shields.io/github/contributors/LauraBeatris/typeorm-relations?color=ED341F&style=flat-square)](https://github.com/LauraBeatris/typeorm-relations/graphs/contributors) 16 | 17 | # :pushpin: Table of Contents 18 | 19 | * [Features](#rocket-features) 20 | * [Database Model](#clipboard-features) 21 | * [Learning Sources](#orange_book-learning-sources) 22 | * [Installation](#construction_worker-installation) 23 | * [Getting Started](#runner-getting-started) 24 | * [FAQ](#postbox-faq) 25 | * [Found a bug? Missing a specific feature?](#bug-issues) 26 | * [Contributing](#tada-contributing) 27 | * [License](#closed_book-license) 28 | 29 | # :rocket: Features 30 | 31 | * 🛍  Store orders, products and customers 32 | * 📔  Store students, classes and teachers 33 | 34 | # :clipboard: Database Model 35 | 36 | In order to understand the relationships, I've created the following database models: 37 | 38 |

39 | 40 |

41 | 42 |

43 | 44 |

45 | 46 | # :orange_book: Learning Sources 47 | 48 | - [Relations in TypeORM](https://orkhan.gitbook.io/typeorm/docs/relations) 49 | - [What are Many To Many Relations](https://typeorm.io/#/many-to-many-relations/what-are-many-to-many-relations) 50 | - [Many to Many Entities in TypeORM](https://www.youtube.com/watch?v=RH_es0awU_A) 51 | - [Saving Many to Many Relations with only one save call](https://typeorm.io/#/relations/cascades) 52 | 53 | # :construction_worker: Installation 54 | 55 | **You need to install [Node.js](https://nodejs.org/en/download/) and [Yarn](https://yarnpkg.com/) first, then in order to clone the project via HTTPS, run this command:** 56 | 57 | ``` 58 | git clone https://github.com/LauraBeatris/typeorm-relations.git 59 | ``` 60 | 61 | SSH URLs provide access to a Git repository via SSH, a secure protocol. If you use a SSH key registered in your Github account, clone the project using this command: 62 | 63 | ``` 64 | git clone git@github.com:LauraBeatris/typeorm-relations.git 65 | ``` 66 | 67 | **Install dependencies** 68 | 69 | ``` 70 | yarn install 71 | ``` 72 | 73 | Or 74 | 75 | ``` 76 | npm install 77 | ``` 78 | 79 | Create your enviroment variables based on the examples of ```.env.example``` 80 | 81 | ``` 82 | cp .env.example .env 83 | ``` 84 | 85 | After copying the examples, make sure to fill the variables with new values. 86 | 87 | **Setup a database** 88 | 89 | Install [Postgres](https://www.postgresql.org/) to create a database or if you have [Docker](https://www.docker.com/) in your machine, fill the environment values related to database configurations and then run the following commands in order to create a Postgres container. 90 | 91 | ```docker-compose up``` 92 | 93 | # :runner: Getting Started 94 | 95 | Run the transactions in order to configure the database schema 96 | 97 | ```yarn typeorm migration:run``` 98 | 99 | Run the following command in order to start the application in a development environment: 100 | 101 | ```yarn dev:server``` 102 | 103 | # :postbox: Faq 104 | 105 | **Question:** What are the tecnologies used in this project? 106 | 107 | **Answer:** The tecnologies used in this project are [NodeJS](https://nodejs.org/en/) + [Express Framework](http://expressjs.com/en/) to handle the server and [TypeORM](https://typeorm.io/#/) 108 | 109 | # :bug: Issues 110 | 111 | Feel free to **file a new issue** with a respective title and description on the the [TypeORM Relations](https://github.com/LauraBeatris/typeorm-relations/issues) repository. If you already found a solution to your problem, **I would love to review your pull request**! Have a look at our [contribution guidelines](https://github.com/LauraBeatris/typeorm-relations/blob/master/CONTRIBUTING.md) to find out about the coding standards. 112 | 113 | # :tada: Contributing 114 | 115 | Check out the [contributing](https://github.com/LauraBeatris/typeorm-relations/blob/master/CONTRIBUTING.md) page to see the best places to file issues, start discussions and begin contributing. 116 | 117 | # :closed_book: License 118 | 119 | Released in 2020. 120 | This project is under the [MIT license](https://github.com/LauraBeatris/typeorm-relations/master/LICENSE). 121 | 122 | Made with love by [Laura Beatris](https://github.com/LauraBeatris) 💜🚀 123 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | services: 4 | postgres: 5 | container_name: postgres 6 | image: postgres 7 | environment: 8 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} 9 | POSTGRES_USER: ${POSTGRES_USER} 10 | POSTGRES_DB: ${POSTGRES_DATABASE} 11 | ports: 12 | - "5432:5432" 13 | restart: unless-stopped 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // All imported modules in your tests should be mocked automatically 3 | // automock: false, 4 | 5 | // Stop running tests after `n` failures 6 | // bail: 0, 7 | 8 | // Respect "browser" field in package.json when resolving modules 9 | // browser: false, 10 | 11 | // The directory where Jest should store its cached dependency information 12 | // cacheDirectory: "/private/var/folders/qv/s8ph22xx2fnfxdh3pq14t4d40000gn/T/jest_dx", 13 | 14 | // Automatically clear mock calls and instances between every test 15 | clearMocks: true, 16 | 17 | // Indicates whether the coverage information should be collected while executing the test 18 | // collectCoverage: false, 19 | 20 | // An array of glob patterns indicating a set of files for which coverage information should be collected 21 | // collectCoverageFrom: undefined, 22 | 23 | // The directory where Jest should output its coverage files 24 | // coverageDirectory: undefined, 25 | 26 | // An array of regexp pattern strings used to skip coverage collection 27 | // coveragePathIgnorePatterns: [ 28 | // "/node_modules/" 29 | // ], 30 | 31 | // A list of reporter names that Jest uses when writing coverage reports 32 | // coverageReporters: [ 33 | // "json", 34 | // "text", 35 | // "lcov", 36 | // "clover" 37 | // ], 38 | 39 | // An object that configures minimum threshold enforcement for coverage results 40 | // coverageThreshold: undefined, 41 | 42 | // A path to a custom dependency extractor 43 | // dependencyExtractor: undefined, 44 | 45 | // Make calling deprecated APIs throw helpful error messages 46 | // errorOnDeprecated: false, 47 | 48 | // Force coverage collection from ignored files using an array of glob patterns 49 | // forceCoverageMatch: [], 50 | 51 | // A path to a module which exports an async function that is triggered once before all test suites 52 | // globalSetup: undefined, 53 | 54 | // A path to a module which exports an async function that is triggered once after all test suites 55 | // globalTeardown: undefined, 56 | 57 | // A set of global variables that need to be available in all test environments 58 | // globals: {}, 59 | 60 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 61 | // maxWorkers: "50%", 62 | 63 | // An array of directory names to be searched recursively up from the requiring module's location 64 | // moduleDirectories: [ 65 | // "node_modules" 66 | // ], 67 | 68 | // An array of file extensions your modules use 69 | // moduleFileExtensions: [ 70 | // "js", 71 | // "json", 72 | // "jsx", 73 | // "ts", 74 | // "tsx", 75 | // "node" 76 | // ], 77 | 78 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 79 | // moduleNameMapper: {}, 80 | 81 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 82 | // modulePathIgnorePatterns: [], 83 | 84 | // Activates notifications for test results 85 | // notify: false, 86 | 87 | // An enum that specifies notification mode. Requires { notify: true } 88 | // notifyMode: "failure-change", 89 | 90 | // A preset that is used as a base for Jest's configuration 91 | preset: 'ts-jest', 92 | 93 | // Run tests from one or more projects 94 | // projects: undefined, 95 | 96 | // Use this configuration option to add custom reporters to Jest 97 | // reporters: undefined, 98 | 99 | // Automatically reset mock state between every test 100 | // resetMocks: false, 101 | 102 | // Reset the module registry before running each individual test 103 | // resetModules: false, 104 | 105 | // A path to a custom resolver 106 | // resolver: undefined, 107 | 108 | // Automatically restore mock state between every test 109 | // restoreMocks: false, 110 | 111 | // The root directory that Jest should scan for tests and modules within 112 | // rootDir: undefined, 113 | 114 | // A list of paths to directories that Jest should use to search for files in 115 | // roots: [ 116 | // "" 117 | // ], 118 | 119 | // Allows you to use a custom runner instead of Jest's default test runner 120 | // runner: "jest-runner", 121 | 122 | // The paths to modules that run some code to configure or set up the testing environment before each test 123 | // setupFiles: [], 124 | 125 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 126 | // setupFilesAfterEnv: ['./jest.setup.ts'], 127 | 128 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 129 | // snapshotSerializers: [], 130 | 131 | // The test environment that will be used for testing 132 | testEnvironment: 'node', 133 | 134 | // Options that will be passed to the testEnvironment 135 | // testEnvironmentOptions: {}, 136 | 137 | // Adds a location field to test results 138 | // testLocationInResults: false, 139 | 140 | // The glob patterns Jest uses to detect test files 141 | testMatch: ['**/src/__tests__/**/*.[jt]s?(x)'], 142 | 143 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 144 | // testPathIgnorePatterns: [ 145 | // "/node_modules/" 146 | // ], 147 | 148 | // The regexp pattern or array of patterns that Jest uses to detect test files 149 | // testRegex: [], 150 | 151 | // This option allows the use of a custom results processor 152 | // testResultsProcessor: undefined, 153 | 154 | // This option allows use of a custom test runner 155 | // testRunner: "jasmine2", 156 | 157 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 158 | // testURL: "http://localhost", 159 | 160 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 161 | // timers: "real", 162 | 163 | // A map from regular expressions to paths to transformers 164 | // transform: undefined, 165 | 166 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 167 | // transformIgnorePatterns: [ 168 | // "/node_modules/" 169 | // ], 170 | 171 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 172 | // unmockedModulePathPatterns: undefined, 173 | 174 | // Indicates whether each individual test should be reported during the run 175 | // verbose: undefined, 176 | 177 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 178 | // watchPathIgnorePatterns: [], 179 | 180 | // Whether to use watchman for file crawling 181 | // watchman: true, 182 | moduleNameMapper: { 183 | '^@modules/(.*)$': '/src/modules/$1', 184 | '^@config/(.*)$': '/src/config/$1', 185 | '^@shared/(.*)$': '/src/shared/$1', 186 | }, 187 | }; 188 | -------------------------------------------------------------------------------- /ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": "5432", 5 | "username": "postgres", 6 | "password": "docker", 7 | "database": "gostack_desafio09", 8 | "entities": ["./src/modules/**/infra/typeorm/entities/*.ts"], 9 | "migrations": ["./src/shared/infra/typeorm/migrations/*.ts"], 10 | "cli": { 11 | "migrationsDir": "./src/shared/infra/typeorm/migrations" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gostack-desafio-09", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "tsc", 8 | "dev:server": "ts-node-dev -r tsconfig-paths/register --transpileOnly --ignore-watch node_modules src/shared/infra/http/server.ts", 9 | "typeorm": "ts-node-dev -r tsconfig-paths/register ./node_modules/typeorm/cli.js", 10 | "test": "cross-env NODE_ENV=test jest", 11 | "lint": "yarn eslint --ext .ts ./src --fix" 12 | }, 13 | "dependencies": { 14 | "celebrate": "^12.2.0", 15 | "cors": "^2.8.5", 16 | "cross-env": "^7.0.2", 17 | "express": "^4.17.1", 18 | "express-async-errors": "^3.1.1", 19 | "pg": "^8.1.0", 20 | "reflect-metadata": "^0.1.10", 21 | "tsyringe": "^4.2.0", 22 | "typeorm": "0.2.24" 23 | }, 24 | "devDependencies": { 25 | "@commitlint/cli": "^9.1.2", 26 | "@commitlint/config-conventional": "^9.1.2", 27 | "@types/cors": "^2.8.6", 28 | "@types/express": "^4.17.6", 29 | "@types/jest": "^25.2.1", 30 | "@types/node": "^8.0.29", 31 | "@types/supertest": "^2.0.9", 32 | "@typescript-eslint/eslint-plugin": "^2.31.0", 33 | "@typescript-eslint/parser": "^2.31.0", 34 | "cz-conventional-changelog": "^3.3.0", 35 | "eslint": "^7.0.0", 36 | "eslint-config-airbnb-base": "^14.1.0", 37 | "eslint-config-prettier": "^6.11.0", 38 | "eslint-import-resolver-typescript": "^2.0.0", 39 | "eslint-plugin-import": "^2.20.2", 40 | "eslint-plugin-prettier": "^3.1.3", 41 | "husky": "^4.2.5", 42 | "jest": ">=25.0.0 <26.0.0", 43 | "lint-staged": "^10.2.13", 44 | "prettier": "^2.0.5", 45 | "supertest": "^4.0.2", 46 | "ts-jest": "^25.5.1", 47 | "ts-node": "3.3.0", 48 | "ts-node-dev": "^1.0.0-pre.44", 49 | "tsconfig-paths": "^3.9.0", 50 | "typescript": "^3.8.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'all', 4 | arrowParens: 'avoid', 5 | }; 6 | -------------------------------------------------------------------------------- /src/__tests__/App.spec.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | 3 | import { Connection, getConnection, getRepository } from 'typeorm'; 4 | import createConnection from '@shared/infra/typeorm/index'; 5 | 6 | import Product from '@modules/products/infra/typeorm/entities/Product'; 7 | 8 | import app from '@shared/infra/http/app'; 9 | 10 | let connection: Connection; 11 | 12 | describe('App', () => { 13 | beforeAll(async () => { 14 | connection = await createConnection('test-connection'); 15 | 16 | await connection.query('DROP TABLE IF EXISTS orders_products'); 17 | await connection.query('DROP TABLE IF EXISTS orders'); 18 | await connection.query('DROP TABLE IF EXISTS products'); 19 | await connection.query('DROP TABLE IF EXISTS customers'); 20 | await connection.query('DROP TABLE IF EXISTS student_classes'); 21 | await connection.query('DROP TABLE IF EXISTS classes'); 22 | await connection.query('DROP TABLE IF EXISTS students'); 23 | await connection.query('DROP TABLE IF EXISTS teachers'); 24 | await connection.query('DROP TABLE IF EXISTS migrations'); 25 | 26 | await connection.runMigrations(); 27 | }); 28 | 29 | beforeEach(async () => { 30 | await connection.query('DELETE FROM orders_products'); 31 | await connection.query('DELETE FROM orders'); 32 | await connection.query('DELETE FROM products'); 33 | await connection.query('DELETE FROM customers'); 34 | }); 35 | 36 | afterAll(async () => { 37 | const mainConnection = getConnection(); 38 | 39 | await connection.close(); 40 | await mainConnection.close(); 41 | }); 42 | 43 | it('should be able to create a new customer', async () => { 44 | const response = await request(app).post('/customers').send({ 45 | name: 'Rocketseat', 46 | email: 'oi@rocketseat.com.br', 47 | }); 48 | 49 | expect(response.body).toEqual( 50 | expect.objectContaining({ 51 | name: 'Rocketseat', 52 | email: 'oi@rocketseat.com.br', 53 | }), 54 | ); 55 | }); 56 | 57 | it('should not be able to create a customer with one e-mail thats already registered', async () => { 58 | const customer = await request(app).post('/customers').send({ 59 | name: 'Rocketseat', 60 | email: 'oi@rocketseat.com.br', 61 | }); 62 | 63 | expect(customer.body).toEqual( 64 | expect.objectContaining({ 65 | name: 'Rocketseat', 66 | email: 'oi@rocketseat.com.br', 67 | }), 68 | ); 69 | 70 | const response = await request(app).post('/customers').send({ 71 | name: 'Rocketseat', 72 | email: 'oi@rocketseat.com.br', 73 | }); 74 | 75 | expect(response.status).toBe(400); 76 | }); 77 | 78 | it('should be able to create a new product', async () => { 79 | const response = await request(app).post('/products').send({ 80 | name: 'Produto 01', 81 | price: 500, 82 | quantity: 50, 83 | }); 84 | 85 | expect(response.body).toEqual( 86 | expect.objectContaining({ 87 | name: 'Produto 01', 88 | price: 500, 89 | quantity: 50, 90 | }), 91 | ); 92 | }); 93 | 94 | it('should not be able to create a duplicated product', async () => { 95 | const product = await request(app).post('/products').send({ 96 | name: 'Produto 01', 97 | price: 500, 98 | quantity: 50, 99 | }); 100 | 101 | expect(product.body).toEqual( 102 | expect.objectContaining({ 103 | name: 'Produto 01', 104 | price: 500, 105 | quantity: 50, 106 | }), 107 | ); 108 | 109 | const response = await request(app).post('/products').send({ 110 | name: 'Produto 01', 111 | price: 500, 112 | quantity: 50, 113 | }); 114 | 115 | expect(response.status).toBe(400); 116 | }); 117 | 118 | it('should be able to create a new order', async () => { 119 | const product = await request(app).post('/products').send({ 120 | name: 'Produto 01', 121 | price: 500, 122 | quantity: 50, 123 | }); 124 | 125 | const customer = await request(app).post('/customers').send({ 126 | name: 'Rocketseat', 127 | email: 'oi@rocketseat.com.br', 128 | }); 129 | 130 | const response = await request(app) 131 | .post('/orders') 132 | .send({ 133 | customer_id: customer.body.id, 134 | products: [ 135 | { 136 | id: product.body.id, 137 | quantity: 5, 138 | }, 139 | ], 140 | }); 141 | 142 | expect(response.body).toEqual( 143 | expect.objectContaining({ 144 | customer: expect.objectContaining({ 145 | id: customer.body.id, 146 | name: 'Rocketseat', 147 | email: 'oi@rocketseat.com.br', 148 | }), 149 | orders_products: expect.arrayContaining([ 150 | expect.objectContaining({ 151 | product_id: product.body.id, 152 | price: '500.00', 153 | quantity: 5, 154 | }), 155 | ]), 156 | }), 157 | ); 158 | }); 159 | 160 | it('should not be able to create an order with a invalid customer', async () => { 161 | const response = await request(app).post('/orders').send({ 162 | customer_id: '6a1922c8-af6e-470e-9a34-621cb0643911', 163 | }); 164 | 165 | expect(response.status).toEqual(400); 166 | }); 167 | 168 | it('should not be able to create an order with invalid products', async () => { 169 | const customer = await request(app).post('/customers').send({ 170 | name: 'Rocketseat', 171 | email: 'oi@rocketseat.com.br', 172 | }); 173 | 174 | const response = await request(app) 175 | .post('/orders') 176 | .send({ 177 | customer_id: customer.body.id, 178 | products: [ 179 | { 180 | id: '6a1922c8-af6e-470e-9a34-621cb0643911', 181 | }, 182 | ], 183 | }); 184 | 185 | expect(response.status).toEqual(400); 186 | }); 187 | 188 | it('should not be able to create an order with products with insufficient quantities', async () => { 189 | const customer = await request(app).post('/customers').send({ 190 | name: 'Rocketseat', 191 | email: 'oi@rocketseat.com.br', 192 | }); 193 | 194 | const product = await request(app).post('/products').send({ 195 | name: 'Produto 01', 196 | price: 500, 197 | quantity: 50, 198 | }); 199 | 200 | const response = await request(app) 201 | .post('/orders') 202 | .send({ 203 | customer_id: customer.body.id, 204 | products: [ 205 | { 206 | id: product.body.id, 207 | quantity: 500, 208 | }, 209 | ], 210 | }); 211 | 212 | expect(response.status).toEqual(400); 213 | }); 214 | 215 | it('should be able to subtract an product total quantity when it is ordered', async () => { 216 | const productsRepository = getRepository(Product); 217 | 218 | const customer = await request(app).post('/customers').send({ 219 | name: 'Rocketseat', 220 | email: 'oi@rocketseat.com.br', 221 | }); 222 | 223 | const product = await request(app).post('/products').send({ 224 | name: 'Produto 01', 225 | price: 500, 226 | quantity: 50, 227 | }); 228 | 229 | await request(app) 230 | .post('/orders') 231 | .send({ 232 | customer_id: customer.body.id, 233 | products: [ 234 | { 235 | id: product.body.id, 236 | quantity: 5, 237 | }, 238 | ], 239 | }); 240 | 241 | let foundProduct = await productsRepository.findOne(product.body.id); 242 | 243 | expect(foundProduct).toEqual( 244 | expect.objectContaining({ 245 | quantity: 45, 246 | }), 247 | ); 248 | 249 | await request(app) 250 | .post('/orders') 251 | .send({ 252 | customer_id: customer.body.id, 253 | products: [ 254 | { 255 | id: product.body.id, 256 | quantity: 5, 257 | }, 258 | ], 259 | }); 260 | 261 | foundProduct = await productsRepository.findOne(product.body.id); 262 | 263 | expect(foundProduct).toEqual( 264 | expect.objectContaining({ 265 | quantity: 40, 266 | }), 267 | ); 268 | }); 269 | 270 | it('should be able to list one specific order', async () => { 271 | const customer = await request(app).post('/customers').send({ 272 | name: 'Rocketseat', 273 | email: 'oi@rocketseat.com.br', 274 | }); 275 | 276 | const product = await request(app).post('/products').send({ 277 | name: 'Produto 01', 278 | price: 500, 279 | quantity: 50, 280 | }); 281 | 282 | const order = await request(app) 283 | .post('/orders') 284 | .send({ 285 | customer_id: customer.body.id, 286 | products: [ 287 | { 288 | id: product.body.id, 289 | quantity: 5, 290 | }, 291 | ], 292 | }); 293 | 294 | const response = await request(app).get(`/orders/${order.body.id}`); 295 | 296 | expect(response.body).toEqual( 297 | expect.objectContaining({ 298 | customer: expect.objectContaining({ 299 | id: customer.body.id, 300 | name: 'Rocketseat', 301 | email: 'oi@rocketseat.com.br', 302 | }), 303 | orders_products: expect.arrayContaining([ 304 | expect.objectContaining({ 305 | product_id: product.body.id, 306 | price: '500.00', 307 | quantity: 5, 308 | }), 309 | ]), 310 | }), 311 | ); 312 | }); 313 | }); 314 | -------------------------------------------------------------------------------- /src/modules/classes/dtos/ICreateClassDTO.ts: -------------------------------------------------------------------------------- 1 | import Teacher from '@modules/teachers/infra/typeorm/entities/Teacher'; 2 | 3 | export interface IStudent { 4 | student_id: string; 5 | } 6 | 7 | export default interface ICreateClassDTO { 8 | subject: string; 9 | teacher: Teacher; 10 | students: IStudent[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/classes/infra/http/controllers/ClassesController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { container } from 'tsyringe'; 3 | 4 | import CreateClassService from '@modules/classes/services/CreateClassService'; 5 | import Class from '@modules/classes/infra/typeorm/entities/Class'; 6 | 7 | class ClassesController { 8 | public async create( 9 | request: Request, 10 | response: Response, 11 | ): Promise> { 12 | const { subject, teacher_id, students } = request.body; 13 | 14 | const createClass = container.resolve(CreateClassService); 15 | 16 | const classRegister = await createClass.execute({ 17 | subject, 18 | students, 19 | teacher_id, 20 | }); 21 | 22 | return response.json(classRegister); 23 | } 24 | } 25 | 26 | export default ClassesController; 27 | -------------------------------------------------------------------------------- /src/modules/classes/infra/http/routes/classes.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import ClassesController from '@modules/classes/infra/http/controllers/ClassesController'; 4 | 5 | const classesRouter = Router(); 6 | const classesController = new ClassesController(); 7 | 8 | classesRouter.post('/', classesController.create); 9 | 10 | export default classesRouter; 11 | -------------------------------------------------------------------------------- /src/modules/classes/infra/typeorm/entities/Class.ts: -------------------------------------------------------------------------------- 1 | import StudentClasses from '@modules/students/infra/typeorm/entities/StudentClasses'; 2 | import Teacher from '@modules/teachers/infra/typeorm/entities/Teacher'; 3 | import { 4 | Entity, 5 | Column, 6 | ManyToOne, 7 | OneToMany, 8 | JoinColumn, 9 | CreateDateColumn, 10 | UpdateDateColumn, 11 | PrimaryGeneratedColumn, 12 | } from 'typeorm'; 13 | 14 | @Entity('classes') 15 | class Class { 16 | @Column('uuid') 17 | @PrimaryGeneratedColumn('uuid') 18 | id: string; 19 | 20 | @Column('text') 21 | subject: string; 22 | 23 | @ManyToOne(() => Teacher, teacher => teacher.classes) 24 | @JoinColumn({ name: 'teacher_id' }) 25 | teacher: Teacher; 26 | 27 | @CreateDateColumn() 28 | created_at: Date; 29 | 30 | @OneToMany(() => StudentClasses, studentClasses => studentClasses.class, { 31 | cascade: ['insert'], 32 | }) 33 | student_classes: StudentClasses[]; 34 | 35 | @UpdateDateColumn() 36 | updated_at: Date; 37 | } 38 | 39 | export default Class; 40 | -------------------------------------------------------------------------------- /src/modules/classes/infra/typeorm/repositories/ClassesRepository.ts: -------------------------------------------------------------------------------- 1 | import IClassesRepository from '@modules/classes/repositories/IClassesRepository'; 2 | import { Repository, getRepository } from 'typeorm'; 3 | 4 | import Class from '@modules/classes/infra/typeorm/entities/Class'; 5 | import ICreateClassDTO from '@modules/classes/dtos/ICreateClassDTO'; 6 | 7 | class ClassesRepository implements IClassesRepository { 8 | private ormRepository: Repository; 9 | 10 | constructor() { 11 | this.ormRepository = getRepository(Class); 12 | } 13 | 14 | public async create({ 15 | subject, 16 | teacher, 17 | students, 18 | }: ICreateClassDTO): Promise { 19 | const createClass = this.ormRepository.create({ 20 | subject, 21 | teacher, 22 | student_classes: students, 23 | }); 24 | 25 | await this.ormRepository.save(createClass); 26 | 27 | return createClass; 28 | } 29 | 30 | public async findBySubject(subject: string): Promise { 31 | const findClassBySubject = await this.ormRepository.findOne({ 32 | where: { subject }, 33 | }); 34 | 35 | return findClassBySubject; 36 | } 37 | } 38 | 39 | export default ClassesRepository; 40 | -------------------------------------------------------------------------------- /src/modules/classes/infra/validators/createClass.ts: -------------------------------------------------------------------------------- 1 | import { celebrate, Segments, Joi } from 'celebrate'; 2 | 3 | const createClassValidator = celebrate({ 4 | [Segments.BODY]: { 5 | subject: Joi.string().required(), 6 | teacher_id: Joi.number().integer().positive().required(), 7 | }, 8 | }); 9 | 10 | export default createClassValidator; 11 | -------------------------------------------------------------------------------- /src/modules/classes/repositories/IClassesRepository.ts: -------------------------------------------------------------------------------- 1 | import Class from '@modules/classes/infra/typeorm/entities/Class'; 2 | import ICreateClassDTO from '@modules/classes/dtos/ICreateClassDTO'; 3 | 4 | export default interface IClassesRepository { 5 | create(data: ICreateClassDTO): Promise; 6 | findBySubject(subject: string): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/classes/services/CreateClassService.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'tsyringe'; 2 | 3 | import ITeachersRepository from '@modules/teachers/repositories/ITeachersRepository'; 4 | import IClassesRepository from '@modules/classes/repositories/IClassesRepository'; 5 | import Class from '@modules/classes/infra/typeorm/entities/Class'; 6 | import AppError from '@shared/errors/AppError'; 7 | import IStudentsRepository from '@modules/students/repositories/IStudentsRepository'; 8 | 9 | interface IStudent { 10 | id: string; 11 | } 12 | 13 | interface IRequest { 14 | subject: string; 15 | teacher_id: string; 16 | students: IStudent[]; 17 | } 18 | 19 | @injectable() 20 | class CreateClassService { 21 | constructor( 22 | @inject('ClassesRepository') 23 | private classesRepository: IClassesRepository, 24 | 25 | @inject('TeachersRepository') 26 | private teachersRepository: ITeachersRepository, 27 | 28 | @inject('StudentsRepository') 29 | private studentsRepository: IStudentsRepository, 30 | ) {} 31 | 32 | public async execute({ 33 | subject, 34 | teacher_id, 35 | students, 36 | }: IRequest): Promise { 37 | const findTeacher = await this.teachersRepository.findById(teacher_id); 38 | 39 | if (!findTeacher) { 40 | throw new AppError('Teacher not found'); 41 | } 42 | 43 | const findClassWithSameSubject = await this.classesRepository.findBySubject( 44 | subject, 45 | ); 46 | 47 | if (findClassWithSameSubject) { 48 | throw new AppError( 49 | "There's already a class with the same subject. Please, try it again with another subject", 50 | ); 51 | } 52 | 53 | try { 54 | const findStudents = students.map(student => 55 | this.studentsRepository.findOneOrFail(student.id), 56 | ); 57 | 58 | await Promise.all(findStudents); 59 | } catch (error) { 60 | throw new AppError('Student not found'); 61 | } 62 | 63 | const studentsIds = students.map(student => ({ student_id: student.id })); 64 | 65 | const createClass = await this.classesRepository.create({ 66 | subject, 67 | teacher: findTeacher, 68 | students: studentsIds, 69 | }); 70 | 71 | return createClass; 72 | } 73 | } 74 | 75 | export default CreateClassService; 76 | -------------------------------------------------------------------------------- /src/modules/customers/dtos/ICreateCustomerDTO.ts: -------------------------------------------------------------------------------- 1 | export default interface ICreateCustomerDTO { 2 | name: string; 3 | email: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/customers/infra/http/controller/CustomersController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { container } from 'tsyringe'; 3 | 4 | import CreateCustomerService from '@modules/customers/services/CreateCustomerService'; 5 | 6 | import AppError from '@shared/errors/AppError'; 7 | import Customer from '@modules/customers/infra/typeorm/entities/Customer'; 8 | 9 | export default class CustomersController { 10 | public async create( 11 | request: Request, 12 | response: Response, 13 | ): Promise> { 14 | const { name, email } = request.body; 15 | 16 | const hasInvalidData = !name || !email; 17 | 18 | if (hasInvalidData) { 19 | throw new AppError( 20 | 'Please, provide valid data in order to create a customer', 21 | ); 22 | } 23 | 24 | const createCustomer = container.resolve(CreateCustomerService); 25 | 26 | const customer = await createCustomer.execute({ name, email }); 27 | 28 | return response.json(customer); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/customers/infra/http/routes/customers.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import createCustomerValidator from '@modules/customers/infra/validators/createCustomer'; 4 | import CustomersController from '@modules/customers/infra/http/controller/CustomersController'; 5 | 6 | const customersRouter = Router(); 7 | const customersController = new CustomersController(); 8 | 9 | customersRouter.post('/', createCustomerValidator, customersController.create); 10 | 11 | export default customersRouter; 12 | -------------------------------------------------------------------------------- /src/modules/customers/infra/typeorm/entities/Customer.ts: -------------------------------------------------------------------------------- 1 | import Order from '@modules/orders/infra/typeorm/entities/Order'; 2 | import { 3 | Entity, 4 | Column, 5 | OneToMany, 6 | CreateDateColumn, 7 | UpdateDateColumn, 8 | PrimaryGeneratedColumn, 9 | } from 'typeorm'; 10 | 11 | @Entity('customers') 12 | class Customer { 13 | @Column('uuid') 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @Column() 18 | name: string; 19 | 20 | @Column() 21 | email: string; 22 | 23 | @OneToMany(() => Order, order => order.customer) 24 | order: Order; 25 | 26 | @CreateDateColumn() 27 | created_at: Date; 28 | 29 | @UpdateDateColumn() 30 | updated_at: Date; 31 | } 32 | 33 | export default Customer; 34 | -------------------------------------------------------------------------------- /src/modules/customers/infra/typeorm/repositories/CustomersRepository.ts: -------------------------------------------------------------------------------- 1 | import { getRepository, Repository } from 'typeorm'; 2 | 3 | import ICustomersRepository from '@modules/customers/repositories/ICustomersRepository'; 4 | import ICreateCustomerDTO from '@modules/customers/dtos/ICreateCustomerDTO'; 5 | import Customer from '@modules/customers/infra/typeorm/entities/Customer'; 6 | 7 | class CustomersRepository implements ICustomersRepository { 8 | private ormRepository: Repository; 9 | 10 | constructor() { 11 | this.ormRepository = getRepository(Customer); 12 | } 13 | 14 | public async create({ name, email }: ICreateCustomerDTO): Promise { 15 | const customer = this.ormRepository.create({ 16 | name, 17 | email, 18 | }); 19 | 20 | await this.ormRepository.save(customer); 21 | 22 | return customer; 23 | } 24 | 25 | public async findById(id: string): Promise { 26 | const findCustomer = await this.ormRepository.findOne(id); 27 | 28 | return findCustomer; 29 | } 30 | 31 | public async findByEmail(email: string): Promise { 32 | const findCustomer = await this.ormRepository.findOne({ 33 | where: { 34 | email, 35 | }, 36 | }); 37 | 38 | return findCustomer; 39 | } 40 | } 41 | 42 | export default CustomersRepository; 43 | -------------------------------------------------------------------------------- /src/modules/customers/infra/validators/createCustomer.ts: -------------------------------------------------------------------------------- 1 | import { celebrate, Segments, Joi } from 'celebrate'; 2 | 3 | const createCustomerValidator = celebrate({ 4 | [Segments.BODY]: { 5 | name: Joi.string().required(), 6 | email: Joi.string().email().required(), 7 | }, 8 | }); 9 | 10 | export default createCustomerValidator; 11 | -------------------------------------------------------------------------------- /src/modules/customers/repositories/ICustomersRepository.ts: -------------------------------------------------------------------------------- 1 | import Customer from '../infra/typeorm/entities/Customer'; 2 | 3 | import ICreateCustomerDTO from '../dtos/ICreateCustomerDTO'; 4 | 5 | export default interface ICustomersRepository { 6 | create(data: ICreateCustomerDTO): Promise; 7 | findByEmail(email: string): Promise; 8 | findById(id: string): Promise; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/customers/services/CreateCustomerService.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'tsyringe'; 2 | 3 | import AppError from '@shared/errors/AppError'; 4 | 5 | import Customer from '../infra/typeorm/entities/Customer'; 6 | import ICustomersRepository from '../repositories/ICustomersRepository'; 7 | 8 | interface IRequest { 9 | name: string; 10 | email: string; 11 | } 12 | 13 | @injectable() 14 | class CreateCustomerService { 15 | constructor( 16 | @inject('CustomersRepository') 17 | private customersRepository: ICustomersRepository, 18 | ) {} 19 | 20 | public async execute({ name, email }: IRequest): Promise { 21 | const findCustomerWithSameEmail = await this.customersRepository.findByEmail( 22 | email, 23 | ); 24 | 25 | if (findCustomerWithSameEmail) { 26 | throw new AppError( 27 | "There's already a customer registered with that email", 28 | ); 29 | } 30 | 31 | const customer = await this.customersRepository.create({ name, email }); 32 | 33 | return customer; 34 | } 35 | } 36 | 37 | export default CreateCustomerService; 38 | -------------------------------------------------------------------------------- /src/modules/orders/dtos/ICreateOrderDTO.ts: -------------------------------------------------------------------------------- 1 | import Customer from '@modules/customers/infra/typeorm/entities/Customer'; 2 | 3 | interface IProduct { 4 | product_id: string; 5 | price: number; 6 | quantity: number; 7 | } 8 | 9 | export default interface ICreateOrderDTO { 10 | customer: Customer; 11 | products: IProduct[]; 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/orders/infra/http/controller/OrdersController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | 3 | import { container } from 'tsyringe'; 4 | 5 | import CreateOrderService from '@modules/orders/services/CreateOrderService'; 6 | import FindOrderService from '@modules/orders/services/FindOrderService'; 7 | import AppError from '@shared/errors/AppError'; 8 | 9 | export default class OrdersController { 10 | public async show(request: Request, response: Response): Promise { 11 | const { id } = request.params; 12 | 13 | const findOrder = container.resolve(FindOrderService); 14 | 15 | const order = await findOrder.execute({ id }); 16 | 17 | return response.json(order); 18 | } 19 | 20 | public async create(request: Request, response: Response): Promise { 21 | const { customer_id, products } = request.body; 22 | 23 | const hasInvalidData = !customer_id || !products || products.length <= 0; 24 | 25 | if (hasInvalidData) { 26 | throw new AppError( 27 | 'Please, provide valid data in order to create an order', 28 | ); 29 | } 30 | 31 | const createOrder = container.resolve(CreateOrderService); 32 | 33 | const order = await createOrder.execute({ customer_id, products }); 34 | 35 | return response.json(order); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/modules/orders/infra/http/routes/orders.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import OrdersController from '@modules/orders/infra/http/controller/OrdersController'; 4 | import createOrderValidator from '@modules/orders/infra/validators/createOrder'; 5 | import showOrderValidator from '@modules/orders/infra/validators/showOrder'; 6 | 7 | const ordersRouter = Router(); 8 | const ordersController = new OrdersController(); 9 | 10 | ordersRouter.post('/', createOrderValidator, ordersController.create); 11 | ordersRouter.get('/:id', showOrderValidator, ordersController.show); 12 | 13 | export default ordersRouter; 14 | -------------------------------------------------------------------------------- /src/modules/orders/infra/typeorm/entities/Order.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | ManyToOne, 5 | OneToMany, 6 | JoinColumn, 7 | CreateDateColumn, 8 | UpdateDateColumn, 9 | PrimaryGeneratedColumn, 10 | } from 'typeorm'; 11 | 12 | import Customer from '@modules/customers/infra/typeorm/entities/Customer'; 13 | import OrdersProducts from '@modules/orders/infra/typeorm/entities/OrdersProducts'; 14 | 15 | @Entity('orders') 16 | class Order { 17 | @Column('uuid') 18 | @PrimaryGeneratedColumn('uuid') 19 | id: string; 20 | 21 | @ManyToOne(() => Customer, customer => customer.order, { eager: true }) 22 | @JoinColumn({ name: 'customer_id' }) 23 | customer: Customer; 24 | 25 | @OneToMany(() => OrdersProducts, ordersProducts => ordersProducts.order, { 26 | cascade: ['insert'], 27 | }) 28 | orders_products: OrdersProducts[]; 29 | 30 | @CreateDateColumn() 31 | created_at: Date; 32 | 33 | @UpdateDateColumn() 34 | updated_at: Date; 35 | } 36 | 37 | export default Order; 38 | -------------------------------------------------------------------------------- /src/modules/orders/infra/typeorm/entities/OrdersProducts.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | ManyToOne, 5 | JoinColumn, 6 | CreateDateColumn, 7 | UpdateDateColumn, 8 | PrimaryGeneratedColumn, 9 | } from 'typeorm'; 10 | 11 | import Order from '@modules/orders/infra/typeorm/entities/Order'; 12 | import Product from '@modules/products/infra/typeorm/entities/Product'; 13 | 14 | @Entity('orders_products') 15 | class OrdersProducts { 16 | @Column('uuid') 17 | @PrimaryGeneratedColumn('uuid') 18 | id: string; 19 | 20 | @ManyToOne(() => Order, order => order.orders_products) 21 | @JoinColumn({ name: 'order_id' }) 22 | order: Order; 23 | 24 | @ManyToOne(() => Product, product => product.orders_products) 25 | @JoinColumn({ name: 'product_id' }) 26 | product: Product; 27 | 28 | @Column('uuid') 29 | product_id: string; 30 | 31 | @Column('uuid') 32 | order_id: string; 33 | 34 | @Column('decimal', { precision: 5, scale: 2 }) 35 | price: number; 36 | 37 | @Column('integer') 38 | quantity: number; 39 | 40 | @CreateDateColumn() 41 | created_at: Date; 42 | 43 | @UpdateDateColumn() 44 | updated_at: Date; 45 | } 46 | 47 | export default OrdersProducts; 48 | -------------------------------------------------------------------------------- /src/modules/orders/infra/typeorm/repositories/OrdersRepository.ts: -------------------------------------------------------------------------------- 1 | import { getRepository, Repository } from 'typeorm'; 2 | 3 | import IOrdersRepository from '@modules/orders/repositories/IOrdersRepository'; 4 | import ICreateOrderDTO from '@modules/orders/dtos/ICreateOrderDTO'; 5 | import Order from '@modules/orders/infra/typeorm/entities/Order'; 6 | 7 | class OrdersRepository implements IOrdersRepository { 8 | private ormRepository: Repository; 9 | 10 | constructor() { 11 | this.ormRepository = getRepository(Order); 12 | } 13 | 14 | public async create({ customer, products }: ICreateOrderDTO): Promise { 15 | const order = this.ormRepository.create({ 16 | customer, 17 | orders_products: products, 18 | }); 19 | 20 | order.customer = customer; 21 | 22 | await this.ormRepository.save(order); 23 | 24 | return order; 25 | } 26 | 27 | public async findById(id: string): Promise { 28 | const order = await this.ormRepository.findOne({ 29 | relations: ['orders_products', 'customer'], 30 | where: { id }, 31 | }); 32 | 33 | return order; 34 | } 35 | } 36 | 37 | export default OrdersRepository; 38 | -------------------------------------------------------------------------------- /src/modules/orders/infra/validators/createOrder.ts: -------------------------------------------------------------------------------- 1 | import { celebrate, Segments, Joi } from 'celebrate'; 2 | 3 | const createOrderValidator = celebrate({ 4 | [Segments.BODY]: { 5 | customer_id: Joi.string().uuid().required(), 6 | products: Joi.array() 7 | .items( 8 | Joi.object({ 9 | id: Joi.string().uuid().required(), 10 | quantity: Joi.number().integer().positive().required(), 11 | }), 12 | ) 13 | .required(), 14 | }, 15 | }); 16 | 17 | export default createOrderValidator; 18 | -------------------------------------------------------------------------------- /src/modules/orders/infra/validators/showOrder.ts: -------------------------------------------------------------------------------- 1 | import { celebrate, Segments, Joi } from 'celebrate'; 2 | 3 | const showOrderValidator = celebrate({ 4 | [Segments.PARAMS]: { 5 | id: Joi.string().uuid().required(), 6 | }, 7 | }); 8 | 9 | export default showOrderValidator; 10 | -------------------------------------------------------------------------------- /src/modules/orders/repositories/IOrdersRepository.ts: -------------------------------------------------------------------------------- 1 | import Order from '@modules/orders/infra/typeorm/entities/Order'; 2 | 3 | import ICreateOrderDTO from '@modules/orders/dtos/ICreateOrderDTO'; 4 | 5 | export default interface IOrdersRepository { 6 | create(data: ICreateOrderDTO): Promise; 7 | findById(id: string): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/orders/services/CreateOrderService.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'tsyringe'; 2 | 3 | import AppError from '@shared/errors/AppError'; 4 | 5 | import IProductsRepository from '@modules/products/repositories/IProductsRepository'; 6 | import ICustomersRepository from '@modules/customers/repositories/ICustomersRepository'; 7 | import Order from '@modules/orders/infra/typeorm/entities/Order'; 8 | import IOrdersRepository from '@modules/orders/repositories/IOrdersRepository'; 9 | 10 | interface IProduct { 11 | id: string; 12 | quantity: number; 13 | } 14 | 15 | interface IRequest { 16 | customer_id: string; 17 | products: IProduct[]; 18 | } 19 | 20 | @injectable() 21 | class CreateOrderService { 22 | constructor( 23 | @inject('OrdersRepository') 24 | private ordersRepository: IOrdersRepository, 25 | 26 | @inject('ProductsRepository') 27 | private productsRepository: IProductsRepository, 28 | 29 | @inject('CustomersRepository') 30 | private customersRepository: ICustomersRepository, 31 | ) {} 32 | 33 | public async execute({ customer_id, products }: IRequest): Promise { 34 | const customer = await this.customersRepository.findById(customer_id); 35 | 36 | if (!customer) { 37 | throw new AppError('Customer not found'); 38 | } 39 | 40 | const productsIds = products.map(product => ({ id: product.id })); 41 | 42 | const originalProducts = await this.productsRepository.findAllById( 43 | productsIds, 44 | ); 45 | 46 | if (!customer) { 47 | throw new AppError('Customer not found'); 48 | } 49 | 50 | if (products.length <= 0) { 51 | throw new AppError('You must provide products to create an order'); 52 | } 53 | 54 | const hasInvalidProducts = 55 | originalProducts.length <= 0 || 56 | originalProducts.length !== products.length; 57 | 58 | if (hasInvalidProducts) { 59 | throw new AppError('Product(s) not found'); 60 | } 61 | 62 | products.forEach(product => { 63 | const originalProduct = originalProducts.find( 64 | findProduct => findProduct.id === product.id, 65 | ); 66 | 67 | const hasInsufficientQuantity = 68 | originalProduct && product.quantity > originalProduct.quantity; 69 | 70 | if (hasInsufficientQuantity) { 71 | throw new AppError( 72 | `The product ${originalProduct?.name} has insufficient quantity on the stock.`, 73 | ); 74 | } 75 | }); 76 | 77 | const orderProducts = products.map(product => { 78 | const originalProduct = originalProducts.find( 79 | findProduct => findProduct.id === product.id, 80 | ); 81 | 82 | if (!originalProduct) { 83 | throw new AppError('Product not found'); 84 | } 85 | 86 | return { 87 | product_id: originalProduct.id, 88 | quantity: product.quantity, 89 | price: originalProduct.price, 90 | }; 91 | }); 92 | 93 | const order = await this.ordersRepository.create({ 94 | customer, 95 | products: orderProducts, 96 | }); 97 | 98 | await this.productsRepository.updateQuantity(products); 99 | 100 | return order; 101 | } 102 | } 103 | 104 | export default CreateOrderService; 105 | -------------------------------------------------------------------------------- /src/modules/orders/services/FindOrderService.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'tsyringe'; 2 | 3 | import Order from '@modules/orders/infra/typeorm/entities/Order'; 4 | import IOrdersRepository from '@modules/orders/repositories/IOrdersRepository'; 5 | 6 | interface IRequest { 7 | id: string; 8 | } 9 | 10 | @injectable() 11 | class FindOrderService { 12 | constructor( 13 | @inject('OrdersRepository') 14 | private ordersRepository: IOrdersRepository, 15 | ) {} 16 | 17 | public async execute({ id }: IRequest): Promise { 18 | const order = await this.ordersRepository.findById(id); 19 | 20 | return order; 21 | } 22 | } 23 | 24 | export default FindOrderService; 25 | -------------------------------------------------------------------------------- /src/modules/products/dtos/ICreateProductDTO.ts: -------------------------------------------------------------------------------- 1 | export default interface ICreateProductDTO { 2 | name: string; 3 | price: number; 4 | quantity: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/products/dtos/IUpdateProductsQuantityDTO.ts: -------------------------------------------------------------------------------- 1 | export default interface IUpdateProductsQuantityDTO { 2 | id: string; 3 | quantity: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/products/infra/http/controller/ProductsController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { container } from 'tsyringe'; 3 | 4 | import CreateProductService from '@modules/products/services/CreateProductService'; 5 | import AppError from '@shared/errors/AppError'; 6 | import Product from '@modules/products/infra/typeorm/entities/Product'; 7 | 8 | export default class ProductsController { 9 | public async create( 10 | request: Request, 11 | response: Response, 12 | ): Promise> { 13 | const { name, price, quantity } = request.body; 14 | 15 | const hasInvalidData = !name || !price || !quantity; 16 | 17 | if (hasInvalidData) { 18 | throw new AppError( 19 | 'Please, provide valid data in order to create a product', 20 | ); 21 | } 22 | 23 | const createProduct = container.resolve(CreateProductService); 24 | 25 | const product = await createProduct.execute({ name, price, quantity }); 26 | 27 | return response.json(product); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/products/infra/http/routes/products.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import ProductsController from '@modules/products/infra/http/controller/ProductsController'; 4 | import createProductValidator from '../../validators/createProduct'; 5 | 6 | const productsRouter = Router(); 7 | const productsController = new ProductsController(); 8 | 9 | productsRouter.post('/', createProductValidator, productsController.create); 10 | 11 | export default productsRouter; 12 | -------------------------------------------------------------------------------- /src/modules/products/infra/typeorm/entities/Product.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | OneToMany, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | PrimaryGeneratedColumn, 8 | } from 'typeorm'; 9 | 10 | import OrdersProducts from '@modules/orders/infra/typeorm/entities/OrdersProducts'; 11 | 12 | @Entity('products') 13 | class Product { 14 | @Column('uuid') 15 | @PrimaryGeneratedColumn('uuid') 16 | id: string; 17 | 18 | @Column() 19 | name: string; 20 | 21 | @Column('decimal', { precision: 5, scale: 2 }) 22 | price: number; 23 | 24 | @Column('integer') 25 | quantity: number; 26 | 27 | @OneToMany(() => OrdersProducts, ordersProduct => ordersProduct.product) 28 | orders_products: OrdersProducts[]; 29 | 30 | @CreateDateColumn() 31 | created_at: Date; 32 | 33 | @UpdateDateColumn() 34 | updated_at: Date; 35 | } 36 | 37 | export default Product; 38 | -------------------------------------------------------------------------------- /src/modules/products/infra/typeorm/repositories/ProductsRepository.ts: -------------------------------------------------------------------------------- 1 | import { getRepository, Repository } from 'typeorm'; 2 | 3 | import IProductsRepository from '@modules/products/repositories/IProductsRepository'; 4 | import ICreateProductDTO from '@modules/products/dtos/ICreateProductDTO'; 5 | import IUpdateProductsQuantityDTO from '@modules/products/dtos/IUpdateProductsQuantityDTO'; 6 | import Product from '@modules/products/infra/typeorm/entities/Product'; 7 | import AppError from '@shared/errors/AppError'; 8 | 9 | interface IFindProducts { 10 | id: string; 11 | } 12 | 13 | class ProductsRepository implements IProductsRepository { 14 | private ormRepository: Repository; 15 | 16 | constructor() { 17 | this.ormRepository = getRepository(Product); 18 | } 19 | 20 | public async create({ 21 | name, 22 | price, 23 | quantity, 24 | }: ICreateProductDTO): Promise { 25 | const product = this.ormRepository.create({ 26 | name, 27 | price, 28 | quantity, 29 | }); 30 | 31 | await this.ormRepository.save(product); 32 | 33 | return product; 34 | } 35 | 36 | public async findByName(name: string): Promise { 37 | const findProduct = await this.ormRepository.findOne({ 38 | where: { 39 | name, 40 | }, 41 | }); 42 | 43 | return findProduct; 44 | } 45 | 46 | public async findAllById(productsIds: IFindProducts[]): Promise { 47 | const products = await this.ormRepository.findByIds(productsIds); 48 | 49 | return products; 50 | } 51 | 52 | public async updateQuantity( 53 | updateProductsData: IUpdateProductsQuantityDTO[], 54 | ): Promise { 55 | const productsIds = updateProductsData.map(product => product.id); 56 | 57 | const findProducts = await this.ormRepository.findByIds(productsIds); 58 | 59 | const updateProductsQuantity = findProducts.map(product => { 60 | const findUpdateProduct = updateProductsData.find( 61 | updateProduct => product.id === updateProduct.id, 62 | ); 63 | 64 | if (!findUpdateProduct) { 65 | throw new AppError('Product not found'); 66 | } 67 | 68 | return this.ormRepository.save({ 69 | ...product, 70 | quantity: product.quantity - findUpdateProduct?.quantity, 71 | }); 72 | }); 73 | 74 | const products = await Promise.all(updateProductsQuantity); 75 | 76 | return products; 77 | } 78 | } 79 | 80 | export default ProductsRepository; 81 | -------------------------------------------------------------------------------- /src/modules/products/infra/validators/createProduct.ts: -------------------------------------------------------------------------------- 1 | import { celebrate, Segments, Joi } from 'celebrate'; 2 | 3 | const createProductValidator = celebrate({ 4 | [Segments.BODY]: { 5 | name: Joi.string().required(), 6 | price: Joi.number().positive().required(), 7 | quantity: Joi.number().positive().required(), 8 | }, 9 | }); 10 | 11 | export default createProductValidator; 12 | -------------------------------------------------------------------------------- /src/modules/products/repositories/IProductsRepository.ts: -------------------------------------------------------------------------------- 1 | import Product from '@modules/products/infra/typeorm/entities/Product'; 2 | import ICreateProductDTO from '@modules/products/dtos/ICreateProductDTO'; 3 | import IUpdateProductsQuantityDTO from '@modules/products/dtos/IUpdateProductsQuantityDTO'; 4 | 5 | interface IFindProducts { 6 | id: string; 7 | } 8 | 9 | export default interface IProductsRepository { 10 | create(data: ICreateProductDTO): Promise; 11 | findByName(name: string): Promise; 12 | findAllById(productsIds: IFindProducts[]): Promise; 13 | updateQuantity( 14 | updateQuantityData: IUpdateProductsQuantityDTO[], 15 | ): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/products/services/CreateProductService.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'tsyringe'; 2 | 3 | import AppError from '@shared/errors/AppError'; 4 | import Product from '@modules/products/infra/typeorm/entities/Product'; 5 | import IProductsRepository from '@modules/products/repositories/IProductsRepository'; 6 | 7 | interface IRequest { 8 | name: string; 9 | price: number; 10 | quantity: number; 11 | } 12 | 13 | @injectable() 14 | class CreateProductService { 15 | constructor( 16 | @inject('ProductsRepository') 17 | private productsRepository: IProductsRepository, 18 | ) {} 19 | 20 | public async execute({ name, price, quantity }: IRequest): Promise { 21 | const findProductWithSameName = await this.productsRepository.findByName( 22 | name, 23 | ); 24 | 25 | if (findProductWithSameName) { 26 | throw new AppError( 27 | "There's already a product registered with that name. Please, try it again with another name.", 28 | ); 29 | } 30 | 31 | const product = await this.productsRepository.create({ 32 | name, 33 | price, 34 | quantity, 35 | }); 36 | 37 | return product; 38 | } 39 | } 40 | 41 | export default CreateProductService; 42 | -------------------------------------------------------------------------------- /src/modules/students/dtos/ICreateStudentDTO.ts: -------------------------------------------------------------------------------- 1 | export default interface ICreateStudentDTO { 2 | age: number; 3 | name: string; 4 | email: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/students/infra/http/controllers/StudentsController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | 3 | import CreateStudentService from '@modules/students/services/CreateStudentService'; 4 | import { container } from 'tsyringe'; 5 | import Student from '../../typeorm/entities/Student'; 6 | 7 | class StudentsController { 8 | public async create( 9 | request: Request, 10 | response: Response, 11 | ): Promise> { 12 | const { age, name, email } = request.body; 13 | 14 | const createStudent = container.resolve(CreateStudentService); 15 | 16 | const student = await createStudent.execute({ age, name, email }); 17 | 18 | return response.json(student); 19 | } 20 | } 21 | 22 | export default StudentsController; 23 | -------------------------------------------------------------------------------- /src/modules/students/infra/http/routes/students.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import createStudentValidator from '@modules/students/validators/createStudent'; 4 | import StudentsController from '@modules/students/infra/http/controllers/StudentsController'; 5 | 6 | const studentsRouter = Router(); 7 | const studentsController = new StudentsController(); 8 | 9 | studentsRouter.post('/', createStudentValidator, studentsController.create); 10 | 11 | export default studentsRouter; 12 | -------------------------------------------------------------------------------- /src/modules/students/infra/typeorm/entities/Student.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | OneToMany, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | PrimaryGeneratedColumn, 8 | } from 'typeorm'; 9 | import StudentClasses from './StudentClasses'; 10 | 11 | @Entity('students') 12 | class Student { 13 | @Column('uuid') 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @Column('text') 18 | name: string; 19 | 20 | @Column('text') 21 | email: string; 22 | 23 | @Column('integer') 24 | age: number; 25 | 26 | @OneToMany(() => StudentClasses, studentClasses => studentClasses.student) 27 | student_classes: StudentClasses[]; 28 | 29 | @CreateDateColumn() 30 | created_at: Date; 31 | 32 | @UpdateDateColumn() 33 | updated_at: Date; 34 | } 35 | 36 | export default Student; 37 | -------------------------------------------------------------------------------- /src/modules/students/infra/typeorm/entities/StudentClasses.ts: -------------------------------------------------------------------------------- 1 | import Class from '@modules/classes/infra/typeorm/entities/Class'; 2 | import { 3 | Entity, 4 | Column, 5 | JoinColumn, 6 | ManyToOne, 7 | CreateDateColumn, 8 | UpdateDateColumn, 9 | PrimaryGeneratedColumn, 10 | } from 'typeorm'; 11 | import Student from './Student'; 12 | 13 | @Entity('student_classes') 14 | class StudentClasses { 15 | @Column('uuid') 16 | @PrimaryGeneratedColumn('uuid') 17 | id: string; 18 | 19 | @ManyToOne(() => Student, student => student.student_classes) 20 | @JoinColumn({ name: 'student_id' }) 21 | student: Student; 22 | 23 | @ManyToOne(() => Class, classEntity => classEntity.student_classes) 24 | @JoinColumn({ name: 'class_id' }) 25 | class: Class; 26 | 27 | @Column('uuid') 28 | student_id: string; 29 | 30 | @Column('uuid') 31 | class_id: string; 32 | 33 | @CreateDateColumn() 34 | created_at: Date; 35 | 36 | @UpdateDateColumn() 37 | updated_at: Date; 38 | } 39 | 40 | export default StudentClasses; 41 | -------------------------------------------------------------------------------- /src/modules/students/infra/typeorm/repositories/StudentsRepository.ts: -------------------------------------------------------------------------------- 1 | import { getRepository, Repository } from 'typeorm'; 2 | 3 | import ICreateStudentDTO from '@modules/students/dtos/ICreateStudentDTO'; 4 | import IStudentsRepository from '@modules/students/repositories/IStudentsRepository'; 5 | import Student from '@modules/students/infra/typeorm/entities/Student'; 6 | 7 | class StudentsRepository implements IStudentsRepository { 8 | private ormRepository: Repository; 9 | 10 | constructor() { 11 | this.ormRepository = getRepository(Student); 12 | } 13 | 14 | public async create({ 15 | age, 16 | name, 17 | email, 18 | }: ICreateStudentDTO): Promise { 19 | const student = this.ormRepository.create({ name, email, age }); 20 | 21 | await this.ormRepository.save(student); 22 | 23 | return student; 24 | } 25 | 26 | public async findByEmail(email: string): Promise { 27 | const student = await this.ormRepository.findOne({ where: { email } }); 28 | 29 | return student; 30 | } 31 | 32 | public async findOneOrFail(id: string): Promise { 33 | const student = await this.ormRepository.findOneOrFail(id); 34 | 35 | return student; 36 | } 37 | 38 | public async findByIds(data: string[]): Promise { 39 | const students = await this.ormRepository.findByIds(data); 40 | 41 | return students; 42 | } 43 | } 44 | 45 | export default StudentsRepository; 46 | -------------------------------------------------------------------------------- /src/modules/students/repositories/IStudentsRepository.ts: -------------------------------------------------------------------------------- 1 | import Student from '@modules/students/infra/typeorm/entities/Student'; 2 | import ICreateStudentDTO from '@modules/students/dtos/ICreateStudentDTO'; 3 | 4 | export default interface IStudentsRepository { 5 | create(data: ICreateStudentDTO): Promise; 6 | findByIds(data: string[]): Promise; 7 | findOneOrFail(id: string): Promise; 8 | findByEmail(email: string): Promise; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/students/services/CreateStudentService.ts: -------------------------------------------------------------------------------- 1 | import AppError from '@shared/errors/AppError'; 2 | import { inject, injectable } from 'tsyringe'; 3 | 4 | import Student from '@modules/students/infra/typeorm/entities/Student'; 5 | import IStudentsRepository from '@modules/students/repositories/IStudentsRepository'; 6 | 7 | interface IRequest { 8 | age: number; 9 | name: string; 10 | email: string; 11 | } 12 | 13 | @injectable() 14 | class CreateStudentService { 15 | constructor( 16 | @inject('StudentsRepository') 17 | private studentsRepository: IStudentsRepository, 18 | ) {} 19 | 20 | public async execute({ age, name, email }: IRequest): Promise { 21 | const findStudentWithSameEmail = await this.studentsRepository.findByEmail( 22 | email, 23 | ); 24 | 25 | if (findStudentWithSameEmail) { 26 | throw new AppError( 27 | "There's already a student registered with that email. Please, try it again with another email.", 28 | ); 29 | } 30 | 31 | const student = await this.studentsRepository.create({ name, email, age }); 32 | 33 | return student; 34 | } 35 | } 36 | 37 | export default CreateStudentService; 38 | -------------------------------------------------------------------------------- /src/modules/students/validators/createStudent.ts: -------------------------------------------------------------------------------- 1 | import { celebrate, Joi, Segments } from 'celebrate'; 2 | 3 | const createStudentValidator = celebrate({ 4 | [Segments.BODY]: { 5 | age: Joi.number().integer().positive().required(), 6 | name: Joi.string().required(), 7 | email: Joi.string().email().required(), 8 | }, 9 | }); 10 | 11 | export default createStudentValidator; 12 | -------------------------------------------------------------------------------- /src/modules/teachers/dtos/ICreateTeacherDTO.ts: -------------------------------------------------------------------------------- 1 | export default interface ICreateTeacherDTO { 2 | age: number; 3 | name: string; 4 | email: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/teachers/infra/http/controllers/TeachersController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | 3 | import { container } from 'tsyringe'; 4 | 5 | import Teacher from '@modules/teachers/infra/typeorm/entities/Teacher'; 6 | import CreateTeacherService from '@modules/teachers/services/CreateTeacherService'; 7 | 8 | class TeachersController { 9 | public async create( 10 | request: Request, 11 | response: Response, 12 | ): Promise> { 13 | const { age, name, email } = request.body; 14 | 15 | const createTeacher = container.resolve(CreateTeacherService); 16 | 17 | const teacher = await createTeacher.execute({ age, name, email }); 18 | 19 | return response.json(teacher); 20 | } 21 | } 22 | 23 | export default TeachersController; 24 | -------------------------------------------------------------------------------- /src/modules/teachers/infra/http/routes/teachers.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import TeachersController from '@modules/teachers/infra/http/controllers/TeachersController'; 4 | import createTeacherValidator from '@modules/teachers/validators/createTeacher'; 5 | 6 | const teachersRouter = Router(); 7 | const teachersController = new TeachersController(); 8 | 9 | teachersRouter.post('/', createTeacherValidator, teachersController.create); 10 | 11 | export default teachersRouter; 12 | -------------------------------------------------------------------------------- /src/modules/teachers/infra/typeorm/entities/Teacher.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | OneToMany, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | PrimaryGeneratedColumn, 8 | } from 'typeorm'; 9 | 10 | import Class from '@modules/classes/infra/typeorm/entities/Class'; 11 | 12 | @Entity('teachers') 13 | class Teacher { 14 | @Column('uuid') 15 | @PrimaryGeneratedColumn('uuid') 16 | id: string; 17 | 18 | @Column('text') 19 | name: string; 20 | 21 | @Column('text') 22 | email: string; 23 | 24 | @Column('integer') 25 | age: number; 26 | 27 | @OneToMany(() => Class, classRegister => classRegister.teacher) 28 | classes: Class[]; 29 | 30 | @CreateDateColumn() 31 | created_at: Date; 32 | 33 | @UpdateDateColumn() 34 | updated_at: Date; 35 | } 36 | 37 | export default Teacher; 38 | -------------------------------------------------------------------------------- /src/modules/teachers/infra/typeorm/repositories/TeachersRepository.ts: -------------------------------------------------------------------------------- 1 | import { getRepository, Repository } from 'typeorm'; 2 | 3 | import ICreateTeacherDTO from '@modules/teachers/dtos/ICreateTeacherDTO'; 4 | import ITeachersRepository from '@modules/teachers/repositories/ITeachersRepository'; 5 | import Teacher from '@modules/teachers/infra/typeorm/entities/Teacher'; 6 | 7 | class TeachersRepository implements ITeachersRepository { 8 | private ormRepository: Repository; 9 | 10 | constructor() { 11 | this.ormRepository = getRepository(Teacher); 12 | } 13 | 14 | public async create({ 15 | age, 16 | name, 17 | email, 18 | }: ICreateTeacherDTO): Promise { 19 | const teacher = this.ormRepository.create({ name, email, age }); 20 | 21 | await this.ormRepository.save(teacher); 22 | 23 | return teacher; 24 | } 25 | 26 | public async findByEmail(email: string): Promise { 27 | const teacher = await this.ormRepository.findOne({ where: { email } }); 28 | 29 | return teacher; 30 | } 31 | 32 | public async findById(id: string): Promise { 33 | const teacher = await this.ormRepository.findOne(id); 34 | 35 | return teacher; 36 | } 37 | } 38 | 39 | export default TeachersRepository; 40 | -------------------------------------------------------------------------------- /src/modules/teachers/repositories/ITeachersRepository.ts: -------------------------------------------------------------------------------- 1 | import Teacher from '@modules/teachers/infra/typeorm/entities/Teacher'; 2 | import ICreateTeacherDTO from '@modules/teachers/dtos/ICreateTeacherDTO'; 3 | 4 | export default interface ITeachersRepository { 5 | create(data: ICreateTeacherDTO): Promise; 6 | findById(id: string): Promise; 7 | findByEmail(email: string): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/teachers/services/CreateTeacherService.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from 'tsyringe'; 2 | 3 | import Teacher from '@modules/teachers/infra/typeorm/entities/Teacher'; 4 | import ITeachersRepository from '@modules/teachers/repositories/ITeachersRepository'; 5 | import AppError from '@shared/errors/AppError'; 6 | 7 | interface IRequest { 8 | age: number; 9 | name: string; 10 | email: string; 11 | } 12 | 13 | @injectable() 14 | class CreateTeacherService { 15 | constructor( 16 | @inject('TeachersRepository') 17 | private teachersRepository: ITeachersRepository, 18 | ) {} 19 | 20 | public async execute({ age, name, email }: IRequest): Promise { 21 | const findTeacherWithSameEmail = await this.teachersRepository.findByEmail( 22 | email, 23 | ); 24 | 25 | if (findTeacherWithSameEmail) { 26 | throw new AppError( 27 | "There's already a teacher registered with that email. Please, try it again with another email.", 28 | ); 29 | } 30 | 31 | const teacher = await this.teachersRepository.create({ age, name, email }); 32 | 33 | return teacher; 34 | } 35 | } 36 | 37 | export default CreateTeacherService; 38 | -------------------------------------------------------------------------------- /src/modules/teachers/validators/createTeacher.ts: -------------------------------------------------------------------------------- 1 | import { celebrate, Segments, Joi } from 'celebrate'; 2 | 3 | const createTeacherValidator = celebrate({ 4 | [Segments.BODY]: { 5 | name: Joi.string().required(), 6 | email: Joi.string().email().required(), 7 | age: Joi.number().integer().positive().required(), 8 | }, 9 | }); 10 | 11 | export default createTeacherValidator; 12 | -------------------------------------------------------------------------------- /src/shared/container/index.ts: -------------------------------------------------------------------------------- 1 | import { container } from 'tsyringe'; 2 | 3 | import ICustomersRepository from '@modules/customers/repositories/ICustomersRepository'; 4 | import CustomersRepository from '@modules/customers/infra/typeorm/repositories/CustomersRepository'; 5 | 6 | import IProductsRepository from '@modules/products/repositories/IProductsRepository'; 7 | import ProductsRepository from '@modules/products/infra/typeorm/repositories/ProductsRepository'; 8 | 9 | import IOrdersRepository from '@modules/orders/repositories/IOrdersRepository'; 10 | import OrdersRepository from '@modules/orders/infra/typeorm/repositories/OrdersRepository'; 11 | 12 | import IStudentsRepository from '@modules/students/repositories/IStudentsRepository'; 13 | import StudentsRepository from '@modules/students/infra/typeorm/repositories/StudentsRepository'; 14 | 15 | import ITeachersRepository from '@modules/teachers/repositories/ITeachersRepository'; 16 | import TeachersRepository from '@modules/teachers/infra/typeorm/repositories/TeachersRepository'; 17 | import IClassesRepository from '@modules/classes/repositories/IClassesRepository'; 18 | import ClassesRepository from '@modules/classes/infra/typeorm/repositories/ClassesRepository'; 19 | 20 | container.registerSingleton( 21 | 'CustomersRepository', 22 | CustomersRepository, 23 | ); 24 | 25 | container.registerSingleton( 26 | 'ProductsRepository', 27 | ProductsRepository, 28 | ); 29 | 30 | container.registerSingleton( 31 | 'OrdersRepository', 32 | OrdersRepository, 33 | ); 34 | 35 | container.registerSingleton( 36 | 'StudentsRepository', 37 | StudentsRepository, 38 | ); 39 | 40 | container.registerSingleton( 41 | 'TeachersRepository', 42 | TeachersRepository, 43 | ); 44 | 45 | container.registerSingleton( 46 | 'ClassesRepository', 47 | ClassesRepository, 48 | ); 49 | -------------------------------------------------------------------------------- /src/shared/errors/AppError.ts: -------------------------------------------------------------------------------- 1 | class AppError { 2 | public readonly message: string; 3 | 4 | public readonly statusCode: number; 5 | 6 | constructor(message: string, statusCode = 400) { 7 | this.message = message; 8 | this.statusCode = statusCode; 9 | } 10 | } 11 | 12 | export default AppError; 13 | -------------------------------------------------------------------------------- /src/shared/handlers/errorsHandler.ts: -------------------------------------------------------------------------------- 1 | import { Response, Request, NextFunction } from 'express'; 2 | 3 | import AppError from '@shared/errors/AppError'; 4 | 5 | const errorsHandler = ( 6 | error: Error, 7 | _request: Request, 8 | response: Response, 9 | _: NextFunction, 10 | ): Response => { 11 | if (error instanceof AppError) { 12 | return response.status(error.statusCode).json({ 13 | status: 'error', 14 | message: error.message, 15 | }); 16 | } 17 | 18 | // eslint-disable-next-line no-console 19 | console.error(error); 20 | 21 | return response.status(500).json({ 22 | status: 'error', 23 | message: 'Internal Server Error', 24 | }); 25 | }; 26 | 27 | export default errorsHandler; 28 | -------------------------------------------------------------------------------- /src/shared/infra/http/app.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'express-async-errors'; 3 | 4 | import express from 'express'; 5 | import cors from 'cors'; 6 | import { errors as validationErrorsHandler } from 'celebrate'; 7 | 8 | import createConnection from '@shared/infra/typeorm'; 9 | import errorsHandler from '@shared/handlers/errorsHandler'; 10 | import '@shared/container'; 11 | 12 | import routes from './routes'; 13 | 14 | createConnection(); 15 | 16 | const app = express(); 17 | 18 | app.use(cors()); 19 | app.use(express.json()); 20 | app.use(routes); 21 | 22 | app.use(validationErrorsHandler()); 23 | app.use(errorsHandler); 24 | 25 | export default app; 26 | -------------------------------------------------------------------------------- /src/shared/infra/http/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | import customersRouter from '@modules/customers/infra/http/routes/customers.routes'; 4 | import productsRouter from '@modules/products/infra/http/routes/products.routes'; 5 | import ordersRouter from '@modules/orders/infra/http/routes/orders.routes'; 6 | 7 | import studentsRouter from '@modules/students/infra/http/routes/students.routes'; 8 | import teachersRouter from '@modules/teachers/infra/http/routes/teachers.routes'; 9 | import classesRouter from '@modules/classes/infra/http/routes/classes.routes'; 10 | 11 | const routes = Router(); 12 | 13 | routes.use('/customers', customersRouter); 14 | routes.use('/products', productsRouter); 15 | routes.use('/orders', ordersRouter); 16 | 17 | routes.use('/students', studentsRouter); 18 | routes.use('/teachers', teachersRouter); 19 | routes.use('/classes', classesRouter); 20 | 21 | export default routes; 22 | -------------------------------------------------------------------------------- /src/shared/infra/http/server.ts: -------------------------------------------------------------------------------- 1 | import app from './app'; 2 | 3 | app.listen(3333, () => { 4 | // eslint-disable-next-line no-console 5 | console.log('🚀 Server started on port 3333!'); 6 | }); 7 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/index.ts: -------------------------------------------------------------------------------- 1 | import { createConnection, getConnectionOptions, Connection } from 'typeorm'; 2 | 3 | export default async (name = 'default'): Promise => { 4 | const defaultOptions = await getConnectionOptions(); 5 | 6 | return createConnection( 7 | Object.assign(defaultOptions, { 8 | name, 9 | database: 10 | process.env.NODE_ENV === 'test' 11 | ? 'gostack_desafio09_tests' 12 | : defaultOptions.database, 13 | }), 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598266682467-create_customers_table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from 'typeorm'; 2 | 3 | export default class CreateCustomersTable1598266682467 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable( 7 | new Table({ 8 | name: 'customers', 9 | columns: [ 10 | { 11 | name: 'id', 12 | type: 'varchar', 13 | generationStrategy: 'uuid', 14 | default: 'uuid_generate_v4()', 15 | isPrimary: true, 16 | }, 17 | { 18 | name: 'name', 19 | type: 'varchar', 20 | isNullable: false, 21 | }, 22 | { 23 | name: 'email', 24 | type: 'varchar', 25 | isNullable: false, 26 | }, 27 | { 28 | name: 'created_at', 29 | type: 'date', 30 | default: 'now()', 31 | }, 32 | { 33 | name: 'updated_at', 34 | type: 'date', 35 | default: 'now()', 36 | }, 37 | ], 38 | }), 39 | ); 40 | } 41 | 42 | public async down(queryRunner: QueryRunner): Promise { 43 | await queryRunner.dropTable('customers'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598269989216-CreateProductTable.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from 'typeorm'; 2 | 3 | export default class CreateProductTable1598269989216 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable( 7 | new Table({ 8 | name: 'products', 9 | columns: [ 10 | { 11 | name: 'id', 12 | type: 'varchar', 13 | default: 'uuid_generate_v4()', 14 | isPrimary: true, 15 | generationStrategy: 'uuid', 16 | }, 17 | { 18 | name: 'name', 19 | type: 'varchar', 20 | isNullable: false, 21 | }, 22 | { 23 | name: 'price', 24 | type: 'decimal', 25 | scale: 2, 26 | precision: 5, 27 | isNullable: false, 28 | }, 29 | { 30 | name: 'quantity', 31 | type: 'integer', 32 | isNullable: false, 33 | }, 34 | { 35 | name: 'created_at', 36 | type: 'date', 37 | default: 'now()', 38 | }, 39 | { 40 | name: 'updated_at', 41 | type: 'date', 42 | default: 'now()', 43 | }, 44 | ], 45 | }), 46 | ); 47 | } 48 | 49 | public async down(queryRunner: QueryRunner): Promise { 50 | await queryRunner.dropTable('products'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598279750914-CreateOrderTable.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from 'typeorm'; 2 | 3 | export default class CreateOrderTable1598279750914 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable( 7 | new Table({ 8 | name: 'orders', 9 | columns: [ 10 | { 11 | name: 'id', 12 | type: 'varchar', 13 | default: 'uuid_generate_v4()', 14 | isPrimary: true, 15 | generationStrategy: 'uuid', 16 | }, 17 | { 18 | name: 'customer_id', 19 | type: 'varchar', 20 | default: 'uuid_generate_v4()', 21 | generationStrategy: 'uuid', 22 | }, 23 | { 24 | name: 'created_at', 25 | type: 'timestamp', 26 | default: 'now()', 27 | }, 28 | { 29 | name: 'updated_at', 30 | type: 'timestamp', 31 | default: 'now()', 32 | }, 33 | ], 34 | foreignKeys: [ 35 | { 36 | name: 'customer_order_fk', 37 | onUpdate: 'CASCADE', 38 | onDelete: 'CASCADE', 39 | columnNames: ['customer_id'], 40 | referencedColumnNames: ['id'], 41 | referencedTableName: 'customers', 42 | }, 43 | ], 44 | }), 45 | ); 46 | } 47 | 48 | public async down(queryRunner: QueryRunner): Promise { 49 | await queryRunner.dropTable('orders'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598353275953-CreateOrderProductsTable.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from 'typeorm'; 2 | 3 | export default class CreateOrderProductsTable1598353275953 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable( 7 | new Table({ 8 | name: 'orders_products', 9 | columns: [ 10 | { 11 | name: 'id', 12 | type: 'uuid', 13 | isPrimary: true, 14 | generationStrategy: 'uuid', 15 | }, 16 | { 17 | name: 'price', 18 | type: 'decimal', 19 | precision: 5, 20 | scale: 2, 21 | }, 22 | { 23 | name: 'quantity', 24 | type: 'integer', 25 | }, 26 | { 27 | name: 'order_id', 28 | type: 'varchar', 29 | }, 30 | { 31 | name: 'product_id', 32 | type: 'varchar', 33 | }, 34 | { 35 | name: 'created_at', 36 | type: 'timestamp', 37 | default: 'now()', 38 | }, 39 | { 40 | name: 'updated_at', 41 | type: 'timestamp', 42 | default: 'now()', 43 | }, 44 | ], 45 | foreignKeys: [ 46 | { 47 | name: 'orders_order_products_fk', 48 | onUpdate: 'CASCADE', 49 | onDelete: 'CASCADE', 50 | columnNames: ['order_id'], 51 | referencedColumnNames: ['id'], 52 | referencedTableName: 'orders', 53 | }, 54 | { 55 | name: 'order_products_products_fk', 56 | onUpdate: 'CASCADE', 57 | onDelete: 'CASCADE', 58 | columnNames: ['product_id'], 59 | referencedColumnNames: ['id'], 60 | referencedTableName: 'products', 61 | }, 62 | ], 63 | }), 64 | ); 65 | } 66 | 67 | public async down(queryRunner: QueryRunner): Promise { 68 | await queryRunner.dropTable('orders_products'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598359381489-UpdateOrdersProductsIdColumn.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; 2 | 3 | export default class UpdateOrdersProductsIdColumn1598359381489 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.changeColumn( 7 | 'orders_products', 8 | 'id', 9 | new TableColumn({ 10 | name: 'id', 11 | type: 'uuid', 12 | default: 'uuid_generate_v4()', 13 | isPrimary: true, 14 | generationStrategy: 'uuid', 15 | }), 16 | ); 17 | } 18 | 19 | public async down(queryRunner: QueryRunner): Promise { 20 | await queryRunner.changeColumn( 21 | 'orders_products', 22 | 'id', 23 | new TableColumn({ 24 | name: 'id', 25 | type: 'uuid', 26 | isPrimary: true, 27 | generationStrategy: 'uuid', 28 | }), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598364937210-UpdatePricePrecisions.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; 2 | 3 | export default class UpdatePricePrecisions1598364937210 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.changeColumn( 7 | 'orders_products', 8 | 'price', 9 | new TableColumn({ 10 | name: 'price', 11 | type: 'decimal', 12 | precision: 10, 13 | scale: 2, 14 | }), 15 | ); 16 | 17 | await queryRunner.changeColumn( 18 | 'products', 19 | 'price', 20 | new TableColumn({ 21 | name: 'price', 22 | type: 'decimal', 23 | precision: 10, 24 | scale: 2, 25 | }), 26 | ); 27 | } 28 | 29 | public async down(queryRunner: QueryRunner): Promise { 30 | await queryRunner.changeColumn( 31 | 'orders_products', 32 | 'price', 33 | new TableColumn({ 34 | name: 'price', 35 | type: 'decimal', 36 | precision: 5, 37 | scale: 2, 38 | }), 39 | ); 40 | 41 | await queryRunner.changeColumn( 42 | 'products', 43 | 'price', 44 | new TableColumn({ 45 | name: 'price', 46 | type: 'decimal', 47 | precision: 5, 48 | scale: 2, 49 | }), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598871336598-Student.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'; 2 | 3 | export default class Student1598871336598 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.createTable( 6 | new Table({ 7 | name: 'students', 8 | columns: [ 9 | new TableColumn({ 10 | name: 'id', 11 | type: 'uuid', 12 | default: 'uuid_generate_v4()', 13 | isPrimary: true, 14 | generationStrategy: 'uuid', 15 | }), 16 | new TableColumn({ 17 | name: 'name', 18 | type: 'text', 19 | }), 20 | new TableColumn({ 21 | name: 'email', 22 | type: 'text', 23 | }), 24 | new TableColumn({ 25 | name: 'age', 26 | type: 'integer', 27 | }), 28 | new TableColumn({ 29 | name: 'created_at', 30 | type: 'timestamp', 31 | default: 'now()', 32 | }), 33 | new TableColumn({ 34 | name: 'updated_at', 35 | type: 'timestamp', 36 | default: 'now()', 37 | }), 38 | ], 39 | }), 40 | ); 41 | } 42 | 43 | public async down(queryRunner: QueryRunner): Promise { 44 | await queryRunner.dropTable('students'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598874390567-Teacher.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'; 2 | 3 | export default class Teacher1598874390567 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.createTable( 6 | new Table({ 7 | name: 'teachers', 8 | columns: [ 9 | new TableColumn({ 10 | name: 'id', 11 | type: 'uuid', 12 | default: 'uuid_generate_v4()', 13 | isPrimary: true, 14 | generationStrategy: 'uuid', 15 | }), 16 | new TableColumn({ 17 | name: 'name', 18 | type: 'text', 19 | }), 20 | new TableColumn({ 21 | name: 'email', 22 | type: 'text', 23 | }), 24 | new TableColumn({ 25 | name: 'age', 26 | type: 'integer', 27 | }), 28 | new TableColumn({ 29 | name: 'created_at', 30 | type: 'timestamp', 31 | default: 'now()', 32 | }), 33 | new TableColumn({ 34 | name: 'updated_at', 35 | type: 'timestamp', 36 | default: 'now()', 37 | }), 38 | ], 39 | }), 40 | ); 41 | } 42 | 43 | public async down(queryRunner: QueryRunner): Promise { 44 | await queryRunner.dropTable('teachers'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598879822720-Classes.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'; 2 | 3 | export default class Classes1598879822720 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.createTable( 6 | new Table({ 7 | name: 'classes', 8 | columns: [ 9 | new TableColumn({ 10 | name: 'id', 11 | type: 'uuid', 12 | default: 'uuid_generate_v4()', 13 | isPrimary: true, 14 | generationStrategy: 'uuid', 15 | }), 16 | new TableColumn({ 17 | name: 'subject', 18 | type: 'text', 19 | }), 20 | new TableColumn({ 21 | name: 'teacher_id', 22 | type: 'uuid', 23 | }), 24 | new TableColumn({ 25 | name: 'created_at', 26 | type: 'timestamp', 27 | }), 28 | new TableColumn({ 29 | name: 'updated_at', 30 | type: 'timestamp', 31 | }), 32 | ], 33 | foreignKeys: [ 34 | { 35 | name: 'class_teachers_fk', 36 | onUpdate: 'CASCADE', 37 | onDelete: 'SET NULL', 38 | columnNames: ['teacher_id'], 39 | referencedColumnNames: ['id'], 40 | referencedTableName: 'teachers', 41 | }, 42 | ], 43 | }), 44 | ); 45 | } 46 | 47 | public async down(queryRunner: QueryRunner): Promise { 48 | await queryRunner.dropTable('classes'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598883848517-AddNowDefaultToTimestampColumns.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; 2 | 3 | export default class AddNowDefaultToTimestampColumns1598883848517 4 | implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.changeColumn( 7 | 'classes', 8 | new TableColumn({ 9 | name: 'created_at', 10 | type: 'timestamp', 11 | }), 12 | new TableColumn({ 13 | name: 'created_at', 14 | type: 'timestamp', 15 | default: 'now()', 16 | }), 17 | ); 18 | 19 | await queryRunner.changeColumn( 20 | 'classes', 21 | new TableColumn({ 22 | name: 'updated_at', 23 | type: 'timestamp', 24 | }), 25 | new TableColumn({ 26 | name: 'updated_at', 27 | type: 'timestamp', 28 | default: 'now()', 29 | }), 30 | ); 31 | } 32 | 33 | public async down(queryRunner: QueryRunner): Promise { 34 | await queryRunner.changeColumn( 35 | 'classes', 36 | new TableColumn({ 37 | name: 'created_at', 38 | type: 'timestamp', 39 | default: 'now()', 40 | }), 41 | new TableColumn({ 42 | name: 'created_at', 43 | type: 'timestamp', 44 | }), 45 | ); 46 | 47 | await queryRunner.changeColumn( 48 | 'classes', 49 | new TableColumn({ 50 | name: 'updated_at', 51 | type: 'timestamp', 52 | default: 'now()', 53 | }), 54 | new TableColumn({ 55 | name: 'updated_at', 56 | type: 'timestamp', 57 | }), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/shared/infra/typeorm/migrations/1598885739547-StudentClasses.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'; 2 | 3 | export default class StudentClasses1598885739547 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.createTable( 6 | new Table({ 7 | name: 'student_classes', 8 | columns: [ 9 | new TableColumn({ 10 | name: 'id', 11 | type: 'uuid', 12 | isPrimary: true, 13 | default: 'uuid_generate_v4()', 14 | generationStrategy: 'uuid', 15 | }), 16 | new TableColumn({ 17 | name: 'student_id', 18 | type: 'uuid', 19 | }), 20 | new TableColumn({ 21 | name: 'class_id', 22 | type: 'uuid', 23 | }), 24 | new TableColumn({ 25 | name: 'updated_at', 26 | type: 'timestamp', 27 | default: 'now()', 28 | }), 29 | new TableColumn({ 30 | name: 'created_at', 31 | type: 'timestamp', 32 | default: 'now()', 33 | }), 34 | ], 35 | foreignKeys: [ 36 | { 37 | name: 'student_classes_students_fk', 38 | onUpdate: 'CASCADE', 39 | onDelete: 'CASCADE', 40 | columnNames: ['student_id'], 41 | referencedColumnNames: ['id'], 42 | referencedTableName: 'students', 43 | }, 44 | { 45 | name: 'student_classes_classes_fk', 46 | onUpdate: 'CASCADE', 47 | onDelete: 'CASCADE', 48 | columnNames: ['class_id'], 49 | referencedColumnNames: ['id'], 50 | referencedTableName: 'classes', 51 | }, 52 | ], 53 | }), 54 | ); 55 | } 56 | 57 | public async down(queryRunner: QueryRunner): Promise { 58 | await queryRunner.dropTable('student_classes'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 6 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist" /* Redirect output structure to the directory. */, 16 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | "baseUrl": "./src" /* Base directory to resolve non-absolute module names. */, 44 | "paths": { 45 | "@modules/*": ["modules/*"], 46 | "@config/*": ["config/*"], 47 | "@shared/*": ["shared/*"] 48 | } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */, 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 65 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 66 | 67 | /* Advanced Options */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | } 70 | } 71 | --------------------------------------------------------------------------------