Skip to content

Finished dev test #117

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 8 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
16 changes: 15 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
#TODO Configure o Dockerfile
FROM node:lts-alpine

WORKDIR /home/node/app

COPY package*.json ./

RUN npm install

USER node

COPY --chown=node:node . .

EXPOSE 3000

CMD [ "npm", "run", "start" ]
155 changes: 95 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,95 @@
# Descrição do Teste para a Vaga de Desenvolvedor Jr.

## Contextualização do Desafio

Este teste foi desenvolvido para avaliar suas habilidades práticas em tarefas comuns do dia a dia de um desenvolvedor júnior. Através deste desafio, você terá a oportunidade de demonstrar seu conhecimento na criação de banco de dados, definição de relacionamentos entre tabelas e entidades, além de aplicar boas práticas de desenvolvimento em um ambiente Docker. O objetivo é simular uma situação real de desenvolvimento de uma aplicação simples, onde você deverá criar as estruturas necessárias e garantir que o sistema esteja funcionando corretamente por meio de testes. A conclusão bem-sucedida desta tarefa refletirá seu domínio de conceitos importantes para a vaga.

## 1º Passo: Criação das Tabelas no `init.sql`

Dentro do arquivo `init.sql`, crie as seguintes tabelas:

### Tabela `user`
- **id** – Tipo: `Int`, autoincremental, chave primária (PK).
- **firstName** – Tipo: `Varchar(100)`, não nulo.
- **lastName** – Tipo: `Varchar(100)`, não nulo.
- **email** – Tipo: `Varchar(100)`, não nulo.

### Tabela `post`
- **id** – Tipo: `Int`, autoincremental, chave primária (PK).
- **title** – Tipo: `Varchar(100)`, não nulo.
- **description** – Tipo: `Varchar(100)`, não nulo.
- **userId** – Tipo: `Int`, não nulo (chave estrangeira referenciando a tabela `user`).

---

## 2º Passo: Criação das Entidades `User` e `Post`

Dentro da pasta `src/Entity`, crie as entidades correspondentes às tabelas `User` e `Post`.

---

## 3º Passo: Configurar endpoints `users` e `posts`

Dentro de `src/index.ts`, configure dois endpoints `users` & `posts`

---

## 4º Passo: Configuração do Dockerfile

Configure o `Dockerfile` da aplicação para garantir que ela seja construída corretamente no ambiente Docker.

---

## 5º Passo: Teste da Aplicação

Execute os seguintes comandos para testar a aplicação:

1. **Subir a aplicação utilizando Docker Compose**:
```bash
docker compose up --build
docker exec -it <Container Name> /bin/sh

```

Dentro do container, execute o teste:
```bash
npm test
```

## 6º Passo: Crie um fork desse repositório e submita o código preenchido nele.
Crie um Pull Request para a brach master nos enviando o código
# Dev Test API - Node.js

## Table of Contents

