diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..309ca81d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +dist +node_modules +.tool-versions +README.md +src/db.test.ts +docker-compose.yml +init.sql \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 00000000..fec96708 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +.tool-versions +dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile old mode 100644 new mode 100755 index baec7ba2..da48e983 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1,12 @@ -#TODO Configure o Dockerfile +FROM node:20.18.0-alpine + +WORKDIR /usr/src/app + +COPY . . +RUN npm install + +RUN npm run build + +EXPOSE 3000 + +CMD ["node dist/index.js"] \ No newline at end of file diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/docker-compose.yml b/docker-compose.yml old mode 100644 new mode 100755 diff --git a/init.sql b/init.sql old mode 100644 new mode 100755 index e2c36c6f..7237f63b --- a/init.sql +++ b/init.sql @@ -1,5 +1,18 @@ +CREATE DATABASE test_db; + USE test_db; ---TODO Crie a tabela de user; +CREATE TABLE user ( + id INT PRIMARY KEY AUTO_INCREMENT, + firstName VARCHAR(100) NOT NULL, + lastName VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL, +); ---TODO Crie a tabela de posts; +CREATE TABLE post ( + id INT PRIMARY KEY AUTO_INCREMENT, + title VARCHAR(100) NOT NULL, + description VARCHAR(100) NOT NULL, + userId INT NOT NULL, + FOREIGN KEY (userId) REFERENCES user(id) +); \ No newline at end of file diff --git a/ormconfig.json b/ormconfig.json new file mode 100755 index 00000000..36f4bf09 --- /dev/null +++ b/ormconfig.json @@ -0,0 +1,10 @@ +{ + "type": "mysql", + "host": "localhost", + "port": 3306, + "username": "root", + "password": "password", + "database": "test_db", + "entities": ["./src/entity/*.ts"], + "synchronize": true +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json old mode 100644 new mode 100755 index febfdc4f..b98c7af9 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,10 @@ "": { "name": "node-starter", "dependencies": { + "@nestjs/common": "^10.4.8", + "@nestjs/typeorm": "^10.0.2", "axios": "^1.5.0", + "class-validator": "^0.14.1", "express": "^4.18.2", "mysql2": "^3.6.1", "reflect-metadata": "^0.1.13", @@ -145,6 +148,138 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/common": { + "version": "10.4.8", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.8.tgz", + "integrity": "sha512-PVor9dxihg3F2LMnVNkQu42vUmea2+qukkWXUSumtVKDsBo7X7jnZWXtF5bvNTcYK7IYL4/MM4awNfJVJcJpFg==", + "license": "MIT", + "dependencies": { + "iterare": "1.2.1", + "tslib": "2.7.0", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/common/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "node_modules/@nestjs/core": { + "version": "10.4.8", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.8.tgz", + "integrity": "sha512-Kdi9rDZdlCkGL2AK9XuJ24bZp2YFV6dWBdogGsAHSP5u95wfnSkhduxHZy6q/i1nFFiLASUHabL8Jwr+bmD22Q==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@nuxtjs/opencollective": "0.3.2", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "3.3.0", + "tslib": "2.7.0", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/core/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT", + "peer": true + }, + "node_modules/@nestjs/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD", + "peer": true + }, + "node_modules/@nestjs/typeorm": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", + "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", + "license": "MIT", + "dependencies": { + "uuid": "9.0.1" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -292,6 +427,12 @@ "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", "dev": true }, + "node_modules/@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -587,6 +728,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/cli-highlight": { "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", @@ -689,6 +841,13 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "license": "MIT", + "peer": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -936,6 +1095,13 @@ "node": ">= 0.10.0" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT", + "peer": true + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1340,6 +1506,15 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -1354,6 +1529,12 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.14.tgz", + "integrity": "sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ==", + "license": "MIT" + }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -1546,6 +1727,27 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1794,6 +1996,16 @@ "rimraf": "bin.js" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2118,6 +2330,13 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "peer": true + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -2432,6 +2651,18 @@ "node": ">=14.17" } }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -2472,6 +2703,15 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "devOptional": true }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2480,6 +2720,24 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 46cdba25..100254f5 --- a/package.json +++ b/package.json @@ -8,11 +8,14 @@ "test": "ts-node src/db.test.ts" }, "dependencies": { + "@nestjs/common": "^10.4.8", + "@nestjs/typeorm": "^10.0.2", + "axios": "^1.5.0", + "class-validator": "^0.14.1", "express": "^4.18.2", "mysql2": "^3.6.1", - "typeorm": "^0.3.17", "reflect-metadata": "^0.1.13", - "axios": "^1.5.0" + "typeorm": "^0.3.17" }, "devDependencies": { "@types/express": "^4.17.17", diff --git a/src/db.test.ts b/src/db.test.ts old mode 100644 new mode 100755 diff --git a/src/entity/Post.ts b/src/entity/Post.ts old mode 100644 new mode 100755 index a1f68038..f827362e --- a/src/entity/Post.ts +++ b/src/entity/Post.ts @@ -1,3 +1,24 @@ -import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"; +import { IsString, IsNotEmpty, MaxLength } from "class-validator"; +import { User } from './User'; -//TODO Crie a entidade de Post +@Entity() +export class Post { + @PrimaryGeneratedColumn() + id!: number; + + @Column() + @IsString() + @IsNotEmpty() + @MaxLength(100) + title?: string; + + @Column() + @IsString() + @IsNotEmpty() + @MaxLength(100) + description?: string; + + @ManyToOne(() => User, user => user.id) + userId?: User; +} \ No newline at end of file diff --git a/src/entity/User.ts b/src/entity/User.ts old mode 100644 new mode 100755 index a8e22632..2a2890f2 --- a/src/entity/User.ts +++ b/src/entity/User.ts @@ -1,3 +1,30 @@ -import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"; +import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"; +import { IsString, IsNotEmpty, MaxLength, IsEmail } from "class-validator"; +import { Post } from "./Post"; -//TODO Crie a entidade de User +@Entity() +export class User { + @PrimaryGeneratedColumn() + id!: number; + + @Column() + @IsString() + @IsNotEmpty() + @MaxLength(100) + firstName?: string; + + @Column() + @IsString() + @IsNotEmpty() + @MaxLength(100) + lastName?: string; + + @Column() + @IsString() + @IsNotEmpty() + @IsEmail() + email?: string; + + @OneToMany(() => Post, post => post.userId) + posts?: Post[]; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts old mode 100644 new mode 100755 index 1af52a84..54377dbf --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import express from 'express'; import { DataSource } from 'typeorm'; import { User } from './entity/User'; import { Post } from './entity/Post'; +import { validate } from 'class-validator'; const app = express(); app.use(express.json()); @@ -34,11 +35,50 @@ const initializeDatabase = async () => { initializeDatabase(); app.post('/users', async (req, res) => { -// Crie o endpoint de users + const { firstName, lastName, email } = req.body; + + const user = new User(); + user.firstName = firstName; + user.lastName = lastName; + user.email = email; + + try { + const errors = await validate(user); + if (errors.length > 0) { + return res.status(400).json({ errors }); + } + const savedUser = await AppDataSource.manager.save(user); + return res.status(201).json(savedUser); + } catch (err) { + console.error("Error saving user:", err); + return res.status(500).send("Internal Server Error"); + } }); app.post('/posts', async (req, res) => { -// Crie o endpoint de posts + const { title, description, userId } = req.body; + + const user = await AppDataSource.manager.findOneBy(User, { id: userId }); + if (user == null) { + return res.status(404).send("Error creating post: User not found"); + } + + const post = new Post(); + post.title = title; + post.description = description; + post.userId = userId; + + try { + const errors = await validate(post); + if (errors.length > 0) { + return res.status(400).json({ errors }); + } + const savedPost = await AppDataSource.manager.save(post); + return res.status(201).json(savedPost); + } catch (err) { + console.error("Error saving post:", err); + res.status(500).send("Internal Server Error"); + } }); const PORT = process.env.PORT || 3000; diff --git a/tsconfig.json b/tsconfig.json old mode 100644 new mode 100755