Skip to content
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

Ederson F. F - Dev challenge by Water Services and Technologies #94

Open
wants to merge 13 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
6 changes: 6 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DB_HOST=db
DB_USER=root
DB_PASSWORD=password
DB_NAME=test_db

PORT=3000
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Build
dist/

# Dependency directory
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:16-alpine

WORKDIR /usr/src/dev_test

COPY --chown=node:node package*.json .

RUN npm install

COPY --chown=node:node . .

RUN npm run build

EXPOSE 3000

CMD [ "node", "./dist/index.js" ]
84 changes: 46 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,67 @@
# Descrição do Teste para a Vaga de Desenvolvedor Jr.
![Logo](https://s3.amazonaws.com/enlizt-resources-prod/companies/fde32590-751f-11eb-a39f-5ffd1579e1b3_64_avatar?nocache=1708374010185)

## Contextualização do Desafio
### Water Services and Technologies

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.
# Project challenge with Typescript/Typeorm

## 1º Passo: Criação das Tabelas no `init.sql`
Fork project from challenge created by [Water Services and Technologies](https://github.com/Waterservicestech/dev_test). The main goal is to create a API using a pre-created project, configure docker, entities and endpoints.

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

### 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.
![App Screenshot](https://via.placeholder.com/468x300?text=Water%20Services%20and%20Technologies)

### 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`).
## Environment Variables

---
To run this project, you will need to add the following environment variables to your .env file

## 2º Passo: Criação das Entidades `User` e `Post`
`DB_HOST` - Database hostname(Default: localhost)

Dentro da pasta `src/Entity`, crie as entidades correspondentes às tabelas `User` e `Post`.
`DB_USER` - Database username (Default: root)

---
`DB_PASSWORD` - Database username (Default: password)

## 3º Passo: Configurar endpoints `users` e `posts`
`DB_NAME` - Database username (Default: test_db)

Dentro de `src/index.ts`, configure dois endpoints `users` & `posts`
`PORT` - Database username (Default: 3000)

---
## Build and Run Locally

## 4º Passo: Configuração do Dockerfile
Clone the project

Configure o `Dockerfile` da aplicação para garantir que ela seja construída corretamente no ambiente Docker.
```bash
git clone https://github.com/edersonff/dev_test
```

---
Go to the project directory

## 5º Passo: Teste da Aplicação
```bash
cd dev_test
```

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

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

```
```bash
npm install # yarn
```

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

## 6º Passo: Crie um fork desse repositório e submita o código preenchido nele.
```bash
npm run build # yarn build
```

Start the server

```bash
npm start # yarn start
```

# Hi, I'm Ederson! 👋

<div align="center">

[![portfolio](https://img.shields.io/badge/my_portfolio-000?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ederson.tech/)
[![linkedin](https://img.shields.io/badge/linkedin-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/ederson-franzen-fagundes/)
[![instagram](https://img.shields.io/badge/Instagram-E4405F?style=for-the-badge&logo=instagram&logoColor=white)](https://instagram.com/edersonfff)
[![whatsapp](https://img.shields.io/badge/WhatsApp-25D366?style=for-the-badge&logo=whatsapp&logoColor=white)](https://wa.me/5547996556538)
</div>
17 changes: 9 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
version: '3.8'
version: "3.8"

services:
api:
build: .
env_file: ./.env
ports:
- "3000:3000"
environment:
- DB_HOST=db
- DB_USER=root
- DB_PASSWORD=password
- DB_NAME=test_db
- DB_HOST=$DB_HOST
- DB_USER=$DB_USER
- DB_PASSWORD=$DB_PASSWORD
- DB_NAME=$DB_NAME
depends_on:
- db


db:
image: mysql:8.0
env_file: ./.env
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=test_db
- MYSQL_ROOT_PASSWORD=$DB_PASSWORD
- MYSQL_DATABASE=$DB_NAME
ports:
- "3306:3306"
volumes:
Expand Down
6 changes: 6 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=root
DB_NAME=my_db

PORT=3000
15 changes: 13 additions & 2 deletions init.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
USE test_db;

--TODO Crie a tabela de user;
create table user(
id int primary key auto_increment,
first_name varchar(100) not null,
last_name 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,
user_id int not null,
foreign key (user_id) references User(id)
);
1 change: 1 addition & 0 deletions src/@type/error.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ApiError = { error: string; status: number };
51 changes: 51 additions & 0 deletions src/Service/Post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { DeepPartial } from "typeorm";
import { Post } from "../entity/Post";
import { AppDataSource } from "..";

export class PostService {
static async createPost(post: DeepPartial<Post>) {
const isInvalid = this.isInvalid(post);
if (isInvalid) {
return { error: isInvalid.error, status: 400 };
}

try {
return await AppDataSource.getRepository(Post).save(post);
} catch (err) {
return { error: "Error saving post", status: 500 };
}
}

// TODO: Implement body validation, ex: zod or express-validator.
private static isInvalid(post: DeepPartial<Post>) {
post = this.trim(post);

const postLength = post.title?.length || 0;
const descriptionLength = post.description?.length || 0;

if (postLength === 0 || postLength > 100) {
return {
error: "Post is required and must be between 1 and 100 characters",
};
}

if (descriptionLength === 0 || descriptionLength > 100) {
return {
error: "Description is required and must be between 1 and 100 characte",
};
}

if (!post.userId) {
return { error: "User ID is required" };
}

return false;
}

private static trim(post: DeepPartial<Post>) {
post.title = post.title?.trim();
post.description = post.description?.trim();

return post;
}
}
65 changes: 65 additions & 0 deletions src/Service/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { DeepPartial } from "typeorm";
import { User } from "../entity/User";
import { AppDataSource } from "..";

export class UserService {
static async getUsers() {
return AppDataSource.getRepository(User).find();
}

static async createUser(user: DeepPartial<User>) {
const isInvalid = this.isInvalid(user);
if (isInvalid) {
return { error: isInvalid.error, status: 400 };
}

try {
return await AppDataSource.getRepository(User).save(user);
} catch (err) {
return { error: "Error saving post", status: 500 };
}
}

// TODO: Implement body validation, ex: zod or express-validator.
private static isInvalid(user: DeepPartial<User>) {
user = this.trim(user);

const fNameLength = user.firstName?.length || 0;
const lNameLength = user.lastName?.length || 0;
const emailLength = user.email?.length || 0;

const isEmailFormat = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(user.email || "");

if (fNameLength === 0 || fNameLength > 100) {
return {
error: "User is required and must be between 1 and 100 characters",
};
}

if (lNameLength === 0 || lNameLength > 100) {
return {
error: "Description is required and must be between 1 and 100 characte",
};
}

if (emailLength === 0 || emailLength > 100) {
return {
error: "Email is required and must be between 1 and 100 characters",
};
}

if (!isEmailFormat) {
return { error: "Email is not in the correct format" };
}

return false;
}

private static trim(user: DeepPartial<User>) {
user.firstName = user.firstName?.trim();
user.lastName = user.lastName?.trim();
user.email = user.email?.trim();

return user;
}
}
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({ length: 100 })
title: string;

@Column({ length: 100 })
description: string;

@Column()
userId: number;

@ManyToOne((type) => User, (user) => user.posts)
user: User;
}
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({ length: 100 })
firstName: string;

@Column({ length: 100 })
lastName: string;

@Column({ length: 100 })
email: string;

@OneToMany((_type) => Post, (post) => post.user)
posts: Post[];
}
Loading