Skip to content

[Evandro Costa] - Desafio concluído para a vaga de Desenvolvedor Backend Júnior (remoto) #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
#TODO Configure o Dockerfile
FROM node:20-alpine AS build

WORKDIR /usr/app

COPY package.json package-lock.json* ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev"]
9 changes: 8 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ services:
- DB_PASSWORD=password
- DB_NAME=test_db
depends_on:
- db
db:
condition: service_healthy


db:
Expand All @@ -24,6 +25,12 @@ services:
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 30s
retries: 5
start_period: 0s

volumes:
mysql-data:
17 changes: 15 additions & 2 deletions init.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
USE test_db;

--TODO Crie a tabela de user;
CREATE TABLE IF NOT EXISTS `user` (
id int AUTO_INCREMENT NOT NULL,
firstName varchar(100) NOT NULL,
lastName varchar(100) NOT NULL,
email varchar(100) NOT NULL,
PRIMARY KEY (id)
);

--TODO Crie a tabela de posts;
CREATE TABLE IF NOT EXISTS post (
id int AUTO_INCREMENT NOT NULL,
title varchar(100) NOT NULL,
description varchar(100) NOT NULL,
userId int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (userId) REFERENCES `user`(id)
);
12 changes: 11 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
"test": "ts-node src/db.test.ts"
},
"dependencies": {
"axios": "^1.5.0",
"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",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/express": "^4.17.17",
Expand Down
18 changes: 18 additions & 0 deletions src/controllers/PostController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PostService } from "../services/PostService";
import { Request, Response } from "express"

export class PostController {
private postService = new PostService()

async createPost(req: Request, res: Response) {
const { title, description, userId } = req.body;

try {
const createPost = await this.postService.createPost(title, description, userId)
return res.status(201).json(createPost)
} catch (error) {
console.error("Error while creating post: ", error)
return res.status(500).json({ message: "Internal Server Error"})
}
}
}
18 changes: 18 additions & 0 deletions src/controllers/UserController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { UserService } from "../services/UserService";
import { Request, Response } from "express"

export class UserController {
private userService = new UserService()

async createUser(req: Request, res: Response) {
const { firstName, lastName, email } = req.body;

try {
const createUser = await this.userService.createUser(firstName, lastName, email)
return res.status(201).json(createUser)
} catch(error) {
console.error("Error while creating user: ", error)
return res.status(500).json({ message: "Internal Server Error"})
}
}
}
21 changes: 19 additions & 2 deletions src/entity/Post.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { User } from "./User";

//TODO Crie a entidade de Post
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number

@Column({ type: "varchar", length: 100 })
title: string

@Column({ type: "varchar", length: 100 })
description: string

@ManyToOne(() => User, (user) => user.posts, { nullable: false })
user: User

@Column()
userId: number
}
21 changes: 19 additions & 2 deletions src/entity/User.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Post } from "./Post";

//TODO Crie a entidade de User
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column({ type: "varchar", length: 100 })
firstName: string

@Column({ type: "varchar", length: 100 })
lastName: string

@Column({ type: "varchar", length: 100 })
email: string

@OneToMany(() => Post, (post) => post.user)
posts: Post[]
}
48 changes: 15 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,29 @@
import 'reflect-metadata';
import express from 'express';
import { DataSource } from 'typeorm';
import { User } from './entity/User';
import { Post } from './entity/Post';
import database from './infra/db';
import userRoutes from './routes/userRoutes';
import postRoutes from './routes/postRoutes';

const app = express();
app.use(express.json());

const AppDataSource = new DataSource({
type: "mysql",
host: process.env.DB_HOST || "localhost",
port: 3306,
username: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "password",
database: process.env.DB_NAME || "test_db",
entities: [User,Post],
synchronize: true,
});

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

const initializeDatabase = async () => {
await wait(20000);
const waitForDatabase = async () => {
try {
await AppDataSource.initialize();
console.log("Data Source has been initialized!");
} catch (err) {
console.error("Error during Data Source initialization:", err);
await database.initializeDatabase();
console.log('Database initialized');
} catch (error) {
console.error('Error initializing the database:', error);
process.exit(1);
}
};
};
}

