-
- {/*
Sports Matcher
-
The best place to find a local match for a good game of your favourite sport!
*/}
-
-
+
Why?
Because you want to play the sports you love while meeting new friends!
@@ -23,6 +34,7 @@ export default class Welcome extends React.Component {
Available Matches
+
);
diff --git a/sports-matcher/client/src/styles/HomeCarousel.css b/sports-matcher/client/src/styles/HomeCarousel.css
deleted file mode 100644
index ecedbf6..0000000
--- a/sports-matcher/client/src/styles/HomeCarousel.css
+++ /dev/null
@@ -1,15 +0,0 @@
-.captionStyle {
- background-color: seashell;
- color: black;
- outline: 1px solid black;
-}
-
-.carousel-control-next,
-.carousel-control-prev /*, .carousel-indicators */ {
- filter: invert(100%);
-}
-
-.carousel-indicators button {
- filter: invert(100%);
-}
-
diff --git a/sports-matcher/client/src/styles/extra.css b/sports-matcher/client/src/styles/extra.css
index 8aaad9d..8482f5f 100644
--- a/sports-matcher/client/src/styles/extra.css
+++ b/sports-matcher/client/src/styles/extra.css
@@ -1,19 +1,5 @@
-.jumbotron {
- width: 100%;
- padding-left: 1.5rem;
- padding-right: 1.5rem;
- padding-top: 12rem;
- padding-bottom: 1rem;
- text-align: center;
- background-size: cover;
- background-color: black;
- color: white;
-}
-
-.jumbotron h1 {
- font-size: 1.5rem;
-}
-
.horizontal-scroller {
overflow-x: scroll;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
}
diff --git a/sports-matcher/client/src/utils/httpClients.js b/sports-matcher/client/src/utils/httpClients.js
index a1d3187..e512953 100644
--- a/sports-matcher/client/src/utils/httpClients.js
+++ b/sports-matcher/client/src/utils/httpClients.js
@@ -1,6 +1,6 @@
import axios from "axios";
export const apiClient = axios.create({
- baseURL: process.env.API_HOST,
+ baseURL: process.env.REACT_APP_API_HOST,
timeout: 5000,
});
\ No newline at end of file
diff --git a/sports-matcher/client/src/utils/strings.js b/sports-matcher/client/src/utils/strings.js
index 14f18e1..9a47ab9 100644
--- a/sports-matcher/client/src/utils/strings.js
+++ b/sports-matcher/client/src/utils/strings.js
@@ -10,7 +10,9 @@ export function grammaticalListString(items, max) {
return;
}
built += item;
- built += ", ";
+ if (index < items.length - 1) {
+ built += ", ";
+ }
if (index == max - 1) {
built += "and ";
}
diff --git a/sports-matcher/scripts/start_mongo.bat b/sports-matcher/scripts/start_mongo.bat
new file mode 100644
index 0000000..5dcea4f
--- /dev/null
+++ b/sports-matcher/scripts/start_mongo.bat
@@ -0,0 +1 @@
+mongod --dbpath ./server/mongo-data
\ No newline at end of file
diff --git a/sports-matcher/scripts/start_mongo.sh b/sports-matcher/scripts/start_mongo.sh
new file mode 100755
index 0000000..0d12fdc
--- /dev/null
+++ b/sports-matcher/scripts/start_mongo.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+mongod --dbpath ./server/mongo-data
\ No newline at end of file
diff --git a/sports-matcher/server/controllers/matchController.js b/sports-matcher/server/controllers/matchController.js
index 8619e20..0ec8ed0 100644
--- a/sports-matcher/server/controllers/matchController.js
+++ b/sports-matcher/server/controllers/matchController.js
@@ -1,5 +1,5 @@
import express from "express";
-import { authenticationGuard } from "../middleware/authority.js";
+import { requireAuthenticated } from "../middleware/authority.js";
import { needDatabase } from "../middleware/database.js";
import matchModel from "../schemas/matchModel.js";
import sportModel from "../schemas/sportModel.js";
@@ -18,7 +18,7 @@ MatchController.get("/search/:sport", needDatabase, async (req, res) => {
if (req.query.beforeDate) query.where("when").lte(req.query.beforeDate);
let queryResults = await query;
- res.send({ queryResults });
+ res.send({ results: queryResults });
} catch (error) {
console.error(error);
res.status(500).send("Internal server error.");
@@ -26,27 +26,43 @@ MatchController.get("/search/:sport", needDatabase, async (req, res) => {
});
MatchController.get("/recent/:limit?", needDatabase, async (req, res) => {
- let limit = req.params.limit;
- if (!req.params.limit) limit = 10;
- if (isNaN(limit)) {
- res.status(400).send("Limit parameter not a number.");
- return;
- }
- if (limit > 50) {
- res.status(400).send("Limit greater than maximum limit of 50.");
- return;
- }
try {
- const recent = await matchModel.find().where("publicity").gte(2).limit(limit).sort({ createDate: -1 });
+ let user = null;
+ if (req.session.userId) {
+ user = await userModel.findById(req.session.userId);
+ }
+ let limit = parseInt(req.params.limit);
+ if (!req.params.limit) limit = 10;
+ if (isNaN(limit)) {
+ console.log(typeof (limit));
+ res.status(400).send("Limit parameter is not a number.");
+ return;
+ }
+ if (isNaN(limit)) {
+ res.status(400).send("Limit parameter not a number.");
+ return;
+ }
+ if (limit > 50) {
+ res.status(400).send("Limit greater than maximum limit of 50.");
+ return;
+ }
+ let recent = null;
+ if (user) {
+ await user.populate("participatingMatches");
+ recent = user.participatingMatches.slice(-limit);
+ } else {
+ recent = await matchModel.find().where("publicity").gte(2).limit(limit).sort({ createDate: -1 });
+ }
+ await recent.populate("members.$"); // Populates all references.
res.status(200).send({ recent: recent });
- } catch (err) {
- console.error(err);
+ } catch (error) {
+ console.error(error);
res.status(500).send("Internal server error.");
// TODO: Check and improve error handling.
}
});
-MatchController.post("/", needDatabase, authenticationGuard, async (req, res) => {
+MatchController.post("/", needDatabase, requireAuthenticated, async (req, res) => {
try {
const userId = req.session.userId;
const user = await userModel.findById(userId);
@@ -64,7 +80,7 @@ MatchController.post("/", needDatabase, authenticationGuard, async (req, res) =>
user.createdMatches.push(match._id);
user.participatingMatches.push(match._id);
await user.save();
- res.status(201).send(match);
+ res.status(201).send({ createdMatch: match });
} catch (error) {
console.error(error);
res.status(500).send("Internal server error.");
@@ -72,110 +88,129 @@ MatchController.post("/", needDatabase, authenticationGuard, async (req, res) =>
}
});
-MatchController.patch("/:id", needDatabase, authenticationGuard, async (req, res) => {
- const match = await matchModel.findById(req.params.id);
- if (!match) {
- res.status(400).send("Invalid match ID provided.");
- return;
- }
-
- if (req.user._id !== match.creator && req.user.accessLevel < 3) {
- res.status(401).send("Not authorized.");
- return;
- }
-
- if (req.body._id) {
- res.status(400).send("Cannot change ID of match.");
- return;
- }
-
- if (req.body.creator) {
- res.status(400).send("Cannot change creator of match.");
- return;
- }
-
- await match.updateOne(req.body);
-
- res.status(200).send(match);
-});
-
-MatchController.delete("/:id", needDatabase, authenticationGuard, async (req, res) => {
- const match = await matchModel.findById(req.params.id);
- if (!match) {
- res.status(400).send("Invalid match ID provided.");
- return;
- }
-
- if (req.user._id !== match.creator && req.user.accessLevel < 3) {
- res.status(401).send("Not authorized.");
- return;
- }
- await match.deleteOne();
-});
-
-MatchController.get("/:matchId", needDatabase, async (req, res) => {
- if (!req.params.matchId) {
- res.status(404).send("Id must be provided to retrieve match");
- return;
- }
+MatchController.patch("/:id", needDatabase, requireAuthenticated, async (req, res) => {
try {
- const match = await matchModel.findById(req.params.matchId);
+ const match = await matchModel.findById(req.params.id);
+ if (!match) {
+ res.status(400).send("Invalid match ID provided.");
+ return;
+ }
+
+ if (req.user._id !== match.creator && req.user.accessLevel < 3) {
+ res.status(401).send("Not authorized.");
+ return;
+ }
+
+ if (req.body._id) {
+ res.status(400).send("Cannot change ID of match.");
+ return;
+ }
+
+ if (req.body.creator) {
+ res.status(400).send("Cannot change creator of match.");
+ return;
+ }
+ await match.updateOne(req.body);
+ res.status(200).send({ updatedMatch: match });
+
+ } catch (error) {
+ res.status(200).send("Internal server error.");
+ }
+});
+
+MatchController.delete("/:id", needDatabase, requireAuthenticated, async (req, res) => {
+ try {
+ const match = await matchModel.findById(req.params.id);
+ if (!match) {
+ res.status(400).send("Invalid match ID provided.");
+ return;
+ }
+ if (req.user._id !== match.creator && req.user.accessLevel < 3) {
+ res.status(401).send("Not authorized.");
+ return;
+ }
+ await match.deleteOne();
+ res.status(200).send("Deleted.");
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error");
+ }
+});
+
+MatchController.get("/:id", needDatabase, async (req, res) => {
+ try {
+ if (!req.params.id) {
+ res.status(404).send("Id must be provided to retrieve match");
+ return;
+ }
+ const match = await matchModel.findById(req.params.id).populate("sport");
if (match) {
- res.status(200).send(match);
+ res.status(200).send({ match: match });
} else {
- res.status(404).send("Could not find match with ID: " + req.params.matchId);
+ res.status(404).send("Could not find match with ID: " + req.params.id);
}
} catch (error) {
+ console.error(error);
res.status(500).send("Internal server error.");
- // TODO: Develop the error handling.
+ // TODO: Improve the error handling.
}
});
-MatchController.get("/join/:id", needDatabase, authenticationGuard, async (req, res) => {
- const match = await matchModel.findById(req.params.id);
- const user = req.user;
- if (!match) {
- res.status(400).send("Invalid match ID provided.");
- return;
+MatchController.get("/join/:id", needDatabase, requireAuthenticated, async (req, res) => {
+ try {
+ const match = await matchModel.findById(req.params.id);
+ const user = req.user;
+ if (!match) {
+ res.status(400).send("Invalid match ID provided.");
+ return;
+ }
+
+ if (user.participatingMatches.includes(match._id)) {
+ res.status(400).send("Already participating in match.");
+ return;
+ }
+
+ match.participants.push(user._id);
+ user.participatingMatches.push(match._id);
+
+ await match.save();
+ await user.save();
+
+ res.status(200).send("Joined.");
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error.");
}
-
- if (user.participatingMatches.includes(match._id)) {
- res.status(400).send("Already participating in match.");
- return;
- }
-
- match.participants.push(user._id);
- user.participatingMatches.push(match._id);
-
- await match.save();
- await user.save();
-
- res.status(200).send("Joined.");
});
-MatchController.get("/leave/:id", needDatabase, authenticationGuard, async (req, res) => {
- const match = await matchModel.findById(req.params.id);
- const user = req.user;
+MatchController.get("/leave/:id", needDatabase, requireAuthenticated, async (req, res) => {
+ try {
+ const match = await matchModel.findById(req.params.id);
+ const user = req.user;
- if (!match) {
- res.status(400).send("Invalid match ID provided.");
- return;
+ if (!match) {
+ res.status(400).send("Invalid match ID provided.");
+ return;
+ }
+
+ if (!user.participatingMatches.includes(match._id)) {
+ res.status(400).send("Not part of match.");
+ return;
+ }
+
+ const userIndex = match.participants.indexOf(user._id);
+ match.participants.splice(userIndex, 1);
+ await match.save();
+
+ const matchIndex = user.participatingMatches.indexOf(match._id);
+ user.participatingMatches.splice(matchIndex, 1);
+ await user.save();
+
+ res.status(200).send("Left match.");
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error.");
}
-
- if (!user.participatingMatches.includes(match._id)) {
- res.status(400).send("Not part of match.");
- return;
- }
-
- const userIndex = match.participants.indexOf(user._id);
- match.participants.splice(userIndex, 1);
- await match.save();
-
- const matchIndex = user.participatingMatches.indexOf(match._id);
- user.participatingMatches.splice(matchIndex, 1);
- await user.save();
-
- res.status(200).send("Left match.");
});
export default MatchController;
\ No newline at end of file
diff --git a/sports-matcher/server/controllers/rentalController.js b/sports-matcher/server/controllers/rentalController.js
new file mode 100644
index 0000000..616c09b
--- /dev/null
+++ b/sports-matcher/server/controllers/rentalController.js
@@ -0,0 +1,116 @@
+import express from "express";
+import { requireAuthenticated } from "../middleware/authority.js";
+import { needDatabase } from "../middleware/database.js";
+import rentalModel from "../schemas/rentalModel.js";
+import userModel from "../schemas/userModel.js";
+const rentalController = express.Router();
+
+
+rentalController.post("/", needDatabase, requireAuthenticated, async (req, res) => {
+ try {
+ const user = req.user;
+ req.body.createDate = undefined;
+ req.body.creator = user._id;
+ const rental = new rentalModel(req.body);
+ await rental.save();
+ res.status(201).send({ createdRental: rental });
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error.");
+ }
+});
+
+rentalController.get("/:id", needDatabase, async (req, res) => {
+ try {
+ const rental = await rentalModel.findById(req.params.id).populate("creator");
+ res.status(200).send({ rental: rental });
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error");
+ }
+});
+
+rentalController.get("/recent/:limit?", needDatabase, async (req, res) => {
+ try {
+ let user = null;
+ if (req.session.userId) {
+ user = await userModel.findById(req.session.userId);
+ }
+ let limit = parseInt(req.params.limit);
+ if (!req.params.limit) limit = 10;
+ if (isNaN(limit)) {
+ console.log(typeof (limit));
+ res.status(400).send("Limit parameter is not a number.");
+ return;
+ }
+ if (isNaN(limit)) {
+ res.status(400).send("Limit parameter not a number.");
+ return;
+ }
+ if (limit > 50) {
+ res.status(400).send("Limit greater than maximum limit of 50.");
+ return;
+ }
+ let recent = null;
+ if (user) {
+ await user.populate("createdRentals");
+ recent = user.createdRentals.slice(-limit);
+ } else {
+ recent = await rentalModel.find().limit(limit).sort({ createDate: -1 });
+ }
+ await recent.populate("members.$");
+ res.status(200).send({ recent: recent });
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error.");
+ }
+});
+
+rentalController.patch("/:id", needDatabase, requireAuthenticated, async (req, res) => {
+ try {
+ const rental = await rentalModel.findById(req.params.id);
+ if (!rental) {
+ res.status(400).send("Invalid rental ID provided.");
+ return;
+ }
+ if (req.body._id) {
+ res.status(400).send("Cannot change ID of rental.");
+ return;
+ }
+ if (req.body.creator) {
+ res.status(400).send("Cannot change creator of rental.");
+ return;
+ }
+ if (req.user._id !== rental.creator && req.user.accessLevel < 3) {
+ res.status(401).send("Not authorized.");
+ return;
+ }
+ await rental.updateOne(req.body);
+ res.status(200).send({ updated: rental });
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error.");
+ }
+});
+
+rentalController.delete("/:id", needDatabase, requireAuthenticated, async (req, res) => {
+ try {
+ const rental = await rentalModel.findById(req.params.id);
+ if (!rental) {
+ res.status(400).send("Invalid match ID provided.");
+ return;
+ }
+
+ if (req.user._id !== rental.creator && req.user.accessLevel < 3) {
+ res.status(401).send("Not authorized.");
+ return;
+ }
+ await rental.deleteOne();
+ res.status(200).send("Deleted.");
+ } catch (error) {
+ console.error(error);
+ res.status(500).send("Internal server error");
+ }
+});
+
+export default rentalController;
\ No newline at end of file
diff --git a/sports-matcher/server/controllers/sportController.js b/sports-matcher/server/controllers/sportController.js
index 40be0c1..0d7b667 100644
--- a/sports-matcher/server/controllers/sportController.js
+++ b/sports-matcher/server/controllers/sportController.js
@@ -1,12 +1,12 @@
import express from "express";
-import { authenticationGuard } from "../middleware/authority.js";
+import { requireAuthenticated } from "../middleware/authority.js";
import { needDatabase } from "../middleware/database.js";
import sportModel from "../schemas/sportModel.js";
import userModel from "../schemas/userModel.js";
const SportController = express.Router();
-SportController.post("/", needDatabase, authenticationGuard, async (req, res) => {
+SportController.post("/", needDatabase, requireAuthenticated, async (req, res) => {
const user = await userModel.findById(req.session.userId);
try {
if (user.accessLevel <= 2) {
diff --git a/sports-matcher/server/controllers/userController.js b/sports-matcher/server/controllers/userController.js
index 4ca3888..520b788 100644
--- a/sports-matcher/server/controllers/userController.js
+++ b/sports-matcher/server/controllers/userController.js
@@ -1,5 +1,5 @@
import express from "express";
-import { authenticationGuard } from "../middleware/authority.js";
+import { requireAuthenticated } from "../middleware/authority.js";
import { needDatabase } from "../middleware/database.js";
import userModel from "../schemas/userModel.js";
import User from "../schemas/userModel.js";
@@ -34,7 +34,7 @@ UserController.post("/login", needDatabase, async (req, res) => {
}
});
-UserController.get("/logout", authenticationGuard, (req, res) => {
+UserController.get("/logout", requireAuthenticated, (req, res) => {
req.session.destroy((err) => {
if (err) {
console.error(err);
@@ -50,7 +50,7 @@ UserController.get("/logout", authenticationGuard, (req, res) => {
});
});
-UserController.get("/:id?", needDatabase, authenticationGuard, async (req, res) => {
+UserController.get("/:id?", needDatabase, requireAuthenticated, async (req, res) => {
let user = null;
if (req.params.id) {
if (req.user.accessLevel > 2) {
@@ -66,7 +66,7 @@ UserController.get("/:id?", needDatabase, authenticationGuard, async (req, res)
res.status(200).send(user);
});
-UserController.patch("/:id?", needDatabase, authenticationGuard, async (req, res) => {
+UserController.patch("/:id?", needDatabase, requireAuthenticated, async (req, res) => {
let user = null;
if (req.params.id) {
if (req.user.accessLevel > 2) {
@@ -114,7 +114,7 @@ UserController.patch("/:id?", needDatabase, authenticationGuard, async (req, res
/* TODO: Implement middleware for removing users.
-UserController.delete("/:id?", needDatabase, authenticationGuard, async (req, res) => {
+UserController.delete("/:id?", needDatabase, requireAuthenticated, async (req, res) => {
let user = null;
if (req.params.id) {
if (req.user.accessLevel > 2) {
diff --git a/sports-matcher/server/middleware/authority.js b/sports-matcher/server/middleware/authority.js
index bcab71e..9c09f66 100644
--- a/sports-matcher/server/middleware/authority.js
+++ b/sports-matcher/server/middleware/authority.js
@@ -17,7 +17,7 @@ if (process.env.NODE_ENV === "production") {
}
export const userSession = session(sessionConf);
-export async function authenticationGuard(req, res, next) {
+export async function requireAuthenticated(req, res, next) {
if (req.session.userId) {
req.user = await userModel.findById(req.session.userId);
next();
@@ -26,7 +26,3 @@ export async function authenticationGuard(req, res, next) {
return;
}
}
-
-// TODO: Authentication
-// TODO: Identity
-// TODO: Authority
\ No newline at end of file
diff --git a/sports-matcher/server/schemas/modelNameRegister.js b/sports-matcher/server/schemas/modelNameRegister.js
index 3c3b6e6..decc2b8 100644
--- a/sports-matcher/server/schemas/modelNameRegister.js
+++ b/sports-matcher/server/schemas/modelNameRegister.js
@@ -1,5 +1,6 @@
export default {
Match: "match",
User: "user",
- Sport: "sport"
+ Sport: "sport",
+ Rental: "rental",
};
\ No newline at end of file
diff --git a/sports-matcher/server/schemas/rentalModel.js b/sports-matcher/server/schemas/rentalModel.js
new file mode 100644
index 0000000..31e2d24
--- /dev/null
+++ b/sports-matcher/server/schemas/rentalModel.js
@@ -0,0 +1,23 @@
+import mongoose from "mongoose";
+import modelNameRegister from "./modelNameRegister.js";
+
+const Types = mongoose.Schema.Types;
+
+const rentalSchema = new mongoose.Schema({
+ title: { type: String, required: true, trim: true },
+ rate: { type: String, required: true, trim: true },
+ description: { type: String, required: true },
+ contact: { type: String, required: true },
+ createDate: { type: Date, required: true, default: Date.now },
+ creator: { type: Types.ObjectId, ref: modelNameRegister.User }
+});
+
+rentalSchema.pre("remove", async function (next) {
+ const rental = this;
+ const rentalInd = rental.creator.createdRentals.indexOf(rental._id);
+ rental.creator.createdRentals.splice(rentalInd, 1);
+ await rental.save();
+ next();
+});
+
+export default mongoose.model(modelNameRegister.Rental, rentalSchema);
\ No newline at end of file
diff --git a/sports-matcher/server/schemas/userModel.js b/sports-matcher/server/schemas/userModel.js
index 4c21b66..28a2be5 100644
--- a/sports-matcher/server/schemas/userModel.js
+++ b/sports-matcher/server/schemas/userModel.js
@@ -29,6 +29,7 @@ const userSchema = new mongoose.Schema({
},
createdMatches: { type: [{ type: Types.ObjectId, ref: modelNameRegister.Match }], required: true, default: [] },
participatingMatches: { type: [{ type: Types.ObjectId, ref: modelNameRegister.Match }], required: true, default: [] },
+ createdRentals: { type: [{ type: Types.ObjectId, ref: modelNameRegister.Rental }], required: true, default: [] },
emailPublicity: { type: Number, required: true, default: 0 },
bioPublicity: { type: Boolean, required: true, default: false },
phonePublicity: { type: Boolean, required: true, default: false },
diff --git a/sports-matcher/server/server.js b/sports-matcher/server/server.js
index 08dbae4..dcb9da6 100644
--- a/sports-matcher/server/server.js
+++ b/sports-matcher/server/server.js
@@ -7,6 +7,7 @@ import SportController from "./controllers/sportController.js";
import { userSession } from "./middleware/authority.js";
import { mongooseDbName, mongoURI } from "./database/mongoose.js";
import cors from "cors";
+import rentalController from "./controllers/rentalController.js";
const server = express();
const port = process.env.PORT || 5000;
@@ -40,7 +41,7 @@ server.use(userSession);
server.use("/user", UserController);
server.use("/match", MatchController);
server.use("/sport", SportController);
-
+server.use("/rental", rentalController);
server.listen(port, () => {
console.log(`Server listening on port ${port}.`);