- [Overview](#overview)
- [Requirements](#requirements)
- [Features](#features)
- [Usage](#usage)
- [Run Server](#run-server)
- [Test](#test)
- [Endpoints](#endpoints)

## Overview

This is a [Node.js](https://nodejs.org/en) API project built with [TypeScript](https://www.typescriptlang.org/) and [Express](https://expressjs.com/), designed to manage **users**, **posts**, and their relationships. The API interacts with a [MySQL](https://www.mysql.com/) database using [TypeORM](http://typeorm.io/) for data management. Both the API and the database are containerized using [Docker](https://www.docker.com/).

## Requirements

For this project, the following (essential for execution) resources were used:
- [Node.js](https://nodejs.org/)
- [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/)

## Features

- **Create a user**: A user could be created with `firstName`, `lastName` and `email`. The email field is unique.
- **Create a post**: A post could be created with `title`, `description` and `userId`. The `userId` establishes a relationship with a user.

## Usage

### Run Server

Build and start containers.

```
docker-compose up --build -d
```

The server will run at port `3000` or the specified port.

### Test

First, access API:

```
docker exec -it dev_test-api-1 /bin/sh
```

And then, run test:

```
npm test
```

## Endpoints
- **POST** - `/users`: Create a new user
- Data should be sent in the request body in JSON format:
```
{
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]"
}
```
- The return will be the created user object:
```
{
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"id": 1
}
```
- **POST** - `/posts`: Create a new post
- Data should be sent in the request body in JSON format:
```
{
"title": "Some title",
"description": "some description",
"user": "1"
}
```
- The return will be the created post object:
```
{
"title": "Some title",
"description": "some description",
"user": {
"id": 1,
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]"
},
"id": 1
}
```
42 changes: 42 additions & 0 deletions dist/db.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = __importDefault(require("axios"));
const testUser = {
firstName: "John",
lastName: "Doe",
email: "[email protected]"
};
let userId = null;
async function testCreateUser() {
try {
const response = await axios_1.default.post('http://localhost:3000/users', testUser);
userId = response.data.id;
console.log('User created successfully:', response.data);
}
catch (error) {
console.error('Error creating user:', error);
}
}
const testPost = {
title: "Some message",
description: "Some description",
userId: null
};
async function testCreatePost() {
testPost.userId = userId;
try {
const response = await axios_1.default.post('http://localhost:3000/posts', testPost);
console.log('Post created successfully:', response.data);
}
catch (error) {
console.error('Error creating post:', error);
}
}
async function init() {
await testCreateUser();
await testCreatePost();
}
init();
44 changes: 44 additions & 0 deletions dist/entity/Post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Post = void 0;
const typeorm_1 = require("typeorm");
const User_1 = require("./User");
let Post = class Post {
constructor(data) {
this.title = '';
this.description = '';
if (data) {
Object.assign(this, data);
}
}
};
exports.Post = Post;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)(),
__metadata("design:type", Number)
], Post.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], Post.prototype, "title", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], Post.prototype, "description", void 0);
__decorate([
(0, typeorm_1.ManyToOne)(() => User_1.User, (user) => user.posts, { onDelete: 'CASCADE' }),
__metadata("design:type", User_1.User)
], Post.prototype, "user", void 0);
exports.Post = Post = __decorate([
(0, typeorm_1.Entity)(),
__metadata("design:paramtypes", [Object])
], Post);
49 changes: 49 additions & 0 deletions dist/entity/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.User = void 0;
const typeorm_1 = require("typeorm");
const Post_1 = require("./Post");
let User = class User {
constructor(data) {
this.firstName = '';
this.lastName = '';
this.email = '';
if (data) {
Object.assign(this, data);
}
}
};
exports.User = User;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)(),
__metadata("design:type", Number)
], User.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], User.prototype, "firstName", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], User.prototype, "lastName", void 0);
__decorate([
(0, typeorm_1.Column)({ unique: true }),
__metadata("design:type", String)
], User.prototype, "email", void 0);
__decorate([
(0, typeorm_1.OneToMany)(() => Post_1.Post, (post) => post.user),
__metadata("design:type", Array)
], User.prototype, "posts", void 0);
exports.User = User = __decorate([
(0, typeorm_1.Entity)(),
__metadata("design:paramtypes", [Object])
], User);
77 changes: 77 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
require("dotenv/config");
require("reflect-metadata");
const express_1 = __importDefault(require("express"));
const typeorm_1 = require("typeorm");
const User_1 = require("./entity/User");
const Post_1 = require("./entity/Post");
const zod_1 = require("zod");
const app = (0, express_1.default)();
app.use(express_1.default.json());
const AppDataSource = new typeorm_1.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_1.User, Post_1.Post],
synchronize: true,
logging: true
});
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const initializeDatabase = async () => {
await wait(20000);
try {
await AppDataSource.initialize();
console.log("Data Source has been initialized!");
}
catch (err) {
console.error("Error during Data Source initialization:", err);
process.exit(1);
}
};
initializeDatabase();
app.post('/users', async (req, res) => {
const userBodySchema = zod_1.z.object({
firstName: zod_1.z.string(),
lastName: zod_1.z.string(),
email: zod_1.z.string().email()
});
const { firstName, lastName, email } = userBodySchema.parse(req.body);
const userRepository = AppDataSource.getRepository(User_1.User);
const user = new User_1.User();
user.firstName = firstName;
user.lastName = lastName;
user.email = email;
const userCreated = await userRepository.save(user);
res.status(201).send(userCreated);
});
app.post('/posts', async (req, res) => {
const postBodySchema = zod_1.z.object({
title: zod_1.z.string(),
description: zod_1.z.string(),
userId: zod_1.z.number()
});
const { title, description, userId } = postBodySchema.parse(req.body);
const postRepository = AppDataSource.getRepository(Post_1.Post);
const userRepository = AppDataSource.getRepository(User_1.User);
const user = await userRepository.findOne({ where: { id: userId } });
if (!user) {
throw new Error('User not found');
}
const post = new Post_1.Post();
post.title = title;
post.description = description;
post.user = user;
const postCreated = await postRepository.save(post);
res.status(201).send(postCreated);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Loading