diff --git a/src/backend/app.js b/src/backend/app.js
index 2a9f631..dcc554f 100644
--- a/src/backend/app.js
+++ b/src/backend/app.js
@@ -8,7 +8,7 @@ const mongoose = require('mongoose');
require('dotenv').config();
const routes = require('./routes');
const passport = require('./config/passport');
-
+const cache = require('express-cache-headers');
const cors = require('cors');
const app = express();
@@ -22,7 +22,7 @@ const app = express();
// }
// Initializing middlewares
-
+app.use(cache(30));
app.use(session({
secret:process.env.SESSION_SECRET,
resave: false,
diff --git a/src/backend/controllers/issueController.js b/src/backend/controllers/issueController.js
index 85aef5d..48ff798 100644
--- a/src/backend/controllers/issueController.js
+++ b/src/backend/controllers/issueController.js
@@ -1,127 +1,177 @@
-const Issue = require( '../models/issueModel');
+const Issue = require("../models/issueModel");
const { authenticate } = require("../config/passport");
exports.issue_list = [
- async (req,res)=>{
- try{
- const issues = await Issue.find({}).populate({
- path: 'replies',
- populate: {
- path: 'sender',
- model: 'User'
- }
- }).populate('sender').exec();
- res.status(200).json(issues||[]);
- }catch(error){
- res.status(500).json({ error: 'Internal Server Error' });
- }
+ authenticate,
+ async (req, res) => {
+ try {
+ const issues = await Issue.find({})
+ .populate({
+ path: "replies",
+ populate: {
+ path: "sender",
+ model: "User",
+ },
+ })
+ .populate("sender")
+ .exec();
+ res.status(200).json(issues || []);
+ } catch (error) {
+ res.status(500).json({ error: "Internal Server Error" });
}
+ },
];
-exports.issue_detail = async (req,res)=>{
- try{
- const id = req.params.issueId;
- const issue = await Issue.findById(id).populate({
- path: 'replies',
- populate: {
- path: 'sender',
- model: 'User'
- }
- }).populate('sender').exec();
- if(issue !== null)
- return res.status(200).json(issue);
- else
- return res.status(404).json({error:"Not found"});
-
- }catch(error){
- res.status(500).json({ error: 'Internal Server Error' });
+exports.issue_detail = [
+ authenticate,
+ async (req, res) => {
+ try {
+ const id = req.params.issueId;
+ const issue = await Issue.findById(id)
+ .populate({
+ path: "replies",
+ populate: {
+ path: "sender",
+ model: "User",
+ },
+ })
+ .populate("sender")
+ .exec();
+ if (issue !== null) return res.status(200).json(issue);
+ else return res.status(404).json({ error: "Not found" });
+ } catch (error) {
+ res.status(500).json({ error: "Internal Server Error" });
}
-}
-
-exports.issue_list_user = async (req,res)=>{
- try{
- const id = req.params.userId;
- const userIssues = await Issue.find({sender:id}).populate({
- path: 'replies',
- populate: {
- path: 'sender',
- model: 'User'
- }
- }).populate('sender').exec();
- return res.status(200).json(userIssues||[]);
-
+ },
+];
- }catch(error){
- res.status(500).json({ error: 'Internal Server Error' });
+exports.issue_list_user = [
+ authenticate,
+ async (req, res) => {
+ try {
+ const id = req.params.userId;
+ const userIssues = await Issue.find({ sender: id })
+ .populate({
+ path: "replies",
+ populate: {
+ path: "sender",
+ model: "User",
+ },
+ })
+ .populate("sender")
+ .exec();
+ return res.status(200).json(userIssues || []);
+ } catch (error) {
+ res.status(500).json({ error: "Internal Server Error" });
}
-}
+ },
+];
exports.issue_create = [
- async (req,res)=>{
- try{
- const {
- sender,subject,description
- } = req.body;
- if(!sender||!subject||!description){
- return res.status(400).json({error:"Some of the required fields are empty"});
- }
- const issue= new Issue({
- seen:false,
- sender:sender,
- description:description,
- status:"open",
- subject:subject,
- replies:[]
- });
- issue.save();
- res.status(200).json(issue);
-
- }catch(error){
- res.status(500).json({ error: 'Internal Server Error' });
- }
+ authenticate,
+ async (req, res) => {
+ try {
+ const { sender, subject, description } = req.body;
+ if (!sender || !subject || !description) {
+ return res
+ .status(400)
+ .json({ error: "Some of the required fields are empty" });
+ }
+ const issue = new Issue({
+ seen: false,
+ sender: sender,
+ description: description,
+ status: "open",
+ subject: subject,
+ replies: [],
+ });
+ issue.save();
+ res.status(200).json(issue);
+ } catch (error) {
+ res.status(500).json({ error: "Internal Server Error" });
}
-]
+ },
+];
exports.issue_delete = [
- authenticate,
- async (req, res) => {
- try{
- const issueId = req.params.issueId;
- const deletedIssue = await Issue.findByIdAndDelete(issueId);
-
- if(!deletedIssue)
- res.status(404).json({error:"Issue doesn't exist"});
- else
- return res.status(200).json({message:("successfully deleted issue " +issueId)})
- } catch(error){
- res.status(500).json({ error: 'Internal Server Error' });
+ authenticate,
+ async (req, res) => {
+ try {
+ const issueId = req.params.issueId;
+ const deletedIssue = await Issue.findByIdAndDelete(issueId);
- }
+ if (!deletedIssue) res.status(404).json({ error: "Issue doesn't exist" });
+ else
+ return res
+ .status(200)
+ .json({ message: "successfully deleted issue " + issueId });
+ } catch (error) {
+ res.status(500).json({ error: "Internal Server Error" });
}
- ];
+ },
+];
+exports.issue_reply = [
+ authenticate,
+ async (req, res) => {
+ try {
+ const issueId = req.params.issueId;
+ const { sender, body } = req.body;
+ if (!sender || !body)
+ return res.status(400).json({ error: "Some fields are not specified" });
+ let issue = await Issue.findById(issueId);
+
- exports.issue_reply = async (req, res) => {
- try {
- const issueId = req.params.issueId;
- const { sender, body } = req.body;
-
- if (!sender || !body)
- return res.status(400).json({ error: "Some fields are not specified" });
-
- const issue = await Issue.findById(issueId);
-
- if (!issue)
- return res.status(404).json({ error: "Issue not found" });
-
- issue.replies.push({ sender, body });
- await issue.save(); // Save the updated issue
-
- return res.status(200).json(issue);
- } catch (error) {
- console.error('Error adding reply:', error);
- return res.status(500).json({ error: 'Internal Server Error' });
- }
- };
-
\ No newline at end of file
+ if (!issue) return res.status(404).json({ error: "Issue not found" });
+ issue.seen = false;
+ issue.replies.push({ sender, body });
+ await issue.save(); // Save the updated issue
+ issue = await Issue.findById(issueId)
+ .populate({
+ path: "replies",
+ populate: {
+ path: "sender",
+ model: "User",
+ },
+ })
+ .populate("sender")
+ .exec();
+
+ return res.status(200).json(issue);
+ } catch (error) {
+ console.error("Error adding reply:", error);
+ return res.status(500).json({ error: "Internal Server Error" });
+ }
+ },
+];
+
+exports.issue_seen = [
+ authenticate,
+ async (req, res) => {
+ try {
+ const {userId}=req.body;
+ const issueId = req.params.issueId;
+ const issue = await Issue.findById(issueId)
+ .populate({
+ path: "replies",
+ populate: {
+ path: "sender",
+ model: "User",
+ },
+ })
+ .populate("sender")
+ .exec();
+ if (!issue) return res.status(404).json({ error: "Issue not found" });
+ issue.replies.forEach((reply) => {
+ if(reply.sender._id!==userId) reply.seen = true;
+ });
+ issue.seen=true;
+ await issue.save();
+ res.status(200).json(issue);
+ } catch (error) {
+ console.error("Error marking replies as seen:", error);
+ return res.status(500).json({ error: "Internal Server Error" });
+ }
+ },
+];
diff --git a/src/backend/package-lock.json b/src/backend/package-lock.json
index ad7dc53..65c30b9 100644
--- a/src/backend/package-lock.json
+++ b/src/backend/package-lock.json
@@ -16,6 +16,7 @@
"dotenv": "^16.4.5",
"express": "~4.16.1",
"express-async-handler": "^1.2.0",
+ "express-cache-headers": "^0.1.4",
"express-validator": "^7.0.1",
"jest": "^29.7.0",
"jsonwebtoken": "^9.0.2",
@@ -2063,6 +2064,11 @@
"resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz",
"integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w=="
},
+ "node_modules/express-cache-headers": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/express-cache-headers/-/express-cache-headers-0.1.4.tgz",
+ "integrity": "sha512-dBYkl2mtb9slDlBIdCL2r8ORSUlhLs/o47498eCwx+s/z0fSfTdHWWo1Y1AyMDSzaw5LlYoGgggRYbT4GLdVPw=="
+ },
"node_modules/express-validator": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz",
diff --git a/src/backend/package.json b/src/backend/package.json
index f114c9e..157fdb4 100644
--- a/src/backend/package.json
+++ b/src/backend/package.json
@@ -17,6 +17,7 @@
"dotenv": "^16.4.5",
"express": "~4.16.1",
"express-async-handler": "^1.2.0",
+ "express-cache-headers": "^0.1.4",
"express-validator": "^7.0.1",
"jest": "^29.7.0",
"jsonwebtoken": "^9.0.2",
diff --git a/src/backend/routes/issueRoute.js b/src/backend/routes/issueRoute.js
index 25af68e..f126e33 100644
--- a/src/backend/routes/issueRoute.js
+++ b/src/backend/routes/issueRoute.js
@@ -9,4 +9,5 @@ router.get('/user/:userId',issue_controller.issue_list_user);
router.post('/',issue_controller.issue_create);
router.delete('/:issueId',issue_controller.issue_delete);
router.put('/reply/:issueId',issue_controller.issue_reply);
+router.put('/seen/:issueId',issue_controller.issue_seen);
module.exports = router;
diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index 7129763..20680a9 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -33,6 +33,7 @@
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.18",
+ "daisyui": "^4.10.1",
"eslint": "^8.55.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
@@ -2444,6 +2445,16 @@
"node": ">= 8"
}
},
+ "node_modules/css-selector-tokenizer": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
+ "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "fastparse": "^1.1.2"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -2460,6 +2471,34 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
+ "node_modules/culori": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
+ "integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/daisyui": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.10.1.tgz",
+ "integrity": "sha512-Ds0Z0Fv+Xf6ZEqV4Q5JIOeKfg83xxnww0Lzid0V94vPtlQ0yYmucEa33zSctsX2VEgBALtmk5zVEqd59pnUbuQ==",
+ "dev": true,
+ "dependencies": {
+ "css-selector-tokenizer": "^0.8",
+ "culori": "^3",
+ "picocolors": "^1",
+ "postcss-js": "^4"
+ },
+ "engines": {
+ "node": ">=16.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/daisyui"
+ }
+ },
"node_modules/date-fns": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
@@ -3131,6 +3170,12 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fastparse": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
+ "dev": true
+ },
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
diff --git a/src/frontend/package.json b/src/frontend/package.json
index bbdd92f..22d7eda 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -35,6 +35,7 @@
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.18",
+ "daisyui": "^4.10.1",
"eslint": "^8.55.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
diff --git a/src/frontend/src/Pages/AdminIssue.jsx b/src/frontend/src/Pages/AdminIssue.jsx
new file mode 100644
index 0000000..cb839d5
--- /dev/null
+++ b/src/frontend/src/Pages/AdminIssue.jsx
@@ -0,0 +1,5 @@
+import CustomerIssue from "./Issue";
+
+export default function AdminIssue() {
+ return
{reply.body}
-{new Date(reply.createdAt).toLocaleString()}
-Posted by: {reply.sender.username}
-{reply.body}
++ {new Date(reply.createdAt).toLocaleString()} +
++ {reply.sender.username} +
+This field is required
+ {!isTextAreaValid && ( +This field is required
)} - - {/*- Are you sure you want to delete{" "} - issue {issueId}? -
-+ Are you sure you want to delete{" "} + issue {issueId}? +
+{issue.description}
+My Account
-window.open("/user/reservation", "_self")}*/>My Reservations
- {(user && user.role === "admin") &&Admin Dashboard
} - {(user && user.role === "representative") &&CSR Dashboard
} - {(user && user.role === "customer") &&Report an Issue
} -logoutAccount(setToken)}>Logout
- -+ My Account +
+ +window.open("/user/reservation", "_self")}*/ + > + My Reservations +
+ + {user && user.role === "admin" && ( + ++ Admin Dashboard +
+ + )} + {user && user.role === "representative" && ( + ++ CSR Dashboard +
+ + )} + {user && user.role === "customer" && ( + +Issues
+ {notification && ( + + + + )} + + )} +logoutAccount(setToken)} + > + Logout +
+