diff --git a/.github/workflows/event-logger.yml b/.github/workflows/event-logger.yml
new file mode 100644
index 0000000..ed5c142
--- /dev/null
+++ b/.github/workflows/event-logger.yml
@@ -0,0 +1,59 @@
+# DO NOT TOUCH THIS FILE!!!
+name: log github events
+on:
+ push:
+ branches: [main, master]
+ pull_request:
+ types: [opened, closed]
+ branches: [main, master]
+jobs:
+ log:
+ runs-on: ubuntu-latest
+ env:
+ COMMIT_LOG_API: ${{ secrets.COMMIT_LOG_API }}
+ COMMITS: ${{ toJSON(github.event.commits) }}
+ REPOSITORY_URL: ${{ github.repositoryUrl }}
+ EVENT_TYPE: ${{ github.event_name }}
+ EVENT_ACTION: ${{ github.event.action }}
+ EVENT_USERNAME: ${{ github.actor }}
+ EVENT_EMAIL: "${{ github.event.pull_request.sender.email }}"
+ PR_MERGED: ${{ github.event.pull_request.merged }}
+ PR_CREATED_AT: ${{ github.event.pull_request.created_at}}
+ PR_CLOSED_AT: ${{ github.event.pull_request.closed_at}}
+ PR_MERGE_USER: ${{ github.event.pull_request.merged_by.login}}
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0 # this is important so git fetches all history.. the actions/checkout by default fetches all history as one commit which throws off stats
+ - uses: actions/setup-python@v3
+ with:
+ python-version: "^3.9"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install --user pipenv
+ pipenv install pytz
+ pipenv install python-dateutil
+ pipenv install build
+ pipenv install requests
+ pipenv install gitcommitlogger
+ - name: Log pull request opened
+ if: github.event_name == 'pull_request' && github.event.action == 'opened'
+ run: |
+ pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t pull_request_opened -d $(echo $PR_CREATED_AT) -un $(echo $EVENT_USERNAME) -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v
+ - name: Log pull request closed and merged
+ if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true
+ run: |
+ echo $COMMITS > commits.json
+ cat commits.json # debugging
+ pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t pull_request_merged -d $(echo $PR_CLOSED_AT) -un $(echo $PR_MERGE_USER) -i commits.json -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v
+ - name: Log pull request closed without merge
+ if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == false
+ run: |
+ pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t pull_request_closed -d $(echo $PR_CLOSED_AT) -un $(echo $EVENT_USERNAME) -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v
+ - name: Log push
+ if: github.event_name == 'push'
+ run: |
+ echo $COMMITS > commits.json
+ cat commits.json # debugging
+ pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t $(echo $EVENT_TYPE) -i commits.json -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v
diff --git a/.gitignore b/.gitignore
index e34b1d3..5528fca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
+# zion's files #
+update_repo.sh
+pm2.config.json
+package.json
+package-lock.json
# Compiled source #
###################
*.com
@@ -39,6 +44,9 @@ Thumbs.db
# Typical Node.js artifacts #
#############################
+#ignore lock file to reduce git log stats bloat
+package-lock.json
+
# Logs
logs
*.log
@@ -123,4 +131,7 @@ typings/
## Jekyll artifacts
**/.jekyll-cache
-**/_site/
\ No newline at end of file
+**/_site/
+
+## VScode
+.vscode
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d5bee28..d1c9df6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,2 +1,78 @@
-# Guide to Contributing
-Delete the contents of this file and replace with the contents of a proper guide to contributing to this project, as described in the [instructions](./instructions.md)
+# Team norms
+## General team guidelines
+- Join the Discord VC while working to boost productivity and speed up team interactions and communications.
+- When you're stuck, **reach out** to the Scrum Master or team member. There's no harm in not knowing certain things.
+## Sprint cadence
+- Sprint length is 2 weeks
+- Sprint planning meeting should be 1-2 hours in length.
+- Backlog grooming meeting is at least once per sprint.
+- When completing a task, put it in the review section of the board and reference the commit(just paste the commit hash) for the Scrum Master to review.
+## Daily standups
+- Daily standups must be done by the end of the day and submitted to the discord channel by the scrum master.
+- Members must meet in person for 1-5 minutes for the standup meeting.
+- Members that make no progress on a task for more than two standups get reported to management.
+
+# Coding/Contributing guidelines
+## Contributing Guide
+- Never push broken code to main, make sure your code is working and tested. If you do push it, you must fix it immediately as to avoid assigning extra work to other teammates.
+- Rarely push to the main branch, always branch and pull request your finished task to the Github. Only push to main if a change is really minor and wont cause significant merge conflicts.
+- If the pull request is related to a completed task, always put the issue number in the pull request to make it easier for fellow developers to review.
+- People assigned to review the pull request must actually read their code in order to ensure it follows team norms.
+
+## Coding standards
+- Use ES6Lint VS code extension for Javascript.
+- Use Typescript when writing your code to avoid misunderstood data input types.
+- Autoformat your code using `CTRL+SHIFT+I` before committing.
+- Do not write short variable names whose names dont relate to their function and comment your functions.
+- Write automated tests to cover critical integration points and functionality (once you learn how to do that).
+- Make granular and small commits, per feature or per bug fix.
+- Make short, straight to the point, yet descriptive commit messages.
+
+## Coding standards - CSS
+- Do not use unecessary amount of divs, instead use flexbox/grids when possible for more responsive design.
+- Do not write unresponsive code(that doesn't adapt to size of container).
+- Use variables for colors in CSS to make it easier to go back and edit if necessary.
+### CSS Units
+How to decide which unit to choose(general guidelines):
+- Font-size = em
+- Padding and margin = rem
+- Width = em or %
+
+
+# Project Setup
+
+## Setup the IDE
+1. Install VS code
+2. Clone the repository using `git clone https://github.com/agiledev-students-spring-2023/final-project-bayt`.
+3. Open the repo in VS code and download the following extensions:
+ - ESlint
+ - React Snippets
+ - GitGraph
+ - Gitlens
+
+### Build and launch the database
+
+- install and run [docker desktop](https://www.docker.com/get-started)
+- create a [dockerhub](https://hub.docker.com/signup) account
+- run command, `docker run --name mongodb_dockerhub -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=secret -d mongo:latest`
+
+The back-end code will integrate with this database. However, it may be occasionally useful interact with the database directly from the command line:
+
+- connect to the database server from the command line: `docker exec -ti mongodb_dockerhub mongosh -u admin -p secret`
+- show the available databases: `show dbs`
+- select the database used by this app: `use example-mern-stack-app`
+- show the documents stored in the `messages` collection: `db.messages.find()` - this will be empty at first, but will later be populated by the app.
+
+If you have trouble running Docker on your computer, use a database hosted on [MongoDB Atlas](https://www.mongodb.com/atlas) instead. Atlas is a "cloud"" MongoDB database service with a free option. Create a database there, and make note of the connection string. In the `.env` file within the `back-end` directory, replace the given `DB_CONNECTION_STRING` value with this one.
+
+### Build and launch the back end
+
+1. Navigate into the `back-end` directory
+1. Run `npm ci` to install all dependencies listed in the `package.json` file.
+1. Run `npm start` to launch the back-end server
+
+### Build and launch the front end
+
+1. Navigate into the `front-end` directory
+1. Run `npm ci` to install all dependencies listed in the `package.json` file.
+1. Run `npm start` to launch the React.js server
diff --git a/README.md b/README.md
index dcc4059..bebde47 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,39 @@
-# Project Repository
+# Bayt - A shared housing app
+## ~~Website deployed!~~ (Currently Unavaliable)
+Link: `http://www.baytmatesfor.life/`
+
+## Product Vision Statement
+We foresee our **Minimum Viable Product(MVP)** as one that makes the issue of accountability and delegation of responsibility in shared accommodations significantly easier than without the app. The product in essence should solve issues in shared accountability through **quick** generation or writing of tasks required based on rooms in the house. The signup process of the app must be incredibly easy if not non-existent in order to encourage ease of use. Roommates should also be able to at least share and keep track of some form of shared purchases made for the dorm such as furniture and kitchen items. The app should provide more value than the energy needed to use it and set it up to encourage people with busy schedules to use it and see it as a tool that saves time rather than wastes.
+
+## What and why?
+When living in shared accomodations, it becomes difficult to delegate house chores, decide grocery options, and split finances for shares household purchases. Purchases such as that mop you might've bought for everybody to clean the residence or that air fryer you bought and decided to split the payment for. With the IOS app Bayt, you can view who's purchased what, and decide how payment should be handled such as one roommate buying the toilet paper next time or just paying outright cash. You should also be able to generate grocery lists that just make sense, based off meal preferences and dietary restrictions of your roommates. Another important feature is to draft living agreements that arent 200 lines long and straight to the point. In summary, Bayt should make living with people much easier by tackling those common issues across roommates.
+
+## For whom?
+The main users would be students in dormitories, families that work together to take care of their home, or anyone that has shared accomodation. This might also be useful for property managers to manage shared housing when it comes to agreements and disputes.
+
+## How?
+- Payment management
+- Chore scheduling
+- Alerts for tasks
+
+## Who are we?
+- JoJo Yang: https://github.com/jojo1042
+- Rami Richani: https://github.com/dolf321
+- Diana Yepes: https://github.com/dianaYepes
+- Zander Chen: https://github.com/ccczy-czy
+- Atib Jawad Zion: https://github.com/zion-off
+
+## Setup instructions
+- [Frontend setup](./front-end/README.md)
+- [Backend setup](./back-end/README.md)
+
+PLease note if you are facing any issues connecting to the Backend from the Frontend on **Mac** do the following:
+- Enter the frontend directory.
+- Delete its node_modules folder.
+- Run `npm install`.
+- Then restart your computer.
+
+## Project Repository
This repository will be used for team projects.
@@ -17,3 +52,4 @@ Several sets of instructions are included in this repository. They should each b
1. See the [Database Integration instructions](./instructions-3-database.md) for the requirements of integrating a database into the back-end.
1. See the [Deployment instructions](./instructions-4-deployment.md) for the requirements of deploying an app.
+
diff --git a/UX-DESIGN.md b/UX-DESIGN.md
index 783b085..cb4bc1d 100644
--- a/UX-DESIGN.md
+++ b/UX-DESIGN.md
@@ -1,8 +1,25 @@
-# User Experience Design
-This repository contains instructions and files for two assignments that together comprise the user experience design phase of a web app.
+
Bayt UX Design
+
+# Prototype
+The Figma prototype link for the Bayt app is here.
+
+# App Map
+
+
+
+# Wireframe Diagrams
+
+| UX-Wireframe | Description |
+| --- | --- |
+| 
Login Page| This is the initial login page, it prompts for users to enter a username and the house code they belong to login rather than a standard password. For now we assume house codes are assigned to appropriate users and this will be addressed in the future.|
+| 
Home Page| This is the home page in which the user can view all available rooms in the house and add rooms. In the top right you have a profile icon and the footer contains the navigation bar with all the pages. The house name in the header or "Ravenclaw" represents the house name in which the user had been assigned to. |
+| 
Room Template Page| This shows the room and the tasks assigned to the room with a button to take you back to the Home page in which you selected the room. The task bars are buttons that will lead you to the Individual Task Page in which the user can edit/modify tasks. Also the square buttons on the left mark the task as complete or not.|
+| 
Tasks Page| This collectively shows all the tasks with the task buttons and the checkmark as stated in the previous description. The filter button allows you to filter task based on criterea such as date or room the task is assigned to, if any at all. The sort button allows you to sort in Chronological order to choose most recently due tasks or not. |
+| 
Indiviual Task Page| This shows the task details in which you can modify information relating to the task such as the date or time in which it is due to be completed. |
+| 
Finances Page| This page allows users to communicate and keep track of shared purchases in the living space. |
+| 
Profile Page| This page allows users to edit their profile details. |
+| 
Alerts Page| This is the page to which the user gets notified of near tasks that they are responsible for and the user is led to this page through the footer or through notification.|
+| 
Settings Page| Allows the user to modify the house details such as name, view whose in their housing group, and log out of their account if needed.|
-Replace the contents of this file with the completed assignments, as described in:
-- [app map & wireframe instructions](instructions-0a-app-map-wireframes.md).
-- [prototype instructions](instructions-0b-prototyping.md)
diff --git a/back-end/README.md b/back-end/README.md
new file mode 100644
index 0000000..81cc491
--- /dev/null
+++ b/back-end/README.md
@@ -0,0 +1,38 @@
+# Backend File Structure
+ .
+ ├── src/ # Contains the main application code
+ │ ├── controllers/ # Contains controller functions for handling requests
+ │ ├── middleware/ # Contains middleware functions for processing requests and responses
+ │ ├── models/ # Contains data models for interacting with the database
+ │ ├── routes/ # Contains route definitions and handlers
+ │ ├── services/ # Responsible for querying the database
+ │ ├── json/ # Contains json files
+ │ ├── app.js # Main Express.js application file
+ │ ├── configs/ # Configuration files for the application
+ │ └── validations/ # Backend calls validations
+ │── test/ # Contains unit tests for the application
+ │ ├── controllers/ # Contains unit tests for controller functions
+ │ ├── middleware/ # Contains unit tests for middleware functions
+ │ ├── models/ # Contains unit tests for data models
+ │ ├── routes/ # Contains unit tests for route handlers
+ │ ├── utils/ # Contains unit tests for utility functions and modules
+ │ └── setup.js # Setup file for running tests
+ ├── node_modules/ # Contains all installed node modules
+ ├── package.json # Contains metadata about the project and its dependencies
+ └── README.md # Contains documentation for the project
+
+# Deploying Instructions
+
+Run `npm install` when in this directory before running any other command.
+
+### `npm start`
+
+Used to initialize the backend with nodemon.
+
+### `npm test`
+
+Used to run the Mocha Unit Tests of the system.
+
+### `npm coverage`
+
+Used to run the unit tests as well as show code coverage using Istanbul(nyc).
\ No newline at end of file
diff --git a/back-end/package.json b/back-end/package.json
new file mode 100644
index 0000000..77c8db8
--- /dev/null
+++ b/back-end/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "back-end",
+ "version": "1.0.0",
+ "description": "The back-end of your project will live in this directory.",
+ "main": "./src/server.js",
+ "scripts": {
+ "start": "export NODE_ENV=production || set NODE_ENV=production&& nodemon ./src/server.js",
+ "test": "export NODE_ENV=test || set NODE_ENV=test&& npx mocha --recursive",
+ "coverage": "export NODE_ENV=test || set NODE_ENV=test&& npx nyc npm test"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.20.2",
+ "chai": "^4.3.7",
+ "chai-http": "^4.3.0",
+ "cors": "^2.8.5",
+ "dotenv": "^10.0.0",
+ "express": "^4.18.2",
+ "express-validator": "^7.0.0",
+ "istanbul": "^0.4.5",
+ "mongoose": "^7.0.3",
+ "cookie-parser": "^1.4.5",
+ "passport": "^0.5.0",
+ "passport-jwt": "^4.0.0",
+ "bcryptjs": "^2.4.3",
+ "multer": "^1.4.5-lts.1"
+ },
+ "devDependencies": {
+ "mocha": "^10.2.0",
+ "nodemon": "^2.0.21",
+ "nyc": "^15.1.0",
+ "sinon": "^15.0.3",
+ "supertest": "^6.3.3"
+ }
+}
diff --git a/back-end/readme.txt b/back-end/readme.txt
deleted file mode 100644
index 49d79ec..0000000
--- a/back-end/readme.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-The back-end of your project will live in this directory.
-
-You will most likely initiate the back-end Node.js/Express.js server by running the "npm init" command from within this directory.
diff --git a/back-end/src/app.js b/back-end/src/app.js
new file mode 100644
index 0000000..2e26de9
--- /dev/null
+++ b/back-end/src/app.js
@@ -0,0 +1,74 @@
+require('dotenv').config({ silent: true }); // load environmental variables from a hidden file named .env
+// import and instantiate express
+const express = require("express");
+const bodyParser = require('body-parser');
+const cors = require('cors') // middleware for enabling CORS (Cross-Origin Resource Sharing) requests.
+require("dotenv").config({ silent: true });
+
+// The following are used for authentication with JSON Web Tokens
+const passport = require("passport");
+
+// Import JWT
+const jwtConfig = require("./configs/jwt.config.js");
+passport.use(jwtConfig.jwtStrategy);
+
+const app = express(); // instantiate an Express object
+
+// use passport middleware
+app.use(passport.initialize());
+app.use(jwtConfig.protectContentMiddleware);
+
+// import and instantiate mongoose
+const mongoose = require("mongoose");
+
+// connect to database
+if (process.env.NODE_ENV === "production") {
+ console.log("Production mode activated.");
+ mongoose
+ .connect(`${process.env.DB_CONNECTION_STRING}`)
+ .then(data => console.log(`Connected to MongoDB`))
+ .catch(err => console.error(`Failed to connect to MongoDB: ${err}`));
+} else {
+ console.log("Testing mode activated.");
+}
+
+const taskRouter = require('./routes/task.route.js');
+const profRouter = require('./routes/prof.route.js');
+const financesRouter = require('./routes/finances.route.js');
+const settingsRouter = require('./routes/settings.route.js');
+const loginRouter = require('./routes/login.route.js');
+const addMembersRouter = require('./routes/addmembers.route.js');
+const homeRouter = require('./routes/home.route.js');
+const alertsRouter = require('./routes/alerts.route.js');
+const signupRouter = require('./routes/signup.route.js');
+const protectedContentRouter = require("./routes/protectcontent.route.js");
+
+
+// parse application/json
+app.use(bodyParser.json());
+app.use(cors());
+app.use(bodyParser.urlencoded({ extended: false }));
+
+// protect content
+app.use(`/protected`, protectedContentRouter);
+// parse profile data
+app.use('/Profile', profRouter);
+// parse task data
+app.use('/tasks', taskRouter);
+//parse finances data
+app.use('/finances', financesRouter);
+//parse alerts data
+app.use('/alerts', alertsRouter);
+//parse settings data
+app.use('/settings', settingsRouter);
+// parse login data
+app.use('/login', loginRouter);
+//parse addMembers data
+app.use('/addMembers', addMembersRouter);
+// parse home data
+app.use('/home', homeRouter);
+// parse signup data
+app.use(`/signup`, signupRouter);
+
+// export the express app we created to make it available to other modules
+module.exports = app
diff --git a/back-end/src/configs/jwt.config.js b/back-end/src/configs/jwt.config.js
new file mode 100644
index 0000000..c55aa3f
--- /dev/null
+++ b/back-end/src/configs/jwt.config.js
@@ -0,0 +1,45 @@
+const mongoose = require("mongoose")
+const ObjectId = mongoose.Types.ObjectId
+const userModel = require("../models/users.model.js")
+
+const passportJWT = require("passport-jwt")
+const passport = require("passport")
+const ExtractJwt = passportJWT.ExtractJwt
+const JwtStrategy = passportJWT.Strategy
+
+// set up JWT authentication options for passport
+let jwtOptions = {
+ jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme("jwt"), // look for the Authorization request header
+ secretOrKey: process.env.JWT_SECRET,
+}
+
+// payload of JWT token
+const jwtVerifyToken = async function (jwt_payload, next) {
+
+ // find user in the database
+ const userId = new ObjectId(jwt_payload.id) // convert the string id to an ObjectId
+ const user = await userModel.findOne({ _id: userId }).exec()
+
+ // Populate user house and ass
+ if (user) {
+ // we found the user... keep going
+ next(null, user)
+ } else {
+ // we didn't find the user... fail!
+ next(null, false)
+ }
+}
+
+const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerifyToken);
+
+function protectContentMiddleware(req, res, next) {
+ if (req.path.startsWith('/login') || req.path.startsWith('/signup')) {
+ // If the request path starts with /login or /signup,
+ // skip this middleware and continue to the next one.
+ next();
+ } else {
+ passport.authenticate("jwt", { session: false })(req, res, next);
+ }
+}
+
+module.exports = { jwtStrategy, protectContentMiddleware };
diff --git a/back-end/src/controllers/addmembers.controller.js b/back-end/src/controllers/addmembers.controller.js
new file mode 100644
index 0000000..1cb5751
--- /dev/null
+++ b/back-end/src/controllers/addmembers.controller.js
@@ -0,0 +1,16 @@
+const addMembersService = require("../services/addmembers.services.js");
+
+
+async function saveUser(req,res){
+ try {
+ const saved = await addMembersService.savingUser(req,res);
+ res.status(200).json(saved);
+ } catch (err) {
+ res.status(500).json({ message: err.message });
+ }
+ }
+
+
+module.exports = {
+ saveUser
+ };
\ No newline at end of file
diff --git a/back-end/src/controllers/alerts.controller.js b/back-end/src/controllers/alerts.controller.js
new file mode 100644
index 0000000..44ede76
--- /dev/null
+++ b/back-end/src/controllers/alerts.controller.js
@@ -0,0 +1,25 @@
+const alertsService = require("../services/alerts.service.js");
+
+const getAlerts = async (req, res, next) => {
+ try {
+ const alerts = await alertsService.getAlerts(req.user.houses._id, req.user._id);
+ res.json(alerts);
+ } catch (error) {
+ next(error);
+ }
+};
+
+const updateAlertState = async (req, res, next) => {
+ try {
+ const { alertId, isChecked } = req.body;
+ await alertsService.logAlertState(alertId, isChecked); // call logAlertState from alerts.service.js with the alertId and isChecked values
+ res.json({ success: true });
+ } catch (error) {
+ next(error);
+ }
+};
+
+module.exports = {
+ getAlerts,
+ updateAlertState,
+};
\ No newline at end of file
diff --git a/back-end/src/controllers/finances.controller.js b/back-end/src/controllers/finances.controller.js
new file mode 100644
index 0000000..588b8b1
--- /dev/null
+++ b/back-end/src/controllers/finances.controller.js
@@ -0,0 +1,25 @@
+const financesService = require("../services/finances.service.js");
+
+async function getAllTransactions(req, res) {
+ try {
+ const transactions = await financesService.getAllTransactions(req.user.houses._id);
+ res.status(200).json(transactions);
+ } catch (err) {
+ console.error(err);
+ res.status(400).json({
+ error: err,
+ status: "failed to retrieve data",
+ });
+ }
+}
+
+async function addTransaction(req, res) {
+ const transaction = req.body;
+ const newTransaction = await financesService.addTransaction(transaction, req.user.houses._id);
+ res.status(200).json(newTransaction);
+}
+
+module.exports = {
+ getAllTransactions,
+ addTransaction,
+};
diff --git a/back-end/src/controllers/home.controller.js b/back-end/src/controllers/home.controller.js
new file mode 100644
index 0000000..c85c58f
--- /dev/null
+++ b/back-end/src/controllers/home.controller.js
@@ -0,0 +1,32 @@
+const homeService = require("../services/home.service.js");
+
+async function getRooms(req, res) {
+ try {
+ const rooms = await homeService.getAllRooms(req?.user.houses)
+ res.status(200).json(rooms)
+ }
+ catch(err) {
+ res.status(500).json({
+ error: err,
+ status: "data retrieval failed",
+ });
+ }
+}
+
+async function addRoom(req, res) {
+ try {
+ const room = await homeService.addRoom(req?.user.houses, req.body)
+ res.status(200).json(room)
+ }
+ catch(err) {
+ res.status(500).json({
+ error: err,
+ status: "add room failed",
+ })
+ }
+}
+
+module.exports = {
+ getRooms,
+ addRoom,
+};
\ No newline at end of file
diff --git a/back-end/src/controllers/login.controller.js b/back-end/src/controllers/login.controller.js
new file mode 100644
index 0000000..44dbebf
--- /dev/null
+++ b/back-end/src/controllers/login.controller.js
@@ -0,0 +1,49 @@
+const mongoose = require("mongoose");
+const userModel = require("../models/users.model.js");
+
+async function login(req, res) {
+ const username = req.body.username;
+ const password = req.body.password;
+
+ if (!username || !password) {
+ res.status(401).json({ success: false, message: `No username or password supplied.` });
+ }
+
+ try {
+ const user = await userModel.findOne({ username: username }).exec();
+ // check if user was found
+ if (!user) {
+ return res.status(401).json({
+ success: false,
+ message: "User not found.",
+ });
+ }
+ // if user exists, check if password is correct
+ else if (!user.validPassword(password)) {
+ return res.status(401).json({
+ success: false,
+ message: "Incorrect password.",
+ });
+ }
+ // user found and password is correct... send a success response
+ const token = user.generateJWT(); // generate a signed token
+ res.json({
+ success: true,
+ message: "User logged in successfully.",
+ token: token,
+ username: user.username,
+ }); // send the token to the client to store
+ } catch (err) {
+ // check error
+ console.log(`Error looking up user: ${err}`);
+ return res.status(500).json({
+ success: false,
+ message: "Error looking up user in database.",
+ error: err,
+ });
+ }
+}
+
+module.exports = {
+ login,
+};
\ No newline at end of file
diff --git a/back-end/src/controllers/prof.controller.js b/back-end/src/controllers/prof.controller.js
new file mode 100644
index 0000000..b90c9e6
--- /dev/null
+++ b/back-end/src/controllers/prof.controller.js
@@ -0,0 +1,138 @@
+//const userData = require('../json/hardcode.json')
+const fs = require('fs');
+const multer = require('multer');
+const path = require('path');
+const User = require('../models/users.model.js');
+
+
+const storage = multer.diskStorage({
+ destination: function (req, file, cb) {
+ cb(null, path.join(__dirname, '../uploads'))
+ },
+ filename: function (req, file, cb) {
+ const userid = req.user._id;
+ const timestamp = Date.now();
+ const extension = path.extname(file.originalname);
+ const newFilename = `${timestamp}_${userid}${extension}`;
+ cb(null, newFilename);
+ }
+});
+
+const upload = multer({ storage: storage});
+
+
+//ideally we query with mongoose and pull object using ID
+async function gets(req, res) {
+ try {
+ const userData = await User.findById(req.user._id).populate('houses','name');
+ // If no user is found, return a 404 error
+ if (!userData) {
+ return res.status(404).json({
+ message: 'User not found',
+ });
+ }
+
+ const responseObject = { data: userData };
+
+ // If the user has a profile picture, send it back as an image
+ if (userData.profile_pic != 'Default.svg') {
+ console.log(userData.profile_pic);
+ const imagePath = path.join(__dirname, '../uploads', userData.profile_pic);
+ try {
+ await fs.promises.access(imagePath, fs.constants.F_OK);
+ const imageBuffer = await fs.promises.readFile(imagePath);
+ responseObject.image = imageBuffer.toString('base64');
+ } catch (error) {
+ console.error(`File '${userData.profile_pic}' does not exist in the 'uploads' folder. Setting default profile now`);
+ responseObject.data.profile_pic = 'Default.svg';
+ await User.updateOne({ _id: req.user.id}, { profile_pic: 'Default.svg' });
+ }
+ }
+
+ res.json(responseObject);
+ }
+ catch (err) {
+ console.error(err);
+ res.status(500).json({
+ error: err,
+ message: 'Failed to retrieve data',
+ });
+ }
+ };
+
+
+
+
+ //handle user uploading files, updating db, removing previous file for user
+ async function store(req,res){
+ const file = req.file;
+ const user = req.user;
+
+ // Get the current prof_pic filename for the user
+ const currentFilename = user.profile_pic;
+
+ // If the current filename is 'default', update the prof_pic field and save the new file to the uploads folder
+ if (currentFilename === 'Default.svg') {
+ user.profile_pic = file.filename;
+ await user.save();
+ return res.status(200).send('File uploaded successfully');
+ }
+
+ // If the current filename is not 'default', delete the old file from the uploads folder
+ const oldFilePath = path.join(__dirname, '../uploads', currentFilename);
+ fs.unlink(oldFilePath, async (unlinkErr) => {
+ if (unlinkErr) {
+ console.error(unlinkErr);
+ return res.status(500).send(unlinkErr);
+ }
+
+ // Update the prof_pic field and save the new file to the uploads folder
+ user.profile_pic = file.filename;
+ await user.save();
+ return res.status(200).send('File uploaded successfully');
+ });
+ };
+
+
+
+
+
+ //change all this when we use database. Will be a lot simpler.
+ async function update(req, res) {
+ try {
+ const username = req.user.username;
+ const updatedData = req.body;
+ // Find the user by username
+ const user = await User.findOne({ username });
+
+ if (!user) {
+ return res.status(404).json({
+ error: 'User not found',
+ status: 'failed to update data',
+ });
+ }
+
+ // Update the existing data with the updated fields
+ Object.assign(user, updatedData);
+
+ // Update the user data in the MongoDB collection
+ await User.updateOne({ username }, { $set: user });
+ // Send a success response to the client-side application
+ res.send(user);
+ }
+ catch (err) {
+ console.error(err);
+ res.status(400).json({
+ error: err,
+ status: 'failed to update data',
+ });
+ }
+};
+
+
+ module.exports = {
+ gets,
+ update,
+ store,
+ upload
+ };
diff --git a/back-end/src/controllers/protectcontent.controller.js b/back-end/src/controllers/protectcontent.controller.js
new file mode 100644
index 0000000..c4ce190
--- /dev/null
+++ b/back-end/src/controllers/protectcontent.controller.js
@@ -0,0 +1,16 @@
+async function getPage(req, res) {
+ // send data to authenticated users
+ res.json({
+ success: true,
+ user: {
+ id: req.user?._id,
+ username: req.user?.username,
+ },
+ message:
+ "Congratulations: you have accessed this route because you have a valid JWT token!",
+ });
+}
+
+module.exports = {
+ getPage,
+};
\ No newline at end of file
diff --git a/back-end/src/controllers/settings.controller.js b/back-end/src/controllers/settings.controller.js
new file mode 100644
index 0000000..ae9eaec
--- /dev/null
+++ b/back-end/src/controllers/settings.controller.js
@@ -0,0 +1,22 @@
+const mydata = require('../json/household_info.json');
+const usersModel = require('../models/users.model');
+
+// Get users in home and their info
+async function gets(req, res) {
+ try{
+ const users = await usersModel.find({houses: req?.user.houses}).lean();
+ res.status(200).json(users);
+ }
+ catch (err) {
+ console.error(err)
+ res.status(400).json({
+ error: err,
+ status: 'failed to retrieve data',
+ })
+ }
+ };
+
+
+ module.exports = {
+ gets
+ };
diff --git a/back-end/src/controllers/signup.controller.js b/back-end/src/controllers/signup.controller.js
new file mode 100644
index 0000000..9c2c565
--- /dev/null
+++ b/back-end/src/controllers/signup.controller.js
@@ -0,0 +1,61 @@
+// mongoose models for MongoDB data manipulation
+const mongoose = require("mongoose")
+const userModel = require("../models/users.model.js");
+const houseModel = require("../models/house.model.js");
+
+async function signup(req, res) {
+ const houseName = req.body.houseName;
+ const code = req.body.password;
+ const username = req.body.username;
+ const email = req.body.email;
+ const role = req.body.role;
+
+ if (!houseName || !code || !username || !email || !role) {
+ // incomplete info received in the POST body
+ res.status(401).json({
+ success: false,
+ message: `Incomplete info supplied.`,
+ });
+ }
+
+ try {
+ const house = await new houseModel({ code: code, name: houseName }).save();
+
+ try {
+ const user = await new userModel({ username: username, first_name: "Set your first name", last_name: "Set your last name", email: email, password: code, role: role, profile_pic: "Default.svg", houses: house._id}).save();
+ house.users.push(user);
+ await house.save();
+
+ // user saved successfully
+
+ const token = user.generateJWT(); // generate a signed token
+ res.json({
+ success: true,
+ message: "User saved successfully.",
+ token: token,
+ username: user.username,
+ }); // send the token to the client to store
+ } catch (err) {
+ // error saving user to database
+ console.log(`Failed to save user: ${err}`);
+ res.status(500).json({
+ success: false,
+ message: "Error saving user to database.",
+ error: err,
+ });
+ }
+
+ } catch(err) {
+ // error saving user to database
+ console.log(`Failed to save house: ${err}`)
+ res.status(500).json({
+ success: false,
+ message: "Error saving house to database.",
+ error: err,
+ });
+ }
+}
+
+module.exports = {
+ signup,
+};
\ No newline at end of file
diff --git a/back-end/src/controllers/task.controller.js b/back-end/src/controllers/task.controller.js
new file mode 100644
index 0000000..cadb910
--- /dev/null
+++ b/back-end/src/controllers/task.controller.js
@@ -0,0 +1,76 @@
+const taskService = require("../services/task.service.js");
+
+// get task by id taskSer
+async function get(req, res) {
+ console.log("get")
+ try {
+ let task = await taskService.getTask(req.user.houses._id, req.params.id);
+
+ if (process.env.NODE_ENV === 'production') {
+ if (task === null) throw new Error("Task not found");
+ // Set assignee to the first name of the assignee and only access assignee if its not null
+ task.assignee = task.assignee?.first_name ?? "No Assignee";
+ task.room = task.room?.roomName;
+ } else {
+ if (task === undefined) throw new Error("Task not found");
+ }
+ res.status(200).json(task);
+ } catch (err) {
+ res.status(500).json({ message: err.message });
+ }
+}
+
+// list all tasks in the database
+async function gets(req, res) {
+ try {
+ let tasks = await taskService.getTasks(req.user.houses._id);
+ if (process.env.NODE_ENV === 'production') {
+ // Set assignee to the first name of the assignee and only access assignee if its not null
+ tasks.forEach((task) => {
+ task.assignee = task.assignee?.first_name ?? "No Assignee";
+ task.room = task.room?.roomName;
+ });
+ }
+ res.status(200).json(tasks);
+ } catch (err) {
+ res.status(500).json({ message: err.message });
+ }
+}
+
+// create a task and add it to the database
+async function create(req, res) {
+ try {
+ const task = await taskService.createTask(req.user.houses._id, req.body);
+ res.status(200).json(task);
+ } catch (err) {
+ res.status(500).json({ message: err.message });
+ }
+}
+
+// remove a task from the database
+async function remove(req, res) {
+ try {
+ const task = await taskService.removeTask(req.params.id);
+ res.json(task);
+ } catch (err) {
+ res.status(500).json({ message: err.message });
+ }
+}
+
+// update a task in the database
+async function update(req, res) {
+ try {
+ const task = await taskService.updateTask(req.params.id, req.body);
+ res.json(task);
+ } catch (err) {
+ res.status(500).json({ message: err.message });
+ }
+}
+
+module.exports = {
+ get,
+ gets,
+ create,
+ update,
+ remove,
+};
diff --git a/back-end/src/json/alerts_list.json b/back-end/src/json/alerts_list.json
new file mode 100644
index 0000000..3f53c64
--- /dev/null
+++ b/back-end/src/json/alerts_list.json
@@ -0,0 +1,12 @@
+[
+ { "task": "cubilia curae", "date": "9/3/2022" },
+ { "task": "ligula in", "date": "10/5/2022" },
+ { "task": "velit", "date": "3/6/2023" },
+ { "task": "interdum", "date": "10/8/2022" },
+ { "task": "rutrum rutrum", "date": "6/11/2022" },
+ { "task": "dapibus augue", "date": "1/5/2023" },
+ { "task": "in faucibus", "date": "10/7/2022" },
+ { "task": "turpis elementum", "date": "9/5/2022" },
+ { "task": "etiam vel", "date": "7/18/2022" },
+ { "task": "tortor eu", "date": "6/7/2022" }
+ ]
\ No newline at end of file
diff --git a/back-end/src/json/hardcode.json b/back-end/src/json/hardcode.json
new file mode 100644
index 0000000..4bc3cc3
--- /dev/null
+++ b/back-end/src/json/hardcode.json
@@ -0,0 +1 @@
+{"_id":3,"username":"badbunny","email":"updatedemail@example.com","role":"testing","lastname":"Bunny","houses":"Bayt","firstname":"Ben","image":"default"}
\ No newline at end of file
diff --git a/back-end/src/json/household_info.json b/back-end/src/json/household_info.json
new file mode 100644
index 0000000..fa35de1
--- /dev/null
+++ b/back-end/src/json/household_info.json
@@ -0,0 +1,42 @@
+[
+ {
+ "_id": "64055f38f032391df0001d6ad",
+ "member_name": "Zander",
+ "username": "zander123",
+ "role": "roomate",
+ "contact": "555-555-5555"
+ },
+ {
+ "_id": "64055f38f032391df0001d6bd",
+ "member_name": "Rami",
+ "username": "rami123",
+ "role": "Admin",
+ "contact": "666-666-6666"
+ },
+ {
+ "_id": "64055f38f032391df0001d6cd",
+ "member_name": "Jojo",
+ "username": "jojo123",
+ "role": "roomate",
+ "contact": "666-666-6666"
+ },
+ {
+ "_id": "64055f38f032391df0001d6dd",
+ "member_name": "Zion",
+ "username": "zion123",
+ "role": "roomate",
+ "contact": "666-666-6666"
+ },
+ {
+ "_id": "64055f38f032391df0001d6ed",
+ "member_name": "Diana",
+ "username": "diana123",
+ "role": "roomate",
+ "contact": "666-666-6666"
+ },
+ {
+ "username": "hames",
+ "email": "rir8190@nyu.edu",
+ "role": "admin"
+ }
+]
\ No newline at end of file
diff --git a/back-end/src/json/rooms.json b/back-end/src/json/rooms.json
new file mode 100644
index 0000000..256f587
--- /dev/null
+++ b/back-end/src/json/rooms.json
@@ -0,0 +1,9 @@
+[
+ {"roomName": "Living Room",
+ "url": "livingRoom",
+ "home": "Ravenclaw"},
+
+ {"roomName": "Bathroom",
+ "url": "bathroom",
+ "home": "Ravenclaw"}
+]
\ No newline at end of file
diff --git a/back-end/src/json/signups.json b/back-end/src/json/signups.json
new file mode 100644
index 0000000..ba49e4e
--- /dev/null
+++ b/back-end/src/json/signups.json
@@ -0,0 +1,18 @@
+[
+ {
+ "houseName": "Baddies",
+ "password": "666666",
+ "passwordConfirm": "666666",
+ "username": "ddy123",
+ "email": "kms@nyu.edu",
+ "role": "roomate"
+ },
+ {
+ "houseName": "boomers",
+ "password": "boomies",
+ "passwordConfirm": "boomies",
+ "username": "boomer1234",
+ "email": "boomer256@google.com",
+ "role": "roomate"
+ }
+]
\ No newline at end of file
diff --git a/back-end/src/json/tasklist.json b/back-end/src/json/tasklist.json
new file mode 100644
index 0000000..35fe631
--- /dev/null
+++ b/back-end/src/json/tasklist.json
@@ -0,0 +1,102 @@
+[
+ {
+ "_id": "642e3662fc13ae490678cb3a",
+ "task_name": "call maintenance",
+ "description": "clean properly this time",
+ "room": "Ilaka",
+ "assignee": "badbunny",
+ "due_time": 164707834700,
+ "complete": true,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb3b",
+ "task_name": "wipe floor in kitchen",
+ "description": "wake up dog",
+ "room": "Saúl",
+ "assignee": "Stearne",
+ "due_time": 164685391300,
+ "complete": false,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb3c",
+ "task_name": "call maintenance",
+ "description": "clean properly this time",
+ "room": "Mthatha",
+ "assignee": "Gayel",
+ "due_time": 164624792200,
+ "complete": true,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb3d",
+ "task_name": "wipe floor in kitchen",
+ "description": "wake up dog",
+ "room": "Tuntutuliak",
+ "assignee": "Lombard",
+ "due_time": 164711442500,
+ "complete": false,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb3e",
+ "task_name": "take out trash",
+ "description": "wipe the bed",
+ "room": "Palm Springs",
+ "assignee": "Waldemar",
+ "due_time": 164682987900,
+ "complete": false,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb3f",
+ "task_name": "call maintenance",
+ "description": "clean properly this time",
+ "room": "Maun",
+ "assignee": "Gerrie",
+ "due_time": 164649352400,
+ "complete": false,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb40",
+ "task_name": "clean bathroom",
+ "description": "wipe the bed",
+ "room": "Norfolk",
+ "assignee": "Mickie",
+ "due_time": 164618175700,
+ "complete": true,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb41",
+ "task_name": "cook spaghetti",
+ "description": "wake up dog",
+ "room": "Anuradhapura",
+ "assignee": "Saree",
+ "due_time": 164670398700,
+ "complete": false,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb42",
+ "task_name": "cook spaghetti",
+ "description": "wipe the bed",
+ "room": "Kapal",
+ "assignee": "Petunia",
+ "due_time": 164641017100,
+ "complete": true,
+ "repeat": 1
+ },
+ {
+ "_id": "642e3662fc13ae490678cb43",
+ "task_name": "cook spaghetti",
+ "description": "wake up dog",
+ "room": "Berbérati",
+ "assignee": "badbunny",
+ "due_time": 164673072900,
+ "complete": false,
+ "repeat": 1
+ }
+]
\ No newline at end of file
diff --git a/back-end/src/json/transactions.json b/back-end/src/json/transactions.json
new file mode 100644
index 0000000..5574409
--- /dev/null
+++ b/back-end/src/json/transactions.json
@@ -0,0 +1,82 @@
+[
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 68,
+ "toOrFrom": "from",
+ "user": "nunc",
+ "forWhat": "ut suscipit",
+ "date": "6/3/2022"
+ },
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 25,
+ "toOrFrom": "to",
+ "user": "nibh",
+ "forWhat": "neque",
+ "date": "2/28/2023"
+ },
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 45,
+ "toOrFrom": "from",
+ "user": "vitae",
+ "forWhat": "sit",
+ "date": "4/3/2022"
+ },
+ {
+ "paidOrRequesting": "Requesting",
+ "amount": 47,
+ "toOrFrom": "from",
+ "user": "mauris",
+ "forWhat": "auctor",
+ "date": "8/11/2022"
+ },
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 59,
+ "toOrFrom": "from",
+ "user": "diam",
+ "forWhat": "nam tristique",
+ "date": "8/9/2022"
+ },
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 90,
+ "toOrFrom": "from",
+ "user": "justo",
+ "forWhat": "sapien",
+ "date": "4/18/2022"
+ },
+ {
+ "paidOrRequesting": "Requesting",
+ "amount": 88,
+ "toOrFrom": "from",
+ "user": "eu",
+ "forWhat": "tempor",
+ "date": "8/31/2022"
+ },
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 15,
+ "toOrFrom": "from",
+ "user": "maecenas",
+ "forWhat": "sapien",
+ "date": "2/4/2023"
+ },
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 87,
+ "toOrFrom": "to",
+ "user": "consequat",
+ "forWhat": "aenean lectus",
+ "date": "1/25/2023"
+ },
+ {
+ "paidOrRequesting": "Paid",
+ "amount": 22,
+ "toOrFrom": "from",
+ "user": "hac",
+ "forWhat": "platea",
+ "date": "11/25/2022"
+ }
+ ]
\ No newline at end of file
diff --git a/back-end/src/json/users.json b/back-end/src/json/users.json
new file mode 100644
index 0000000..07a0b69
--- /dev/null
+++ b/back-end/src/json/users.json
@@ -0,0 +1,630 @@
+[
+ {
+ "_id": "67320bd0fc12ac1e606b8ke0",
+ "username": "fishc0",
+ "first_name": "Zander",
+ "last_name": "Chen",
+ "email": "zc2122@nyu.edu",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0ee1",
+ "task_name": "cook spaghetti",
+ "description": "wake up dog",
+ "room": "Kadanwari",
+ "assignee":"64320bf0fc13ae1e696b0ee2",
+ "due_time": 166467332400,
+ "repeat": 1
+ }
+ ],
+ "houses": [
+ {
+ "_id":"64320bf0fc13ae1e696b0efc",
+ "code": "12345",
+ "name": "Gloriane",
+ "users": [
+ {
+ "user": "Kaylyn"
+ },
+ {
+ "user": "Jacynth"
+ },
+ {
+ "user": "Joel"
+ },
+ {
+ "user": "Hervey"
+ },
+ {
+ "user": "Hashim"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0ee0",
+ "username": "adevuyst0",
+ "first_name": "Alice",
+ "last_name": "De Vuyst",
+ "email": "adevuyst0@jimdo.com",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0ee1",
+ "task_name": "cook spaghetti",
+ "description": "wake up dog",
+ "room": "Kadanwari",
+ "assignee":"64320bf0fc13ae1e696b0ee2",
+ "due_time": 166467332400,
+ "repeat": 1
+ }
+ ],
+ "houses": []
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0ee3",
+ "username": "mcampanelli1",
+ "first_name": "Mirelle",
+ "last_name": "Campanelli",
+ "email": "mcampanelli1@bandcamp.com",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0ee4",
+ "task_name": "take out trash",
+ "description": "wake up dog",
+ "room": "La Fortuna/San Carlos",
+ "assignee":"64320bf0fc13ae1e696b0ee5",
+ "due_time": 164710094300,
+ "repeat": 1
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0ee6",
+ "task_name": "take out trash",
+ "description": "clean properly this time",
+ "room": "Wedau",
+ "assignee":"64320bf0fc13ae1e696b0ee7",
+ "due_time": 166704991400,
+ "repeat": 1
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0ee8",
+ "task_name": "buy an air fryer",
+ "description": "clean properly this time",
+ "room": "Pweto",
+ "assignee":"64320bf0fc13ae1e696b0ee9",
+ "due_time": 165771165900,
+ "repeat": 1
+ }
+ ],
+ "houses": [
+ {
+ "_id":"64320bf0fc13ae1e696b0eea",
+ "code": "AKRHSR",
+ "name": "Jamima",
+ "users": [
+ {
+ "user": "Lexine"
+ },
+ {
+ "user": "Eliza"
+ },
+ {
+ "user": "Jennica"
+ },
+ {
+ "user": "Cacilie"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0eeb",
+ "code": "FSHLBQ",
+ "name": "Lorraine",
+ "users": [
+ {
+ "user": "Stillmann"
+ },
+ {
+ "user": "Katlin"
+ },
+ {
+ "user": "Eloise"
+ },
+ {
+ "user": "Randee"
+ },
+ {
+ "user": "Ambrosi"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0eec",
+ "code": "JCBGKO",
+ "name": "Misty",
+ "users": [
+ {
+ "user": "Shanna"
+ },
+ {
+ "user": "Emelen"
+ },
+ {
+ "user": "Bathsheba"
+ },
+ {
+ "user": "Delcine"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0eed",
+ "username": "ckirman2",
+ "first_name": "Celle",
+ "last_name": "Kirman",
+ "email": "ckirman2@abc.net.au",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0eee",
+ "task_name": "clean bathroom",
+ "description": "wipe the bed",
+ "room": "Sui",
+ "assignee":"64320bf0fc13ae1e696b0eef",
+ "due_time": 167461307000,
+ "repeat": 1
+ }
+ ],
+ "houses": []
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0ef0",
+ "username": "ctanser3",
+ "first_name": "Charline",
+ "last_name": "Tanser",
+ "email": "ctanser3@princeton.edu",
+ "assigned_tasks": [],
+ "houses": [
+ {
+ "_id":"64320bf0fc13ae1e696b0ef1",
+ "code": "JWBMQF",
+ "name": "Iorgo",
+ "users": [
+ {
+ "user": "Libbi"
+ },
+ {
+ "user": "Mead"
+ },
+ {
+ "user": "Spike"
+ },
+ {
+ "user": "Weston"
+ },
+ {
+ "user": "Maud"
+ },
+ {
+ "user": "Jourdain"
+ },
+ {
+ "user": "Riva"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0ef2",
+ "code": "MTGQXH",
+ "name": "Joyous",
+ "users": [
+ {
+ "user": "Hedvig"
+ },
+ {
+ "user": "Vita"
+ },
+ {
+ "user": "Katheryn"
+ },
+ {
+ "user": "Danyette"
+ },
+ {
+ "user": "Gonzales"
+ },
+ {
+ "user": "Fayina"
+ },
+ {
+ "user": "Cyril"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0ef3",
+ "code": "LUXUPF",
+ "name": "Ira",
+ "users": [
+ {
+ "user": "Sunny"
+ },
+ {
+ "user": "Genevra"
+ },
+ {
+ "user": "Kareem"
+ },
+ {
+ "user": "Milicent"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0ef4",
+ "username": "nroalfe4",
+ "first_name": "Niel",
+ "last_name": "Roalfe",
+ "email": "nroalfe4@sina.com.cn",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0ef5",
+ "task_name": "take out trash",
+ "description": "clean properly this time",
+ "room": "RCAF Station Saglek",
+ "assignee":"64320bf0fc13ae1e696b0ef6",
+ "due_time": 165979454400,
+ "repeat": 1
+ }
+ ],
+ "houses": [
+ {
+ "_id":"64320bf0fc13ae1e696b0ef7",
+ "code": "HGTFSJ",
+ "name": "Leland",
+ "users": [
+ {
+ "user": "Sadye"
+ },
+ {
+ "user": "Kameko"
+ },
+ {
+ "user": "Hercules"
+ },
+ {
+ "user": "Margie"
+ },
+ {
+ "user": "Alejandra"
+ },
+ {
+ "user": "Gaspard"
+ },
+ {
+ "user": "Nicoli"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0ef8",
+ "code": "NDNRPM",
+ "name": "Aleece",
+ "users": [
+ {
+ "user": "Pate"
+ },
+ {
+ "user": "Linnell"
+ },
+ {
+ "user": "Peterus"
+ },
+ {
+ "user": "Gates"
+ },
+ {
+ "user": "Alfy"
+ },
+ {
+ "user": "Geralda"
+ },
+ {
+ "user": "Boothe"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0ef9",
+ "code": "DKXDOM",
+ "name": "Dona",
+ "users": [
+ {
+ "user": "Gradey"
+ },
+ {
+ "user": "Nikkie"
+ },
+ {
+ "user": "Waite"
+ },
+ {
+ "user": "Friedrich"
+ },
+ {
+ "user": "Leda"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0efa",
+ "username": "eclemitt5",
+ "first_name": "Esdras",
+ "last_name": "Clemitt",
+ "email": "eclemitt5@stumbleupon.com",
+ "assigned_tasks": [],
+ "houses": [
+ {
+ "_id":"64320bf0fc13ae1e696b0efb",
+ "code": "GOYLPY",
+ "name": "Zolly",
+ "users": [
+ {
+ "user": "Linnell"
+ },
+ {
+ "user": "Shirlene"
+ },
+ {
+ "user": "Shari"
+ },
+ {
+ "user": "Kippie"
+ },
+ {
+ "user": "Jonathon"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0efc",
+ "code": "CKZLMB",
+ "name": "Gloriane",
+ "users": [
+ {
+ "user": "Kaylyn"
+ },
+ {
+ "user": "Jacynth"
+ },
+ {
+ "user": "Joel"
+ },
+ {
+ "user": "Hervey"
+ },
+ {
+ "user": "Hashim"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0efd",
+ "code": "PMUYWO",
+ "name": "Miof mela",
+ "users": [
+ {
+ "user": "Greta"
+ },
+ {
+ "user": "Daryn"
+ },
+ {
+ "user": "Rex"
+ },
+ {
+ "user": "Allie"
+ },
+ {
+ "user": "Sibyl"
+ },
+ {
+ "user": "Dido"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0efe",
+ "username": "kaves6",
+ "first_name": "Koressa",
+ "last_name": "Aves",
+ "email": "kaves6@1und1.de",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0eff",
+ "task_name": "call maintenance",
+ "description": "take dog on walk",
+ "room": "Lancang Lahu Autonomous County",
+ "assignee":"64320bf0fc13ae1e696b0f00",
+ "due_time": 165529202400,
+ "repeat": 1
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0f01",
+ "task_name": "cook spaghetti",
+ "description": "wipe the bed",
+ "room": "Tumbang Samba-Borneo Island",
+ "assignee":"64320bf0fc13ae1e696b0f02",
+ "due_time": 165551333700,
+ "repeat": 1
+ }
+ ],
+ "houses": []
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0f03",
+ "username": "cfoot7",
+ "first_name": "Cissiee",
+ "last_name": "Foot",
+ "email": "cfoot7@xrea.com",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0f04",
+ "task_name": "take out trash",
+ "description": "wipe the bed",
+ "room": "Minot",
+ "assignee":"64320bf0fc13ae1e696b0f05",
+ "due_time": 166904083300,
+ "repeat": 1
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0f06",
+ "task_name": "call maintenance",
+ "description": "clean properly this time",
+ "room": "Ghanzi",
+ "assignee":"64320bf0fc13ae1e696b0f07",
+ "due_time": 166697320900,
+ "repeat": 1
+ }
+ ],
+ "houses": []
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0f08",
+ "username": "dleyzell8",
+ "first_name": "Durand",
+ "last_name": "Leyzell",
+ "email": "dleyzell8@uiuc.edu",
+ "assigned_tasks": [],
+ "houses": [
+ {
+ "_id":"64320bf0fc13ae1e696b0f09",
+ "code": "FGJLXO",
+ "name": "West",
+ "users": [
+ {
+ "user": "Mandi"
+ },
+ {
+ "user": "Lenora"
+ },
+ {
+ "user": "Eamon"
+ },
+ {
+ "user": "Madeleine"
+ },
+ {
+ "user": "Jessie"
+ },
+ {
+ "user": "Merwin"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0f0a",
+ "username": "jcolmer9",
+ "first_name": "Jillian",
+ "last_name": "Colmer",
+ "email": "jcolmer9@diigo.com",
+ "assigned_tasks": [
+ {
+ "_id":"64320bf0fc13ae1e696b0f0b",
+ "task_name": "cook spaghetti",
+ "description": "wipe the bed",
+ "room": "Hong Kong",
+ "assignee":"64320bf0fc13ae1e696b0f0c",
+ "due_time": 165220069700,
+ "repeat": 1
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0f0d",
+ "task_name": "call maintenance",
+ "description": "wipe the bed",
+ "room": null,
+ "assignee":"64320bf0fc13ae1e696b0f0e",
+ "due_time": 165024307900,
+ "repeat": 1
+ }
+ ],
+ "houses": [
+ {
+ "_id":"64320bf0fc13ae1e696b0f0f",
+ "code": "XYLSHU",
+ "name": "Netta",
+ "users": [
+ {
+ "user": "Christina"
+ },
+ {
+ "user": "Fabien"
+ },
+ {
+ "user": "Hanni"
+ },
+ {
+ "user": "Bron"
+ },
+ {
+ "user": "Shaun"
+ }
+ ]
+ },
+ {
+ "_id":"64320bf0fc13ae1e696b0f10",
+ "code": "HCBHSL",
+ "name": "Huntley",
+ "users": [
+ {
+ "user": "Marquita"
+ },
+ {
+ "user": "Emma"
+ },
+ {
+ "user": "Nolly"
+ },
+ {
+ "user": "Roger"
+ },
+ {
+ "user": "Cullen"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "username": "bloomberg",
+ "email": "bloomberg@nyu.edu",
+ "role": "roomate",
+ "houses": [
+ {
+ "code": "boomies",
+ "codeConfirm": "boomies",
+ "name": "Boomers"
+ }
+ ]
+ },
+ {
+ "username": "advad",
+ "email": "adv",
+ "role": "admin",
+ "houses": [
+ {
+ "code": "adv",
+ "codeConfirm": "adv",
+ "name": "asdv"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/back-end/src/middleware/logging.middleware.js b/back-end/src/middleware/logging.middleware.js
new file mode 100644
index 0000000..0243854
--- /dev/null
+++ b/back-end/src/middleware/logging.middleware.js
@@ -0,0 +1,9 @@
+app = require("../app");
+
+/* Error handler middleware from https://github.com/geshan/expressjs-structure/blob/master/index.js */
+app.use((err, req, res, next) => {
+ const statusCode = err.statusCode;
+ console.error(err.message, err.stack);
+ res.status(statusCode).json({'message': err.message});
+ next();
+});
diff --git a/back-end/src/middleware/validation.middleware.js b/back-end/src/middleware/validation.middleware.js
new file mode 100644
index 0000000..aaf0274
--- /dev/null
+++ b/back-end/src/middleware/validation.middleware.js
@@ -0,0 +1,12 @@
+const { validationResult } = require("express-validator");
+
+//Middleware function to make sure data validated following checkschema before passing to controller
+const validate = (req, res, next) => {
+ const result = validationResult(req);
+ if (result.isEmpty()) {
+ return next();
+ }
+ res.status(400).json({errors: result.array()});
+};
+
+module.exports = validate;
\ No newline at end of file
diff --git a/back-end/src/models/house.model.js b/back-end/src/models/house.model.js
new file mode 100644
index 0000000..451aff5
--- /dev/null
+++ b/back-end/src/models/house.model.js
@@ -0,0 +1,74 @@
+const mongoose = require('mongoose')
+const Schema = mongoose.Schema
+const bcrypt = require("bcryptjs");
+// Mongoose house schema following this json file format:
+// {
+// "_id": "64320bf0fc13ae1e696b0efc"
+// "code": "12345",
+// "name": "Gloriane",
+// "users": [
+// {
+// "user": "Kaylyn"
+// },
+// {
+// "user": "Jacynth"
+// },
+// {
+// "user": "Joel"
+// },
+// {
+// "user": "Hervey"
+// },
+// {
+// "user": "Hashim"
+// }
+// ]
+// }
+
+const HouseSchema = new Schema({
+ code: {
+ type: String,
+ required: true
+ },
+ name: {
+ type: String,
+ required: true
+ },
+ users: [{
+ type: Schema.Types.ObjectId,
+ ref: 'user'
+ }],
+ rooms: [{
+ type: Schema.Types.ObjectId,
+ ref: 'room'
+ }],
+ tasks: [{
+ type: Schema.Types.ObjectId,
+ ref: 'task'
+ }],
+ finances: [{
+ type: Schema.Types.ObjectId,
+ ref: 'transactions'
+ }]
+});
+
+// hash the password before the user is saved
+HouseSchema.pre("save", function (next) {
+ const house = this;
+ if (!house.isModified("code")) return next();
+ // otherwise, the password is being modified, so hash it
+ bcrypt.hash(house.code, 10, (err, hash) => {
+ if (err) return next(err);
+ house.code = hash; // update the password to the hashed version
+ next();
+ });
+});
+
+HouseSchema.methods.toAuthJSON = function () {
+ return {
+ name: this.name,
+ // token: this.generateJWT(),
+ };
+}
+
+module.exports = mongoose.model('house', HouseSchema);
\ No newline at end of file
diff --git a/back-end/src/models/room.model.js b/back-end/src/models/room.model.js
new file mode 100644
index 0000000..aeb39bf
--- /dev/null
+++ b/back-end/src/models/room.model.js
@@ -0,0 +1,39 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+// Create Mongoose schema for a room of json format:
+// [
+// {"roomName": "Living Room",
+// "url": "livingRoom"},
+// {"roomName": "Bathroom",
+// "url": "bathroom"}
+// ]
+
+const RoomSchema = new Schema({
+ roomName: {
+ type: String,
+ required: true
+ },
+ url: {
+ type: String,
+ required: true
+ },
+ home: {
+ type: mongoose.ObjectId,
+ ref: 'house'
+ }
+});
+
+// When creating new room add it to the house
+RoomSchema.post('create', function (next) {
+ this.model('house').updateOne({
+ _id: this.home
+ }, {
+ $push: {
+ rooms: this._id
+ }
+ });
+ next();
+});
+
+module.exports = mongoose.model('room', RoomSchema);
\ No newline at end of file
diff --git a/back-end/src/models/task.list.model.js b/back-end/src/models/task.list.model.js
new file mode 100644
index 0000000..080fc39
--- /dev/null
+++ b/back-end/src/models/task.list.model.js
@@ -0,0 +1,162 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema
+
+// Schema for tasks matching this json object format:
+// {
+// "_id": "642e3662fc13ae490678cb3a"
+// "task_name": "call maintenance",
+// "description": "clean properly this time",
+// "room": "Ilaka", // Should be a room id when PUT/POST
+// "assignee": "badbunny", or ID OF USER FOR POST AND ID FOR PUT/POST
+// "due_time": 164707834700,
+// "complete": true,
+// "repeat": 1
+// }
+
+const TaskSchema = new Schema({
+ task_name: {
+ type: String,
+ required: true
+ },
+ description: {
+ type: String,
+ required: true
+ },
+ room: { // have to populate these three
+ type: Schema.Types.ObjectId,
+ ref: 'room'
+ },
+ assignee: {
+ type: Schema.Types.ObjectId,
+ ref: 'user'
+ },
+ house: {
+ type: Schema.Types.ObjectId,
+ ref: 'house'
+ },
+ due_time: { // MongoDB date type
+ type: Number,
+ required: true
+ },
+ complete: {
+ type: Boolean,
+ required: true
+ },
+ repeat: {
+ type: Number,
+ required: true
+ },
+});
+
+// Add Cascading remove to delete all references to tasks when a task is deleted
+TaskSchema.pre('remove', function (next) {
+ // Remove all the assignment docs that reference the removed task
+ this.model('user').updateOne({
+ _id: this.assignee // Task may not be assigned to anyone
+ }, {
+ $pull: {
+ assigned_tasks: this._id
+ }
+ });
+
+ this.model('room').updateOne({
+ _id: this.room
+ }, { // Task may not be assigned to any room
+ $pull: {
+ tasks: this._id
+ }
+ });
+
+ this.model('house').updateOne({
+ _id: this.house
+ }, { // Filter by house id
+ $pull: {
+ tasks: this._id
+ }
+ });
+
+ next();
+});
+
+// If a task is created not updated, add it to the room, user, and house it is assigned to
+TaskSchema.post('create', function (next) {
+ if (this.assignee != null) {
+ this.model('user').updateOne({
+ _id: this.assignee
+ }, {
+ $push: {
+ assigned_tasks: this._id
+ }
+ });
+ }
+ if (this.room != null) {
+ this.model('room').updateOne({
+ _id: this.room
+ }, {
+ $push: {
+ tasks: this._id
+ }
+ });
+ }
+
+ this.model('house').updateOne({
+ _id: this.house
+ }, {
+ $push: {
+ tasks: this._id
+ }
+ });
+
+ next();
+});
+
+// If we update a task, we need to update the room, user, and house it is assigned to
+TaskSchema.post('update', function (next) {
+
+ // Check if there is a change in assignee
+ if (this._update.assignee != null && this._update.assignee != this._conditions.assignee) {
+ // Remove the task from the old assignee
+ this.model('user').updateOne({
+ _id: this._conditions.assignee
+ }, {
+ $pull: {
+ assigned_tasks: this._conditions._id
+ }
+ });
+
+ // Add the task to the new assignee
+ this.model('user').updateOne({
+ _id: this._update.assignee
+ }, {
+ $push: {
+ assigned_tasks: this._conditions._id
+ }
+ });
+ }
+
+ // Check if there is a change in room
+ if (this._update.room != null && this._update.room != this._conditions.room) {
+ // Remove the task from the old room
+ this.model('room').updateOne({
+ _id: this._conditions.room
+ }, {
+ $pull: {
+ tasks: this._conditions._id
+ }
+ });
+
+ // Add the task to the new room
+ this.model('room').updateOne({
+ _id: this._update.room
+ }, {
+ $push: {
+ tasks: this._conditions._id
+ }
+ });
+ }
+
+ // NO CHANGE IN HOUSE ALLOWED FOR TASKS
+ next();
+});
+
+module.exports = Task = mongoose.model('task', TaskSchema);
\ No newline at end of file
diff --git a/back-end/src/models/transaction.model.js b/back-end/src/models/transaction.model.js
new file mode 100644
index 0000000..36cdd86
--- /dev/null
+++ b/back-end/src/models/transaction.model.js
@@ -0,0 +1,40 @@
+const mongoose = require("mongoose");
+const Schema = mongoose.Schema;
+
+const transactionSchema = new Schema({
+ paidOrRequesting: {
+ type: String,
+ required: true,
+ enum: ["Paid", "Requesting"],
+ },
+ amount: {
+ type: Number,
+ required: true,
+ min: 0,
+ },
+ toOrFrom: {
+ type: String,
+ required: true,
+ enum: ["to", "from"],
+ },
+ user: {
+ type: String,
+ required: true,
+ },
+ forWhat: {
+ type: String,
+ required: true,
+ },
+ date: {
+ type: Date,
+ required: true,
+ },
+ house: {
+ type: Schema.Types.ObjectId,
+ ref: "house",
+ },
+});
+
+const transaction = mongoose.model("transaction", transactionSchema);
+
+module.exports = transaction;
diff --git a/back-end/src/models/users.model.js b/back-end/src/models/users.model.js
new file mode 100644
index 0000000..29e35a2
--- /dev/null
+++ b/back-end/src/models/users.model.js
@@ -0,0 +1,114 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+const bcrypt = require("bcryptjs");
+const jwt = require("jsonwebtoken");
+const jwtStrategy = require("../configs/jwt.config.js") // import setup options for using JWT in passport
+
+// Should be a schema for users of the form:
+// {
+// "_id": "67320bd0fc12ac1e606b8ke0",
+// "username": "fishc0",
+// "first_name": "Zander",
+// "last_name": "Chen",
+// "email": "zc2122@nyu.edu",
+// "assigned_tasks": [ // array of task ids to be populated
+// {
+// "_id": "64320bf0fc13ae1e696b0ee1"
+// "task_name": "cook spaghetti",
+// "description": "wake up dog",
+// "room": "Kadanwari",
+// "assignee": 64320bf0fc13ae1e696b0ee2",
+// "due_time": 166467332400
+// "repeat": 1
+// }
+// ]
+// }
+
+const UserSchema = new Schema({
+ username: {
+ type: String,
+ required: true
+ },
+ first_name: {
+ type: String,
+ required: true
+ },
+ last_name: {
+ type: String,
+ required: true
+ },
+ email: {
+ type: String,
+ required: true
+ },
+ password: {
+ type: String,
+ required: true,
+ },
+ contact: {
+ type: String,
+ required: false
+ },
+ assigned_tasks: [{
+ type: Schema.Types.ObjectId,
+ ref: 'task',
+ }],
+ role: {
+ type: String,
+ required: true
+ },
+ profile_pic: {
+ type: String,
+ required: true
+ },
+ houses: {
+ type: Schema.Types.ObjectId,
+ ref: 'house',
+ },
+});
+
+// hash the password before the user is saved
+UserSchema.pre("save", function (next) {
+ const user = this;
+ if (!user.isModified("password")) return next();
+ // otherwise, the password is being modified, so hash it
+ bcrypt.hash(user.password, 10, (err, hash) => {
+ if (err) return next(err);
+ user.password = hash; // update the password to the hashed version
+ next();
+ });
+});
+
+// compare a given password with the database hash
+UserSchema.methods.validPassword = function (password) {
+ return bcrypt.compareSync(password, this.password);
+}
+
+// return a JWT token for the user
+UserSchema.methods.generateJWT = function () {
+ const today = new Date();
+ const exp = new Date(today);
+ exp.setDate(today.getDate() + process.env.JWT_EXP_DAYS);
+
+ return jwt.sign(
+ {
+ id: this._id,
+ user_id: this._id,
+ house_id: this.houses._id,
+ username: this.username,
+ exp: parseInt(exp.getTime() / 1000),
+ },
+ process.env.JWT_SECRET
+ );
+}
+
+// return the user information without sensitive data
+UserSchema.methods.toAuthJSON = function () {
+ return {
+ username: this.username,
+ token: this.generateJWT(),
+ };
+}
+
+module.exports = User = mongoose.model('user', UserSchema);
+
diff --git a/back-end/src/routes/addmembers.route.js b/back-end/src/routes/addmembers.route.js
new file mode 100644
index 0000000..5493688
--- /dev/null
+++ b/back-end/src/routes/addmembers.route.js
@@ -0,0 +1,10 @@
+const express = require("express");
+const router = express.Router();
+const addMembers_controller = require("../controllers/addmembers.controller.js");
+
+const {addMembersDataValidationSchema} = require("../validations/addMembers.validation.js");
+const validate = require("../middleware/validation.middleware.js");
+
+router.post("/",addMembersDataValidationSchema, validate, addMembers_controller.saveUser);
+
+module.exports = router;
diff --git a/back-end/src/routes/alerts.route.js b/back-end/src/routes/alerts.route.js
new file mode 100644
index 0000000..5540b53
--- /dev/null
+++ b/back-end/src/routes/alerts.route.js
@@ -0,0 +1,9 @@
+const express = require('express');
+const router = express.Router();
+
+const alertsController = require("../controllers/alerts.controller.js");
+
+router.get('/', alertsController.getAlerts);
+router.post("/update", alertsController.updateAlertState);
+
+module.exports = router;
\ No newline at end of file
diff --git a/back-end/src/routes/cookie.route.js b/back-end/src/routes/cookie.route.js
new file mode 100644
index 0000000..e86d919
--- /dev/null
+++ b/back-end/src/routes/cookie.route.js
@@ -0,0 +1,103 @@
+const express = require("express") // CommonJS import style!
+
+// a method that constains code to handle cookie-related routes
+const cookieRouter = () => {
+ // create a new router that we can customize
+ const router = express.Router()
+
+ router.get("/set", (req, res) => {
+ res
+ .cookie("userId", req.userId) // send a cookie in the response with the key 'foo' and value 'bar'
+ .send({
+ success: true,
+ message: "Sent a cookie to the browser... hopefully it saved it.",
+ })
+ })
+
+ // a route that looks for a Cookie header in the request and sends back whatever data was found in it.
+ router.get("/", (req, res) => {
+ console.log(`Incoming cookie data:`)
+ console.log(`Incoming cookie data: ${req.cookies}`)
+ })
+
+ return router
+}
+
+// export the router
+module.exports = cookieRouter
+
+
+
+
+
+// import path from 'path';
+// import { fileURLToPath } from 'url';
+// import bodyParser from 'body-parser';
+// import bcrypt from 'bcryptjs';
+// import { User } from '../schemas/User.mjs';
+
+// const app = express();
+// const registerRouter = express.Router();
+
+// app.set('view engine', 'pug');
+// app.set('views', path.join(path.dirname(fileURLToPath(import.meta.url)), 'views'));
+
+// app.use(bodyParser.urlencoded({ extended: false }));
+
+// registerRouter.get('/', (req, res) => {
+// res.render('register', {pageTitle: 'Register'});
+// });
+
+// registerRouter.post('/', async (req, res) => {
+// //------------validation------------
+// const firstName = req.body.firstName.trim();
+// const lastName = req.body.lastName.trim();
+// const username = req.body.username.trim();
+// const email = req.body.email.trim();
+// const password = req.body.password;
+
+// const context = req.body;
+
+// if (firstName && lastName && username && email && password) {
+// const user = await User.findOne({
+// $or: [ //https://www.mongodb.com/docs/manual/reference/operator/query/or/
+// {username: username},
+// {email: email}
+// ]
+// }).catch((err) => {
+// console.log(err);
+// context.errorMessage = 'Oops, something went wrong.';
+// context.pageTitle = 'Register';
+// res.render('register', context);
+// });
+
+// if (user) { //user found
+// if (email === user.email) {
+// context.errorMessage = 'Email already in use.';
+// }
+// else {
+// context.errorMessage = 'Username already in use.';
+// }
+// context.pageTitle = 'Register';
+// res.render('register', context);
+// }
+// else { //no user found
+// req.body.password = await bcrypt.hash(password, 10);
+
+// User.create(req.body).then((user) => {
+// req.session.user = user;
+// console.log(user);
+// res.redirect('/');
+// });
+// }
+// }
+// else {
+// context.errorMessage = 'Make sure each field is valid.';
+// context.pageTitle = 'Register';
+// res.render('register', context);
+// }
+// });
+
+// export {
+// registerRouter
+// };
\ No newline at end of file
diff --git a/back-end/src/routes/finances.route.js b/back-end/src/routes/finances.route.js
new file mode 100644
index 0000000..f4cf345
--- /dev/null
+++ b/back-end/src/routes/finances.route.js
@@ -0,0 +1,9 @@
+const express = require("express");
+const router = express.Router();
+const finances_controller = require("../controllers/finances.controller.js");
+const transactionValidation = require("../validations/transaction.validation.js");
+
+router.get("/", finances_controller.getAllTransactions);
+router.post("/", transactionValidation, finances_controller.addTransaction);
+
+module.exports = router;
\ No newline at end of file
diff --git a/back-end/src/routes/home.route.js b/back-end/src/routes/home.route.js
new file mode 100644
index 0000000..7218978
--- /dev/null
+++ b/back-end/src/routes/home.route.js
@@ -0,0 +1,12 @@
+const express = require("express");
+const router = express.Router();
+const home_controller = require("../controllers/home.controller.js");
+const { roomDataValidationSchema } = require("../validations/rooms.validation.js");
+const validate = require("../middleware/validation.middleware.js");
+
+// Create routes and validate/sanitize data before passing to controller and make sure user is authenticated using passport
+router.get("/", home_controller.getRooms);
+router.post("/", roomDataValidationSchema, validate, home_controller.addRoom);
+
+
+module.exports = router;
\ No newline at end of file
diff --git a/back-end/src/routes/login.route.js b/back-end/src/routes/login.route.js
new file mode 100644
index 0000000..600c4a6
--- /dev/null
+++ b/back-end/src/routes/login.route.js
@@ -0,0 +1,7 @@
+const express = require('express');
+const router = express.Router();
+const login_controller = require('../controllers/login.controller.js');
+
+router.post('/', login_controller.login);
+
+module.exports = router;
\ No newline at end of file
diff --git a/back-end/src/routes/prof.route.js b/back-end/src/routes/prof.route.js
new file mode 100644
index 0000000..9ed26ab
--- /dev/null
+++ b/back-end/src/routes/prof.route.js
@@ -0,0 +1,12 @@
+const express = require('express');
+const router = express.Router();
+const prof_controller = require('../controllers/prof.controller.js');
+
+const {profileInfoDataValidationSchema} = require("../validations/profile.validation.js");
+const validate = require("../middleware/validation.middleware.js");
+
+router.get('/', prof_controller.gets);
+router.post('/', prof_controller.upload.single('file'), prof_controller.store);
+router.put('/', profileInfoDataValidationSchema, validate, prof_controller.update);
+
+module.exports = router;
\ No newline at end of file
diff --git a/back-end/src/routes/protectcontent.route.js b/back-end/src/routes/protectcontent.route.js
new file mode 100644
index 0000000..b250c6a
--- /dev/null
+++ b/back-end/src/routes/protectcontent.route.js
@@ -0,0 +1,14 @@
+const express = require("express");
+const router = express.Router();
+const passport = require("passport");
+const protectcontent_controller = require('../controllers/protectcontent.controller.js');
+
+router.get('/home', protectcontent_controller.getPage);
+router.get('/alerts', protectcontent_controller.getPage);
+router.get('/tasks', protectcontent_controller.getPage);
+router.get('/profile', protectcontent_controller.getPage);
+router.get('/settings', protectcontent_controller.getPage);
+router.get('/finances', protectcontent_controller.getPage);
+router.get('/addmembers', protectcontent_controller.getPage);
+
+module.exports = router;
diff --git a/back-end/src/routes/settings.route.js b/back-end/src/routes/settings.route.js
new file mode 100644
index 0000000..e9962a3
--- /dev/null
+++ b/back-end/src/routes/settings.route.js
@@ -0,0 +1,7 @@
+const express = require("express");
+const router = express.Router();
+const settings_controller = require("../controllers/settings.controller.js");
+
+router.get('/', settings_controller.gets);
+
+module.exports = router;
diff --git a/back-end/src/routes/signup.route.js b/back-end/src/routes/signup.route.js
new file mode 100644
index 0000000..ee71b4d
--- /dev/null
+++ b/back-end/src/routes/signup.route.js
@@ -0,0 +1,7 @@
+const express = require('express');
+const router = express.Router();
+const signup_controller = require('../controllers/signup.controller.js');
+
+router.post('/', signup_controller.signup);
+
+module.exports = router;
\ No newline at end of file
diff --git a/back-end/src/routes/task.route.js b/back-end/src/routes/task.route.js
new file mode 100644
index 0000000..c4aebb8
--- /dev/null
+++ b/back-end/src/routes/task.route.js
@@ -0,0 +1,15 @@
+const express = require('express');
+const router = express.Router();
+const task_controller = require('../controllers/task.controller.js');
+
+const { taskDataValidationSchema, taskDataUpdateValidationSchema, taskIDValidationSchema} = require("../validations/task.validation.js");
+const validate = require("../middleware/validation.middleware.js");
+
+// Create routes and validate/sanitize data before passing to controller
+router.get('/', task_controller.gets);
+router.get('/:id', taskIDValidationSchema, validate, task_controller.get);
+router.post('/', taskDataValidationSchema, validate, task_controller.create);
+router.put('/:id', taskDataUpdateValidationSchema, taskIDValidationSchema, validate, task_controller.update);
+router.delete('/:id', taskIDValidationSchema, validate, task_controller.remove);
+
+module.exports = router;
diff --git a/back-end/src/server.js b/back-end/src/server.js
new file mode 100644
index 0000000..bb8b1e0
--- /dev/null
+++ b/back-end/src/server.js
@@ -0,0 +1,18 @@
+#!/usr/bin/env node
+const app = require("./app")
+const server = require("./app") // load up the web server
+const port = 8000 // the port to listen to for incoming requests
+require("./middleware/logging.middleware") // load middlewares
+
+// call express's listen function to start listening to the port
+const listener = server.listen(port, function () {
+ console.log(`Server running on port: ${port}`)
+})
+// a function to stop listening to the port
+const close = () => {
+ listener.close()
+}
+
+module.exports = {
+ close: close,
+}
\ No newline at end of file
diff --git a/back-end/src/services/addmembers.services.js b/back-end/src/services/addmembers.services.js
new file mode 100644
index 0000000..1861fd2
--- /dev/null
+++ b/back-end/src/services/addmembers.services.js
@@ -0,0 +1,77 @@
+const fs = require('fs');
+const path = require('path');
+const userModel = require("../models/users.model.js");
+const houseModel = require("../models/house.model.js");
+
+
+async function savingUser(req,res, err) {
+ const {username, email, password, file} = req.body;
+
+ let profile_pic = 'Default.svg';
+
+ try {
+ const loggeduser = await User.findById(req.user._id).populate('houses').exec();
+
+ if (!loggeduser.validPassword(password)) {
+ res.status(401).json({
+ success: false,
+ message: "Incorrect housecode",
+ });
+
+ } else {
+ console.log(loggeduser)
+ // if user exists, check if password is correct
+ if (file) {
+ const {base64, filename} = file;
+ const buffer = Buffer.from(base64, "base64");
+ const timestamp = Date.now();
+ const extension = path.extname(filename);
+ const pathy = path.join(__dirname, '../uploads');
+ const newFilename = `${pathy}/${timestamp}_${username}${extension}`;
+ const dbName = `${timestamp}_${username}${extension}`;
+ fs.writeFileSync(newFilename, buffer);
+ profile_pic = dbName;
+ }
+
+ console.log(loggeduser.houses)
+ const addUser = await new userModel({ username: username, first_name: "Set your first name", last_name: "Set your last name", email: email, password:password, role: "roomate", profile_pic: profile_pic, houses:loggeduser.houses });
+ addUser.save();
+ loggeduser.houses.users.push(addUser);
+ await loggeduser.houses.save()
+
+ res.status(200).json({
+ success: true,
+ message: "Good job diana."
+ });
+ }
+
+ } catch (err) {
+ // error saving user to database
+ console.log(`Failed to save user: ${err}`);
+ res.status(500).json({
+ success: false,
+ message: "Error saving user to database.",
+ error: err
+ });
+ }
+
+ /*
+ fs.readFile(require.resolve('../json/household_info.json'), (err, data) => {
+ if (err) throw err;
+ console.log('err',err)
+
+ const users = JSON.parse(data);
+ users.push(newUser);
+
+ fs.writeFile(require.resolve('../json/household_info.json'), JSON.stringify(users, null, 2), err => {
+ if (err) throw err;
+ console.log('err',err)
+ });
+ });
+}*/
+
+
+}
+module.exports = {
+ savingUser
+ };
diff --git a/back-end/src/services/alerts.service.js b/back-end/src/services/alerts.service.js
new file mode 100644
index 0000000..6eb9b05
--- /dev/null
+++ b/back-end/src/services/alerts.service.js
@@ -0,0 +1,51 @@
+// Import json array from task.service.js
+let { task_json } = require("./task.service.js");
+const userData = require("../json/hardcode.json");
+const tasks = require("../models/task.list.model.js");
+
+async function getAlerts(house_id, user_id) {
+ const tasks = await Task.find({ house: house_id, assignee: user_id }).lean();
+ const filteredAlerts = tasks
+ .filter((task) => !task.complete)
+ .map((task) => {
+ const date = new Date(task.due_time).toLocaleDateString("en-US");
+ return {
+ task: task.task_name,
+ date: date,
+ _id: task._id,
+ };
+ });
+ // const user = userData;
+ // const filteredAlerts = task_json
+ // .filter((alert) => {
+ // return !alert.complete && alert.assignee === user.username;
+ // })
+ // .map((alert) => {
+ // const timestamp = alert.due_time;
+ // const date = new Date(timestamp).toLocaleDateString("en-US");
+ // return {
+ // task: alert.task_name,
+ // date: date,
+ // _id: alert._id,
+ // };
+ // });
+ return filteredAlerts;
+}
+
+async function logAlertState(alertId, isChecked) {
+ // console.log(`Alert ID: ${alertId}, Checked: ${isChecked}`);
+ // const alertIndex = task_json.findIndex((alert) => alert._id === alertId);
+ // if (alertIndex >= 0) {
+ // task_json[alertIndex].complete = isChecked;
+ // }
+ const task = await Task.findByIdAndUpdate(
+ alertId,
+ { complete: isChecked },
+ { new: true }
+ );
+}
+
+module.exports = {
+ getAlerts,
+ logAlertState,
+};
diff --git a/back-end/src/services/finances.service.js b/back-end/src/services/finances.service.js
new file mode 100644
index 0000000..06d0b11
--- /dev/null
+++ b/back-end/src/services/finances.service.js
@@ -0,0 +1,28 @@
+let transaction_json = require("../json/transactions.json");
+const Transaction = require("../models/transaction.model.js");
+const House = require("../models/house.model.js");
+
+async function getAllTransactions(house_id) {
+ // return transaction_json;
+ const transactions = await Transaction.find({ house: house_id }).lean();
+ return transactions;
+}
+
+async function addTransaction(transaction, house_id) {
+ const newTransaction = {
+ paidOrRequesting: transaction.paidOrRequesting,
+ amount: transaction.amount,
+ toOrFrom: transaction.toOrFrom,
+ user: transaction.user,
+ forWhat: transaction.forWhat,
+ date: transaction.date,
+ };
+ newTransaction.house = house_id;
+ // transaction_json.push(newTransaction);
+ Transaction.create(newTransaction);
+}
+
+module.exports = {
+ getAllTransactions,
+ addTransaction,
+};
diff --git a/back-end/src/services/home.service.js b/back-end/src/services/home.service.js
new file mode 100644
index 0000000..5aecb27
--- /dev/null
+++ b/back-end/src/services/home.service.js
@@ -0,0 +1,33 @@
+let rooms_json = require('../json/rooms.json')
+const Room = require('../models/room.model.js')
+const House = require('../models/house.model.js')
+let getAllRooms, addRoom;
+
+if (process.env.NODE_ENV === 'production') {
+ getAllRooms = async (house_id) => {
+ // Return rooms with a specific house id
+ return Room.find({ home: house_id }).lean();
+ };
+
+ addRoom = async (house_id, room) => {
+ room.home = house_id; // Add house id to room data
+ const newRoom = await Room.create(room); // Add room and append to the correct house
+ return newRoom;
+ }
+}
+
+else {
+ getAllRooms = async () => {
+ return rooms_json
+ }
+
+ addRoom = async (room) => {
+ const newRoom = room
+ rooms_json.push(newRoom)
+ }
+}
+
+module.exports = {
+ getAllRooms,
+ addRoom,
+};
\ No newline at end of file
diff --git a/back-end/src/services/task.service.js b/back-end/src/services/task.service.js
new file mode 100644
index 0000000..d6e6a53
--- /dev/null
+++ b/back-end/src/services/task.service.js
@@ -0,0 +1,95 @@
+
+let getTasks, getTask, CreateTask, UpdateTask, RemoveTask;
+let task_json = require('../json/tasklist.json')
+// load the dataabase models we want to deal with
+const Task = require('../models/task.list.model.js');
+const User = require('../models/users.model.js');
+const Room = require('../models/room.model.js');
+const House = require('../models/house.model.js');
+
+//Import the task
+if (process.env.NODE_ENV === 'production') {
+ getTasks = async (house_id) => {
+ // Find tasks with house id and return it
+ return Task.find({ house: house_id }).populate('assignee', '-_id first_name').populate('room', '-_id roomName').lean();
+ };
+
+ getTask = async (house_id, task_id) => {
+ // Find task with house id and task id and return it
+ return Task.findOne({ house: house_id, _id: task_id }).populate('assignee', '-_id first_name').populate('room', '-_id roomName').lean();
+ };
+
+ // make sure task doesnt exist in database then add the task to the Task
+ createTask = async (house_id, task_data) => {
+ const task_query = await getTask(house_id, task_data._id);
+ if (task_query !== null) throw new Error("Task already exists");
+ task_data.house = house_id; // Add house id to task data
+ await Task.create(task_data);
+ return "Task created successfully";
+ };
+
+ // find and update the task MongoDB document based off task_data json object
+ updateTask = async (task_id, task_data) => {
+ Task.findByIdAndUpdate(task_id, task_data).then((updatedTask) => {
+ if (!updatedTask) {
+ throw new Error("Error in updating task");
+ }
+ return "Task updated successfully";
+ }).catch(() => {
+ throw new Error("DB Error in updating task");
+ });
+ };
+
+ // find and remove the task MongoDB document based off task_id
+ removeTask = async (task_id) => {
+ Task.findByIdAndDelete(task_id).then((deletedTask) => {
+ if (!deletedTask) {
+ throw new Error("Error in deleting task");
+ }
+ return "Task deleted successfully";
+ }).catch(() => {
+ throw new Error("DB Error in deleting task");
+ });
+ };
+} else {
+ getTasks = async () => {
+ return task_json;
+ };
+
+ getTask = async (task_id) => {
+ return task_json.find((task) => task._id === task_id);
+ };
+
+ // add task data to task data json array if it does not exist
+ createTask = async (task_data) => {
+ const task = await getTask(task_data._id);
+ if (task !== undefined) throw new Error("Task already exists");
+ task_json.push(task_data);
+ return "Task created successfully";
+ };
+
+ // find and update the task data array based off task_data json object
+ updateTask = async (task_id, task_data) => {
+ const indexToUpdate = task_json.findIndex((task) => task._id === task_id);
+ if (indexToUpdate === -1) throw new Error("Error in updating task");
+ task_json[indexToUpdate] = task_data;
+ return "Task updated successfully";
+ };
+
+ removeTask = async (task_id) => {
+ const indexToRemove = task_json.findIndex((task) => task._id === task_id);
+ if (indexToRemove === -1) throw new Error("Error in deleting task");
+ task_json.splice(indexToRemove, 1);
+ return "Task deleted successfully";
+ };
+}
+
+
+module.exports = {
+ getTask,
+ getTasks,
+ createTask,
+ updateTask,
+ removeTask,
+ task_json,
+};
\ No newline at end of file
diff --git a/back-end/src/uploads/1680631557616.jpeg b/back-end/src/uploads/1680631557616.jpeg
new file mode 100644
index 0000000..b52c9fd
Binary files /dev/null and b/back-end/src/uploads/1680631557616.jpeg differ
diff --git a/back-end/src/uploads/1683125947429_6452766e17c77e086bbf9496.jpeg b/back-end/src/uploads/1683125947429_6452766e17c77e086bbf9496.jpeg
new file mode 100644
index 0000000..9460996
Binary files /dev/null and b/back-end/src/uploads/1683125947429_6452766e17c77e086bbf9496.jpeg differ
diff --git a/back-end/src/validations/addMembers.validation.js b/back-end/src/validations/addMembers.validation.js
new file mode 100644
index 0000000..30f1b86
--- /dev/null
+++ b/back-end/src/validations/addMembers.validation.js
@@ -0,0 +1,37 @@
+const { checkSchema } = require('express-validator');
+
+const addMembersDataValidationSchema = {
+ username: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "username is a required field",
+ },
+
+ email: {
+ in: ["body"],
+ exists: true,
+ isEmail: true,
+ notEmpty: true,
+ normalizeEmail: true,
+ errorMessage: "email must be a valid email address",
+ },
+
+ password: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "housecode is a required field",
+ }
+}
+
+
+module.exports = {
+ addMembersDataValidationSchema: checkSchema(addMembersDataValidationSchema)
+}
diff --git a/back-end/src/validations/profile.validation.js b/back-end/src/validations/profile.validation.js
new file mode 100644
index 0000000..438c1b3
--- /dev/null
+++ b/back-end/src/validations/profile.validation.js
@@ -0,0 +1,45 @@
+const { checkSchema } = require('express-validator');
+
+const profileInfoDataValidationSchema = {
+ first_name: {
+ in: ["body"],
+ optional: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "firstname must be a non-empty string",
+ },
+ last_name: {
+ in: ["body"],
+ optional: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "lastname must be a non-empty string",
+ },
+ email: {
+ in: ["body"],
+ optional: true,
+ isEmail: true,
+ normalizeEmail: true,
+ errorMessage: "email must be a valid email address",
+ },
+ role: {
+ in: ["body"],
+ optional: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "household_role must be a non-empty string",
+ }
+};
+
+//still need to add validation for profile picture but this is somewhat complicated. Will figure it out eventually.
+
+
+module.exports = {
+ profileInfoDataValidationSchema: checkSchema(profileInfoDataValidationSchema)
+}
\ No newline at end of file
diff --git a/back-end/src/validations/rooms.validation.js b/back-end/src/validations/rooms.validation.js
new file mode 100644
index 0000000..3884918
--- /dev/null
+++ b/back-end/src/validations/rooms.validation.js
@@ -0,0 +1,24 @@
+const { checkSchema } = require("express-validator");
+
+const roomDataValidationSchema = {
+ roomName: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ errorMessage: "room name must be a string",
+ },
+ url: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ errorMessage: "url did not generate",
+ }
+};
+
+module.exports = {
+ roomDataValidationSchema: checkSchema(roomDataValidationSchema)
+}
\ No newline at end of file
diff --git a/back-end/src/validations/task.validation.js b/back-end/src/validations/task.validation.js
new file mode 100644
index 0000000..9e35797
--- /dev/null
+++ b/back-end/src/validations/task.validation.js
@@ -0,0 +1,86 @@
+const { checkSchema } = require("express-validator");
+// Schema to validate data for task update so not all fields are required
+
+// Validate data using express-validator validator schemas following the schema defined in task.list.model.js
+const taskDataValidationSchema = {
+ task_name: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "task_name must be a string",
+ },
+ description: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "description must be a string",
+ },
+ room: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ // notEmpty: true,
+ trim: true,
+ escape: true,
+ errorMessage: "room must be a string",
+ },
+ assignee: {
+ in: ["body"],
+ exists: true,
+ isString: true,
+ trim: true,
+ escape: true,
+ errorMessage: "assignee must be a string",
+ },
+ //needs to be mongodb date type
+ due_time: {
+ in: ["body"],
+ exists: true,
+ isInt: true,
+ notEmpty: true,
+ errorMessage: "due_time must be a date",
+ },
+ complete: {
+ in: ["body"],
+ exists: true,
+ isBoolean: true,
+ notEmpty: true,
+ errorMessage: "complete must be a boolean",
+ },
+ repeat: {
+ in: ["body"],
+ exists: true,
+ isInt: true,
+ notEmpty: true,
+ errorMessage: "repeat must be an integer",
+ },
+};
+
+// Same like the json above but we set all fields to optional
+const taskDataUpdateValidationSchema = Object.keys(taskDataValidationSchema).reduce((acc, key) => {
+ acc[key] = { ...taskDataValidationSchema[key], optional: true };
+ return acc;
+}, {});
+
+// Schema to make sure a valid mongodb task id is provided
+const taskIDValidationSchema = {
+ id: {
+ in: ["params"],
+ exists: true,
+ isMongoId: true,
+ notEmpty: true,
+ errorMessage: "id must be a valid mongodb id",
+ },
+};
+// export the schema middleware functions using checkSchema
+module.exports = {
+ taskDataValidationSchema: checkSchema(taskDataValidationSchema),
+ taskIDValidationSchema: checkSchema(taskIDValidationSchema),
+ taskDataUpdateValidationSchema: checkSchema(taskDataUpdateValidationSchema),
+};
diff --git a/back-end/src/validations/transaction.validation.js b/back-end/src/validations/transaction.validation.js
new file mode 100644
index 0000000..aba5a16
--- /dev/null
+++ b/back-end/src/validations/transaction.validation.js
@@ -0,0 +1,35 @@
+const { body } = require("express-validator");
+
+const transactionValidation = [
+ body("paidOrRequesting")
+ .isString()
+ .withMessage("Paid or Requesting must be a string")
+ .notEmpty()
+ .withMessage("Paid or Requesting is required")
+ .isIn(["Paid", "Requesting"])
+ .withMessage("Paid or Requesting must be either Paid or Requesting"),
+ body("amount")
+ .isNumeric()
+ .withMessage("Amount must be a number")
+ .notEmpty()
+ .withMessage("Amount is required")
+ .isFloat({ min: 0 })
+ .withMessage("Amount must be a positive number"),
+ body("user")
+ .isString()
+ .withMessage("User must be a string")
+ .notEmpty()
+ .withMessage("User is required"),
+ body("forWhat")
+ .isString()
+ .withMessage("For What must be a string")
+ .notEmpty()
+ .withMessage("For What is required"),
+ body("date")
+ .isDate()
+ .withMessage("Date must be a valid date")
+ .notEmpty()
+ .withMessage("Date is required"),
+];
+
+module.exports = transactionValidation;
diff --git a/back-end/test/controllers/alerts.controller.test.js b/back-end/test/controllers/alerts.controller.test.js
new file mode 100644
index 0000000..a6802e9
--- /dev/null
+++ b/back-end/test/controllers/alerts.controller.test.js
@@ -0,0 +1,85 @@
+const { expect } = require("chai");
+const sinon = require("sinon");
+const alertsService = require("../../src/services/alerts.service");
+const alertsController = require("../../src/controllers/alerts.controller");
+
+describe("Alerts Controller", () => {
+ describe("getAlerts", () => {
+ it("should return an array of alerts for a user", async () => {
+ const req = {};
+ const res = {
+ json: sinon.spy()
+ };
+ const alerts = [
+ { task: "Task 1", date: "04/10/2023", id: "123" },
+ { task: "Task 2", date: "04/11/2023", id: "456" }
+ ];
+ sinon.stub(alertsService, "getAlerts").returns(alerts);
+
+ await alertsController.getAlerts(req, res);
+
+ expect(res.json.calledOnce).to.be.true;
+ expect(res.json.firstCall.args[0]).to.deep.equal(alerts);
+
+ alertsService.getAlerts.restore();
+ });
+
+ it("should handle errors by calling the error handling middleware", async () => {
+ const req = {};
+ const res = {};
+ const next = sinon.spy();
+ const error = new Error("Something went wrong.");
+ sinon.stub(alertsService, "getAlerts").rejects(error);
+
+ await alertsController.getAlerts(req, res, next);
+
+ expect(next.calledOnce).to.be.true;
+ expect(next.firstCall.args[0]).to.equal(error);
+
+ alertsService.getAlerts.restore();
+ });
+ });
+
+ describe("updateAlertState", () => {
+ it("should update the complete state of an alert and return a success message", async () => {
+ const req = {
+ body: {
+ alertId: "123",
+ isChecked: true
+ }
+ };
+ const res = {
+ json: sinon.spy()
+ };
+ sinon.stub(alertsService, "logAlertState");
+
+ await alertsController.updateAlertState(req, res);
+
+ expect(alertsService.logAlertState.calledOnceWith(req.body.alertId, req.body.isChecked)).to.be.true;
+ expect(res.json.calledOnce).to.be.true;
+ expect(res.json.firstCall.args[0]).to.deep.equal({ success: true });
+
+ alertsService.logAlertState.restore();
+ });
+
+ it("should handle errors by calling the error handling middleware", async () => {
+ const req = {
+ body: {
+ alertId: "123",
+ isChecked: true
+ }
+ };
+ const res = {};
+ const next = sinon.spy();
+ const error = new Error("Something went wrong.");
+ sinon.stub(alertsService, "logAlertState").rejects(error);
+
+ await alertsController.updateAlertState(req, res, next);
+
+ expect(next.calledOnce).to.be.true;
+ expect(next.firstCall.args[0]).to.equal(error);
+
+ alertsService.logAlertState.restore();
+ });
+ });
+});
diff --git a/back-end/test/controllers/login.controller.test.js b/back-end/test/controllers/login.controller.test.js
new file mode 100644
index 0000000..725806a
--- /dev/null
+++ b/back-end/test/controllers/login.controller.test.js
@@ -0,0 +1,39 @@
+// const { expect } = require("chai");
+// const sinon = require("sinon");
+// const loginService = require("../../src/services/login.service.js");
+// const loginController = require("../../src/controllers/login.controller.js");
+
+// describe("Login Controller", () => {
+// describe("#login(req, res)", () => {
+// it("should return a json of success message if username and password are correct", async () => {
+// const req = {username: 'fishc0', password: '12345'};
+// const res = {
+// json: sinon.spy()
+// };
+// const alerts = "Login successfully";
+// sinon.stub(loginService, "getUser").returns(alerts);
+
+// await loginController.login(req, res);
+
+// expect(res.json.calledOnce).to.be.true;
+// expect(res.json.firstCall.args[0]).to.deep.equal(alerts);
+
+// loginService.getUser.restore();
+// });
+
+// // it("should handle errors by calling the error handling middleware", async () => {
+// // const req = {};
+// // const res = {};
+// // const next = sinon.spy();
+// // const error = new Error("Something went wrong.");
+// // sinon.stub(alertsService, "getAlerts").rejects(error);
+
+// // await alertsController.getAlerts(req, res, next);
+
+// // expect(next.calledOnce).to.be.true;
+// // expect(next.firstCall.args[0]).to.equal(error);
+
+// // alertsService.getAlerts.restore();
+// // });
+// });
+// });
diff --git a/back-end/test/controllers/profile.controller.test.js b/back-end/test/controllers/profile.controller.test.js
new file mode 100644
index 0000000..7a3e214
--- /dev/null
+++ b/back-end/test/controllers/profile.controller.test.js
@@ -0,0 +1,28 @@
+const assert = require('chai').assert;
+const { gets, update } = require('../../src/controllers/prof.controller.js');
+
+describe('gets', function () {
+ it('should return user data with expected properties', async function () {
+ const req = { params: { username: 'badbunny' } };
+ const res = { json: function (data) { this.data = data } };
+ await gets(req, res);
+ const expectedProps = ['_id','username', 'email', 'role', 'lastname', 'houses', 'firstname'];
+ const userProps = Object.keys(res.data);
+ assert.deepStrictEqual(userProps, expectedProps);
+ });
+});
+
+describe('update', () => {
+ describe('update', function () {
+ it('should update user data for an existing username', async function () {
+ const req = { params: { username: 'badbunny' }, body: { lastname: 'Ocasio' } };
+ const res = { send: function (data) { this.data = data } };
+ await update(req, res);
+ // Assert that the response data is not empty
+ assert.notStrictEqual(res.data, undefined);
+ // Assert that the response data contains the updated telephone number
+ assert.strictEqual(res.data.lastname, 'Ocasio');
+ });
+ });
+});
+
diff --git a/back-end/test/routes/alerts.route.test.js b/back-end/test/routes/alerts.route.test.js
new file mode 100644
index 0000000..0db5e48
--- /dev/null
+++ b/back-end/test/routes/alerts.route.test.js
@@ -0,0 +1,31 @@
+const chai = require("chai");
+const chaiHttp = require("chai-http");
+const app = require("../../src/app");
+
+const expect = chai.expect;
+chai.use(chaiHttp);
+
+describe("Alerts route", () => {
+ it("should return all alerts", (done) => {
+ chai
+ .request(app)
+ .get("/alerts")
+ .end((err, res) => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.be.an("array");
+ done();
+ });
+ });
+
+ it("should update an alert state", (done) => {
+ chai
+ .request(app)
+ .post("/alerts/update")
+ .send({ alertId: "123", isChecked: true })
+ .end((err, res) => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.deep.equal({ success: true });
+ done();
+ });
+ });
+});
diff --git a/back-end/test/routes/finances.route.test.js b/back-end/test/routes/finances.route.test.js
new file mode 100644
index 0000000..d27a3eb
--- /dev/null
+++ b/back-end/test/routes/finances.route.test.js
@@ -0,0 +1,65 @@
+const { expect } = require("chai");
+const sinon = require("sinon");
+const express = require("express");
+const request = require("supertest");
+const fs = require("fs");
+const path = require("path");
+const financesRoute = require("../../src/routes/finances.route");
+const financesController = require("../../src/controllers/finances.controller");
+const financesService = require("../../src/services/finances.service");
+
+const app = express();
+app.use(express.json());
+app.use("/", financesRoute);
+
+describe("Finances Route", () => {
+ before(() => {
+ const transactionsData = JSON.stringify([
+ {
+ paidOrRequesting: "Paid",
+ amount: 68,
+ toOrFrom: "from",
+ user: "nunc",
+ forWhat: "ut suscipit",
+ date: "6/3/2022",
+ },
+ // ...
+ ]);
+
+ const filePath = path.join(__dirname, "../transactions.json");
+ sinon.stub(fs, "readFileSync").returns(transactionsData);
+ });
+
+ after(() => {
+ fs.readFileSync.restore();
+ });
+
+ describe("GET /", () => {
+ it("should return a list of all transactions", async () => {
+ const res = await request(app).get("/");
+ expect(res.status).to.equal(200);
+ expect(res.body.length).to.equal(10);
+ });
+ });
+
+ describe("POST /", () => {
+ it("should add a new transaction and return the added transaction", async () => {
+ const newTransaction = {
+ paidOrRequesting: "Paid",
+ amount: 100,
+ toOrFrom: "to",
+ user: "test",
+ forWhat: "test",
+ date: "4/9/2023",
+ };
+ sinon.stub(financesService, "addTransaction").resolves(newTransaction);
+
+ const res = await request(app).post("/").send(newTransaction);
+
+ expect(res.status).to.equal(200);
+ expect(res.body).to.deep.equal(newTransaction);
+
+ financesService.addTransaction.restore();
+ });
+ });
+});
diff --git a/back-end/test/routes/home.route.test.js b/back-end/test/routes/home.route.test.js
new file mode 100644
index 0000000..0bec545
--- /dev/null
+++ b/back-end/test/routes/home.route.test.js
@@ -0,0 +1,38 @@
+const chai = require("chai");
+const chaiHttp = require("chai-http");
+const app = require("../../src/app.js");
+
+chai.use(chaiHttp);
+
+const newRoom = {
+ roomName: "Test Room",
+ url: "testRoom"
+};
+
+describe("Home Routes", () => {
+ describe("getRooms", () => {
+ it("should return all rooms", (done) => {
+ chai
+ .request(app)
+ .get("/home")
+ .end((err, res) => {
+ res.should.have.status(200)
+ res.body.should.be.a("array")
+ done()
+ })
+ })
+ })
+ describe("addRoom", () => {
+ it("should add a new room to list", (done) => {
+ chai
+ .request(app)
+ .post("/home")
+ .send(newRoom)
+ .end((err, res) => {
+ res.should.have.status(200)
+ res.body.should.eql(newRoom)
+ done()
+ })
+ })
+ })
+})
\ No newline at end of file
diff --git a/back-end/test/routes/login.route.test.js b/back-end/test/routes/login.route.test.js
new file mode 100644
index 0000000..7b3d1c0
--- /dev/null
+++ b/back-end/test/routes/login.route.test.js
@@ -0,0 +1,88 @@
+const chai = require("chai");
+const chaiHttp = require("chai-http");
+const should = chai.should();
+const app = require("../../src/app.js");
+
+const encodings = require('../../node_modules/iconv-lite/encodings');
+const iconvLite = require('../../node_modules/iconv-lite/lib');
+iconvLite.getCodec('UTF-8');
+
+chai.use(chaiHttp);
+
+const loginSuccess = {
+ username: "fishc0",
+ password: "12345",
+};
+
+const loginWrongPassword = {
+ username: "fishc0",
+ password: "00000",
+};
+
+const loginEmptyHouse = {
+ username: "adevuyst0",
+ password: "00000",
+};
+
+const loginUserNotFound = {
+ username: "ppp1",
+ password: "00000",
+};
+
+describe("Login Routes", () => {
+
+ before(async () => {
+ const users_json = require("../../src/json/users.json");
+ users_json.should.be.a("array").has.length.greaterThan(0);
+ });
+
+ describe("POST LOGIN", () => {
+ it("should login successfully", (done) => {
+ chai
+ .request(app)
+ .post("/login")
+ .send(loginSuccess)
+ .end((err, res) => {
+ res.should.have.status(200);
+ res.body.should.eql("Login successfully");
+ done();
+ });
+ });
+
+ it("should return wrong password if password is wrong", (done) => {
+ chai
+ .request(app)
+ .post("/login")
+ .send(loginWrongPassword)
+ .end((err, res) => {
+ res.should.have.status(500);
+ res.body.should.have.property("message").eql("Wrong password");
+ done();
+ });
+ });
+
+ it("should return user has not been invited into a house if user houses array is empty", (done) => {
+ chai
+ .request(app)
+ .post("/login")
+ .send(loginEmptyHouse)
+ .end((err, res) => {
+ res.should.have.status(500);
+ res.body.should.have.property("message").eql("User has not been invited into a house");
+ done();
+ });
+ });
+
+ it("should return username not found if username is not found", (done) => {
+ chai
+ .request(app)
+ .post("/login")
+ .send(loginUserNotFound)
+ .end((err, res) => {
+ res.should.have.status(500);
+ res.body.should.have.property("message").eql("Username not found");
+ done();
+ });
+ });
+ });
+ });
diff --git a/back-end/test/routes/profile.route.test.js b/back-end/test/routes/profile.route.test.js
new file mode 100644
index 0000000..ea3bd9f
--- /dev/null
+++ b/back-end/test/routes/profile.route.test.js
@@ -0,0 +1,83 @@
+const chai = require('chai');
+const chaiHttp = require('chai-http');
+const app = require("../../src/app.js");
+const expect = chai.expect;
+
+
+chai.use(chaiHttp);
+
+describe("Profile Routes", () => {
+
+ before(async () => {
+ const hardcode_json = require("../../src/json/hardcode.json");
+ hardcode_json.should.be.a("object").that.is.not.empty;
+ });
+
+
+ describe('GET /api/profile/:username', () => {
+
+ it('should return a user profile', (done) => {
+ const usernameFound = 'badbunny';
+
+ chai.request(app)
+ .get(`/profile/${usernameFound}`)
+ .end((err, res) => {
+ expect(res).to.have.status(200);
+ expect(res.body.username).to.equal(usernameFound);
+ done();
+ });
+ });
+
+ it('should return an error if the user profile does not exist', (done) => {
+ const usernameNotFound = 'KimKardashian';
+
+
+ chai.request(app)
+ .get(`/profile/${usernameNotFound}`)
+ .end((err, res) => {
+ expect(res).to.have.status(404);
+ expect(res.body.message).to.equal('User not found');
+ done();
+ });
+ });
+ });
+
+
+ describe('PUT /api/profile/:username', () => {
+
+ const updatedProfile = {
+ email: 'updatedemail@example.com',
+ firstname: 'Ben',
+ lastname:'Bunny',
+ role: 'testing',
+ houses:'Bayt'
+ };
+
+ it('should update a user profile', (done) => {
+ const username = 'badbunny';
+
+ chai.request(app)
+ .put(`/profile/${username}`)
+ .send(updatedProfile)
+ .end((err, res) => {
+ expect(res).to.have.status(200);
+ expect(res.body).to.deep.include(updatedProfile);
+ done();
+ });
+ });
+
+
+ it('should return an error if the user profile does not exist', (done) => {
+ const username = 'nonexistentuser';
+
+ chai.request(app)
+ .put(`/profile/${username}`)
+ .send(updatedProfile)
+ .end((err, res) => {
+ expect(res).to.have.status(404);
+ expect(res.body.error).to.equal('User not found');
+ done();
+ });
+ });
+ });
+});
diff --git a/back-end/test/routes/task.route.test.js b/back-end/test/routes/task.route.test.js
new file mode 100644
index 0000000..defa029
--- /dev/null
+++ b/back-end/test/routes/task.route.test.js
@@ -0,0 +1,141 @@
+const chai = require("chai");
+const chaiHttp = require("chai-http");
+const should = chai.should();
+const app = require("../../src/app.js");
+
+const encodings = require('../../node_modules/iconv-lite/encodings');
+const iconvLite = require('../../node_modules/iconv-lite/lib');
+iconvLite.getCodec('UTF-8');
+
+chai.use(chaiHttp);
+
+let task_id = 0;
+const inv_task_id = -123132132; // Invalid task ID
+
+// New task data we add to the database
+const newTaskData = {
+ _id: "test-new-task-iddddddd",
+ task_name: "new task",
+ description: "new task description",
+ room: "new room",
+ assignee: "new assignee",
+ due_time: 164717936800,
+ complete: false,
+ repeat: 1,
+};
+
+describe("Task Routes", () => {
+ // Check if there is data in tasklist.json to sample before anything
+ before(async () => {
+ const task_json = require("../../src/json/tasklist.json");
+ task_json.should.be.a("array").has.length.greaterThan(0);
+ task_id = task_json[0]._id;
+ });
+
+ // Check if the get function works and if create actually created the task
+ describe("GET REQ", () => {
+ it("should return the task ", (done) => {
+ chai
+ .request(app)
+ .get(`/tasks/${task_id}`)
+ .end((err, res) => {
+ res.should.have.status(200);
+ res.body.should.be.a("object");
+ res.body.should.have
+ .property("_id")
+ .eql(task_id);
+ done();
+ });
+ });
+
+ it("should return an error when an invalid task ID is provided", (done) => {
+ chai
+ .request(app)
+ .get(`/tasks/${inv_task_id}`)
+ .end((err, res) => {
+ res.should.have.status(400);
+ res.body.should.be.a("object");
+ res.body.should.have.property("errors");
+ done();
+ });
+ });
+ });
+
+ describe("GET REQ ALL TASKS", () => {
+ it("should return a list of tasks", (done) => {
+ chai
+ .request(app)
+ .get("/tasks")
+ .end((err, res) => {
+ res.should.have.status(200);
+ res.body.should.be.a("array");
+ done();
+ });
+ });
+ });
+ describe("POST TASK", () => {
+ it("should create a new task", (done) => {
+ chai
+ .request(app)
+ .post("/tasks")
+ .send(newTaskData)
+ .end((err, res) => {
+ res.should.have.status(200);
+ res.body.should.eql("Task created successfully");
+ done();
+ });
+ });
+
+ it("should return already exists if trying to feed an existing task", (done) => {
+ chai
+ .request(app)
+ .post("/tasks")
+ .send(newTaskData)
+ .end((err, res) => {
+ res.should.have.status(500);
+ res.body.should.have.property("message").eql("Task already exists");
+ done();
+ });
+ });
+
+ it("should return an error when required fields are missing", (done) => {
+ const task = {
+ // title and description fields are missing
+ };
+ chai
+ .request(app)
+ .post("/tasks")
+ .send(task)
+ .end((err, res) => {
+ res.should.have.status(400);
+ res.body.should.be.a("object");
+ res.body.should.have.property("errors");
+ done();
+ });
+ });
+ });
+
+ describe("PUT TASK", () => {
+ it("should update an existing task", (done) => {
+ chai
+ .request(app)
+ .put(`/tasks/${newTaskData._id}`)
+ .send(newTaskData)
+ .end((err, res) => {
+ res.should.have.status(200);
+ done();
+ });
+ });
+
+ it("should return an error when trying to update a non-existent task", (done) => {
+ chai
+ .request(app)
+ .put(`/tasks/${inv_task_id}`)
+ .send(newTaskData)
+ .end((err, res) => {
+ res.should.have.status(500);
+ done();
+ });
+ });
+ });
+ });
diff --git a/back-end/test/services/alerts.service.test.js b/back-end/test/services/alerts.service.test.js
new file mode 100644
index 0000000..461c4f9
--- /dev/null
+++ b/back-end/test/services/alerts.service.test.js
@@ -0,0 +1,27 @@
+const { expect } = require("chai");
+const { getAlerts, logAlertState } = require("../../src/services/alerts.service");
+
+describe("Alerts Service", () => {
+ describe("getAlerts", () => {
+ it("should return an array of alerts for a user", async () => {
+ const alerts = await getAlerts();
+ expect(alerts).to.be.an("array");
+ expect(alerts.length).to.be.greaterThan(0);
+ expect(alerts[0]).to.have.property("task");
+ expect(alerts[0]).to.have.property("date");
+ expect(alerts[0]).to.have.property("_id");
+ });
+ });
+
+ describe("logAlertState", () => {
+ it("should update the complete state of an alert", () => {
+ const alertId = "642e3662fc13ae490678cb3a";
+ logAlertState(alertId, true);
+
+ const alerts = require("../../src/json/tasklist.json");
+ const updatedAlert = alerts.find((alert) => alert._id === alertId);
+ expect(updatedAlert).to.have.property("complete");
+ expect(updatedAlert.complete).to.be.true;
+ });
+ });
+});
diff --git a/back-end/test/services/home.service.test.js b/back-end/test/services/home.service.test.js
new file mode 100644
index 0000000..506210a
--- /dev/null
+++ b/back-end/test/services/home.service.test.js
@@ -0,0 +1,22 @@
+const assert = require("assert");
+const homeService = require("../../src/services/home.service.js");
+
+const newRoom = {
+ roomName: "Test Room",
+ url: "testRoom"
+}
+
+describe("Home Service", () => {
+ describe("getAllRooms", () => {
+ it("should return array of all rooms", async() => {
+ const rooms = await homeService.getAllRooms()
+ assert(Array.isArray(rooms))
+ })
+ })
+ describe("addRoom", () => {
+ it("should add room and room url to array", async() => {
+ const rooms = await(homeService.addRoom(newRoom))
+ assert.deepStrictEqual(rooms, newRoom)
+ })
+ })
+})
\ No newline at end of file
diff --git a/back-end/test/services/login.service.test.js b/back-end/test/services/login.service.test.js
new file mode 100644
index 0000000..b5846ac
--- /dev/null
+++ b/back-end/test/services/login.service.test.js
@@ -0,0 +1,146 @@
+const assert = require("assert");
+const loginService = require("../../src/services/login.service.js");
+
+const loginSuccess = {
+ username: "fishc0",
+ password: "12345",
+};
+
+const loginWrongPassword = {
+ username: "fishc0",
+ password: "00000",
+};
+
+const loginEmptyHouse = {
+ username: "adevuyst0",
+ password: "00000",
+};
+
+const loginUserNotFound = {
+ username: "ppp1",
+ password: "00000",
+};
+
+const housesData = [
+ {
+ "_id": "64320bf0fc13ae1e696b0eea",
+ "code": "AKRHSR",
+ "name": "Jamima",
+ "users": [
+ {
+ "user": "Lexine"
+ },
+ {
+ "user": "Eliza"
+ },
+ {
+ "user": "Jennica"
+ },
+ {
+ "user": "Cacilie"
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0eeb",
+ "code": "FSHLBQ",
+ "name": "Lorraine",
+ "users": [
+ {
+ "user": "Stillmann"
+ },
+ {
+ "user": "Katlin"
+ },
+ {
+ "user": "Eloise"
+ },
+ {
+ "user": "Randee"
+ },
+ {
+ "user": "Ambrosi"
+ }
+ ]
+ },
+ {
+ "_id": "64320bf0fc13ae1e696b0eec",
+ "code": "JCBGKO",
+ "name": "Misty",
+ "users": [
+ {
+ "user": "Shanna"
+ },
+ {
+ "user": "Emelen"
+ },
+ {
+ "user": "Bathsheba"
+ },
+ {
+ "user": "Delcine"
+ }
+ ]
+ }
+];
+
+const houseCodeCorrect = ["AKRHSR"];
+const houseCodeWrong = ["00000"];
+
+
+describe("Login Service", () => {
+ describe("#findHouseCode(houses, password)", () => {
+ it("should return the house object with matching house code", async () => {
+ const house = await loginService.findHouseCode(housesData, houseCodeCorrect[0]);
+ assert.strictEqual(typeof house, "object");
+ });
+
+ it("should return the undefined if no matching house code is found", async () => {
+ const house = await loginService.findHouseCode(housesData, houseCodeWrong[0]);
+ assert.strictEqual(house, undefined);
+ });
+ });
+
+ describe("#getUser(input_data)", () => {
+ it('should return "Login successfully" if username and password are correct', async () => {
+ const message = await loginService.getUser(loginSuccess);
+ assert.strictEqual(message, "Login successfully");
+ });
+
+ it('should return "Wrong password" if only the password is wrong', async () => {
+ await assert.rejects(
+ async () => {
+ await loginService.getUser(loginWrongPassword);
+ },
+ {
+ name: "Error",
+ message: "Wrong password",
+ }
+ );
+ });
+
+ it('should return "User has not been invited into a house" if the user has empty houses array', async () => {
+ await assert.rejects(
+ async () => {
+ await loginService.getUser(loginEmptyHouse);
+ },
+ {
+ name: "Error",
+ message: "User has not been invited into a house",
+ }
+ );
+ });
+
+ it('should return "Username not found" if the username is not found', async () => {
+ await assert.rejects(
+ async () => {
+ await loginService.getUser(loginUserNotFound);
+ },
+ {
+ name: "Error",
+ message: "Username not found",
+ }
+ );
+ });
+ });
+});
diff --git a/back-end/test/services/task.service.test.js b/back-end/test/services/task.service.test.js
new file mode 100644
index 0000000..295db43
--- /dev/null
+++ b/back-end/test/services/task.service.test.js
@@ -0,0 +1,104 @@
+const assert = require("assert");
+const taskService = require("../../src/services/task.service.js");
+
+// New Task data we add to the database
+let existingTaskData = {};
+const newTaskData = {
+ _id: "test-new-task-id-1",
+ task_name: "new task",
+ description: "new task description",
+ room: "new room",
+ assignee: "new assignee",
+ due_time: 164717936800,
+ complete: false,
+ repeat: 1,
+};
+
+describe("Task Service", () => {
+ // Check if there is data in tasklist.json to sample before anything
+ before(async () => {
+ const task_json = require("../../src/json/tasklist.json");
+ assert(task_json.length > 0, "Task list should have at least one element");
+ existingTaskData = task_json[0];
+ });
+
+ describe("#getTasks()", () => {
+ it("should return an array of tasks", async () => {
+ const tasks = await taskService.getTasks();
+ assert(Array.isArray(tasks));
+ });
+ });
+
+ describe("#getTask(task_id)", () => {
+ it("should return the task with the matching task_id", async () => {
+ const task = await taskService.getTask(existingTaskData._id);
+ assert.strictEqual(task._id, existingTaskData._id);
+ });
+
+ it("should return undefined if no task with matching task_id is found", async () => {
+ const task = await taskService.getTask("nonexistent-task-id");
+ assert.strictEqual(task, undefined);
+ });
+ });
+
+ describe("#createTask(task_data)", () => {
+ it("should add the task_data to the task_json array if it does not already exist", async () => {
+ const message = await taskService.createTask(newTaskData);
+ assert.strictEqual(message, "Task created successfully");
+ const task = await taskService.getTask(newTaskData._id);
+ assert.deepStrictEqual(task, newTaskData);
+ });
+
+ it('should return "Task already exists" if the task_data already exists in the task_json array', async () => {
+ await assert.rejects(
+ async () => {
+ await taskService.createTask(existingTaskData);
+ },
+ {
+ name: "Error",
+ message: "Task already exists",
+ }
+ );
+ });
+ });
+
+ describe("#updateTask(task_id, task_data)", () => {
+ it("should update the task_data in the task_json array for the task with the matching task_id", async () => {
+ const taskDataToUpdate = {
+ _id: existingTaskData._id,
+ task_name: "updated task",
+ description: "updated task description",
+ room: "updated room",
+ assignee: "updated assignee",
+ due_time: 164717936800,
+ complete: false,
+ repeat: 2,
+ };
+ const message = await taskService.updateTask(
+ existingTaskData._id,
+ taskDataToUpdate
+ );
+ assert.strictEqual(message, "Task updated successfully");
+ const task = await taskService.getTask(existingTaskData._id);
+ assert.deepStrictEqual(task, taskDataToUpdate);
+ });
+ });
+
+ describe("#removeTask(task_id)", () => {
+ // create task beforehand
+ before(async () => {
+ try {
+ await taskService.createTask(newTaskData);
+ } catch (e) {
+ // do nothing
+ }
+ });
+
+ it("should remove the task with the matching task_id from the task_json array", async () => {
+ const message = await taskService.removeTask(newTaskData._id);
+ assert.strictEqual(message, "Task deleted successfully");
+ const task = await taskService.getTask(newTaskData._id);
+ assert.strictEqual(task, undefined);
+ });
+ });
+});
diff --git a/front-end/README.md b/front-end/README.md
new file mode 100644
index 0000000..58beeac
--- /dev/null
+++ b/front-end/README.md
@@ -0,0 +1,70 @@
+# Getting Started with Create React App
+
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.\
+Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
+
+The page will reload when you make changes.\
+You may also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.\
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.\
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.\
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can't go back!**
+
+If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
+
+You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).
+
+### Code Splitting
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
+
+### Analyzing the Bundle Size
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
+
+### Making a Progressive Web App
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
+
+### Advanced Configuration
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
+
+### Deployment
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
+
+### `npm run build` fails to minify
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
diff --git a/front-end/images/logo ideas/Bayt.png b/front-end/images/logo ideas/Bayt.png
new file mode 100644
index 0000000..7670ead
Binary files /dev/null and b/front-end/images/logo ideas/Bayt.png differ
diff --git a/front-end/images/logo ideas/experiment.ai b/front-end/images/logo ideas/experiment.ai
new file mode 100644
index 0000000..ccc1d5e
--- /dev/null
+++ b/front-end/images/logo ideas/experiment.ai
@@ -0,0 +1,1074 @@
+%PDF-1.5
%
+1 0 obj
<>/OCGs[22 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
+
+
+
+
+ application/pdf
+
+
+ experiment
+
+
+ Adobe Illustrator 24.0 (Macintosh)
+ 2023-03-05T19:40:05-05:00
+ 2023-03-05T19:40:06-05:00
+ 2023-03-05T19:40:06-05:00
+
+
+
+ 256
+ 256
+ JPEG
+ /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9K5is3Yq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
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
qMwJdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi
rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir
sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs
VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV
dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd
irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi
rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir
sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs
VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV
dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVf/Z
+
+
+
+ uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7
+ xmp.did:9ecf70d4-1860-47f4-a581-70af1e3c74a0
+ uuid:f1203d5b-e117-f345-8206-8403ad604d44
+ proof:pdf
+
+ xmp.iid:4eff1a31-fff8-4b7b-ba0e-825a5f980482
+ xmp.did:4eff1a31-fff8-4b7b-ba0e-825a5f980482
+ uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7
+ proof:pdf
+
+
+
+
+ saved
+ xmp.iid:00f07b70-4960-4504-9f08-86f111c34914
+ 2023-02-28T21:03:52-05:00
+ Adobe Illustrator 24.0 (Macintosh)
+ /
+
+
+ saved
+ xmp.iid:9ecf70d4-1860-47f4-a581-70af1e3c74a0
+ 2023-03-05T19:39:35-05:00
+ Adobe Illustrator 24.0 (Macintosh)
+ /
+
+
+
+ Basic RGB
+ AIRobin
+ Document
+ Adobe PDF library 15.00
+ 1
+ False
+ False
+
+ 1728.000000
+ 1728.000000
+ Points
+
+
+
+
+ CoolveticaRg-Regular
+ Coolvetica
+ Regular
+ Open Type
+ Version 5.001
+ False
+ coolvetica rg.otf
+
+
+
+
+
+ Cyan
+ Magenta
+ Yellow
+ Black
+
+
+
+
+
+ Default Swatch Group
+ 0
+
+
+
+ White
+ RGB
+ PROCESS
+ 255
+ 255
+ 255
+
+
+ Black
+ RGB
+ PROCESS
+ 0
+ 0
+ 0
+
+
+ RGB Red
+ RGB
+ PROCESS
+ 255
+ 0
+ 0
+
+
+ RGB Yellow
+ RGB
+ PROCESS
+ 255
+ 255
+ 0
+
+
+ RGB Green
+ RGB
+ PROCESS
+ 0
+ 255
+ 0
+
+
+ RGB Cyan
+ RGB
+ PROCESS
+ 0
+ 255
+ 255
+
+
+ RGB Blue
+ RGB
+ PROCESS
+ 0
+ 0
+ 255
+
+
+ RGB Magenta
+ RGB
+ PROCESS
+ 255
+ 0
+ 255
+
+
+ R=193 G=39 B=45
+ RGB
+ PROCESS
+ 193
+ 39
+ 45
+
+
+ R=237 G=28 B=36
+ RGB
+ PROCESS
+ 237
+ 28
+ 36
+
+
+ R=241 G=90 B=36
+ RGB
+ PROCESS
+ 241
+ 90
+ 36
+
+
+ R=247 G=147 B=30
+ RGB
+ PROCESS
+ 247
+ 147
+ 30
+
+
+ R=251 G=176 B=59
+ RGB
+ PROCESS
+ 251
+ 176
+ 59
+
+
+ R=252 G=238 B=33
+ RGB
+ PROCESS
+ 252
+ 238
+ 33
+
+
+ R=217 G=224 B=33
+ RGB
+ PROCESS
+ 217
+ 224
+ 33
+
+
+ R=140 G=198 B=63
+ RGB
+ PROCESS
+ 140
+ 198
+ 63
+
+
+ R=57 G=181 B=74
+ RGB
+ PROCESS
+ 57
+ 181
+ 74
+
+
+ R=0 G=146 B=69
+ RGB
+ PROCESS
+ 0
+ 146
+ 69
+
+
+ R=0 G=104 B=55
+ RGB
+ PROCESS
+ 0
+ 104
+ 55
+
+
+ R=34 G=181 B=115
+ RGB
+ PROCESS
+ 34
+ 181
+ 115
+
+
+ R=0 G=169 B=157
+ RGB
+ PROCESS
+ 0
+ 169
+ 157
+
+
+ R=41 G=171 B=226
+ RGB
+ PROCESS
+ 41
+ 171
+ 226
+
+
+ R=0 G=113 B=188
+ RGB
+ PROCESS
+ 0
+ 113
+ 188
+
+
+ R=46 G=49 B=146
+ RGB
+ PROCESS
+ 46
+ 49
+ 146
+
+
+ R=27 G=20 B=100
+ RGB
+ PROCESS
+ 27
+ 20
+ 100
+
+
+ R=102 G=45 B=145
+ RGB
+ PROCESS
+ 102
+ 45
+ 145
+
+
+ R=147 G=39 B=143
+ RGB
+ PROCESS
+ 147
+ 39
+ 143
+
+
+ R=158 G=0 B=93
+ RGB
+ PROCESS
+ 158
+ 0
+ 93
+
+
+ R=212 G=20 B=90
+ RGB
+ PROCESS
+ 212
+ 20
+ 90
+
+
+ R=237 G=30 B=121
+ RGB
+ PROCESS
+ 237
+ 30
+ 121
+
+
+ R=199 G=178 B=153
+ RGB
+ PROCESS
+ 199
+ 178
+ 153
+
+
+ R=153 G=134 B=117
+ RGB
+ PROCESS
+ 153
+ 134
+ 117
+
+
+ R=115 G=99 B=87
+ RGB
+ PROCESS
+ 115
+ 99
+ 87
+
+
+ R=83 G=71 B=65
+ RGB
+ PROCESS
+ 83
+ 71
+ 65
+
+
+ R=198 G=156 B=109
+ RGB
+ PROCESS
+ 198
+ 156
+ 109
+
+
+ R=166 G=124 B=82
+ RGB
+ PROCESS
+ 166
+ 124
+ 82
+
+
+ R=140 G=98 B=57
+ RGB
+ PROCESS
+ 140
+ 98
+ 57
+
+
+ R=117 G=76 B=36
+ RGB
+ PROCESS
+ 117
+ 76
+ 36
+
+
+ R=96 G=56 B=19
+ RGB
+ PROCESS
+ 96
+ 56
+ 19
+
+
+ R=66 G=33 B=11
+ RGB
+ PROCESS
+ 66
+ 33
+ 11
+
+
+
+
+
+ Cold
+ 1
+
+
+
+ C=56 M=0 Y=20 K=0
+ RGB
+ PROCESS
+ 101
+ 200
+ 208
+
+
+ C=51 M=43 Y=0 K=0
+ RGB
+ PROCESS
+ 131
+ 139
+ 197
+
+
+ C=26 M=41 Y=0 K=0
+ RGB
+ PROCESS
+ 186
+ 155
+ 201
+
+
+
+
+
+ Grays
+ 1
+
+
+
+ R=0 G=0 B=0
+ RGB
+ PROCESS
+ 0
+ 0
+ 0
+
+
+ R=26 G=26 B=26
+ RGB
+ PROCESS
+ 26
+ 26
+ 26
+
+
+ R=51 G=51 B=51
+ RGB
+ PROCESS
+ 51
+ 51
+ 51
+
+
+ R=77 G=77 B=77
+ RGB
+ PROCESS
+ 77
+ 77
+ 77
+
+
+ R=102 G=102 B=102
+ RGB
+ PROCESS
+ 102
+ 102
+ 102
+
+
+ R=128 G=128 B=128
+ RGB
+ PROCESS
+ 128
+ 128
+ 128
+
+
+ R=153 G=153 B=153
+ RGB
+ PROCESS
+ 153
+ 153
+ 153
+
+
+ R=179 G=179 B=179
+ RGB
+ PROCESS
+ 179
+ 179
+ 179
+
+
+ R=204 G=204 B=204
+ RGB
+ PROCESS
+ 204
+ 204
+ 204
+
+
+ R=230 G=230 B=230
+ RGB
+ PROCESS
+ 230
+ 230
+ 230
+
+
+ R=242 G=242 B=242
+ RGB
+ PROCESS
+ 242
+ 242
+ 242
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
endstream
endobj
3 0 obj
<>
endobj
5 0 obj
<>/Resources<>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/Thumb 27 0 R/TrimBox[0.0 0.0 1728.0 1728.0]/Type/Page>>
endobj
24 0 obj
<>stream
+HlVMo6WDs>I^=Era4( ?7V9X<h~JJz|O%]_Sj