From f18bab145f8fea523e54c61a8487fa0316884e5e Mon Sep 17 00:00:00 2001 From: canove Date: Mon, 14 Sep 2020 12:42:20 -0300 Subject: [PATCH] started migration of user domain to ts --- backend/package.json | 6 + backend/src/@types/express.d.ts | 5 + backend/src/config/database.js | 16 -- backend/src/config/database.ts | 17 ++ backend/src/controllers/OldUserController.js | 181 ++++++++++++++++++ backend/src/controllers/UserController.js | 181 ------------------ backend/src/controllers/UserController.ts | 69 +++++++ backend/src/database/index.js | 36 ---- backend/src/database/index.ts | 53 +++++ backend/src/errors/AppError.ts | 11 ++ backend/src/helpers/CheckSettingsHelper.ts | 18 ++ backend/src/models/User.js | 32 ---- backend/src/models/User.ts | 82 ++++++++ backend/src/{models => modelsOld}/Contact.js | 0 .../ContactCustomField.js | 0 backend/src/{models => modelsOld}/Message.js | 0 backend/src/{models => modelsOld}/Setting.js | 0 backend/src/{models => modelsOld}/Ticket.js | 0 backend/src/modelsOld/User.js | 32 ++++ backend/src/{models => modelsOld}/Whatsapp.js | 0 backend/src/routes/userRoutes.ts | 10 +- backend/src/server.ts | 19 +- backend/src/services/CreateUserService.ts | 53 +++++ backend/src/services/FindUserService.ts | 21 ++ backend/src/services/ListUsersService.ts | 54 ++++++ backend/src/services/UpdateUserService.ts | 61 ++++++ backend/tsconfig.json | 120 ++++++------ backend/yarn.lock | 44 +++++ 28 files changed, 781 insertions(+), 340 deletions(-) create mode 100644 backend/src/@types/express.d.ts delete mode 100644 backend/src/config/database.js create mode 100644 backend/src/config/database.ts create mode 100644 backend/src/controllers/OldUserController.js delete mode 100644 backend/src/controllers/UserController.js create mode 100644 backend/src/controllers/UserController.ts delete mode 100644 backend/src/database/index.js create mode 100644 backend/src/database/index.ts create mode 100644 backend/src/errors/AppError.ts create mode 100644 backend/src/helpers/CheckSettingsHelper.ts delete mode 100644 backend/src/models/User.js create mode 100644 backend/src/models/User.ts rename backend/src/{models => modelsOld}/Contact.js (100%) rename backend/src/{models => modelsOld}/ContactCustomField.js (100%) rename backend/src/{models => modelsOld}/Message.js (100%) rename backend/src/{models => modelsOld}/Setting.js (100%) rename backend/src/{models => modelsOld}/Ticket.js (100%) create mode 100644 backend/src/modelsOld/User.js rename backend/src/{models => modelsOld}/Whatsapp.js (100%) create mode 100644 backend/src/services/CreateUserService.ts create mode 100644 backend/src/services/FindUserService.ts create mode 100644 backend/src/services/ListUsersService.ts create mode 100644 backend/src/services/UpdateUserService.ts diff --git a/backend/package.json b/backend/package.json index 3a192fe..142717d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,16 +21,22 @@ "multer": "^1.4.2", "mysql2": "^2.1.0", "qrcode-terminal": "^0.12.0", + "reflect-metadata": "^0.1.13", "sequelize": "5", "sequelize-cli": "5", + "sequelize-typescript": "^1.1.0", "socket.io": "^2.3.0", "whatsapp-web.js": "^1.8.2", "yup": "^0.29.3" }, "devDependencies": { + "@types/bluebird": "^3.5.32", "@types/cors": "^2.8.7", "@types/express": "^4.17.8", "@types/multer": "^1.4.4", + "@types/node": "^14.10.1", + "@types/validator": "^13.1.0", + "@types/yup": "^0.29.7", "@typescript-eslint/eslint-plugin": "^4.1.0", "@typescript-eslint/parser": "^4.1.0", "eslint": "^7.9.0", diff --git a/backend/src/@types/express.d.ts b/backend/src/@types/express.d.ts new file mode 100644 index 0000000..204f2e0 --- /dev/null +++ b/backend/src/@types/express.d.ts @@ -0,0 +1,5 @@ +declare namespace Express { + export interface Request { + user: { id: string; profile: string }; + } +} diff --git a/backend/src/config/database.js b/backend/src/config/database.js deleted file mode 100644 index 5fae881..0000000 --- a/backend/src/config/database.js +++ /dev/null @@ -1,16 +0,0 @@ -require("dotenv/config"); - -module.exports = { - define: { - charset: "utf8mb4", - collate: "utf8mb4_bin", - }, - dialect: "mysql", - timezone: "-03:00", - host: process.env.DB_HOST, - database: process.env.DB_NAME, - username: process.env.DB_USER, - password: process.env.DB_PASS, - logging: false, - seederStorage: "sequelize", -}; diff --git a/backend/src/config/database.ts b/backend/src/config/database.ts new file mode 100644 index 0000000..33ccc78 --- /dev/null +++ b/backend/src/config/database.ts @@ -0,0 +1,17 @@ +import "dotenv/config"; + +const dbConfig = { + define: { + charset: "utf8mb4", + collate: "utf8mb4_bin" + }, + dialect: "mysql", + timezone: "-03:00", + host: process.env.DB_HOST, + database: process.env.DB_NAME, + username: process.env.DB_USER, + password: process.env.DB_PASS, + logging: false +}; + +export default dbConfig; diff --git a/backend/src/controllers/OldUserController.js b/backend/src/controllers/OldUserController.js new file mode 100644 index 0000000..13f8186 --- /dev/null +++ b/backend/src/controllers/OldUserController.js @@ -0,0 +1,181 @@ +// const Sequelize = require("sequelize"); +// const Yup = require("yup"); +// const { Op } = require("sequelize"); + +// const User = require("../models/User"); +// const Setting = require("../models/Setting"); + +// const { getIO } = require("../libs/socket"); + +// exports.index = async (req, res) => { +// if (req.user.profile !== "admin") { +// return res +// .status(403) +// .json({ error: "Only administrators can access this route." }); +// } + +// const { searchParam = "", pageNumber = 1 } = req.query; + +// const whereCondition = { +// [Op.or]: [ +// { +// name: Sequelize.where( +// Sequelize.fn("LOWER", Sequelize.col("name")), +// "LIKE", +// "%" + searchParam.toLowerCase() + "%" +// ), +// }, +// { email: { [Op.like]: `%${searchParam.toLowerCase()}%` } }, +// ], +// }; + +// let limit = 20; +// let offset = limit * (pageNumber - 1); + +// const { count, rows: users } = await User.findAndCountAll({ +// attributes: ["name", "id", "email", "profile"], +// where: whereCondition, +// limit, +// offset, +// order: [["createdAt", "DESC"]], +// }); + +// const hasMore = count > offset + users.length; + +// return res.status(200).json({ users, count, hasMore }); +// }; + +export default async (req, res, next) => { + console.log(req.url); + const schema = Yup.object().shape({ + name: Yup.string().required().min(2), + email: Yup.string() + .email() + .required() + .test( + "Check-email", + "An user with this email already exists", + async value => { + const userFound = await User.findOne({ where: { email: value } }); + return !Boolean(userFound); + } + ), + password: Yup.string().required().min(5) + }); + + if (req.url === "/signup") { + const { value: userCreation } = await Setting.findByPk("userCreation"); + + if (userCreation === "disabled") { + return res + .status(403) + .json({ error: "User creation is disabled by administrator." }); + } + } else if (req.user.profile !== "admin") { + return res + .status(403) + .json({ error: "Only administrators can create users." }); + } + + try { + await schema.validate(req.body); + } catch (err) { + return res.status(400).json({ error: err.message }); + } + + const io = getIO(); + + const { name, id, email, profile } = await User.create(req.body); + + io.emit("user", { + action: "create", + user: { name, id, email, profile } + }); + + return res.status(201).json({ message: "User created!", userId: id }); +}; + +// exports.show = async (req, res) => { +// const { userId } = req.params; + +// const user = await User.findByPk(userId, { +// attributes: ["id", "name", "email", "profile"], +// }); + +// if (!user) { +// res.status(400).json({ error: "No user found with this id." }); +// } + +// return res.status(200).json(user); +// }; + +// exports.update = async (req, res) => { +// const schema = Yup.object().shape({ +// name: Yup.string().min(2), +// email: Yup.string().email(), +// password: Yup.string(), +// }); + +// if (req.user.profile !== "admin") { +// return res +// .status(403) +// .json({ error: "Only administrators can edit users." }); +// } + +// await schema.validate(req.body); + +// const io = getIO(); +// const { userId } = req.params; + +// const user = await User.findByPk(userId, { +// attributes: ["name", "id", "email", "profile"], +// }); + +// if (!user) { +// res.status(404).json({ error: "No user found with this id." }); +// } + +// if (user.profile === "admin" && req.body.profile === "user") { +// const adminUsers = await User.count({ where: { profile: "admin" } }); +// if (adminUsers <= 1) { +// return res +// .status(403) +// .json({ error: "There must be at leat one admin user." }); +// } +// } + +// await user.update(req.body); + +// io.emit("user", { +// action: "update", +// user: user, +// }); + +// return res.status(200).json(user); +// }; + +// exports.delete = async (req, res) => { +// const io = getIO(); +// const { userId } = req.params; + +// const user = await User.findByPk(userId); + +// if (!user) { +// res.status(400).json({ error: "No user found with this id." }); +// } + +// if (req.user.profile !== "admin") { +// return res +// .status(403) +// .json({ error: "Only administrators can edit users." }); +// } + +// await user.destroy(); + +// io.emit("user", { +// action: "delete", +// userId: userId, +// }); + +// return res.status(200).json({ message: "User deleted" }); +// }; diff --git a/backend/src/controllers/UserController.js b/backend/src/controllers/UserController.js deleted file mode 100644 index 6dc0e0f..0000000 --- a/backend/src/controllers/UserController.js +++ /dev/null @@ -1,181 +0,0 @@ -const Sequelize = require("sequelize"); -const Yup = require("yup"); -const { Op } = require("sequelize"); - -const User = require("../models/User"); -const Setting = require("../models/Setting"); - -const { getIO } = require("../libs/socket"); - -exports.index = async (req, res) => { - if (req.user.profile !== "admin") { - return res - .status(403) - .json({ error: "Only administrators can access this route." }); - } - - const { searchParam = "", pageNumber = 1 } = req.query; - - const whereCondition = { - [Op.or]: [ - { - name: Sequelize.where( - Sequelize.fn("LOWER", Sequelize.col("name")), - "LIKE", - "%" + searchParam.toLowerCase() + "%" - ), - }, - { email: { [Op.like]: `%${searchParam.toLowerCase()}%` } }, - ], - }; - - let limit = 20; - let offset = limit * (pageNumber - 1); - - const { count, rows: users } = await User.findAndCountAll({ - attributes: ["name", "id", "email", "profile"], - where: whereCondition, - limit, - offset, - order: [["createdAt", "DESC"]], - }); - - const hasMore = count > offset + users.length; - - return res.status(200).json({ users, count, hasMore }); -}; - -exports.store = async (req, res, next) => { - console.log(req.url); - const schema = Yup.object().shape({ - name: Yup.string().required().min(2), - email: Yup.string() - .email() - .required() - .test( - "Check-email", - "An user with this email already exists", - async value => { - const userFound = await User.findOne({ where: { email: value } }); - return !Boolean(userFound); - } - ), - password: Yup.string().required().min(5), - }); - - if (req.url === "/signup") { - const { value: userCreation } = await Setting.findByPk("userCreation"); - - if (userCreation === "disabled") { - return res - .status(403) - .json({ error: "User creation is disabled by administrator." }); - } - } else if (req.user.profile !== "admin") { - return res - .status(403) - .json({ error: "Only administrators can create users." }); - } - - try { - await schema.validate(req.body); - } catch (err) { - return res.status(400).json({ error: err.message }); - } - - const io = getIO(); - - const { name, id, email, profile } = await User.create(req.body); - - io.emit("user", { - action: "create", - user: { name, id, email, profile }, - }); - - return res.status(201).json({ message: "User created!", userId: id }); -}; - -exports.show = async (req, res) => { - const { userId } = req.params; - - const user = await User.findByPk(userId, { - attributes: ["id", "name", "email", "profile"], - }); - - if (!user) { - res.status(400).json({ error: "No user found with this id." }); - } - - return res.status(200).json(user); -}; - -exports.update = async (req, res) => { - const schema = Yup.object().shape({ - name: Yup.string().min(2), - email: Yup.string().email(), - password: Yup.string(), - }); - - if (req.user.profile !== "admin") { - return res - .status(403) - .json({ error: "Only administrators can edit users." }); - } - - await schema.validate(req.body); - - const io = getIO(); - const { userId } = req.params; - - const user = await User.findByPk(userId, { - attributes: ["name", "id", "email", "profile"], - }); - - if (!user) { - res.status(404).json({ error: "No user found with this id." }); - } - - if (user.profile === "admin" && req.body.profile === "user") { - const adminUsers = await User.count({ where: { profile: "admin" } }); - if (adminUsers <= 1) { - return res - .status(403) - .json({ error: "There must be at leat one admin user." }); - } - } - - await user.update(req.body); - - io.emit("user", { - action: "update", - user: user, - }); - - return res.status(200).json(user); -}; - -exports.delete = async (req, res) => { - const io = getIO(); - const { userId } = req.params; - - const user = await User.findByPk(userId); - - if (!user) { - res.status(400).json({ error: "No user found with this id." }); - } - - if (req.user.profile !== "admin") { - return res - .status(403) - .json({ error: "Only administrators can edit users." }); - } - - await user.destroy(); - - io.emit("user", { - action: "delete", - userId: userId, - }); - - return res.status(200).json({ message: "User deleted" }); -}; diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts new file mode 100644 index 0000000..46d81dd --- /dev/null +++ b/backend/src/controllers/UserController.ts @@ -0,0 +1,69 @@ +import { Request, Response } from "express"; + +// import CheckSettingsHelper from "../helpers/CheckSettingsHelper"; +import AppError from "../errors/AppError"; + +import CreateUserService from "../services/CreateUserService"; +// import UpdateUserService from "../services/UpdateUserService"; +// import ListUsersService from "../services/ListUsersService"; +// import FindUserService from "../services/FindUserService"; + +export const index = async (req: Request, res: Response): Promise => { + if (req.user.profile !== "admin") { + throw new AppError("Only administrators can access this route.", 403); // should be handled better. + } + const { searchParam, pageNumber } = req.query as any; + + const { users, count, hasMore } = await ListUsersService({ + searchParam, + pageNumber + }); + + return res.json({ users, count, hasMore }); +}; + +export const store = async (req: Request, res: Response): Promise => { + const { email, password, name, profile } = req.body; + + // if ( + // req.url === "/signup" && + // (await CheckSettingsHelper("userCreation")) === "disabled" + // ) { + // throw new AppError("User creation is disabled by administrator.", 403); + // } else if (req.user.profile !== "admin") { + // throw new AppError("Only administrators can create users.", 403); + // } + + const user = await CreateUserService({ + email, + password, + name, + profile + }); + + return res.status(200).json(user); +}; + +export const show = async (req: Request, res: Response): Promise => { + const { userId } = req.params; + + const user = await FindUserService(userId); + + return res.status(200).json(user); +}; + +export const update = async ( + req: Request, + res: Response +): Promise => { + if (req.user.profile !== "admin") { + throw new AppError("Only administrators can edit users.", 403); + } + + const { userId } = req.params; + const userData = req.body; + + const user = await UpdateUserService({ userData, userId }); + + return res.status(200).json(user); +}; diff --git a/backend/src/database/index.js b/backend/src/database/index.js deleted file mode 100644 index fe9956c..0000000 --- a/backend/src/database/index.js +++ /dev/null @@ -1,36 +0,0 @@ -const Sequelize = require("sequelize"); -const dbConfig = require("../config/database"); - -const User = require("../models/User"); -const Contact = require("../models/Contact"); -const Ticket = require("../models/Ticket"); -const Message = require("../models/Message"); -const Whatsapp = require("../models/Whatsapp"); -const ContactCustomField = require("../models/ContactCustomField"); -const Setting = require("../models/Setting"); - -const models = [ - User, - Contact, - Ticket, - Message, - Whatsapp, - ContactCustomField, - Setting, -]; - -class Database { - constructor() { - this.init(); - } - - init() { - this.sequelize = new Sequelize(dbConfig); - - models - .map(model => model.init(this.sequelize)) - .map(model => model.associate && model.associate(this.sequelize.models)); - } -} - -module.exports = new Database(); diff --git a/backend/src/database/index.ts b/backend/src/database/index.ts new file mode 100644 index 0000000..c1aa9aa --- /dev/null +++ b/backend/src/database/index.ts @@ -0,0 +1,53 @@ +import { Sequelize } from "sequelize-typescript"; +import { resolve } from "path"; +// import dbConfig from "../config/database"; +import "dotenv/config"; + +// import User from "../models/User"; +// const Contact = require("../models/Contact"); +// const Ticket = require("../models/Ticket"); +// const Message = require("../models/Message"); +// const Whatsapp = require("../models/Whatsapp"); +// const ContactCustomField = require("../models/ContactCustomField"); +// const Setting = require("../models/Setting"); + +const sequelize = new Sequelize({ + define: { + charset: "utf8mb4", + collate: "utf8mb4_bin" + }, + dialect: "mysql", + timezone: "-03:00", + host: process.env.DB_HOST, + database: process.env.DB_NAME, + username: process.env.DB_USER, + password: process.env.DB_PASS, + models: [resolve(__dirname, "..", "models")], + logging: false +}); + +// const models = [ +// User +// // Contact, +// // Ticket, +// // Message, +// // Whatsapp, +// // ContactCustomField, +// // Setting, +// ]; + +// class Database { +// constructor() { +// this.init(); +// } + +// init() { +// this.sequelize = new Sequelize(dbConfig); + +// models +// .map(model => model.init(this.sequelize)) +// .map(model => model.associate && model.associate(this.sequelize.models)); +// } +// } + +export default sequelize; diff --git a/backend/src/errors/AppError.ts b/backend/src/errors/AppError.ts new file mode 100644 index 0000000..28e6c22 --- /dev/null +++ b/backend/src/errors/AppError.ts @@ -0,0 +1,11 @@ +class AppError { + public readonly message: string; + public readonly statusCode: number; + + constructor(message: string, statusCode = 400) { + this.message = message; + this.statusCode = statusCode; + } +} + +export default AppError; diff --git a/backend/src/helpers/CheckSettingsHelper.ts b/backend/src/helpers/CheckSettingsHelper.ts new file mode 100644 index 0000000..39e8a95 --- /dev/null +++ b/backend/src/helpers/CheckSettingsHelper.ts @@ -0,0 +1,18 @@ +import AppError from "../errors/AppError"; +import Setting from "../models/Setting"; + +const CheckSettings = async (key: string): Promise => { + const settingsRepository = getRepository(Setting); + + const setting = await settingsRepository.findOne({ + where: { key } + }); + + if (!setting) { + throw new AppError("No setting found with this id.", 404); + } + + return setting.value; +}; + +export default CheckSettings; diff --git a/backend/src/models/User.js b/backend/src/models/User.js deleted file mode 100644 index 375f129..0000000 --- a/backend/src/models/User.js +++ /dev/null @@ -1,32 +0,0 @@ -const Sequelize = require("sequelize"); -const bcrypt = require("bcryptjs"); - -class User extends Sequelize.Model { - static init(sequelize) { - super.init( - { - name: { type: Sequelize.STRING }, - password: { type: Sequelize.VIRTUAL }, - profile: { type: Sequelize.STRING, defaultValue: "admin" }, - passwordHash: { type: Sequelize.STRING }, - email: { type: Sequelize.STRING }, - }, - { - sequelize, - } - ); - - this.addHook("beforeSave", async user => { - if (user.password) { - user.passwordHash = await bcrypt.hash(user.password, 8); - } - }); - return this; - } - - checkPassword(password) { - return bcrypt.compare(password, this.passwordHash); - } -} - -module.exports = User; diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts new file mode 100644 index 0000000..76b71b3 --- /dev/null +++ b/backend/src/models/User.ts @@ -0,0 +1,82 @@ +import { + Table, + Column, + CreatedAt, + UpdatedAt, + Model, + DataType +} from "sequelize-typescript"; + +@Table +class User extends Model { + @Column({ + defaultValue: DataType.UUIDV4, + primaryKey: true, + type: DataType.UUID + }) + id: string; + + @Column + name: string; + + @Column + email: string; + + @Column + passwordHash: string; + + @Column({ + defaultValue: "admin" + }) + profile: string; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + + // @BeforeUpdate + // @BeforeInsert + // hashPassword = async () => { + // if (this.passwordHash) { + // this.passwordHash = await hash(this.passwordHash, 8); + // } + // }; + + // checkPassword = async (password: string) => { + // return await compare(password, this.passwordHash); + // }; +} + +export default User; + +// const bcrypt = require("bcryptjs"); +// @Table +// class User extends Model { +// static init(sequelize) { +// super.init( +// { +// name: { type: Sequelize.STRING }, +// password: { type: Sequelize.VIRTUAL }, +// profile: { type: Sequelize.STRING, defaultValue: "admin" }, +// passwordHash: { type: Sequelize.STRING }, +// email: { type: Sequelize.STRING } +// }, +// { +// sequelize +// } +// ); + +// this.addHook("beforeSave", async user => { +// if (user.password) { +// user.passwordHash = await bcrypt.hash(user.password, 8); +// } +// }); +// return this; +// } + +// checkPassword(password) { +// return bcrypt.compare(password, this.passwordHash); +// } +// } diff --git a/backend/src/models/Contact.js b/backend/src/modelsOld/Contact.js similarity index 100% rename from backend/src/models/Contact.js rename to backend/src/modelsOld/Contact.js diff --git a/backend/src/models/ContactCustomField.js b/backend/src/modelsOld/ContactCustomField.js similarity index 100% rename from backend/src/models/ContactCustomField.js rename to backend/src/modelsOld/ContactCustomField.js diff --git a/backend/src/models/Message.js b/backend/src/modelsOld/Message.js similarity index 100% rename from backend/src/models/Message.js rename to backend/src/modelsOld/Message.js diff --git a/backend/src/models/Setting.js b/backend/src/modelsOld/Setting.js similarity index 100% rename from backend/src/models/Setting.js rename to backend/src/modelsOld/Setting.js diff --git a/backend/src/models/Ticket.js b/backend/src/modelsOld/Ticket.js similarity index 100% rename from backend/src/models/Ticket.js rename to backend/src/modelsOld/Ticket.js diff --git a/backend/src/modelsOld/User.js b/backend/src/modelsOld/User.js new file mode 100644 index 0000000..6617b6c --- /dev/null +++ b/backend/src/modelsOld/User.js @@ -0,0 +1,32 @@ +const Sequelize = require("sequelize"); +const bcrypt = require("bcryptjs"); + +class User extends Sequelize.Model { + static init(sequelize) { + super.init( + { + name: { type: Sequelize.STRING }, + password: { type: Sequelize.VIRTUAL }, + profile: { type: Sequelize.STRING, defaultValue: "admin" }, + passwordHash: { type: Sequelize.STRING }, + email: { type: Sequelize.STRING } + }, + { + sequelize + } + ); + + this.addHook("beforeSave", async user => { + if (user.password) { + user.passwordHash = await bcrypt.hash(user.password, 8); + } + }); + return this; + } + + checkPassword(password) { + return bcrypt.compare(password, this.passwordHash); + } +} + +module.exports = User; diff --git a/backend/src/models/Whatsapp.js b/backend/src/modelsOld/Whatsapp.js similarity index 100% rename from backend/src/models/Whatsapp.js rename to backend/src/modelsOld/Whatsapp.js diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts index cc77043..e9526a0 100644 --- a/backend/src/routes/userRoutes.ts +++ b/backend/src/routes/userRoutes.ts @@ -1,7 +1,7 @@ import { Router } from "express"; // const isAuth = require("../../middleware/is-auth"); -// const UserController = require("../../controllers/UserController"); +import * as UserController from "../controllers/UserController"; const userRoutes = Router(); @@ -9,12 +9,12 @@ userRoutes.get("/users", (req, res) => res.json({ meessage: "lets do some prettier shit here" }) ); -// routes.post("/users", isAuth, UserController.store); +userRoutes.post("/users", UserController.store); -// routes.put("/users/:userId", isAuth, UserController.update); +// userRoutes.put("/users/:userId", isAuth, UserController.update); -// routes.get("/users/:userId", isAuth, UserController.show); +// userRoutes.get("/users/:userId", isAuth, UserController.show); -// routes.delete("/users/:userId", isAuth, UserController.delete); +// userRoutes.delete("/users/:userId", isAuth, UserController.delete); export default userRoutes; diff --git a/backend/src/server.ts b/backend/src/server.ts index bf1b2d1..8dea37a 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,12 +1,14 @@ +import "dotenv/config"; import "express-async-errors"; import express from "express"; +import cors from "cors"; import routes from "./routes"; + // import path from "path"; -// import cors from "cors"; // import multer from "multer"; // import Sentry from "@sentry/node"; -// require("./database"); +import "./database"; // const { initWbot } = require("./libs/wbot"); // const wbotMessageListener = require("./services/wbotMessageListener"); @@ -17,14 +19,13 @@ import routes from "./routes"; const app = express(); +app.use(cors()); +app.use(express.json()); app.use(routes); -app.listen(8080, () => { - console.log("stated"); -}); -// const server = app.listen(process.env.PORT, () => { -// console.log(`Server started on port: ${process.env.PORT}`); -// }); +const server = app.listen(process.env.PORT, () => { + console.log(`Server started on port: ${process.env.PORT}`); +}); // Sentry.init({ dsn: process.env.SENTRY_DSN }); @@ -38,8 +39,6 @@ app.listen(8080, () => { // }); // app.use(Sentry.Handlers.requestHandler()); -// app.use(cors()); -// app.use(express.json()); // app.use(multer({ storage: fileStorage }).single("media")); // app.use("/public", express.static(path.join(__dirname, "..", "public"))); // app.use(Router); diff --git a/backend/src/services/CreateUserService.ts b/backend/src/services/CreateUserService.ts new file mode 100644 index 0000000..edeeaa7 --- /dev/null +++ b/backend/src/services/CreateUserService.ts @@ -0,0 +1,53 @@ +import * as Yup from "yup"; + +import AppError from "../errors/AppError"; +import User from "../models/User"; + +interface Request { + email: string; + password: string; + name: string; + profile?: string; +} + +const CreateUserService = async ({ + email, + password, + name, + profile = "admin" +}: Request): Promise => { + // const schema = Yup.object().shape({ + // name: Yup.string().required().min(2), + // email: Yup.string() + // .email() + // .required() + // .test( + // "Check-email", + // "An user with this email already exists.", + // async value => { + // const emailExists = await User.findOne({ + // where: { email: value } + // }); + // return !Boolean(emailExists); + // } + // ), + // password: Yup.string().required().min(5) + // }); + + // try { + // await schema.validate({ email, password, name }); + // } catch (err) { + // throw new AppError(err.message); + // } + + const user = User.create({ + email, + passwordHash: password, + name, + profile + }); + + return user; +}; + +export default CreateUserService; diff --git a/backend/src/services/FindUserService.ts b/backend/src/services/FindUserService.ts new file mode 100644 index 0000000..17ab763 --- /dev/null +++ b/backend/src/services/FindUserService.ts @@ -0,0 +1,21 @@ +import { getRepository, Raw } from "typeorm"; + +import User from "../models/User"; +import AppError from "../errors/AppError"; + +const FindUserService = async (id: string): Promise => { + const usersRepository = getRepository(User); + + const user = await usersRepository.findOne({ + where: { id }, + select: ["name", "id", "email", "profile"], + }); + + if (!user) { + throw new AppError("No user found with this ID.", 404); + } + + return user; +}; + +export default FindUserService; diff --git a/backend/src/services/ListUsersService.ts b/backend/src/services/ListUsersService.ts new file mode 100644 index 0000000..5381760 --- /dev/null +++ b/backend/src/services/ListUsersService.ts @@ -0,0 +1,54 @@ +import { getRepository, Raw } from "typeorm"; + +import User from "../models/User"; + +interface Request { + searchParam?: string; + pageNumber?: number; +} + +interface Response { + users: User[]; + count: number; + hasMore: boolean; +} + +const ListUsersService = async ({ + searchParam = "", + pageNumber = 1, +}: Request): Promise => { + const usersRepository = getRepository(User); + + const whereCondition = [ + { + name: Raw( + alias => `LOWER(${alias}) Like '%${searchParam.toLowerCase()}%'` + ), + }, + { + email: Raw( + alias => `LOWER(${alias}) Like '%${searchParam.toLowerCase()}%'` + ), + }, + ]; + const take = 20; + const skip = take * (pageNumber - 1); + + const [users, count] = await usersRepository.findAndCount({ + where: whereCondition, + select: ["name", "id", "email", "profile"], + skip, + take, + order: { createdAt: "DESC" }, + }); + + const hasMore = count > skip + users.length; + + return { + users, + count, + hasMore, + }; +}; + +export default ListUsersService; diff --git a/backend/src/services/UpdateUserService.ts b/backend/src/services/UpdateUserService.ts new file mode 100644 index 0000000..39cfe68 --- /dev/null +++ b/backend/src/services/UpdateUserService.ts @@ -0,0 +1,61 @@ +import { getRepository } from "typeorm"; +import * as Yup from "yup"; + +import AppError from "../errors/AppError"; +import User from "../models/User"; + +interface UserData { + email?: string; + password?: string; + name?: string; + profile?: string; +} + +interface Request { + userData: UserData; + userId: string; +} + +const UpdateUserService = async ({ + userData, + userId, +}: Request): Promise => { + const usersRepository = getRepository(User); + + const schema = Yup.object().shape({ + name: Yup.string().min(2), + email: Yup.string().email(), + password: Yup.string(), + }); + + const { email, password, name } = userData; + + try { + await schema.validate({ email, password, name }); + } catch (err) { + throw new AppError(err.message); + } + + const user = await usersRepository.findOne({ + where: { id: userId }, + select: ["name", "id", "email", "profile"], + }); + + if (!user) { + throw new AppError("No user found with this ID.", 404); + } + + const teste = await usersRepository.update(userId, { + email, + passwordHash: password, + name, + }); + + console.log(teste); + + delete user.passwordHash; + + return user; +}; + +export default UpdateUserService; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index e121133..dcfe622 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,69 +1,69 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, - "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist" /* Redirect output structure to the directory. */, - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist" /* Redirect output structure to the directory. */, + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, - "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, + /* Experimental Options */ + "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, + "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, - /* Advanced Options */ - "skipLibCheck": true /* Skip type checking of declaration files. */, - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } } diff --git a/backend/yarn.lock b/backend/yarn.lock index d0a00b2..d498fb3 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -152,6 +152,11 @@ dependencies: defer-to-connect "^1.0.1" +"@types/bluebird@^3.5.32": + version "3.5.32" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.32.tgz#381e7b59e39f010d20bbf7e044e48f5caf1ab620" + integrity sha512-dIOxFfI0C+jz89g6lQ+TqhGgPQ0MxSnh/E4xuC0blhFtyW269+mPG5QeLgbdwst/LvdP8o1y0o/Gz5EHXLec/g== + "@types/body-parser@*": version "1.19.0" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" @@ -225,6 +230,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.4.tgz#a145cc0bb14ef9c4777361b7bbafa5cf8e3acb5a" integrity sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ== +"@types/node@^14.10.1": + version "14.10.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.1.tgz#cc323bad8e8a533d4822f45ce4e5326f36e42177" + integrity sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ== + "@types/qs@*": version "6.9.4" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a" @@ -253,6 +263,11 @@ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/validator@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.1.0.tgz#3d776127dbce7dd31fc06f86d3428b072e631eba" + integrity sha512-gHUHI6pJaANIO2r6WcbT7+WMgbL9GZooR4tWpuBOETpDIqFNxwaJluE+6rj6VGYe8k6OkfhbHz2Fkm8kl06Igw== + "@types/yauzl@^2.9.1": version "2.9.1" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" @@ -260,6 +275,11 @@ dependencies: "@types/node" "*" +"@types/yup@^0.29.7": + version "0.29.7" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.7.tgz#80c5e427a3b152e583ac2859767ccf59db0d3a16" + integrity sha512-x3Zeh8/qLZ6fG4S1EztI1S1mLj6N1pSUV1PAj/9finZba48d3Maxtyz4WYNUY0NE76u1KSukfNLkjcRlb+O00g== + "@typescript-eslint/eslint-plugin@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.1.0.tgz#7d309f60815ff35e9627ad85e41928d7b7fd443f" @@ -1767,6 +1787,18 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.3, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -3178,6 +3210,11 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" @@ -3356,6 +3393,13 @@ sequelize-pool@^2.3.0: resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-2.3.0.tgz#64f1fe8744228172c474f530604b6133be64993d" integrity sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA== +sequelize-typescript@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-1.1.0.tgz#d5c2945e7fbfe55a934917b27d84589858d79123" + integrity sha512-FAPEQPeAhIaFQNLAcf9Q2IWcqWhNcvn5OZZ7BzGB0CJMtImIsGg4E/EAb7huMmPaPwDArxJUWGqk1KurphTNRA== + dependencies: + glob "7.1.2" + sequelize@5: version "5.22.3" resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.22.3.tgz#7e7a92ddd355d883c9eb11cdb106d874d0d2636f"