back-end app
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
13
README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
### Scripts
|
||||
|
||||
| Script | Target |
|
||||
| ------------------------- | -------------------------------------------------- |
|
||||
| `npm run dev` | Run API in **development** environment |
|
||||
| `npm start` | Run API in **production** environment |
|
||||
| `npm run migrate` | Create database tables |
|
||||
| `npm run seed` | Populate database tables |
|
||||
|
||||
|
||||
### API Docs
|
||||
To view the API documentation, run the API and access [http://localhost:3333/api-docs](http://localhost:3333/api-docs) in your browser
|
||||
BIN
exercises/gif/abdutora.gif
Normal file
|
After Width: | Height: | Size: 414 KiB |
BIN
exercises/gif/barra_cross.gif
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
exercises/gif/corda_cross.gif
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
exercises/gif/crucifixo_reto.gif
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
exercises/gif/desenvolvimento_maquina.gif
Normal file
|
After Width: | Height: | Size: 375 KiB |
BIN
exercises/gif/elevacao_lateral_com_halteres_sentado.gif
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
exercises/gif/encolhimento_com_barra.gif
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
exercises/gif/encolhimento_com_halteres.gif
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
exercises/gif/extensor_de_pernas.gif
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
exercises/gif/frances_deitado_com_halteres.gif
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
exercises/gif/leg_press_45_graus.gif
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
exercises/gif/levantamento_terra.gif
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
exercises/gif/martelo_em_pe.gif
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
exercises/gif/neck-press.gif
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
exercises/gif/pulley_atras.gif
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
exercises/gif/pulley_frontal.gif
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
exercises/gif/remada_baixa.gif
Normal file
|
After Width: | Height: | Size: 351 KiB |
BIN
exercises/gif/rosca_alternada_com_banco_inclinado.gif
Normal file
|
After Width: | Height: | Size: 414 KiB |
BIN
exercises/gif/rosca_direta_barra_reta.gif
Normal file
|
After Width: | Height: | Size: 308 KiB |
BIN
exercises/gif/rosca_punho.gif
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
exercises/gif/rosca_scott_barra_w.gif
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
exercises/gif/serrote.gif
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
exercises/gif/stiff.gif
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
exercises/gif/supino_inclinado_com_barra.gif
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
exercises/gif/supino_reto_com_barra.gif
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
exercises/gif/triceps_testa.gif
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
exercises/thumb/abdutora.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
exercises/thumb/barra_cross.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
exercises/thumb/corda_cross.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
exercises/thumb/crucifixo_reto.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
exercises/thumb/desenvolvimento_maquina.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
exercises/thumb/elevacao_lateral_com_halteres_sentado.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
exercises/thumb/encolhimento_com_barra.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
exercises/thumb/encolhimento_com_halteres.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
exercises/thumb/extensor_de_pernas.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
exercises/thumb/frances_deitado_com_halteres.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
exercises/thumb/leg_press_45_graus.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
exercises/thumb/levantamento_terra.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
exercises/thumb/martelo_em_pe.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
exercises/thumb/neck-press.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
exercises/thumb/pulley_atras.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
exercises/thumb/pulley_frontal.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
exercises/thumb/remada_baixa.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
exercises/thumb/rosca_alternada_com_banco_inclinado.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
exercises/thumb/rosca_direta_barra_reta.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
exercises/thumb/rosca_punho.png
Normal file
|
After Width: | Height: | Size: 8 KiB |
BIN
exercises/thumb/rosca_scott_barra_w.png
Normal file
|
After Width: | Height: | Size: 8 KiB |
BIN
exercises/thumb/serrote.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
exercises/thumb/stiff.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
exercises/thumb/supino_inclinado_com_barra.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
exercises/thumb/supino_reto_com_barra.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
exercises/thumb/triceps_testa.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
1
insomnia.json
Normal file
17
knexfile.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
development: {
|
||||
client: "sqlite3",
|
||||
connection: {
|
||||
filename: path.resolve(__dirname, "src", "database", "database.db")
|
||||
},
|
||||
migrations: {
|
||||
directory: path.resolve(__dirname, "src", "database", "migrations")
|
||||
},
|
||||
seeds: {
|
||||
directory: path.resolve(__dirname, "src", "database", "seeds")
|
||||
},
|
||||
useNullAsDefault: true
|
||||
}
|
||||
};
|
||||
4564
package-lock.json
generated
Normal file
29
package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"description": "API desenvolvida para ser utilizada no módulo de consumo de API na trilha de React Native do Ignite.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node ./src/server.js",
|
||||
"dev": "nodemon ./src/server.js",
|
||||
"migrate": "knex migrate:latest",
|
||||
"seed": "knex seed:run"
|
||||
},
|
||||
"author": "Rodrigo Gonçalves Santana",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.5",
|
||||
"express": "^4.18.1",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^2.2.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"sqlite3": "^5.0.11",
|
||||
"swagger-ui-express": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.19"
|
||||
}
|
||||
}
|
||||
6
src/configs/auth.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
jwt: {
|
||||
secret: "rodrigo",
|
||||
expiresIn: "10s"
|
||||
},
|
||||
};
|
||||
24
src/configs/upload.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const multer = require("multer");
|
||||
const crypto = require("crypto");
|
||||
const path = require("path");
|
||||
|
||||
const TMP_FOLDER = path.resolve(__dirname, "..", "..", "tmp");
|
||||
const UPLOADS_FOLDER = path.resolve(TMP_FOLDER, "uploads");
|
||||
|
||||
const MULTER = {
|
||||
storage: multer.diskStorage({
|
||||
destination: TMP_FOLDER,
|
||||
filename(request, file, callback) {
|
||||
const fileHash = crypto.randomBytes(10).toString("hex");
|
||||
const fileName = `${fileHash}-${file.originalname}`;
|
||||
|
||||
return callback(null, fileName);
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
TMP_FOLDER,
|
||||
UPLOADS_FOLDER,
|
||||
MULTER
|
||||
}
|
||||
21
src/controllers/ExercisesController.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const knex = require("../database");
|
||||
|
||||
class ExercisesController {
|
||||
async index(request, response) {
|
||||
const { group } = request.params;
|
||||
|
||||
const exercises = await knex("exercises").where({ group }).orderBy("name");
|
||||
|
||||
return response.json(exercises);
|
||||
}
|
||||
|
||||
async show(request, response) {
|
||||
const { id } = request.params;
|
||||
|
||||
const exercise = await knex("exercises").where({ id }).first();
|
||||
|
||||
return response.json(exercise);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExercisesController;
|
||||
13
src/controllers/GroupsController.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const knex = require("../database");
|
||||
|
||||
class GroupsController {
|
||||
async index(request, response) {
|
||||
const groups = await knex("exercises").select("group").groupBy("group").orderBy("group");
|
||||
|
||||
const formattedGroups = groups.map(item => item.group);
|
||||
|
||||
return response.json(formattedGroups);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GroupsController;
|
||||
62
src/controllers/HistoryController.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
const AppError = require("../utils/AppError");
|
||||
const knex = require("../database");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
class HistoryController {
|
||||
async index(request, response) {
|
||||
const user_id = request.user.id;
|
||||
|
||||
const history = await knex("history")
|
||||
.select(
|
||||
"history.id",
|
||||
"history.user_id",
|
||||
"history.exercise_id",
|
||||
"exercises.name",
|
||||
"exercises.group",
|
||||
"history.created_at"
|
||||
)
|
||||
.leftJoin("exercises", "exercises.id", "=", "history.exercise_id")
|
||||
.where({ user_id }).orderBy("history.created_at", "desc");
|
||||
|
||||
const days = [];
|
||||
|
||||
for (let exercise of history) {
|
||||
const day = dayjs(exercise.created_at).format('DD.MM.YYYY');
|
||||
|
||||
if (!days.includes(day)) {
|
||||
days.push(day);
|
||||
}
|
||||
}
|
||||
|
||||
const exercisesByDay = days.map(day => {
|
||||
const exercises = history
|
||||
.filter((exercise) => dayjs(exercise.created_at).format('DD.MM.YYYY') === day).
|
||||
map((exercise) => {
|
||||
return {
|
||||
...exercise,
|
||||
hour: dayjs(exercise.created_at).format('HH:mm')
|
||||
}
|
||||
});
|
||||
|
||||
return ({ title: day, data: exercises });
|
||||
});
|
||||
|
||||
|
||||
return response.json(exercisesByDay);
|
||||
}
|
||||
|
||||
async create(request, response) {
|
||||
const { exercise_id } = request.body;
|
||||
const user_id = request.user.id;
|
||||
|
||||
if (!exercise_id) {
|
||||
throw new AppError("Informe o id do exercício.");
|
||||
}
|
||||
|
||||
await knex("history").insert({ user_id, exercise_id });
|
||||
|
||||
return response.status(201).json();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HistoryController;
|
||||
39
src/controllers/SessionsController.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
const knex = require("../database");
|
||||
const { compare } = require("bcryptjs");
|
||||
const { sign } = require("jsonwebtoken");
|
||||
const AppError = require("../utils/AppError");
|
||||
const authConfig = require("../configs/auth");
|
||||
const GenerateRefreshToken = require("../providers/GenerateRefreshToken");
|
||||
const GenerateToken = require("../providers/GenerateToken");
|
||||
|
||||
class SessionsController {
|
||||
async create(request, response) {
|
||||
const { email, password } = request.body;
|
||||
|
||||
const user = await knex("users").where({ email }).first();
|
||||
|
||||
if (!user) {
|
||||
throw new AppError("E-mail e/ou senha incorreta.", 404);
|
||||
}
|
||||
|
||||
const passwordMatched = await compare(password, user.password);
|
||||
|
||||
if (!passwordMatched) {
|
||||
throw new AppError("E-mail e/ou senha incorreta.", 404);
|
||||
}
|
||||
|
||||
const generateTokenProvider = new GenerateToken();
|
||||
const token = await generateTokenProvider.execute(user.id);
|
||||
|
||||
await knex("users_tokens").where({ user_id: user.id }).delete();
|
||||
|
||||
const generateRefreshToken = new GenerateRefreshToken();
|
||||
generateRefreshToken.execute(user.id, token);
|
||||
|
||||
delete user.password;
|
||||
|
||||
response.status(201).json({ token, user });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SessionsController;
|
||||
30
src/controllers/UserAvatarController.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
const knex = require("../database");
|
||||
const DiskStorage = require("../providers/DiskStorage");
|
||||
|
||||
class UserAvatarController {
|
||||
async update(request, response) {
|
||||
const user_id = request.user.id;
|
||||
const avatarFilename = request.file.filename;
|
||||
|
||||
const diskStorage = new DiskStorage();
|
||||
|
||||
const user = await knex("users").where({ id: user_id }).first();
|
||||
|
||||
if (!user) {
|
||||
throw new AppError("Somente usuários autenticados podem mudar o avatar", 401);
|
||||
}
|
||||
|
||||
if (user.avatar) {
|
||||
await diskStorage.deleteFile(user.avatar);
|
||||
}
|
||||
|
||||
const filename = await diskStorage.saveFile(avatarFilename);
|
||||
user.avatar = filename;
|
||||
|
||||
await knex("users").where({ id: user_id }).update(user);
|
||||
|
||||
return response.json(user);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserAvatarController;
|
||||
39
src/controllers/UserRefreshToken.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
const knex = require("../database");
|
||||
const AppError = require("../utils/AppError");
|
||||
const GenerateRefreshToken = require("../providers/GenerateRefreshToken");
|
||||
const GenerateToken = require("../providers/GenerateToken");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
class UserRefreshToken {
|
||||
async create(request, response) {
|
||||
const { token } = request.body;
|
||||
|
||||
if (!token) {
|
||||
throw new AppError("Informe o token de autenticação.", 401);
|
||||
}
|
||||
|
||||
const userToken = await knex("users_tokens").where({ token }).first();
|
||||
|
||||
if (!userToken) {
|
||||
throw new AppError("Refresh token não encontrado para este usuário.", 404);
|
||||
}
|
||||
|
||||
const generateTokenProvider = new GenerateToken();
|
||||
const refreshToken = await generateTokenProvider.execute(userToken.user_id);
|
||||
|
||||
const refreshTokenExpired = dayjs().isAfter(dayjs.unix(userToken.expires_in));
|
||||
|
||||
if (refreshTokenExpired) {
|
||||
await knex("users_tokens").where({ user_id: userToken.user_id }).delete();
|
||||
|
||||
const generateRefreshToken = new GenerateRefreshToken();
|
||||
await generateRefreshToken.execute(userToken.user_id, refreshToken);
|
||||
|
||||
return response.json({ token: refreshToken });
|
||||
}
|
||||
|
||||
return response.json({ token });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRefreshToken;
|
||||
71
src/controllers/UsersController.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
const knex = require("../database");
|
||||
const { hash, compare } = require("bcryptjs");
|
||||
const AppError = require("../utils/AppError");
|
||||
|
||||
class UsersController {
|
||||
async create(request, response) {
|
||||
const { name, email, password } = request.body;
|
||||
|
||||
if (!name || !email || !password) {
|
||||
throw new AppError("Informe todos os campos (nome, email e senha).");
|
||||
}
|
||||
|
||||
const checkUserExists = await knex("users").where({ email }).first();
|
||||
|
||||
if (checkUserExists) {
|
||||
throw new AppError("Este e-mail já está em uso.");
|
||||
}
|
||||
|
||||
const hashedPassword = await hash(password, 8);
|
||||
|
||||
await knex("users").insert({
|
||||
name,
|
||||
email,
|
||||
password: hashedPassword
|
||||
});
|
||||
|
||||
return response.status(201).json();
|
||||
}
|
||||
|
||||
async update(request, response) {
|
||||
const { name, password, old_password } = request.body;
|
||||
const user_id = request.user.id;
|
||||
|
||||
const user = await knex("users").where({ id: user_id }).first();
|
||||
|
||||
if (!user) {
|
||||
throw new AppError("Usuário não encontrado", 404);
|
||||
}
|
||||
|
||||
user.name = name ?? user.name;
|
||||
|
||||
if (password && !old_password) {
|
||||
throw new AppError(
|
||||
"Você precisa informar a senha antiga para definir a nova senha.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (!password && old_password) {
|
||||
throw new AppError(
|
||||
"Informe a nova senha.",
|
||||
);
|
||||
}
|
||||
|
||||
if (password && old_password) {
|
||||
const checkOldPassword = await compare(old_password, user.password);
|
||||
|
||||
if (!checkOldPassword) {
|
||||
throw new AppError("A senha antiga não confere.");
|
||||
}
|
||||
|
||||
user.password = await hash(password, 8);
|
||||
}
|
||||
|
||||
await knex("users").where({ id: user_id }).update(user);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UsersController;
|
||||
BIN
src/database/database.db
Normal file
6
src/database/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const config = require("../../knexfile");
|
||||
const knex = require("knex");
|
||||
|
||||
const connection = knex(config.development);
|
||||
|
||||
module.exports = connection;
|
||||
11
src/database/migrations/20220822190845_createUser.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
exports.up = knex => knex.schema.createTable("users", table => {
|
||||
table.increments("id");
|
||||
table.text("name").notNullable();
|
||||
table.text("email").notNullable();
|
||||
table.text("password").notNullable();
|
||||
table.text("avatar");
|
||||
table.timestamp("created_at").default(knex.fn.now());
|
||||
table.timestamp("updated_at").default(knex.fn.now());
|
||||
});
|
||||
|
||||
exports.down = knex => knex.schema.dropTable("users");
|
||||
13
src/database/migrations/20220823105515_createExercises.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
exports.up = knex => knex.schema.createTable("exercises", table => {
|
||||
table.increments("id");
|
||||
table.text("name").notNullable();
|
||||
table.integer("series").notNullable();
|
||||
table.integer("repetitions").notNullable();
|
||||
table.text("group").notNullable();
|
||||
table.text("demo").notNullable();
|
||||
table.text("thumb").notNullable();
|
||||
table.timestamp("created_at").default(knex.fn.now());
|
||||
table.timestamp("updated_at").default(knex.fn.now());
|
||||
});
|
||||
|
||||
exports.down = knex => knex.schema.dropTable("exercises");
|
||||
10
src/database/migrations/20220823114449_createHistory.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
exports.up = knex => knex.schema.createTable("history", table => {
|
||||
table.increments("id");
|
||||
|
||||
table.integer("user_id").references("id").inTable("users");
|
||||
table.integer("exercise_id").references("id").inTable("exercises");
|
||||
|
||||
table.timestamp("created_at").default(knex.fn.now());
|
||||
});
|
||||
|
||||
exports.down = knex => knex.schema.dropTable("history");
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
exports.up = knex => knex.schema.createTable("users_tokens", table => {
|
||||
table.increments("id");
|
||||
table.integer("expires_in")
|
||||
table.integer("user_id").references("id").inTable("users");
|
||||
table.text("token").notNullable();
|
||||
table.timestamp("created_at").default(knex.fn.now());
|
||||
});
|
||||
|
||||
exports.down = knex => knex.schema.dropTable("users_tokens");
|
||||
213
src/database/seeds/createExercises.js
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
exports.seed = async function (knex) {
|
||||
await knex('exercises').del()
|
||||
await knex('exercises').insert([
|
||||
{
|
||||
name: 'Supino inclinado com barra',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'peito',
|
||||
demo: 'supino_inclinado_com_barra.gif',
|
||||
thumb: 'supino_inclinado_com_barra.png',
|
||||
},
|
||||
{
|
||||
name: 'Crucifixo reto',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'peito',
|
||||
demo: 'crucifixo_reto.gif',
|
||||
thumb: 'crucifixo_reto.png'
|
||||
},
|
||||
{
|
||||
name: 'Supino reto com barra',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'peito',
|
||||
demo: 'supino_reto_com_barra.gif',
|
||||
thumb: 'supino_reto_com_barra.png'
|
||||
},
|
||||
{
|
||||
name: 'Francês deitado com halteres',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'tríceps',
|
||||
demo: 'frances_deitado_com_halteres.gif',
|
||||
thumb: 'frances_deitado_com_halteres.png'
|
||||
},
|
||||
{
|
||||
name: 'Corda Cross',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'tríceps',
|
||||
demo: 'corda_cross.gif',
|
||||
thumb: 'corda_cross.png'
|
||||
},
|
||||
{
|
||||
name: 'Barra Cross',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'tríceps',
|
||||
demo: 'barra_cross.gif',
|
||||
thumb: 'barra_cross.png'
|
||||
},
|
||||
{
|
||||
name: 'Tríceps testa',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'tríceps',
|
||||
demo: 'triceps_testa.gif',
|
||||
thumb: 'triceps_testa.png'
|
||||
},
|
||||
{
|
||||
name: 'Levantamento terra',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'costas',
|
||||
demo: 'levantamento_terra.gif',
|
||||
thumb: 'levantamento_terra.png'
|
||||
},
|
||||
{
|
||||
name: 'Pulley frontal',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'costas',
|
||||
demo: 'pulley_frontal.gif',
|
||||
thumb: 'pulley_frontal.png'
|
||||
},
|
||||
{
|
||||
name: 'Pulley atrás',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'costas',
|
||||
demo: 'pulley_atras.gif',
|
||||
thumb: 'pulley_atras.png'
|
||||
},
|
||||
{
|
||||
name: 'Remada baixa',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'costas',
|
||||
demo: 'remada_baixa.gif',
|
||||
thumb: 'remada_baixa.png'
|
||||
},
|
||||
{
|
||||
name: 'Serrote',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'costas',
|
||||
demo: 'serrote.gif',
|
||||
thumb: 'serrote.png'
|
||||
},
|
||||
{
|
||||
name: 'Rosca alternada com banco inclinado',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'bíceps',
|
||||
demo: 'rosca_alternada_com_banco_inclinado.gif',
|
||||
thumb: 'rosca_alternada_com_banco_inclinado.png'
|
||||
},
|
||||
{
|
||||
name: 'Rosca Scott barra w',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'bíceps',
|
||||
demo: 'rosca_scott_barra_w.gif',
|
||||
thumb: 'rosca_scott_barra_w.png'
|
||||
},
|
||||
{
|
||||
name: 'Rosca direta barra reta',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'bíceps',
|
||||
demo: 'rosca_direta_barra_reta.gif',
|
||||
thumb: 'rosca_direta_barra_reta.png'
|
||||
},
|
||||
{
|
||||
name: 'Martelo em pé',
|
||||
series: 3,
|
||||
repetitions: 12,
|
||||
group: 'bíceps',
|
||||
demo: 'martelo_em_pe.gif',
|
||||
thumb: 'martelo_em_pe.png'
|
||||
},
|
||||
{
|
||||
name: 'Rosca punho',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'antebraço',
|
||||
demo: 'rosca_punho.gif',
|
||||
thumb: 'rosca_punho.png'
|
||||
},
|
||||
{
|
||||
name: 'Leg press 45 graus',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'pernas',
|
||||
demo: 'leg_press_45_graus.gif',
|
||||
thumb: 'leg_press_45_graus.png'
|
||||
},
|
||||
{
|
||||
name: 'Extensor de pernas',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'pernas',
|
||||
demo: 'extensor_de_pernas.gif',
|
||||
thumb: 'extensor_de_pernas.png'
|
||||
},
|
||||
{
|
||||
name: 'Abdutora',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'pernas',
|
||||
demo: 'abdutora.gif',
|
||||
thumb: 'abdutora.png'
|
||||
},
|
||||
{
|
||||
name: 'Stiff',
|
||||
series: 4,
|
||||
repetitions: 12,
|
||||
group: 'pernas',
|
||||
demo: 'stiff.gif',
|
||||
thumb: 'stiff.png',
|
||||
},
|
||||
{
|
||||
name: 'Neck Press',
|
||||
series: 4,
|
||||
repetitions: 10,
|
||||
group: 'ombro',
|
||||
demo: 'neck-press.gif',
|
||||
thumb: 'neck-press.png'
|
||||
},
|
||||
{
|
||||
name: 'Desenvolvimento maquina',
|
||||
series: 3,
|
||||
repetitions: 10,
|
||||
group: 'ombro',
|
||||
demo: 'desenvolvimento_maquina.gif',
|
||||
thumb: 'desenvolvimento_maquina.png'
|
||||
},
|
||||
{
|
||||
name: 'Elevação lateral com halteres sentado',
|
||||
series: 4,
|
||||
repetitions: 10,
|
||||
group: 'ombro',
|
||||
demo: 'elevacao_lateral_com_halteres_sentado.gif',
|
||||
thumb: 'elevacao_lateral_com_halteres_sentado.png'
|
||||
},
|
||||
{
|
||||
name: 'Encolhimento com halteres',
|
||||
series: 4,
|
||||
repetitions: 10,
|
||||
group: 'trapézio',
|
||||
demo: 'encolhimento_com_halteres.gif',
|
||||
thumb: 'encolhimento_com_halteres.png'
|
||||
},
|
||||
{
|
||||
name: 'Encolhimento com barra',
|
||||
series: 4,
|
||||
repetitions: 10,
|
||||
group: 'trapézio',
|
||||
demo: 'encolhimento_com_barra.gif',
|
||||
thumb: 'encolhimento_com_barra.png'
|
||||
}
|
||||
]);
|
||||
};
|
||||
502
src/docs/swagger.json
Normal file
|
|
@ -0,0 +1,502 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "Ignite Gym API",
|
||||
"description": "API developed by Rodrigo Gonçalves to be used in Ignite training in the mobile backend integration.",
|
||||
"version": "1.0.0",
|
||||
"contact": {
|
||||
"email": "rodrigo.rgtic@gmail.com"
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/users": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Create",
|
||||
"description": "Create a new user",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"name": "Rodrigo",
|
||||
"email": "rodrigo@email.com",
|
||||
"password": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Update",
|
||||
"description": "Update user profile",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": false
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"required": false
|
||||
},
|
||||
"old_password": {
|
||||
"type": "string",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"name": "Rodrigo Gonçalves",
|
||||
"password": "1234",
|
||||
"old_password": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Updated"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/avatar": {
|
||||
"patch": {
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Upload",
|
||||
"description": "Update user profile picture",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"type": "string",
|
||||
"format": "base64"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"avatar": "rodrigo.png"
|
||||
}
|
||||
},
|
||||
"encoding": {
|
||||
"avatar": {
|
||||
"contentType": "image/png, image/jpeg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Updated"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authorized"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sessions": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Sign In",
|
||||
"description": "User authentication",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"email": "rodrigo@email.com",
|
||||
"password": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"example": {
|
||||
"id": 1,
|
||||
"name": "Rodrigo Gonçalves",
|
||||
"email": "rodrigo@email.com",
|
||||
"avatar": "346dab6b457abadbbb2a-49030804.jpg",
|
||||
"created_at": "2022-08-22 19:59:46",
|
||||
"updated_at": "2022-08-22T20:07:45.340Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authorized/Invalid email or password"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sessions/refresh-token": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Refresh Token",
|
||||
"description": "Auth Refresh Token",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InJvZHJpZ29AZW1haWwuY29tIiwiaWF0IjoxNjYxMjc1NDAxLCJleHAiOjE2NjM4Njc0MDEsInN1YiI6IjEifQ.yQqqvmuZrF9ZM0LThzIu8dlwQtmuHdG0C_nwziXWyMo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InJvZHJpZ29AZW1haWwuY29tIiwiaWF0IjoxNjYxMjc1NDE5LCJleHAiOjE2NjM4Njc0MTksInN1YiI6IjEifQ.kQoOrRyGvSkLcFS49ItDcLUEB7pEhbwyPRoEA5sR4ao"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"404": {
|
||||
"description": "Refresh token not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/exercises": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Exercise"
|
||||
],
|
||||
"summary": "Index",
|
||||
"description": "List all exercises",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"example": [
|
||||
{
|
||||
"id": 16,
|
||||
"name": "Martelo em pé",
|
||||
"series": 3,
|
||||
"repetitions": "10 a 12",
|
||||
"group": "bíceps",
|
||||
"created_at": "2022-08-23 11:12:32",
|
||||
"updated_at": "2022-08-23 11:12:32"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Rosca alternada com banco inclinado",
|
||||
"series": 4,
|
||||
"repetitions": "10 a 12",
|
||||
"group": "bíceps",
|
||||
"created_at": "2022-08-23 11:12:32",
|
||||
"updated_at": "2022-08-23 11:12:32"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Rosca direta barra reta",
|
||||
"series": 3,
|
||||
"repetitions": "10 a 12",
|
||||
"group": "bíceps",
|
||||
"created_at": "2022-08-23 11:12:32",
|
||||
"updated_at": "2022-08-23 11:12:32"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Rosca scott barra w",
|
||||
"series": 4,
|
||||
"repetitions": "10 a 12",
|
||||
"group": "bíceps",
|
||||
"created_at": "2022-08-23 11:12:32",
|
||||
"updated_at": "2022-08-23 11:12:32"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/exercises/{id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Exercise"
|
||||
],
|
||||
"summary": "Index",
|
||||
"description": "List all exercises",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"example": {
|
||||
"id": 1,
|
||||
"name": "Supino inclinado com barra",
|
||||
"series": 4,
|
||||
"repetitions": "10 a 12",
|
||||
"group": "peito",
|
||||
"created_at": "2022-08-23 11:12:32",
|
||||
"updated_at": "2022-08-23 11:12:32"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/groups": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Index",
|
||||
"description": "List all groups",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"example": [
|
||||
"Trapézio",
|
||||
"antebraço",
|
||||
"bíceps",
|
||||
"costas",
|
||||
"ombro",
|
||||
"peito",
|
||||
"pernas",
|
||||
"tríceps"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/history": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"history"
|
||||
],
|
||||
"summary": "Index",
|
||||
"description": "List history by user",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"example": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"exercise_id": 1,
|
||||
"name": "Supino inclinado com barra",
|
||||
"group": "peito",
|
||||
"created_at": "2022-08-23 11:55:29"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"user_id": 1,
|
||||
"exercise_id": 2,
|
||||
"name": "Supino inclinado com barra",
|
||||
"group": "peito",
|
||||
"created_at": "2022-08-23 12:16:01"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"history"
|
||||
],
|
||||
"summary": "Index",
|
||||
"description": "Create user exercise history ",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exercise_id": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"example": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"exercise_id": 1,
|
||||
"name": "Supino inclinado com barra",
|
||||
"group": "peito",
|
||||
"created_at": "2022-08-23 11:55:29"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"user_id": 1,
|
||||
"exercise_id": 2,
|
||||
"name": "Supino inclinado com barra",
|
||||
"group": "peito",
|
||||
"created_at": "2022-08-23 12:16:01"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/${filename.png}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Image"
|
||||
],
|
||||
"summary": "Show",
|
||||
"description": "Show image file",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "filename",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/middlewares/ensureAuthenticated.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const { verify } = require("jsonwebtoken");
|
||||
const AppError = require("../utils/AppError");
|
||||
const authConfig = require("../configs/auth");
|
||||
|
||||
async function ensureAuthenticated(request, response, next) {
|
||||
const authHeader = request.headers.authorization;
|
||||
|
||||
if (!authHeader) {
|
||||
throw new AppError("JWT token não informado", 401);
|
||||
}
|
||||
|
||||
|
||||
const [, token] = authHeader.split(" ");
|
||||
|
||||
try {
|
||||
|
||||
const { sub: user_id } = verify(token, authConfig.jwt.secret);
|
||||
|
||||
request.user = {
|
||||
id: Number(user_id),
|
||||
};
|
||||
|
||||
return next();
|
||||
} catch {
|
||||
throw new AppError("token.invalid", 401);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ensureAuthenticated;
|
||||
29
src/providers/DiskStorage.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const uploadConfig = require("../configs/upload");
|
||||
|
||||
class DiskStorage {
|
||||
async saveFile(file) {
|
||||
|
||||
await fs.promises.rename(
|
||||
path.resolve(uploadConfig.TMP_FOLDER, file),
|
||||
path.resolve(uploadConfig.UPLOADS_FOLDER, file),
|
||||
);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
async deleteFile(file) {
|
||||
const filePath = path.resolve(uploadConfig.UPLOADS_FOLDER, file);
|
||||
|
||||
try {
|
||||
await fs.promises.stat(filePath);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.promises.unlink(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DiskStorage;
|
||||
16
src/providers/GenerateRefreshToken.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const knex = require("../database");
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
class GenerateRefreshToken {
|
||||
async execute(userId, newToken) {
|
||||
const expires_in = dayjs().add(15, "second").unix();
|
||||
|
||||
await knex("users_tokens").insert({
|
||||
user_id: userId,
|
||||
expires_in,
|
||||
token: newToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GenerateRefreshToken;
|
||||
17
src/providers/GenerateToken.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const { sign } = require("jsonwebtoken");
|
||||
const authConfig = require("../configs/auth");
|
||||
|
||||
class GenerateToken {
|
||||
async execute(userId) {
|
||||
const { secret, expiresIn } = authConfig.jwt;
|
||||
|
||||
const token = sign({}, secret, {
|
||||
subject: String(userId),
|
||||
expiresIn
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GenerateToken;
|
||||
12
src/routes/exercises.routes.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const { Router } = require("express");
|
||||
|
||||
const ExercisesController = require("../controllers/ExercisesController");
|
||||
|
||||
const exercisesRoutes = Router();
|
||||
|
||||
const exercisesController = new ExercisesController();
|
||||
|
||||
exercisesRoutes.get("/bygroup/:group", exercisesController.index);
|
||||
exercisesRoutes.get("/:id", exercisesController.show);
|
||||
|
||||
module.exports = exercisesRoutes;
|
||||
11
src/routes/group.routes.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
const { Router } = require("express");
|
||||
|
||||
const GroupsController = require("../controllers/GroupsController");
|
||||
|
||||
const groupRoutes = Router();
|
||||
|
||||
const groupsController = new GroupsController();
|
||||
|
||||
groupRoutes.get("/", groupsController.index);
|
||||
|
||||
module.exports = groupRoutes;
|
||||
15
src/routes/history.routes.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const { Router } = require("express");
|
||||
|
||||
const HistoryController = require("../controllers/HistoryController");
|
||||
const ensureAuthenticated = require("../middlewares/ensureAuthenticated");
|
||||
|
||||
const historyRoutes = Router();
|
||||
|
||||
const historyController = new HistoryController();
|
||||
|
||||
historyRoutes.use(ensureAuthenticated);
|
||||
|
||||
historyRoutes.get("/", historyController.index);
|
||||
historyRoutes.post("/", historyController.create);
|
||||
|
||||
module.exports = historyRoutes;
|
||||
18
src/routes/index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const { Router } = require("express");
|
||||
|
||||
const usersRouter = require("./users.routes");
|
||||
const sessionsRouter = require("./sessions.routes");
|
||||
const exercisesRouter = require("./exercises.routes");
|
||||
const groupRouter = require("./group.routes");
|
||||
const historyRouter = require("./history.routes");
|
||||
|
||||
const routes = Router();
|
||||
|
||||
routes.use("/users", usersRouter);
|
||||
routes.use("/sessions", sessionsRouter);
|
||||
|
||||
routes.use("/exercises", exercisesRouter);
|
||||
routes.use("/groups", groupRouter);
|
||||
routes.use("/history", historyRouter);
|
||||
|
||||
module.exports = routes;
|
||||
13
src/routes/sessions.routes.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const { Router } = require("express");
|
||||
|
||||
const SessionsController = require("../controllers/SessionsController");
|
||||
const UserRefreshToken = require("../controllers/UserRefreshToken");
|
||||
|
||||
const sessionsController = new SessionsController();
|
||||
const userRefreshToken = new UserRefreshToken();
|
||||
|
||||
const sessionsRoutes = Router();
|
||||
sessionsRoutes.post("/", sessionsController.create);
|
||||
sessionsRoutes.post("/refresh-token", userRefreshToken.create);
|
||||
|
||||
module.exports = sessionsRoutes;
|
||||
21
src/routes/users.routes.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const { Router } = require("express");
|
||||
const multer = require("multer");
|
||||
|
||||
const uploadConfig = require("../configs/upload");
|
||||
const ensureAuthenticated = require("../middlewares/ensureAuthenticated");
|
||||
|
||||
const UsersController = require("../controllers/UsersController");
|
||||
const UserAvatarController = require("../controllers/UserAvatarController");
|
||||
|
||||
const usersRoutes = Router();
|
||||
|
||||
const usersController = new UsersController();
|
||||
const userAvatarController = new UserAvatarController();
|
||||
|
||||
const upload = multer(uploadConfig.MULTER);
|
||||
|
||||
usersRoutes.post("/", usersController.create);
|
||||
usersRoutes.put("/", ensureAuthenticated, usersController.update);
|
||||
usersRoutes.patch("/avatar", ensureAuthenticated, upload.single("avatar"), userAvatarController.update);
|
||||
|
||||
module.exports = usersRoutes;
|
||||
49
src/server.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
require("express-async-errors");
|
||||
|
||||
const path = require("path");
|
||||
|
||||
const swaggerDocument = require("./docs/swagger.json");
|
||||
const swaggerUI = require("swagger-ui-express");
|
||||
const uploadConfig = require("./configs/upload");
|
||||
const AppError = require("./utils/AppError");
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use("/avatar", express.static(uploadConfig.UPLOADS_FOLDER));
|
||||
|
||||
const demoExercisePath = path.resolve(__dirname, "..", "exercises", "gif")
|
||||
app.use("/exercise/demo", express.static(demoExercisePath));
|
||||
|
||||
const thumbExercisesPath = path.resolve(__dirname, "..", "exercises", "thumb")
|
||||
app.use("/exercise/thumb", express.static(thumbExercisesPath));
|
||||
|
||||
|
||||
const routes = require("./routes");
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cors());
|
||||
|
||||
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocument));
|
||||
|
||||
app.use(routes);
|
||||
|
||||
app.use((err, request, response, next) => {
|
||||
if (err instanceof AppError) {
|
||||
return response.status(err.statusCode).json({
|
||||
status: "error",
|
||||
message: err.message,
|
||||
});
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
|
||||
return response.status(500).json({
|
||||
status: "error",
|
||||
message: "Internal server error",
|
||||
});
|
||||
});
|
||||
|
||||
const PORT = 3333;
|
||||
app.listen(PORT, () => console.log(`Server is running on Port ${PORT}`));
|
||||
11
src/utils/AppError.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class AppError {
|
||||
message;
|
||||
statusCode;
|
||||
|
||||
constructor(message, statusCode = 400) {
|
||||
this.message = message;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppError;
|
||||
0
tmp/.gitkeep
Normal file
0
tmp/uploads/.gitkeep
Normal file
BIN
tmp/uploads/3ba0d7d8e36412093d3b-Rodrigo Gonalves.jpeg
Normal file
|
After Width: | Height: | Size: 70 KiB |