diff --git a/.env.example b/.env.example index 0b33b40..98db207 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,6 @@ -PORT = \ No newline at end of file +PORT = 3000 +DBNAME = databaseName +DBUSER = databaseUser +DBPASSWORD = databasePassword +DBHOST = 127.0.0.1 +DBDIALECT = postgres \ No newline at end of file diff --git a/README.md b/README.md index f7d562c..2da1500 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,62 @@ # Class Manager -### Introduction - More information coming soon ... - -### Installation Guide -* Clone this repository [here](https://github.com/devcareer/class-manager.git). -* The main branch is the most stable branch at any given time, ensure you're working from it. -* Run npm install to install all dependencies -* Create an .env file in your project root folder and add your variables. See .env.example for assistance. -### Usage -* Run npm watch:dev to start the application. -### License -This project is available for use under the MIT License. \ No newline at end of file + +### The technologies used in creating this project are: +Node.js, ExpressJs, Sequelize ORM, and PostgreSQL + +### :rocket: How to get started +- Make sure to have Git and Node.js installed on your computer +- Clone the project by running: `git clone https://https://github.com/devcareer/class-manager` +- cd into the project and run `npm install` +- create a `.env` file in the root folder and copy the content in the `.env.example`into it. +- run `npm run migrate` to migrate to the database. +- run `npm run migrate:undo` to undo migration. +- run `npm run seed` to seed the database. +- run `npm start` to start the project. + +These are the HTTP response codes used in this project: +| Status Codes | Indication | +| --- | --- | +| `200` | This `OK` status code indicates that a request has succeeded | +| `400` | This `bad request error` status code indicates that the request sent to the server is incorrect | +| `404` | This `not found error` status code indicates that the resource is not found. | +| `500` | This `internal server error` status code indicates that something has gone wrong on the web server | + +
+ +The routes featured in this project: +| API routes(url) | Method | Description | +| --- | --- | --- | +| / | `GET` | Api home page | +| /messages | `GET` | Get all messages | +| /messages | `POST` | Create a message | +| /messages/id | `GET` | Get a message by id | +| /messages/id | `PUT` | Update message| +| /messages/id | `DELETE` | Delete message | +| /students | `GET` | Get all students | +| /students | `POST` | Create a student | +| /students/id | `GET` | Get a student by id | +| /students/id | `PUT` | Update student | +| /students/id | `DELETE` | Delete student | +| /teacher | `GET` | Get all teachers | +| /teacher | `POST` | Create a teacher | +| /teacher/id | `GET` | Get a teacher by id | +| /teacher/id | `PUT` | Update teacher | +| /teacher/id | `DELETE` | Delete teacher | +| /assignment | `GET` | Get all assignment | +| /assignment/id | `GET` | Get a assignment with id | +| /assignment/id | `PUT` | Update assignment | +| /assignment/id | `DELETE` | Delete assignment | + +
+ + +👤 **Authors**: + +| Github | Linkedin | +| ------------- | ------------- | +| [@bellogo](https://github.com/bellogo) | [Ufuoma Ogodo](https://ng.linkedin.com/in/ufuoma-ogodo) | +| [@judyseyram](https://github.com/JudySeyram) | [Judith Amegbe](https://gh.linkedin.com/in/amegbe-judith-5b881811a) | +| [@hazeem01](https://github.com/Hazeem01) | [Adenekan Abdulhazeem](https://www.linkedin.com/in/abdulhazeem-adenekan) | +| [@talktonok](https://github.com/talktonok) | [Mansur Ibrahim Nok](https://www.linkedin.com/in/mansuribrahimnok) | +| [@TijanAyo](https://github.com/TijanAyo) | [Tijani Ayomide](https://www.linkedin.com/in/tijanayo) | +| [@Ekemiben](https://github.com/ekemiben) | [Ekemini Ben](https://www.linkedin.com/in/ekemini-ben) | \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b964439..a258a0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "body-parser": "^1.20.1", "dotenv": "^16.0.3", "express": "^4.18.2", + "express-fileupload": "^1.4.0", "pg": "^8.8.0", "pg-hstore": "^2.3.4", "sequelize": "^6.25.3" @@ -2971,6 +2972,17 @@ "node": ">=4" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -4171,6 +4183,17 @@ "node": ">= 0.10.0" } }, + "node_modules/express-fileupload": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz", + "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7538,6 +7561,14 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -10596,6 +10627,14 @@ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -11495,6 +11534,14 @@ "vary": "~1.1.2" } }, + "express-fileupload": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.0.tgz", + "integrity": "sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==", + "requires": { + "busboy": "^1.6.0" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -13938,6 +13985,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/package.json b/package.json index 1499ac3..572d8fa 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "body-parser": "^1.20.1", "dotenv": "^16.0.3", "express": "^4.18.2", + "express-fileupload": "^1.4.0", "pg": "^8.8.0", "pg-hstore": "^2.3.4", "sequelize": "^6.25.3" diff --git a/src/controllers/assignmentsController.js b/src/controllers/assignmentsController.js new file mode 100644 index 0000000..9d62b7e --- /dev/null +++ b/src/controllers/assignmentsController.js @@ -0,0 +1,128 @@ +import AssignmentService from '../services/AssignmentService.js'; +import Util from '../utils/Utils.js'; + +const util = new Util(); + +class AssignmentController { + static async home(req, res) { + res.send(`

Welcome to the assignment Route

`) + } + + static async getAllAssignments(req, res) { + try { + const allAssignments = await AssignmentService.getAllAssignments(); + if (allAssignments.length > 0) { + util.setSuccess(200, 'Assignments retrieved', allAssignments); + } else { + util.setSuccess(200, 'No Assignment found in the database'); + } + return util.send(res); + } catch (error) { + util.setError(400, error); + return util.send(res); + } + } + + static async addAssignment(req, res) { + if (!req.body.Assignment || !req.body.senderId || !req.body.receiverId) { + util.setError(400, 'Please provide complete details of the Assignment'); + return util.send(res); + } + const newAssignment = req.body; + try { + const createdAssignment = await AssignmentService.addAssignment(newAssignment); + util.setSuccess(201, 'Assignment Added Successfully!', createdAssignment); + return util.send(res); + } catch (error) { + util.setError(400, error.Assignment); + return util.send(res); + } + } + + static async updatedAssignment(req, res) { + const alteredAssignment = req.body; + const { id } = req.params; + if (!Number(id)) { + util.setError(400, 'Please input a valid numeric value'); + return util.send(res); + } + try { + const updateAssignment = await AssignmentService.updateAssignment(id, alteredAssignment); + if (!updateAssignment) { + util.setError(404, `Cannot find a Assignment with the id: ${id}`); + } else { + util.setSuccess(200, 'Assignment updated', updateAssignment); + } + return util.send(res); + } catch (error) { + util.setError(404, error); + return util.send(res); + } + } + + static async getAAssignment(req, res) { + const { id } = req.params; + + if (!Number(id)) { + util.setError(400, 'Please input a valid numeric value'); + return util.send(res); + } + + try { + const theAssignment = await AssignmentService.getAAssignment(id); + + if (!theAssignment) { + util.setError(404, `Cannot find a Assignment with the id ${id}`); + } else { + util.setSuccess(200, 'Found Assignment', theAssignment); + } + return util.send(res); + } catch (error) { + util.setError(404, error); + return util.send(res); + } + } + + static async deleteAssignment(req, res) { + const { id } = req.params; + + if (!Number(id)) { + util.setError(400, 'Please provide a numeric value'); + return util.send(res); + } + + try { + const AssignmentToDelete = await AssignmentService.deleteAssignment(id); + + if (AssignmentToDelete) { + util.setSuccess(200, `Assignment with ${id} deleted`); + } else { + util.setError(404, `Assignment with the id ${id} cannot be found`); + } + return util.send(res); + } catch (error) { + util.setError(400, error); + return util.send(res); + } + } + + static async uploadAssignment(req, res) { + const { id } = req.params; + if (!req.files) { + util.setError(400, 'Please select a file for upload'); + return util.send(res); + } + const newUpload = req.files; + try { + await AssignmentService.uploadAssignment(newUpload, id); + util.setSuccess(201, 'Assignment Uploaded Successfully!'); + return util.send(res); + } catch (error) { + util.setError(400, error.Assignment); + return util.send(res); + } + } + +} + +export default AssignmentController; \ No newline at end of file diff --git a/src/controllers/scoreController.js b/src/controllers/scoreController.js new file mode 100644 index 0000000..c829003 --- /dev/null +++ b/src/controllers/scoreController.js @@ -0,0 +1,106 @@ +import ScoreService from '../services/ScoreService.js'; +import Util from '../utils/Utils.js'; + +const util = new Util(); + +class ScoreController { + static async getAllScore(req, res) { + try { + const allScore = await ScoreService.getAllScores(); + if (allScores.length > 0) { + util.setSuccess(200, 'Scores retrieved', allScores); + } else { + util.setSuccess(200, 'No Score found'); + } + return util.send(res); + } catch (error) { + util.setError(400, error); + return util.send(res); + } + } + + static async addScore(req, res) { + if (!req.body.Score || !req.body.senderId || !req.body.receiverId) { + util.setError(400, 'Please provide complete details'); + return util.send(res); + } + const newScore = req.body; + try { + const createdScore = await ScoreService.addScore(newScore); + util.setSuccess(201, 'Score Added!', createdScore); + return util.send(res); + } catch (error) { + util.setError(400, error.Score); + return util.send(res); + } + } + + static async updatedScore(req, res) { + const alteredScore = req.body; + const { id } = req.params; + if (!Number(id)) { + util.setError(400, 'Please input a valid numeric value'); + return util.send(res); + } + try { + const updateScore = await ScoreService.updateScore(id, alteredScore); + if (!updateScore) { + util.setError(404, `Cannot find Score with the id: ${id}`); + } else { + util.setSuccess(200, 'Score updated', updateScore); + } + return util.send(res); + } catch (error) { + util.setError(404, error); + return util.send(res); + } + } + + static async getAScore(req, res) { + const { id } = req.params; + + if (!Number(id)) { + util.setError(400, 'Please input a valid numeric value'); + return util.send(res); + } + + try { + const theScore = await ScoreService.getAScore(id); + + if (!theScore) { + util.setError(404, `Cannot find Score with the id ${id}`); + } else { + util.setSuccess(200, 'Found Score', theScore); + } + return util.send(res); + } catch (error) { + util.setError(404, error); + return util.send(res); + } + } + + static async deleteScore(req, res) { + const { id } = req.params; + + if (!Number(id)) { + util.setError(400, 'Please provide a numeric value'); + return util.send(res); + } + + try { + const ScoreToDelete = await ScoreService.deleteScore(id); + + if (ScoreToDelete) { + util.setSuccess(200, 'Score deleted'); + } else { + util.setError(404, `Score with the id ${id} cannot be found`); + } + return util.send(res); + } catch (error) { + util.setError(400, error); + return util.send(res); + } + } +} + +export default ScoreController; \ No newline at end of file diff --git a/src/db/config/config.js b/src/db/config/config.js index d86a63d..efdd849 100644 --- a/src/db/config/config.js +++ b/src/db/config/config.js @@ -1,13 +1,11 @@ -'use strict'; -const config = require('../../../config.js') - +require('dotenv').config(); module.exports = { - "development": { - "username": config.username, - "password": config.password, - "database": config.database, - "host": config.host, - "dialect": "postgres" + development: { + database: "classManager", + username: "postgres", + password: "123456789", + host: "127.0.0.1", + dialect: "postgres" }, local: { "username": "tgsowrsnnwphjg", @@ -30,4 +28,5 @@ module.exports = { "host": "ec2-3-227-68-43.compute-1.amazonaws.com", "dialect": "postgres" } -}; \ No newline at end of file +}; +//export default config; diff --git a/src/db/models/assignment.js b/src/db/models/assignment.js index 94868f6..0c12c0b 100644 --- a/src/db/models/assignment.js +++ b/src/db/models/assignment.js @@ -11,7 +11,7 @@ module.exports = (sequelize, DataTypes) => { */ static associate(models) { // define association here - Assignment.belongsTo(models.User, {foreignKey: 'teacherId', as: 'teacher'}); + // Assignment.belongsTo(models.User, {foreignKey: 'teacherId', as: 'teacher'}); Assignment.belongsTo(models.User, {foreignKey: 'studentId', as: 'student'}); Assignment.belongsTo(models.Class, {foreignKey: 'classId', as: 'class'}); } diff --git a/src/db/models/assignmentscore.js b/src/db/models/assignmentscore.js index 19e6f1f..b7d4f4f 100644 --- a/src/db/models/assignmentscore.js +++ b/src/db/models/assignmentscore.js @@ -12,7 +12,7 @@ module.exports = (sequelize, DataTypes) => { static associate(models) { // define association here AssignmentScore.belongsTo(models.Assignment, {foreignKey:'assignmentId', as: 'assignment'}); - AssignmentScore.belongsTo(models.User, {foreignKey: 'studentId', as: 'student'}); + // AssignmentScore.belongsTo(models.User, {foreignKey: 'studentId', as: 'student'}); AssignmentScore.belongsTo(models.User, {foreignKey: 'teacherId', as: 'teacher'}); } } diff --git a/src/index.js b/src/index.js index 2c7c9a7..2a9dc97 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,9 @@ import express from "express"; import dotenv from "dotenv"; +import { assignmentRouter } from './routers/assignmentRouter.js'; import messageRouter from "./routers/MessageRouter.js"; import { studentRouter } from './routers/studentsRoutes.js'; +import scoreRouter from './routers/scoreRouter.js'; const app = express(); dotenv.config(); @@ -10,6 +12,8 @@ app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use('/api/message', messageRouter); app.use('/api/students', studentRouter); +app.use('/api/assignment', assignmentRouter); +app.use('/api/score', scoreRouter); app.get('/', (req, res) => { diff --git a/src/routers/assignmentRouter.js b/src/routers/assignmentRouter.js new file mode 100644 index 0000000..8b63f4c --- /dev/null +++ b/src/routers/assignmentRouter.js @@ -0,0 +1,14 @@ +import { Router } from "express"; +import fileUpload from "express-fileupload" +const assignmentRouter = Router(); +import AssignmentController from '../controllers/assignmentsController.js'; + +assignmentRouter.get('/home', AssignmentController.home); +assignmentRouter.get('/', AssignmentController.getAllAssignments); +assignmentRouter.post('/', AssignmentController.addAssignment); +assignmentRouter.get('/:id', AssignmentController.getAAssignment); +assignmentRouter.put('/:id', AssignmentController.updatedAssignment); +assignmentRouter.delete('/:id', AssignmentController.deleteAssignment); +assignmentRouter.post('/upload/:id', fileUpload({createParentPath: true}), AssignmentController.uploadAssignment); + +export { assignmentRouter }; \ No newline at end of file diff --git a/src/routers/scoreRouter.js b/src/routers/scoreRouter.js new file mode 100644 index 0000000..aeda73b --- /dev/null +++ b/src/routers/scoreRouter.js @@ -0,0 +1,11 @@ +import { Router } from 'express'; +import scoreController from '../controllers/scoreController.js'; + +const scoreRouter = Router(); + + +scoreRouter.post('/', scoreController.addScore); +scoreRouter.put('/:id', scoreController.updatedScore); +scoreRouter.delete('/:id', scoreController.deleteScore); + +export default scoreRouter; \ No newline at end of file diff --git a/src/services/assignmentService.js b/src/services/assignmentService.js new file mode 100644 index 0000000..ba7dcbd --- /dev/null +++ b/src/services/assignmentService.js @@ -0,0 +1,88 @@ +import database from '../db/models/index.js'; +import path from 'path'; + +class AssignmentService { + static async getAllAssignments() { + try { + const result = await database.Assignment.findAll(); + return result; + } catch (error) { + throw error; + } + } + + static async addAssignment(newAssignment) { + try { + const result = await database.Assignment.create(newAssignment); + return result; + } catch (error) { + throw error; + } + } + + static async updateAssignment(id, updateAssignment) { + try { + const AssignmentToUpdate = await database.Assignment.findOne({ + where: { id: Number(id) } + }); + + if (AssignmentToUpdate) { + await database.Assignment.update(updateAssignment, { where: { id: Number(id) } }); + + return updateAssignment; + } + return null; + } catch (error) { + throw error; + } + } + + static async getAAssignment(id) { + try { + const aAssignment = await database.Assignment.findOne({ + where: { id: Number(id) } + }); + + return aAssignment; + } catch (error) { + throw error; + } + } + + static async deleteAssignment(id) { + try { + const AssignmentToDelete = await database.Assignment.findOne({ where: { id: Number(id) } }); + + if (AssignmentToDelete) { + const deletedAssignment = await database.Assignment.destroy({ + where: { id: Number(id) } + }); + return deletedAssignment; + } + return null; + } catch (error) { + throw error; + } + } + + static async uploadAssignment(files, id) { + // const validator = await database.Message.findOne({ + // where: { id: Number(id) } + // }) + // if(!validator){ + // util.setError(400, 'Please select a file for upload'); + // return util.send(res); + // } + try { + Object.keys(files).forEach(key => { + const filePath = path.join('./', 'files/assignmentSubmissions', `${id}`, files[key].name) + files[key].mv(filePath) + }) + } catch (error) { + throw error; + } + } + +} + +export default AssignmentService; diff --git a/src/services/scoreService.js b/src/services/scoreService.js new file mode 100644 index 0000000..2d0dd54 --- /dev/null +++ b/src/services/scoreService.js @@ -0,0 +1,68 @@ +import database from '../db/models/index.js'; + +class ScoreService { + static async getAllScores() { + try { + const result = await database.AssignmentScore.findAll(); + return result; + } catch (error) { + throw error; + } + } + + static async addScore(newScore) { + try { + const result = await database.AssignmentScore.create(newScore); + return result; + } catch (error) { + throw error; + } + } + + static async updateScore(id, updateScore) { + try { + const ScoreToUpdate = await database.AssignmentScore.findOne({ + where: { id: Number(id) } + }); + + if (ScoreToUpdate) { + await database.AssignmentScore.update(updateScore, { where: { id: Number(id) } }); + + return updateScore; + } + return null; + } catch (error) { + throw error; + } + } + + static async getScore(id) { + try { + const Score = await database.AssignmentScore.findOne({ + where: { id: Number(id) } + }); + + return Score; + } catch (error) { + throw error; + } + } + + static async deleteScore(id) { + try { + const ScoreToDelete = await database.AssignmentScore.findOne({ where: { id: Number(id) } }); + + if (ScoreToDelete) { + const deletedScore = await database.AssignmentScore.destroy({ + where: { id: Number(id) } + }); + return deletedScore; + } + return null; + } catch (error) { + throw error; + } + } +} + +export default ScoreService;