A empresa que você trabalha precisa criar um MVP (Minimum Viable Product) de uma API que faz o controle de usuários e cursos em que esses usuários serão matriculados. Essa API também precisa ter um controle de acessos, onde alguns recursos podem ser acessados apenas por usuários que fizeram login na aplicação, e outros recursos apenas usuários que fizeram login e tem permissões de administrador podem acessar.
Você foi o desenvolvedor selecionado para implementar o MVP levando em conta o que está sendo requisitado a seguir.
A entrega deve seguir as seguintes regras:
- O código deve estar em TypeScript, caso não esteja a entrega será zerada;
- Deverá ser utilizado um banco de dados postgres para a elaboração da API;
- O nome da tabela, das colunas e demais especificações, devem ser seguidas à risca. Caso tenha divergência, será descontado nota;
- Tenha muita atenção sobre o nome das chaves nos objetos de entrada e saída de cada requisição;
- Na raiz do diretório deve-se conter uma pasta nomeada sql, com dois arquivos:
- createTables.sql: contendo as queries de criação e inserção das tabelas;
- diagram.png/jpg: um arquivo .png ou .jpg contendo o diagrama da tabela;
- caso o arquivo createTables.sql não exista, a entrega será zerada.
Essa entrega possui testes automatizados, portanto:
-
É necessário executar um npm install assim que fizer o clone do repositório para que as depedências dos testes sejam instaladas.
-
É necessário criar um banco de dados separado para a execução dos testes.
- Faça a criação do banco de testes e coloque os dados de conexão dele nas variáveis de ambiente que contém o indicador TEST no nome, assim sua aplicação vai saber em qual banco deve se conectar no momento de executar os testes, evitando inconsistência nos dados.
-
Para que os testes possam ser executados, existe um script de limpeza do banco que utiliza as queries do arquivo createTables.sql para ser executado, por isso é importante seguir as orientações sobre subdiretório sql e seus arquivos à risca.
- Caso o subdiretório sql e o arquivo createTables.sql não estejam com os nomes corretos ou no caminho correto os testes falharão, pois não será possível encontrar as queries a serem executadas;
- Caso o nome de alguma tabela, tipo ou coluna não esteja de acordo com o esperado, os testes também falharão.
-
A organização dos demais arquivos e pastas deve seguir o que foi visto previamente.
-
Todos os pacotes necessários para desenvolver a aplicação devem ser instalados, já que apenas os pacotes de teste foram incluídos no repositório.
- Nome da tabela: users.
- Colunas:
- id: número, incrementação automática e chave primária.
- name: string, tamanho 50 e obrigatório.
- email: string, tamanho 50, obrigatório e único.
- password: string, tamanho 120 e obrigatório.
- admin: booleano, obrigatório e DEFAULT igual a false.
- Nome da tabela: courses.
- Colunas:
- id: número, incrementação automática e chave primária.
- name: string, tamanho 15 e obrigatório.
- description: texto e obrigatório.
- Nome da tabela: userCourses.
- Representa a tabela intermediária que fará a relação N:N entre users e courses.
- Colunas:
- id: número, incrementação automática e chave primária.
- active: booleano, obrigatório e DEFAULT igual a true.
- userId: inteiro, obrigatório e chave estrangeira.
- courseId: inteiro, obrigatório e chave estrangeira.
- Um usuário pode se matricular em vários cursos e um curso pode ter vários usuários matriculados a ele.
- Caso um course seja deletado, todas as relações com users devem ser deletadas automaticamente da tabela intermediária.
- Caso um user seja deletado, todas as relações com courses devem ser deletadas automaticamente da tabela intermediária.
Método | Endpoint | Responsabilidade |
---|---|---|
POST | /users | Cadastrar um novo usuário |
POST | /login | Criar o token de autenticação para um usuário |
GET | /users | Listar todos os usuários |
GET | /users/:id/courses | Listar todos os cursos de um usuário |
-
Deve ser possível criar um usuário enviando o seguinte através do corpo da requisição;
- name: string, campo obrigatório.
- email: string, campo obrigatório.
- password: string, campo obrigatório
- admin: booleano, opcional.
-
A password deve ser armazenada no banco em formato de hash.
-
Não deve ser possível cadastrar um developer enviando um email já cadastrado no banco de dados.
-
Deve ser feita a serialização de dados de entrada e saída usando o zod:
- Todos os campos obrigatórios devem estar no body da requisição.
- O email deve ser um email válido.
- O campo de password NÃO deve ser retornado na resposta.
-
Sucesso:
- Retorno esperado: um objeto contendo os dados do usuário cadastrado, com excessão do hash da password.
- Status esperado: 201 CREATED
-
Falha:
-
Caso o email já cadastrado no banco:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 409 CONFLICT.
-
Caso a validação do zod dê erro:
- Retorno esperado: um objeto contendo as chaves dos campos com erro e a mensagem adequada de cada erro;
- Status esperado: 400 BAD REQUEST.
-
-
Exemplos de retornos:
-
Criando um developer com sucesso:
Dados de entrada: Body: Formato Json // Cadastrando com admin igual a true { "name": "Ugo", "email": "ugo@kenzie.com.br", "password": "1234", "admin": true } // Cadastrando com admin igual a false ou sem enviar o campo de admin { "name": "Ugo", "email": "ugo@kenzie.com.br", "password": "1234" }
Resposta do servidor: Body: Formato Json Status code: 201 CREATED //Retorno ao enviar o campo de admin igual a true { "id": 1, "name": "Ugo", "email": "ugo@kenzie.com.br", "admin": true } //Retoro ao enviar o campo de admin igual a false ou não enviar o campo de admin { "id": 1, "name": "Ugo", "email": "ugo@kenzie.com.br", "admin": false }
-
Tentando cadastrar com um email existente:
Resposta do servidor: Body: Formato Json Status code: 409 CONFLICT { "message": "Email already registered" }
-
Tentando cadastrar com um body inválido:
Dados de entrada: Body: Formato Json // Cadastrando com admin igual a true { "email": "ugo", "password": 1234, "admin": true }
Resposta do servidor: Body: Formato Json Status code: 400 BAD REQUEST { "name": ["Required"], "email": ["Invalid email"], "password": ["Expected string, received number"] }
-
Deve ser possível criar um token jwt enviando o seguinte através do corpo da requisição:
- email: string, campo obrigatório.
- password: string, campo obrigatório
-
Deve ser feita a serialização de dados de entrada usando o zod:
- Todos os campos obrigatórios devem estar no body da requisição.
-
Sucesso:
- Retorno esperado: um objeto contendo o token jwt.
- Status esperado: 20 OK
-
Falha:
-
Caso o email não exista ou a senha esteja incorreta:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 401 UNAUTHORIZED.
-
Caso a validação do zod dê erro:
- Retorno esperado: um objeto contendo as chaves dos campos com erro e a mensagem adequada de cada erro;
- Status esperado: 400 BAD REQUEST.
-
-
Exemplos de retornos:
-
Tentando fazer login com email e senha corretos:
Dados de entrada: Body: Formato Json { "email": "ugo@kenzie.com.br", "password": "1234" }
Resposta do servidor: Body: Formato Json Status code: 200 OK { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ```
-
Tentando fazer login com email não existente ou senha incorreta:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": "Wrong email/password" }
-
Tentando fazer login com um body inválido:
Dados de entrada: Body: Formato Json { "email": "ugo", "password": 1234 }
Resposta do servidor: Body: Formato Json Status code: 400 BAD REQUEST { "email": ["Invalid email"], "password": ["Expected string, received number"] }
-
Deve listar todos os usuários.
-
O retorno da resposta não deve conter o hash da password.
-
É necessário enviar o Bearer token no Header dessa requisição.
-
Apenas usuários logados e que são admin devem tem permissão de acessar essa rota.
-
Sucesso:
- Retorno esperado: um array contendo os objetos de todos os usuários;
- Status esperado: 200 OK;
-
Falha:
-
Caso o token não seja enviado:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja inválido:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja de um usuário cujo admin é false:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
-
Exemplos de retornos:
-
Listando usuários com sucesso:
Resposta do servidor: Body: Formato Json Status code: 200 OK [ { "id": 1, "name": "Ugo", "email": "ugo@kenzie.com.br", "admin": true }, { "id": 2, "name": "Lucas", "email": "lucas@kenzie.com.br", "admin": false } ]
-
Caso o token não seja enviado:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": "Missing bearer token" }
-
Caso o token enviado seja inválido:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": // mensagem padrão da biblioteca }
-
Caso o token pertença a um usuário cujo o admin seja false:
Resposta do servidor: Body: Formato Json Status code: 403 FORBIDDEN { "message": "Insufficient permission" }
-
Deve ser possível listar todos os cursos de um usuário.
-
É necessário enviar o Bearer token no Header dessa requisição.
-
Apenas usuários logados e que são admin devem tem permissão de acessar essa rota.
-
Sucesso:
- Retorno esperado: um array contendo os objetos de todos os cursos vinculados ao usuário logado, juntamente com os dados do usuário;
- Status esperado: 200 OK;
- Use o alias nas colunas do SELECT para retornar os campos de acordo com os nomes mostrados no JSON de exemplo.
-
Falha:
-
Caso o token não seja enviado:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja inválido:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o usuário não esteja matriculado em nenhum curso:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 404 NOT FOUND.
-
Caso o token seja de um usuário cujo admin é false:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 403 FORBIDDEN.
-
-
Exemplos de retornos:
-
Listando cursos do usuário logado com sucesso:
Resposta do servidor: Body: Formato Json Status code: 200 OK [ { "couseId": 1, "courseName": "Frontend", "courseDescription": "HTML, CSS e JavaScript", "userActiveInCourse": true, "userId": 1, "userName": "Ugo" }, { "couseId": 2, "courseName": "React", "courseDescription": "Biblioteca React para construção de frontend", "userActiveInCourse": false, "userId": 1, "userName": "Ugo" } ]
-
Caso o token não seja enviado:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": "Missing bearer token" }
-
Caso o token enviado seja inválido:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": // mensagem padrão da bibliotecInsufficient permissiona }
-
Caso o usuário não esteja matriculado em nenhum curso:
Resposta do servidor: Body: Formato Json Status code: 404 NOT FOUND { "message": "No course found" }
-
Caso o token pertença a um usuário cujo o admin seja false:
Resposta do servidor: Body: Formato Json Status code: 403 FORBIDDEN { "message": "Insufficient permission" }
Método | Endpoint | Responsabilidade |
---|---|---|
POST | /courses | Cadastrar um novo curso |
GET | /courses | Listar todos os cursos |
POST | /courses/:courseId/users/:userId | Matricular o usuário em um curso |
DELETE | /courses/:courseId/users/:userId | Setar matrícula para false do usuário em um curso |
GET | /courses/:id/users | Listar todos os usuários matriculados em um curso |
-
Deve ser possível criar um curso enviando o seguinte através do corpo da requisição:
- name: string, campo obrigatório.
- description: string, campo obrigatório.
-
Apenas usuários logados e que são admin devem tem permissão de acessar essa rota.
-
Deve ser feita a serialização de dados de entrada e saída usando o zod:
- Todos os campos obrigatórios devem estar no body da requisição.
-
Sucesso:
- Retorno esperado: um objeto contendo os dados do curso cadastrado.
- Status esperado: 201 CREATED
-
Falha:
-
Caso a validação do zod dê erro:
- Retorno esperado: um objeto contendo as chaves dos campos com erro e a mensagem adequada de cada erro;
- Status esperado: 400 BAD REQUEST.
-
Caso o token não seja enviado:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja inválido:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja de um usuário cujo admin é false:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
-
Exemplos de retornos:
-
Criando um course com sucesso:
Dados de entrada: Body: Formato Json { "name": "Frontend", "description": "HTML, CSS e JavaScript" }
Resposta do servidor: Body: Formato Json Status code: 201 CREATED { "id": 1, "name": "Frontend", "description": "HTML, CSS e JavaScript" }
-
Tentando cadastrar com um body inválido:
Dados de entrada: Body: Formato Json { "description": 1234 }
Resposta do servidor: Body: Formato Json Status code: 400 BAD REQUEST { "name": ["Required"], "description": ["Expected string, received number"] }
-
Caso o token não seja enviado:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": "Missing bearer token" }
-
Caso o token enviado seja inválido:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": // mensagem padrão da biblioteca }
-
Caso o token pertença a um usuário cujo o admin seja false:
Resposta do servidor: Body: Formato Json Status code: 403 FORBIDDEN { "message": "Insufficient permission" }
-
Deve listar todos os cursos.
-
Sucesso:
- Retorno esperado: um array contendo os objetos de todos os cursos;
- Status esperado: 200 OK;
-
Exemplos de retornos:
-
Listando usuários com sucesso:
Resposta do servidor: Body: Formato Json Status code: 200 OK [ { "id": 1, "name": "Frontend", "description": "HTML, CSS e JavaScript" }, { "id": 2, "name": "React", "description": "Frontend com a biblioteca React" } ]
-
Deve ser possível matricular o usuário em um curso, enviando o id do curso e o id do user no parâmetro de rota:
- Essa rota irá usar a tabela intermediária userCourses para vincular o usuário ao curso que ele será matriculado.
- O campo active deve ser colocado com true
-
Apenas usuários logados e que são admin devem tem permissão de acessar essa rota.
-
Sucesso:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 201 CREATED
-
Falha:
-
Caso o course ou o user não existam:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 404 NOT FOUND.
-
Caso o token não seja enviado:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja inválido:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja de um usuário cujo admin é false:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
-
Exemplos de retornos:
-
Criando um course com sucesso:
Dados de entrada: Body: Formato Json Resposta do servidor: Body: Formato Json Status code: 201 CREATED { "message": "User successfully vinculed to course" }
-
Caso o course ou o user não existam:
Resposta do servidor: Body: Formato Json Status code: 404 NOT FOUND { "message": "User/course not found" }
-
Caso o token não seja enviado:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": "Missing bearer token" }
-
Caso o token enviado seja inválido:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": // mensagem padrão da biblioteca }
-
Caso o token pertença a um usuário cujo o admin seja false:
Resposta do servidor: Body: Formato Json Status code: 403 FORBIDDEN { "message": "Insufficient permission" }
-
Deve ser possível inativar a matricula de um usuário em um curso, enviando o id do curso e o id do user no parâmetro de rota:
- Essa rota irá atualizar o valor do campo active da tabela intermediária userCourses para false.
-
Apenas usuários logados e que são admin devem tem permissão de acessar essa rota.
-
Sucesso:
- Retorno esperado: não deve retornar nenhum dado;
- Status esperado: 204 NO BODY
-
Falha:
-
Caso o course ou o user não existam:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 404 NOT FOUND.
-
Caso o token não seja enviado:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja inválido:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja de um usuário cujo admin é false:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
-
Exemplos de retornos:
-
Criando um course com sucesso:
Dados de entrada: Body: Formato Json Resposta do servidor: Body: Formato Json Status code: 204 NO BODY -
Caso o course ou o user não existam:
Resposta do servidor: Body: Formato Json Status code: 404 NOT FOUND { "message": "User/course not found" }
-
Caso o token não seja enviado:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": "Missing bearer token" }
-
Caso o token enviado seja inválido:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": // mensagem padrão da biblioteca }
-
Caso o token pertença a um usuário cujo o admin seja false:
Resposta do servidor: Body: Formato Json Status code: 403 FORBIDDEN { "message": "Insufficient permission" }
-
Deve ser possível listar todos os usuários vinculados a um curso.
-
É necessário enviar o Bearer token no Header dessa requisição.
-
Apenas usuários logados e que são admin devem tem permissão de acessar essa rota.
-
Sucesso:
- Retorno esperado: um array contendo os objetos de todos os usuários vinculados a um curso, juntamente com os dados do curso
- Status esperado: 200 OK;
- Use o alias nas colunas do SELECT para retornar os campos de acordo com os nomes mostrados no JSON de exemplo.
-
Falha:
-
Caso o token não seja enviado:
- Retorno esperado: um objeto contendo a chave message com uma mensagem adequada;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja inválido:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
Caso o token seja de um usuário cujo admin é false:
- Retorno esperado: um objeto contendo a chave message com a mensagem de erro retornada pelo lib do jwt;
- Status esperado: 401 UNAUTHORIZED.
-
-
Exemplos de retornos:
-
Listando cursos do usuário logado com sucesso:
Resposta do servidor: Body: Formato Json Status code: 200 OK [ { "userId": 1, "userName": "Ugo", "couseId": 1, "courseName": "Frontend", "courseDescription": "HTML, CSS e JavaScript", "userActiveInCourse": true }, { "userId": 2, "userName": "Lucas", "couseId": 1, "courseName": "Frontend", "courseDescription": "HTML, CSS e JavaScript", "userActiveInCourse": true } ]
-
Caso o token não seja enviado:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": "Missing bearer token" }
-
Caso o token enviado seja inválido:
Resposta do servidor: Body: Formato Json Status code: 401 UNAUTHORIZED { "message": // mensagem padrão da biblioteca }
-
Caso o token pertença a um usuário cujo o admin seja false:
Resposta do servidor: Body: Formato Json Status code: 403 FORBIDDEN { "message": "Insufficient permission" }