\ No newline at end of file
diff --git a/src/views/partials/head.hbs b/public/views/partials/head.hbs
similarity index 100%
rename from src/views/partials/head.hbs
rename to public/views/partials/head.hbs
diff --git a/src/views/partials/homepage/league-card.hbs b/public/views/partials/homepage/league-card.hbs
similarity index 100%
rename from src/views/partials/homepage/league-card.hbs
rename to public/views/partials/homepage/league-card.hbs
diff --git a/src/views/partials/message.hbs b/public/views/partials/message.hbs
similarity index 100%
rename from src/views/partials/message.hbs
rename to public/views/partials/message.hbs
diff --git a/src/views/partials/navigation-bar/nav.hbs b/public/views/partials/navigation-bar/nav.hbs
similarity index 100%
rename from src/views/partials/navigation-bar/nav.hbs
rename to public/views/partials/navigation-bar/nav.hbs
diff --git a/src/views/partials/navigation-bar/scoreboard-header/game-card.hbs b/public/views/partials/navigation-bar/scoreboard-header/game-card.hbs
similarity index 100%
rename from src/views/partials/navigation-bar/scoreboard-header/game-card.hbs
rename to public/views/partials/navigation-bar/scoreboard-header/game-card.hbs
diff --git a/src/views/partials/navigation-bar/scoreboard-header/scoreboard-header.hbs b/public/views/partials/navigation-bar/scoreboard-header/scoreboard-header.hbs
similarity index 100%
rename from src/views/partials/navigation-bar/scoreboard-header/scoreboard-header.hbs
rename to public/views/partials/navigation-bar/scoreboard-header/scoreboard-header.hbs
diff --git a/src/views/partials/navigation-bar/user-menu/account-screen.hbs b/public/views/partials/navigation-bar/user-menu/account-screen.hbs
similarity index 100%
rename from src/views/partials/navigation-bar/user-menu/account-screen.hbs
rename to public/views/partials/navigation-bar/user-menu/account-screen.hbs
diff --git a/src/views/partials/navigation-bar/user-menu/login.hbs b/public/views/partials/navigation-bar/user-menu/login.hbs
similarity index 100%
rename from src/views/partials/navigation-bar/user-menu/login.hbs
rename to public/views/partials/navigation-bar/user-menu/login.hbs
diff --git a/src/views/partials/navigation-bar/user-menu/register.hbs b/public/views/partials/navigation-bar/user-menu/register.hbs
similarity index 100%
rename from src/views/partials/navigation-bar/user-menu/register.hbs
rename to public/views/partials/navigation-bar/user-menu/register.hbs
diff --git a/src/assets/img/logo.png b/src/assets/img/logo.png
deleted file mode 100644
index 36c05c8..0000000
Binary files a/src/assets/img/logo.png and /dev/null differ
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..2bdf18c
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,406 @@
+// *****************************************************
+//
+// *****************************************************
+
+const express = require("express"); // To build an application server or API
+const app = express();
+const handlebars = require("express-handlebars");
+const Handlebars = require("handlebars");
+const path = require("path");
+const pgp = require("pg-promise")(); // To connect to the Postgres DB from the node server
+const bodyParser = require("body-parser");
+const session = require("express-session"); // To set the session object. To store or access session data, use the `req.session`, which is (generally) serialized as JSON by the store.
+const bcrypt = require("bcryptjs"); // To hash passwords
+const axios = require("axios"); // To make HTTP requests from our server. We'll learn more about it in Part C.
+const moment = require("moment"); // To extract current time data
+
+// *****************************************************
+//
+// *****************************************************
+
+// create `ExpressHandlebars` instance and configure the layouts and partials dir.
+const hbs = handlebars.create({
+ extname: "hbs",
+ layoutsDir: __dirname + "/../public/views/layouts",
+ partialsDir: __dirname + "/../public/views/partials",
+});
+
+// database configuration
+// database configuration
+const dbConfig = {
+ host: "db", // the database server
+ port: 5432, // the database port
+ database: "users_db", // the database name
+ user: "postgres", // the user account to connect with
+ password: "pwd", // the password of the user account
+};
+
+const db = pgp(dbConfig);
+
+// test your database
+db.connect()
+ .then((obj) => {
+ console.log("Database connection successful"); // you can view this message in the docker compose logs
+ obj.done(); // success, release the connection;
+ })
+ .catch((error) => {
+ console.log("ERROR:", error.message || error);
+ });
+
+// *****************************************************
+//
+// *****************************************************
+
+// Register `hbs` as our view engine using its bound `engine()` function.
+app.engine("hbs", hbs.engine);
+app.set("view engine", "hbs");
+app.set("views", path.join(__dirname, "/../public/views"));
+app.use(bodyParser.json()); // specify the usage of JSON for parsing request body.
+
+// initialize session variables
+app.get("/welcome", (req, res) => {
+ res.json({ status: "success", message: "Welcome!" });
+});
+app.use(
+ session({
+ secret: process.env.SESSION_SECRET,
+ saveUninitialized: false,
+ resave: false,
+ })
+);
+
+app.use(
+ bodyParser.urlencoded({
+ extended: true,
+ })
+);
+app.use(async function (req, res, next) {
+ res.locals.user = req.session.user;
+
+ if (res.locals.user) {
+ try {
+ res.locals.fav_teams = await getFavoriteTeamsForUser(
+ res.locals.user.userid
+ );
+ } catch (error) {
+ console.error("Error fetching favorite teams:", error);
+ }
+ }
+
+ next();
+});
+
+// Serve static files from the 'public' directory
+app.use(express.static(path.join(__dirname, "/../public/assets")));
+app.use(express.static(path.join(__dirname, "/")));
+
+// *****************************************************
+//
+// *****************************************************
+
+// Middleware to automatically update live scoreboard
+const fetchMatchesData = require("./middleware/navigation-bar/current-match-information");
+app.use(fetchMatchesData);
+
+//Middleware to automatically update in-game time abbreviations
+
+const convert_time = require("./middleware/navigation-bar/convert-time");
+app.use(convert_time);
+
+// Leagues Page Middleware
+
+const fetchLeaguesData = require("./middleware/leagues-page/get-current-league-information");
+const fetchLeagueScorerData = require("./middleware/leagues-page/get-current-league-top-scorers");
+
+app.get(
+ "/league/:leagueID",
+ [fetchLeaguesData, fetchLeagueScorerData],
+ (req, res) => {
+ // Render the Handlebars view with league data
+ res.render("pages/leagues-page", {
+ leagueID: req.params.leagueID,
+ leagues: res.locals.leagues,
+ scorers: res.locals.topScorers, // Assuming fetchLeagueScorerData sets the data in res.locals.scorers
+ });
+ }
+);
+
+// Clubs Page Middleware
+
+const fetchClubsData = require("./middleware/clubs-page/get-current-club-information");
+
+app.get("/club/:clubID", [fetchClubsData], (req, res) => {
+ // Render the Handlebars view with league data
+
+ var isFav = false;
+ var fav_teams = res.locals.fav_teams;
+ if (res.locals.user && fav_teams) {
+ const isTeamIDInFavTeams = fav_teams.some((team) => {
+ const teamIdInt = parseInt(team.teamid);
+ const clubIdInt = parseInt(req.params.clubID);
+ console.log("Checking team:", teamIdInt);
+ console.log("equal to", clubIdInt);
+ return teamIdInt === clubIdInt;
+ });
+ if (isTeamIDInFavTeams) {
+ isFav = true;
+ }
+ }
+ res.render("pages/clubs-page", {
+ isFav: isFav,
+ clubID: req.params.clubID,
+ clubs: res.locals.club,
+ });
+});
+
+// *****************************************************
+//
+// *****************************************************
+
+/************************
+ Login Page Routes
+*************************/
+
+// Redirect to the /login endpoint
+app.get("/", (req, res) => {
+ res.redirect("/home");
+});
+
+// Render login page for /login route
+app.get("/login", (req, res) => {
+ res.render("/");
+});
+
+// Trigger login form to check database for matching username and password
+app.post("/login", async (req, res) => {
+ try {
+ // Check if username exists in DB
+ const user = await db.oneOrNone(
+ "SELECT * FROM users WHERE username = $1",
+ req.body.username
+ );
+
+ if (!user) {
+ // Redirect user to login screen if no user is found with the provided username
+ return res.redirect("/register");
+ }
+
+ // Check if password from request matches with password in DB
+ const match = await bcrypt.compare(req.body.password, user.password);
+
+ // Check if match returns no data
+ if (!match) {
+ // Render the login page with the message parameter
+ return res.render("/", { message: "Password does not match" });
+ } else {
+ // Save user information in the session variable
+ req.session.user = user;
+ req.session.save();
+
+ // Redirect user to the home page
+ res.redirect("/");
+ }
+ } catch (error) {
+ // Direct user to login screen if no user is found with matching password
+ res.redirect("/register");
+ }
+});
+
+/************************
+ Registration Page Routes
+*************************/
+
+// Render registration page for /register route
+app.get("/register", (req, res) => {
+ res.redirect("/");
+});
+
+// Trigger Registration Form to Post
+app.post("/register", async (req, res) => {
+ try {
+ if (!req.body.username || !req.body.password) {
+ // If username or password is missing, respond with status 400 and an error message
+ return res
+ .status(400)
+ .json({ status: "error", message: "Invalid input" });
+ }
+
+ // Check if the username already exists in the database
+ const existingUser = await db.oneOrNone(
+ "SELECT * FROM users WHERE username = $1",
+ req.body.username
+ );
+ if (existingUser) {
+ // If a user with the same username already exists, respond with status 409 and an error message
+ return res
+ .status(409)
+ .json({ status: "error", message: "Username already exists" });
+ }
+
+ // Hash the password using bcrypt library
+ const hash = await bcrypt.hash(req.body.password, 10);
+
+ // Insert username and hashed password into the 'users' table
+ await db.none("INSERT INTO users (username, password) VALUES ($1, $2)", [
+ req.body.username,
+ hash,
+ ]);
+ const user = await db.oneOrNone(
+ "SELECT * FROM users WHERE username = $1",
+ req.body.username
+ );
+ req.session.user = user;
+ req.session.save();
+ // Redirect user to the home page
+ res.redirect("/home");
+ } catch (error) {
+ // If an error occurs during registration, respond with status 500 and an error message
+ res.status(500).json({
+ status: "error",
+ message: "An error occurred during registration",
+ });
+ }
+});
+
+/************************
+ Home Page Routes
+*************************/
+
+app.get("/home", (req, res) => {
+ const loggedIn = req.session.user ? true : false;
+ res.render("pages/home");
+});
+
+app.get("/logout", (req, res) => {
+ req.session.destroy((err) => {
+ if (err) {
+ console.error("Error destroying session:", err);
+ res.status(500).send("Internal Server Error");
+ } else {
+ // Redirect to the same page after destroying the session
+ res.redirect("/"); // You can change '/' to the desired page if it's not the home page
+ }
+ });
+});
+
+/************************
+ League Page Routes
+*************************/
+
+// Import and call generateLeagueRoutes function
+const generateLeagueRoutes = require("./routes/league-pages/generate-league-routes");
+generateLeagueRoutes(app);
+
+/************************
+ Club Page Routes
+*************************/
+
+// Import and call generateLeagueRoutes function
+const generateClubRoutes = require("./routes/club-pages/generate-club-routes");
+generateClubRoutes(app);
+
+/************************
+ Favorite Team Database
+*************************/
+
+// Function to add a new row to the FavoriteTeams table
+// database configuration
+
+app.post("/favteam/add", async (req, res, next) => {
+ try {
+ const { userID, teamID, teamName, teamLogo } = req.body;
+
+ // Check if the user is logged in
+ if (!req.session.user) {
+ return res
+ .status(400)
+ .json({ message: "Login or register to add a favorite team." });
+ }
+
+ // Insert the new favorite team into the database
+ const query = {
+ text: "INSERT INTO FavoriteTeams (UserID, TeamID, TeamName, TeamLogo) VALUES ($1, $2, $3, $4)",
+ values: [userID, teamID, teamName, teamLogo],
+ };
+
+ await db.none(query);
+ console.log("New favorite team added successfully.");
+ return res
+ .status(200)
+ .json({ message: "New favorite team added successfully." });
+ } catch (error) {
+ console.error("Error adding favorite team:", error);
+ return res.status(500).json({ error: "Error adding favorite team" });
+ }
+});
+
+app.post("/favteam/remove", async (req, res) => {
+ try {
+ const { userID, teamID } = req.body;
+
+ // Check if the team exists for the user
+ const existingTeam = await db.oneOrNone(
+ "SELECT * FROM FavoriteTeams WHERE UserID = $1 AND TeamID = $2",
+ [userID, teamID]
+ );
+
+ // If the team does not exist for the user, return a 404 error
+ if (!existingTeam) {
+ return res
+ .status(404)
+ .json({ message: "This team is not in your favorites." });
+ }
+
+ // Remove the favorite team from the database
+ await db.none(
+ "DELETE FROM FavoriteTeams WHERE UserID = $1 AND TeamID = $2",
+ [userID, teamID]
+ );
+
+ console.log("Favorite team removed successfully.");
+ return res
+ .status(200)
+ .json({ message: "Favorite team removed successfully." });
+ } catch (error) {
+ console.error("Error removing favorite team:", error);
+
+ // If the error is a database error, return a 500 error
+ if (error instanceof QueryResultError) {
+ return res.status(500).json({
+ error: "Database error occurred while removing favorite team",
+ });
+ }
+
+ // If the error is a generic error, return a 400 error
+ return res
+ .status(400)
+ .json({ error: "Error occurred while removing favorite team" });
+ }
+});
+
+async function getFavoriteTeamsForUser(userId) {
+ try {
+ // Execute the SQL query
+ const favoriteTeams = await db.any(
+ `
+ SELECT * FROM FavoriteTeams
+ WHERE UserID = $1;
+ `,
+ userId
+ );
+ `a`;
+
+ // Return the result
+ return favoriteTeams;
+ } catch (error) {
+ console.error("Error fetching favorite teams:", error);
+ throw error; // Rethrow the error for handling at a higher level
+ }
+}
+
+// *****************************************************
+//
+// *****************************************************
+// starting the server and keeping the connection open to listen for more requests
+module.exports = app.listen(3000);
+console.log("Server is listening on port 3000");
diff --git a/public/init_data/create.sql b/src/init_data/create.sql
similarity index 100%
rename from public/init_data/create.sql
rename to src/init_data/create.sql
diff --git a/src/assets/middleware/clubs-page/get-current-club-information.js b/src/middleware/clubs-page/get-current-club-information.js
similarity index 100%
rename from src/assets/middleware/clubs-page/get-current-club-information.js
rename to src/middleware/clubs-page/get-current-club-information.js
diff --git a/src/assets/middleware/leagues-page/get-current-league-information.js b/src/middleware/leagues-page/get-current-league-information.js
similarity index 100%
rename from src/assets/middleware/leagues-page/get-current-league-information.js
rename to src/middleware/leagues-page/get-current-league-information.js
diff --git a/src/assets/middleware/leagues-page/get-current-league-top-scorers.js b/src/middleware/leagues-page/get-current-league-top-scorers.js
similarity index 100%
rename from src/assets/middleware/leagues-page/get-current-league-top-scorers.js
rename to src/middleware/leagues-page/get-current-league-top-scorers.js
diff --git a/src/assets/middleware/navigation-bar/convert-time.js b/src/middleware/navigation-bar/convert-time.js
similarity index 100%
rename from src/assets/middleware/navigation-bar/convert-time.js
rename to src/middleware/navigation-bar/convert-time.js
diff --git a/src/assets/middleware/navigation-bar/current-match-information.js b/src/middleware/navigation-bar/current-match-information.js
similarity index 100%
rename from src/assets/middleware/navigation-bar/current-match-information.js
rename to src/middleware/navigation-bar/current-match-information.js
diff --git a/src/assets/routes/club-pages/generate-club-routes.js b/src/routes/club-pages/generate-club-routes.js
similarity index 100%
rename from src/assets/routes/club-pages/generate-club-routes.js
rename to src/routes/club-pages/generate-club-routes.js
diff --git a/src/assets/routes/club-pages/redirect-to-club-url.js b/src/routes/club-pages/redirect-to-club-url.js
similarity index 100%
rename from src/assets/routes/club-pages/redirect-to-club-url.js
rename to src/routes/club-pages/redirect-to-club-url.js
diff --git a/src/assets/routes/league-pages/generate-league-routes.js b/src/routes/league-pages/generate-league-routes.js
similarity index 100%
rename from src/assets/routes/league-pages/generate-league-routes.js
rename to src/routes/league-pages/generate-league-routes.js
diff --git a/src/assets/routes/league-pages/get-team-names-for-registry.js b/src/routes/league-pages/get-team-names-for-registry.js
similarity index 100%
rename from src/assets/routes/league-pages/get-team-names-for-registry.js
rename to src/routes/league-pages/get-team-names-for-registry.js
diff --git a/src/assets/routes/league-pages/redirect-to-league-url.js b/src/routes/league-pages/redirect-to-league-url.js
similarity index 100%
rename from src/assets/routes/league-pages/redirect-to-league-url.js
rename to src/routes/league-pages/redirect-to-league-url.js
diff --git a/src/views/partials/footer.hbs b/src/views/partials/footer.hbs
deleted file mode 100644
index 6368556..0000000
--- a/src/views/partials/footer.hbs
+++ /dev/null
@@ -1,21 +0,0 @@
-
\ No newline at end of file
diff --git a/test/server.spec.js b/test/server.spec.js
index 32df53b..de8a31e 100644
--- a/test/server.spec.js
+++ b/test/server.spec.js
@@ -1,118 +1,119 @@
-
// ********************** Initialize server **********************************
-const server = require('../client/src/index.js'); //TODO: Make sure the path to your index.js is correctly added
+const server = require("../src/index.js"); //TODO: Make sure the path to your index.js is correctly added
// ********************** Import Libraries ***********************************
-const chai = require('chai'); // Chai HTTP provides an interface for live integration testing of the API's.
-const chaiHttp = require('chai-http');
+const chai = require("chai"); // Chai HTTP provides an interface for live integration testing of the API's.
+const chaiHttp = require("chai-http");
chai.should();
chai.use(chaiHttp);
-const {assert, expect} = chai;
+const { assert, expect } = chai;
// ********************** DEFAULT WELCOME TESTCASE ****************************
-describe('Server!', () => {
- // Sample test case given to test / endpoint.
- it('Returns the default welcome message', done => {
- chai
- .request(server)
- .get('/welcome')
- .end((err, res) => {
- expect(res).to.have.status(200);
- expect(res.body.status).to.equals('success');
- assert.strictEqual(res.body.message, 'Welcome!');
- done();
- });
- });
+describe("Server!", () => {
+ // Sample test case given to test / endpoint.
+ it("Returns the default welcome message", (done) => {
+ chai
+ .request(server)
+ .get("/welcome")
+ .end((err, res) => {
+ expect(res).to.have.status(200);
+ expect(res.body.status).to.equals("success");
+ assert.strictEqual(res.body.message, "Welcome!");
+ done();
+ });
+ });
});
// *********************** TODO: WRITE 2 UNIT TESTCASES **************************
// ********************************************************************************
- describe('Testing Add User API', () => {
- it('positive: /register', done => {
- // Define mock user data
- const userData = {
- username: 'Test User',
- password: '123456'
- };
- // Make a POST request to /register with mock user data
- chai.request(server)
- .post('/register')
- .send(userData)
- .end((err, res) => {
- // Assertions
- expect(res).to.have.status(200);
- done();
- });
- });
- });
+describe("Testing Add User API", () => {
+ it("positive: /register", (done) => {
+ // Define mock user data
+ const userData = {
+ username: "Test User",
+ password: "123456",
+ };
+ // Make a POST request to /register with mock user data
+ chai
+ .request(server)
+ .post("/register")
+ .send(userData)
+ .end((err, res) => {
+ // Assertions
+ expect(res).to.have.status(200);
+ done();
+ });
+ });
+});
- //We are checking POST /add_user API by passing the user info in in incorrect manner (name cannot be an integer). This test case should pass and return a status 400 along with a "Invalid input" message.
+//We are checking POST /add_user API by passing the user info in in incorrect manner (name cannot be an integer). This test case should pass and return a status 400 along with a "Invalid input" message.
- describe('Testing Add User API', () => {
- it('Negative: /register. Checking invalid name', done => {
- chai.request(server)
- .post('/register')
- .send({ Username: 10, Password: '2020-02-20'})
- .end((err, res) => {
- // Assertions
- expect(res).to.have.status(400);
- expect(res.body.message).to.equals('Invalid input');
- done();
- });
- });
- });
+describe("Testing Add User API", () => {
+ it("Negative: /register. Checking invalid name", (done) => {
+ chai
+ .request(server)
+ .post("/register")
+ .send({ Username: 10, Password: "2020-02-20" })
+ .end((err, res) => {
+ // Assertions
+ expect(res).to.have.status(400);
+ expect(res.body.message).to.equals("Invalid input");
+ done();
+ });
+ });
+});
- describe('Testing Render', () => {
- // Sample test case given to test /test endpoint.
- it('test "/login" route should render with an html response', done => {
- chai
- .request(server)
- .get('/login') // for reference, see lab 8's login route (/login) which renders home.hbs
- .end((err, res) => {
- res.should.have.status(200); // Expecting a success status code
- res.should.be.html; // Expecting a HTML response
- done();
- });
- });
- });
- var cookies;
+describe("Testing Render", () => {
+ // Sample test case given to test /test endpoint.
+ it('test "/login" route should render with an html response', (done) => {
+ chai
+ .request(server)
+ .get("/login") // for reference, see lab 8's login route (/login) which renders home.hbs
+ .end((err, res) => {
+ res.should.have.status(200); // Expecting a success status code
+ res.should.be.html; // Expecting a HTML response
+ done();
+ });
+ });
+});
+var cookies;
- describe('Login', () => {
- // Sample test case given to test /login endpoint.
- it('Returns the default welcome message', done => {
- chai
- .request(server)
- .get('/login')
- .end((err, res) => {
- if (res.headers['set-cookie']) {
- // Save the cookies
- cookies = res.headers['set-cookie'].pop().split(';')[0];
- } else {
- cookies = null;
- }
- done();
- });
- });
- });
- describe('Home', () => {
- // Sample test case given to test /login endpoint.
- it('Returns the default welcome message', done => {
- chai
- .request(server)
- .get('/home')
- .end((err, res) => {
- if (res.headers['set-cookie']) {
- // Save the cookies
- cookies = res.headers['set-cookie'].pop().split(';')[0];
- } else {
- cookies = null;
- }
- done();
- });
- });
- });
+describe("Login", () => {
+ // Sample test case given to test /login endpoint.
+ it("Returns the default welcome message", (done) => {
+ chai
+ .request(server)
+ .get("/login")
+ .end((err, res) => {
+ if (res.headers["set-cookie"]) {
+ // Save the cookies
+ cookies = res.headers["set-cookie"].pop().split(";")[0];
+ } else {
+ cookies = null;
+ }
+ done();
+ });
+ });
+});
+describe("Home", () => {
+ // Sample test case given to test /login endpoint.
+ it("Returns the default welcome message", (done) => {
+ chai
+ .request(server)
+ .get("/home")
+ .end((err, res) => {
+ if (res.headers["set-cookie"]) {
+ // Save the cookies
+ cookies = res.headers["set-cookie"].pop().split(";")[0];
+ } else {
+ cookies = null;
+ }
+ done();
+ });
+ });
+});