diff --git a/README.md b/README.md index bdbe28f..d5a3371 100644 --- a/README.md +++ b/README.md @@ -1 +1,209 @@ -# tcc-backend \ No newline at end of file + + +# 🖥️ Projexa + +Dashboard to organize and manage the team and the projects of our enterprise. + +With the aim of creating **efficient management** and **centralized data collection**, access to **leaders and** other **members** of the junior company, **Projexa** offers an environment for the construction and continuous evolution of a web system, open-source and, mainly, promoted by developers **CodeX Jr.** internals. +The application has a register of members, projects and links, available for viewing by the entire company, as well as management over each one, according to the authorization level of each user. + +Read [endpoints.md.](https://github.com/codexjr-dev/dashboard-codex-api/blob/main/ENDPOINTS.md) for more API information. + +## 🚀 Starting + +These instructions will allow you to get a copy of the project running on your local machine for development and testing purposes. + +### 📋 Prerequisites + +To run the Backend system, you will need to have Node JS installed. + +You can do this accessing this **[link](https://nodejs.org/en/download)**. + +### 🔧 Installation + +To install, you will need to clone the project, install the dependencies, create a .env file in the project root and add the necessary variables. + +First, to clone the project, run: + +```shell +git clone https://github.com/codexjr-dev/dashboard-codex-api.git +``` + +At the project root, install the dependencies by running: + +```shell +npm install +``` + +Also in the project root, add a ".env" file and insert the following variables: + +```.env +BD_PROD= +BD_DEV= + +PORT= + +SALT_ROUNDS= + +JWT_SECRET= +``` + +Perhaps dependencies such as **cors**, **dotenv**, **express**, **jsonwebtoken** and/or **mongoose** inform you that they need to be installed globally (on your machine). + +To do so, just listen to the following commands: + +```shell +node i -g +``` + +## ⚙️ Configuring Scripts + +Before running, you may need to configure the scripts according to your Operating System. + +Change the package.json according to your need: + +**Linux** + +```json +"scripts": { + "start": "NODE_ENV=prod node src/server.js", + "test": "NODE_ENV=test mocha ./test/integration/*.test.js --timeout 10000 --exit", + "dev": "NODE_ENV=dev nodemon src/server.js", + "debug": "NODE_ENV=dev nodemon --inspect src/server.js" +} +``` + +**Windows** + +```json +"scripts": { + "start": "set NODE_ENV=prod node && src/server.js", + "test": "set NODE_ENV=test && mocha ./test/integration/*.test.js --timeout 10000 --exit", + "dev": "set NODE_ENV=dev && nodemon src/server.js", + "debug": "set NODE_ENV=dev nodemon && --inspect src/server.js" +} +``` + +--- + +## ✅ Running the System + +Execute in production environment: + +```shell +npm start +``` + +Execute in developer environment: + +```shell +npm run dev +``` + +Execute tests, creating a temporary Data Base: + +```shell +npm run test +``` + +## 🛠️ Construído com + +The main technologies used were: + +- [express](https://expressjs.com/) - The API used +- [npm](https://docs.npmjs.com/) - Dependency Manager +- [mongoose](https://mongoosejs.com/) - Mongodb object modeling + +## 🖇️ Collaboration + +Please, read a **[COLLAB.md](https://github.com/codexjr-dev/dashboard-codex-api/blob/main/COLLAB.md)** for details about our code of conduct and the process for submitting requests to us. + +## 📌 Versions + +- [1.0.0](https://github.com/lucasanthony/tcc-backend) - [Completion of Course Work - Lucas Anthony](http://dspace.sti.ufcg.edu.br:8080/xmlui/bitstream/handle/riufcg/29267/LUCAS%20ANTHONY%20FERREIRA%20DE%20OLIVEIRA%20-%20TCC%20ARTIGO%20CI%C3%8ANCIA%20DA%20COMPUTA%C3%87%C3%83O%20CEEI%202022.pdf?sequence=1&isAllowed=y). +- [1.2.1](https://github.com/codexjr-dev/dashboard-codex-api/tree/6300cc4e1b9cf42b05ccc3ead418e30dae10e218) - [User Model Updates and role of user format changed](https://github.com/codexjr-dev/dashboard-codex-api/pull/17). +- [1.2.2](https://github.com/codexjr-dev/dashboard-codex-api/tree/821a75765695012046d16f6540d6c177e31674ce) - [nodeenv configs, member access, role and email verifications and new roles](https://github.com/codexjr-dev/dashboard-codex-api/pull/27). +- [1.2.3](https://github.com/codexjr-dev/dashboard-codex-api/tree/b8a880204d038336145321c9e7436bc1a847a22e) - [Bug Fixes and System Improvements](https://github.com/codexjr-dev/dashboard-codex-api/pull/57). + +## ✒️ Authors + + + + + + +
+ Lucas Anthony Profile Image +
+ @lucasanthony +
+ Initial Work +
+ Gabriel Max Profile Image +
+ @ManoMax +
+ Continuation of Work +
+ +- **Lucas Anthony** - _Initial Work_ - [@lucasanthony](https://github.com/lucasanthony) +- **Gabriel Max** - _Subsequent Project Leader_ - [@ManoMax](https://github.com/ManoMax) + +You can also see the list of all contributors who participated in this project. + + + + + + + + + + + + +
+ Ana Rita Profile Image +
+ @anaritamed +
+ Developer +
+ Filipe Luiz Profile Image +
+ @FLuiz22 +
+ Developer +
+ Maria Clara Profile Image +
+ @maahog +
+ Developer +
+ Matheus Victor Profile Image +
+ @matheusvictoor +
+ Developer +
+ Daniele Oliveira Profile Image +
+ @danieleolivs +
+ UI Design +
+ Carlos Lucena Profile Image +
+ @carlos-lucenag +
+ UI Design +
+ +## 📄 Licença + +This project is under license **GNU General Public License v3.0 (GNU GPLv3)** - see the file [LICENSE.md](https://github.com/usuario/projeto/licenca) for details. + +--- + +⌨️ with ❤️ by [CodeX Jr.](https://codexjr.com.br/) 😊 diff --git a/package.json b/package.json index 463c1b5..c68929d 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@project": "src/modules/Project", "@link": "src/modules/Link", "@member": "src/modules/Member", + "@news": "src/modules/News", "@config": "src/config" } } diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js index 8511423..5dd325d 100644 --- a/src/middlewares/auth.js +++ b/src/middlewares/auth.js @@ -1,21 +1,27 @@ const jwt = require("jsonwebtoken"); const Member = require("@member/Member"); +const Project = require("@project/Project"); +const News = require("../modules/News/News"); module.exports = { - validatedUser(req, res, next) { - authorize(req, res, next, "valid"); + existentUser(req, res, next) { + authorize(req, res, next, "existent"); }, authorizedUser(req, res, next) { - authorize(req, res, next, "user"); + authorize(req, res, next, "authorized"); }, - authorizedLeadership(req, res, next) { + isLeadership(req, res, next) { authorize(req, res, next, "leadership"); }, - authorizePresident(req, res, next) { - next(); + isMemberOnProject(req, res, next){ + authorize(req, res, next, "team"); + }, + + haveRightsToTheNews(req, res, next){ + authorize(req, res, next, "newsOwner"); }, }; @@ -38,12 +44,12 @@ const authorize = (req, res, next, type) => { const member = await Member.findOne({ _id: decoded.sub }); switch (type) { - case "valid": + case "existent": if (!member) return res.status(404).send({ error: "Usuário não existe." }); break; - case "user": + case "authorized": if (!isLeadership(member) && member._id.toString() !== req.body._id) return res.status(403).send({ error: "Usuário sem permissão." }); break; @@ -52,6 +58,18 @@ const authorize = (req, res, next, type) => { if (!isLeadership(member)) return res.status(403).send({ error: "Usuário sem permissão." }); break; + + case "team": + const project = await Project.findOne({ _id: req.params.projectId }); + if(!isLeadership(member) && !isMemberProject(project, member)) + return res.status(403).send({ error: "Usuário sem permissão." }); + break; + + case "newsOwner": + const news = await News.findOne({_id: req.body.newsId}); + if(!isLeadership(member) && !isNewsOwner(news, member)) + return res.status(403).send({ error: "Usuário sem permissão." }); + break; } req.ejId = member.ej; @@ -62,3 +80,9 @@ const authorize = (req, res, next, type) => { const isLeadership = (member) => member && ["Presidente", "Diretor(a)"].includes(`${member.role}`); + +const isMemberProject = (project, member) => + project && project.team.includes(member._id); + +const isNewsOwner = (news, member) => + news.member.toString() === member._id.toString(); \ No newline at end of file diff --git a/src/modules/Link/LinkRoutes.js b/src/modules/Link/LinkRoutes.js index 0a63f5a..86fe1d7 100644 --- a/src/modules/Link/LinkRoutes.js +++ b/src/modules/Link/LinkRoutes.js @@ -1,14 +1,14 @@ const router = require("express").Router(); const { save, findByEj, update, remove } = require("./LinkController"); const { - validatedUser, + existentUser, authorizedUser, - authorizedLeadership, + isLeadership, } = require("@middlewares/auth"); -router.post("/link", authorizedLeadership, save); -router.get("/link", validatedUser, findByEj); +router.post("/link", isLeadership, save); +router.get("/link", existentUser, findByEj); router.patch("/link/:id", authorizedUser, update); -router.delete("/link/:id", authorizedLeadership, remove); +router.delete("/link/:id", isLeadership, remove); module.exports = router; diff --git a/src/modules/Member/MemberRoutes.js b/src/modules/Member/MemberRoutes.js index f55012a..8d70693 100644 --- a/src/modules/Member/MemberRoutes.js +++ b/src/modules/Member/MemberRoutes.js @@ -1,14 +1,14 @@ const router = require("express").Router(); const { save, findByEj, update, remove } = require("./MemberController"); const { - validatedUser, + existentUser, authorizedUser, - authorizedLeadership, + isLeadership, } = require("@middlewares/auth"); -router.post("/member", authorizedLeadership, save); -router.get("/member", validatedUser, findByEj); +router.post("/member", isLeadership, save); +router.get("/member", existentUser, findByEj); router.patch("/member/:id", authorizedUser, update); -router.delete("/member/:id", authorizedLeadership, remove); +router.delete("/member/:id", isLeadership, remove); module.exports = router; diff --git a/src/modules/Member/MemberService.js b/src/modules/Member/MemberService.js index 4f96cd7..0eacf02 100644 --- a/src/modules/Member/MemberService.js +++ b/src/modules/Member/MemberService.js @@ -92,10 +92,19 @@ module.exports = { }, }; -async function verifyEmail(memberEmail) { - const emailInUse = await Member.findOne({ email: memberEmail }); +async function verifyEmail(email) { + if (!email) { + throw new Error('EMPTY_EMAIL'); + } + + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!regex.test(email)) { + throw new Error('INVALID_EMAIL_FORMAT'); + } + + const emailInUse = await Member.findOne({ email: email }); if (emailInUse) { - throw new Error('Já existe um membro cadastrado para esse email!'); + throw new Error('EMAIL_ALREADY_IN_USE'); } } diff --git a/src/modules/News/News.js b/src/modules/News/News.js new file mode 100644 index 0000000..7adf8ab --- /dev/null +++ b/src/modules/News/News.js @@ -0,0 +1,32 @@ +const { Schema, model } = require("mongoose"); +const Member = require("@member/Member") + +const NewsSchema = new Schema({ + member: { + type: Schema.Types.ObjectId, + ref: Member, + required: true + }, + project: { + type: Schema.Types.ObjectId, + ref: 'Project', + required: true + }, + description: { + type: String, + required: true + }, + image: { + type: Buffer, + required: false + }, + updateLink: { + type: String, + required: false + } +}, + { + timestamps: true, + }); + +module.exports = model("News", NewsSchema); \ No newline at end of file diff --git a/src/modules/News/NewsController.js b/src/modules/News/NewsController.js new file mode 100644 index 0000000..2b33bca --- /dev/null +++ b/src/modules/News/NewsController.js @@ -0,0 +1,39 @@ +const { save, findByProject, remove, update } = require("./NewsService"); + +module.exports = { + async save(req, res) { + try { + const news = await save(req.memberId, req.body, req.params.projectId); + return res.status(201).send({ news: news }); + } catch (error) { + return res.status(500).send({ error: error.message }); + } + }, + + async findByProject(req, res) { + try { + const newsAndProject = await findByProject(req.params.projectId); + return res.status(201).send(newsAndProject); + } catch (error) { + return res.status(500).send({ error: error.message }); + } + }, + + async update(req, res) { + try { + const updatedNews = await update(req.body.newsId, req.body); + return res.status(200).send({ news: updatedNews, message: 'Atualização do projeto editada com sucesso!' }); + } catch (error) { + return res.status(500).send({ error: error.message }); + } + }, + + async remove(req, res) { + try { + const newsWithoutRemovedNews = await remove(req.params.projectId, req.body); + return res.status(200).send({ news: newsWithoutRemovedNews, message: "Atualização do projeto removida com sucesso!" }); + } catch (error) { + return res.status(500).send({ error: error.message }); + } + }, +} \ No newline at end of file diff --git a/src/modules/News/NewsRoutes.js b/src/modules/News/NewsRoutes.js new file mode 100644 index 0000000..aa644e8 --- /dev/null +++ b/src/modules/News/NewsRoutes.js @@ -0,0 +1,14 @@ +const router = require("express").Router(); +const { save, findByProject, update, remove } = require("./NewsController"); +const { + existentUser, + isMemberOnProject, + haveRightsToTheNews, +} = require("@middlewares/auth"); + +router.post("/news/:projectId", isMemberOnProject, save); +router.get("/news/:projectId", existentUser, findByProject); +router.patch("/news", haveRightsToTheNews, update); +router.delete("/news/:projectId", haveRightsToTheNews, remove); + +module.exports = router; \ No newline at end of file diff --git a/src/modules/News/NewsService.js b/src/modules/News/NewsService.js new file mode 100644 index 0000000..e100033 --- /dev/null +++ b/src/modules/News/NewsService.js @@ -0,0 +1,85 @@ +const News = require("@news/News"); +const Project = require("@project/Project"); + +module.exports = { + async save(memberId, newsData, projectId) { + const { description, image, updateLink } = newsData; + + const news = new News({ + member: memberId, + project: projectId, + description, + image, + updateLink + }); + + await news.save(); + + await Project.updateOne({ _id: projectId }, { $push: { news: news._id } }); + + return getDTOnews(news); + }, + + async findByProject(projectId) { + const project = await Project.findOne({ _id: projectId }) + .select('_id news name'); + + const news = await News.find({ _id: { $in: project.news } }) + .select('_id member project description images updateLink createdAt updatedAt') + .populate('member', '_id name') + .sort({_id:-1}) + .exec(); + + delete project.news + + return { news: news, project: project }; + }, + + async update(newstId, data) { + const { description, image, updateLink } = data.news; + + const updatedNews = await News.findOneAndUpdate({ _id: newstId }, + { + description: description, + image: image, + updateLink: updateLink + }, + { new: true }); + + return await News.find({ project: updatedNews.project }) + .select('_id member project description images updateLink createdAt updatedAt') + .populate('member', '_id name') + .sort({_id:-1}) + .exec(); + }, + + async remove(projectId, data) { + const project = await Project.findOneAndUpdate({ _id: projectId }, { $pull: { news: data.newsId } }, { new: true }) + + const news = await News.deleteOne({ _id: data.newsId }); + + if (news.deletedCount > 0) { + return await News.find({ _id: { $in: project.news } }) + .select('_id member project description images updateLink createdAt updatedAt') + .populate('member', '_id name') + .sort({_id:-1}) + .exec(); + } else { + throw new Error('Atualização não encontrada.'); + } + } +} + + +function getDTOnews(news) { + return { + _id: news._id, + member: news.member, + project: news.project, + description: news.description, + images: news.images, + updateLink: news.updateLink, + createdAt: news.createdAt, + updatedAt: news.updatedAt + } +} \ No newline at end of file diff --git a/src/modules/Project/Project.js b/src/modules/Project/Project.js index 176c896..de5224e 100644 --- a/src/modules/Project/Project.js +++ b/src/modules/Project/Project.js @@ -1,60 +1,81 @@ -const { Schema, model } = require('mongoose'); -const Ej = require('@ej/Ej') -const Member = require('@member/Member') +const { Schema, model } = require("mongoose"); +const Ej = require("@ej/Ej"); +const Member = require("@member/Member"); -const ProjectSchema = new Schema({ - name: { - type: String, - required: true - }, - description: { - type: String, - required: true - }, - tags: [{ - type: String, - enum: ['Backend', 'Frontend', 'Mobile', 'Wordpress', 'Assessoria', 'Treinamento'], - required: false - }], - ej: { - type: Schema.Types.ObjectId, - ref: Ej, - required: true - }, - team: [{ - type: Schema.Types.ObjectId, - ref: Member, - required: false - }], - startDate: { - type: Date, - required: true - }, - finishDate: { - type: Date, - required: false - }, - contractLink: { - type: String, - required: false - }, - customer: { - email: { - type: String, - required: false - }, - contact: { - type: String, - required: false - }, - name: { - type: String, - required: false - } - }, -}, - { - timestamps: true, - }); +const ProjectSchema = new Schema( + { + name: { + type: String, + required: true, + }, + description: { + type: String, + required: true, + }, + tags: [ + { + type: String, + enum: [ + "Backend", + "Frontend", + "Mobile", + "Wordpress", + "Assessoria", + "Treinamento", + ], + required: false, + }, + ], + ej: { + type: Schema.Types.ObjectId, + ref: Ej, + required: true, + }, + team: [ + { + type: Schema.Types.ObjectId, + ref: Member, + required: false, + }, + ], + startDate: { + type: Date, + required: true, + }, + finishDate: { + type: Date, + required: false, + }, + contractLink: { + type: String, + required: false, + }, + customer: { + email: { + type: String, + required: false, + }, + contact: { + type: String, + required: false, + }, + name: { + type: String, + required: false, + }, + }, + news: [ + { + type: Schema.Types.ObjectId, + ref: 'News', + required: false, + default: [], + }, + ], + }, + { + timestamps: true, + } +); -module.exports = model('Project', ProjectSchema); \ No newline at end of file +module.exports = model("Project", ProjectSchema); diff --git a/src/modules/Project/ProjectRoutes.js b/src/modules/Project/ProjectRoutes.js index 9cd99ac..38b7584 100644 --- a/src/modules/Project/ProjectRoutes.js +++ b/src/modules/Project/ProjectRoutes.js @@ -1,14 +1,14 @@ const router = require("express").Router(); const { save, findByEj, update, remove } = require("./ProjectController"); const { - validatedUser, + existentUser, authorizedUser, - authorizedLeadership, + isLeadership, } = require("@middlewares/auth"); -router.post("/project", authorizedLeadership, save); -router.get("/project", validatedUser, findByEj); +router.post("/project", isLeadership, save); +router.get("/project", existentUser, findByEj); router.patch("/project/:id", authorizedUser, update); -router.delete("/project/:id", authorizedLeadership, remove); +router.delete("/project/:id", isLeadership, remove); module.exports = router; diff --git a/src/modules/Project/ProjectService.js b/src/modules/Project/ProjectService.js index 0c93833..da830c8 100644 --- a/src/modules/Project/ProjectService.js +++ b/src/modules/Project/ProjectService.js @@ -53,6 +53,7 @@ function getDTOproject(project) { startDate: project.startDate, finishDate: project.finishDate, contractLink: project.contractLink, - customer: project.customer + customer: project.customer, + news: project.news }; } \ No newline at end of file diff --git a/src/modules/User/UserRoutes.js b/src/modules/User/UserRoutes.js index 879bec3..9d49ca3 100644 --- a/src/modules/User/UserRoutes.js +++ b/src/modules/User/UserRoutes.js @@ -1,15 +1,14 @@ const router = require("express").Router(); const { save, findByEj, remove, update } = require("./UserController"); const { - validatedUser, + existentUser, authorizedUser, - authorizedLeadership, - authorizePresident, + isLeadership } = require("@middlewares/auth"); -router.post("/user", authorizedLeadership, save); -router.get("/user", validatedUser, findByEj); +router.post("/user", isLeadership, save); +router.get("/user", existentUser, findByEj); router.patch("/user/:id", authorizedUser, update); -router.delete("/user/:id", authorizedLeadership, remove); +router.delete("/user/:id", isLeadership, remove); module.exports = router; diff --git a/src/server.js b/src/server.js index 7bc116b..d113411 100644 --- a/src/server.js +++ b/src/server.js @@ -11,6 +11,7 @@ const AuthRoutes = require('./modules/Member/Auth/AuthRoutes'); const MemberRoutes = require('./modules/Member/MemberRoutes'); const ProjectRoutes = require('./modules/Project/ProjectRoutes'); const LinkRoutes = require('./modules/Link/LinkRoutes'); +const NewsRoutes = require('./modules/News/NewsRoutes'); const server = express(); server.use(cors()); @@ -22,7 +23,7 @@ server.get('/', function (req, res) { databaseConfig(); server.use(express.json()); -server.use(EjRoutes, UserRoutes, MemberRoutes, ProjectRoutes, AuthRoutes, LinkRoutes); +server.use(EjRoutes, UserRoutes, MemberRoutes, ProjectRoutes, AuthRoutes, LinkRoutes, NewsRoutes); server.use(bodyParser.urlencoded({ extended: false })); server.use(bodyParser.json());