initializeDatabase();
waitForDatabase();

app.post('/users', async (req, res) => {
// Crie o endpoint de users
});

app.post('/posts', async (req, res) => {
// Crie o endpoint de posts
});
app.use("/users", userRoutes)
app.use("/posts", postRoutes)

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
});
34 changes: 34 additions & 0 deletions src/infra/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { DataSource } from 'typeorm';
import { User } from '../entity/User';
import { Post } from '../entity/Post';

const AppDataSource = new DataSource({
type: "mysql",
host: process.env.DB_HOST || "localhost",
port: 3306,
username: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "password",
database: process.env.DB_NAME || "test_db",
entities: [User,Post],
logging: true,
synchronize: true,
});

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

const initializeDatabase = async () => {
try {
await AppDataSource.initialize();
console.log("Data Source has been initialized!");
} catch (err) {
console.error("Error during Data Source initialization:", err);
process.exit(1);
}
};

const database = {
AppDataSource,
initializeDatabase,
}

export default database;
20 changes: 20 additions & 0 deletions src/middlewares/validationMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { z, ZodError, ZodIssue } from "zod";
import { Request, Response, NextFunction } from "express";

export function validateFields(schema: z.ZodObject<any, any>) {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse(req.body)
next()
} catch (error) {
if (error instanceof ZodError) {
const errorformatted = error.flatten((issue: ZodIssue) => ({
message: issue.message,
}));
res.status(400).json({ error: "Invalid data", details: errorformatted.fieldErrors})
} else {
res.status(500).json(({ message: "Internal Server Error"}))
}
}
}
}
15 changes: 15 additions & 0 deletions src/routes/postRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Router } from "express";
import { Request, Response } from "express"
import { PostController } from "../controllers/PostController";
import { validateFields } from "../middlewares/validationMiddleware";
import { postSchemaValidation } from "../schemas/postSchema";

const postRoutes = Router()
const postController = new PostController();

postRoutes.post("/",
validateFields(postSchemaValidation), (req: Request, res: Response) =>
postController.createPost(req, res)
)

export default postRoutes;
15 changes: 15 additions & 0 deletions src/routes/userRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Router } from "express";
import { Request, Response } from "express"
import { UserController } from "../controllers/UserController";
import { validateFields } from "../middlewares/validationMiddleware";
import { userSchemaValidation } from "../schemas/userSchema";

const userRoutes = Router();
const userController = new UserController();

userRoutes.post("/",
validateFields(userSchemaValidation), (req: Request, res: Response) =>
userController.createUser(req, res)
)

export default userRoutes;
7 changes: 7 additions & 0 deletions src/schemas/postSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from "zod";

export const postSchemaValidation = z.object({
title: z.string().min(1).max(100),
description: z.string().min(1).max(100),
userId: z.number().int().positive().min(1),
})
7 changes: 7 additions & 0 deletions src/schemas/userSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from "zod";

export const userSchemaValidation = z.object({
firstName: z.string().min(1).max(100),
lastName: z.string().min(1).max(100),
email: z.string().email(),
})
13 changes: 13 additions & 0 deletions src/services/PostService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Post } from "../entity/Post";
import { User } from "../entity/User";
import database from "../infra/db";

export class PostService {
private PostRepository = database.AppDataSource.getRepository(Post)

async createPost(title: string, description: string, userId: number) : Promise<Post> {
const post = this.PostRepository.create({ title, description, userId })

return await this.PostRepository.save(post);
}
}
12 changes: 12 additions & 0 deletions src/services/UserService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { User } from "../entity/User";
import database from "../infra/db";

export class UserService {
private userRepository = database.AppDataSource.getRepository(User)

async createUser(firstName: string, lastName: string, email: string) : Promise<User> {
const user = this.userRepository.create({ firstName, lastName, email})

return await this.userRepository.save(user);
}
}
Loading