diff --git a/ProjectSourceCode/docker-compose.yaml b/ProjectSourceCode/docker-compose.yaml index eca8a77..ba88fc4 100644 --- a/ProjectSourceCode/docker-compose.yaml +++ b/ProjectSourceCode/docker-compose.yaml @@ -33,6 +33,6 @@ services: volumes: - ../ProjectSourceCode:/home/node/app # Mount ProjectSourceCode directory - ../ProjectSourceCode/node_modules:/home/node/app/node_modules # Mount node_modules directory - command: 'npm run testandrun' + command: 'npm run start' volumes: group-project: \ No newline at end of file diff --git a/ProjectSourceCode/src/index.js b/ProjectSourceCode/src/index.js index 825ddc7..7894c9b 100644 --- a/ProjectSourceCode/src/index.js +++ b/ProjectSourceCode/src/index.js @@ -73,6 +73,19 @@ app.use( 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, 'resources'))); @@ -108,11 +121,27 @@ app.get('/league/:leagueID', [fetchLeaguesData, fetchLeagueScorerData], (req, re // Clubs Page Middleware const fetchClubsData = require('./resources/middleware/clubs-page/get-current-club-information'); -const fetchClubTopScorers = require('./resources/middleware/clubs-page/get-current-club-top-scorers'); -app.get('/club/:clubID', [fetchClubsData, fetchClubTopScorers], (req, res) => { +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 }); @@ -155,13 +184,14 @@ app.post('/login', async (req, res) => { // Render the login page with the message parameter return res.render('pages/home', { 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('/home'); + } } catch (error) { // Direct user to login screen if no user is found with matching password res.redirect('/register'); @@ -179,23 +209,33 @@ app.get('/register', (req, res) => { // 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' }); - } - // Hash the password using bcrypt library - const hash = await bcrypt.hash(req.body.password, 10); + 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' }); + } - // Insert username and hashed password into the 'users' table - await db.none('INSERT INTO users (username, password) VALUES ($1, $2)', [req.body.username, hash]); + // 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' }); + } - // Redirect user to the login page after successful registration - res.status(200).json({ status: 'success', message: 'Registration successful' }); - } 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' }); - } + // 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' }); + } }); /************************ @@ -203,7 +243,18 @@ app.post('/register', async (req, res) => { *************************/ app.get('/home', (req, res) => { - res.render('pages/home'); + 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 { + res.render('pages/home', { message: 'Logged out Successfully' }); + } + }); }); /************************ @@ -222,6 +273,89 @@ generateLeagueRoutes(app); const generateClubRoutes = require('./resources/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 + } +} + + // ***************************************************** // // ***************************************************** diff --git a/ProjectSourceCode/src/init_data/create.sql b/ProjectSourceCode/src/init_data/create.sql index de888d7..29ca3ae 100644 --- a/ProjectSourceCode/src/init_data/create.sql +++ b/ProjectSourceCode/src/init_data/create.sql @@ -5,8 +5,8 @@ CREATE TABLE users ( ); CREATE TABLE FavoriteTeams ( - UserID INT REFERENCES users(UserID) ON DELETE CASCADE ON UPDATE CASCADE, TeamID INT, + UserID INT REFERENCES users(UserID), TeamName VARCHAR(50), - PRIMARY KEY (UserID, TeamID) + TeamLogo VARCHAR(100) ); diff --git a/ProjectSourceCode/src/init_data/insert.sql b/ProjectSourceCode/src/init_data/insert.sql index e69de29..4669a7a 100644 --- a/ProjectSourceCode/src/init_data/insert.sql +++ b/ProjectSourceCode/src/init_data/insert.sql @@ -0,0 +1,4 @@ +SELECT FavoriteTeams.* +FROM FavoriteTeams +JOIN users ON FavoriteTeams.UserID = users.UserID +WHERE users.UserID = ; \ No newline at end of file diff --git a/ProjectSourceCode/src/resources/css/navigation-bar/login.css b/ProjectSourceCode/src/resources/css/navigation-bar/login.css index 5539061..885cda6 100644 --- a/ProjectSourceCode/src/resources/css/navigation-bar/login.css +++ b/ProjectSourceCode/src/resources/css/navigation-bar/login.css @@ -30,6 +30,27 @@ border-color: darkred; color: white; } +/* CSS for the account info card */ +.account-info-card { + border: 1px solid #ccc; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 20px; + width: 300px; + } + .info-container { + margin-bottom: 10px; + } + + #username { + font-size: 18px; + font-weight: bold; + color: #333; + } + #teamlogo{ + width: 23px; + height: 23px; + } \ No newline at end of file diff --git a/ProjectSourceCode/src/resources/js/account-info/favorite-team.js b/ProjectSourceCode/src/resources/js/account-info/favorite-team.js new file mode 100644 index 0000000..e69de29 diff --git a/ProjectSourceCode/src/resources/js/club-page/favorite-button.js b/ProjectSourceCode/src/resources/js/club-page/favorite-button.js index 3969016..5697a60 100644 --- a/ProjectSourceCode/src/resources/js/club-page/favorite-button.js +++ b/ProjectSourceCode/src/resources/js/club-page/favorite-button.js @@ -2,7 +2,67 @@ document.addEventListener("DOMContentLoaded", function() { var favoriteButton = document.getElementById("club-favorite-button"); if (favoriteButton) { favoriteButton.addEventListener("click", function() { - favoriteButton.src = "/img/club-page/favorited.png"; + var userID = document.getElementById("userID").value; + var teamID = document.getElementById("teamID").value; + var teamName = document.getElementById("teamName").value; + var teamLogo = document.getElementById("teamLogo").value; + + if (favoriteButton.src.includes("/favorited.png")) { + removeFavoriteTeam(userID, teamID) + } + else { + addFavoriteTeam(userID, teamID, teamName, teamLogo) + } }); } }); + +async function addFavoriteTeam(userID, teamID, teamName, teamLogo){ + try { + const response = await fetch('/favteam/add', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userID: userID, + teamID: teamID, + teamName: teamName, + teamLogo: teamLogo + }) + }); + + if (!response.ok) { + throw new Error('Failed to add favorite team'); + } + //Changes button if favorite team is added// + console.log('New favorite team added successfully.'); + var favoriteButton = document.getElementById("club-favorite-button"); + favoriteButton.src = "/img/club-page/favorited.png"; + + } catch (error) { + console.error('Error adding favorite team:', error); + + } +} +async function removeFavoriteTeam(userID, teamID) { + try { + const response = await fetch('/favteam/remove', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userID: userID, + teamID: teamID + }) + }); + console.log('Favorite team removed successfully.'); + //Change button source// + var favoriteButton = document.getElementById("club-favorite-button"); + favoriteButton.src = "/img/club-page/unfavorited.png"; + + } catch (error) { + console.error('Error removing favorite team:', error); + } +} diff --git a/ProjectSourceCode/src/resources/js/registration/league-and-team-select.js b/ProjectSourceCode/src/resources/js/registration/league-and-team-select.js index 2b9304c..71113fe 100644 --- a/ProjectSourceCode/src/resources/js/registration/league-and-team-select.js +++ b/ProjectSourceCode/src/resources/js/registration/league-and-team-select.js @@ -37,4 +37,5 @@ document.addEventListener("DOMContentLoaded", function() { selectElement.add(option); } }); + \ No newline at end of file diff --git a/ProjectSourceCode/src/resources/middleware/clubs-page/get-current-club-information.js b/ProjectSourceCode/src/resources/middleware/clubs-page/get-current-club-information.js index c28ec43..4e2d8ca 100644 --- a/ProjectSourceCode/src/resources/middleware/clubs-page/get-current-club-information.js +++ b/ProjectSourceCode/src/resources/middleware/clubs-page/get-current-club-information.js @@ -15,7 +15,7 @@ const fetchClubsData = async (req, res, next) => { // Extract relevant data from the API response const clubData = response.data; - + // res.locals.user = users // Attach the data to res.locals res.locals.club = { area: { @@ -63,7 +63,6 @@ const fetchClubsData = async (req, res, next) => { staff: clubData.staff, lastUpdated: clubData.lastUpdated }; - next(); } catch (error) { console.error('Error fetching clubs data:', error); diff --git a/ProjectSourceCode/src/resources/middleware/clubs-page/get-current-club-top-scorers.js b/ProjectSourceCode/src/resources/middleware/clubs-page/get-current-club-top-scorers.js deleted file mode 100644 index 5c2ea41..0000000 --- a/ProjectSourceCode/src/resources/middleware/clubs-page/get-current-club-top-scorers.js +++ /dev/null @@ -1,36 +0,0 @@ -const axios = require('axios'); - -// Middleware function to fetch leagues data -const fetchLeagueScorerData = async (req, res, next) => { - try { - // Extract league ID from the URL - const leagueID = req.params.leagueID; - - // Make GET request to the API endpoint using the league ID - const response = await axios.get(`http://api.football-data.org/v4/teams/${teamID}/scorers?limit=5`, { - headers: { - 'X-Auth-Token': '0aa1ed31245d4a36b1ef5a79150324b3', // Add your API key here - }, - }); - - // Extract relevant data from the API response - const scorerData = response.data; - - // Attach the data to res.locals - res.locals.topScorers = { - scorers: scorerData.scorers.map(player => ({ - playerID: scorer.player.id, - playerName: scorer.player.name, - goals: scorer.numberOfGoals, - })) - }; - - next(); - } catch (error) { - console.error('Error fetching leagues data:', error); - res.locals.topScorers = null; // Set to null if there's an error - next(); // Call next middleware or route handler - } -}; - -module.exports = fetchLeagueScorerData; diff --git a/ProjectSourceCode/src/resources/middleware/leagues-page/get-current-league-information.js b/ProjectSourceCode/src/resources/middleware/leagues-page/get-current-league-information.js index 09eaf0f..4363e25 100644 --- a/ProjectSourceCode/src/resources/middleware/leagues-page/get-current-league-information.js +++ b/ProjectSourceCode/src/resources/middleware/leagues-page/get-current-league-information.js @@ -17,6 +17,8 @@ const fetchLeaguesData = async (req, res, next) => { const leagueData = response.data; // Attach the data to res.locals + //res.locals.username = req.session.user.username; + console.log(req.session.user) res.locals.league = { area: { league_flag: leagueData.area.flag, @@ -39,7 +41,7 @@ const fetchLeaguesData = async (req, res, next) => { draws: team.draw, goal_difference: team.goalDifference, points: team.points - })) + })), }; next(); } catch (error) { diff --git a/ProjectSourceCode/src/resources/routes/club-pages/generate-club-routes.js b/ProjectSourceCode/src/resources/routes/club-pages/generate-club-routes.js index c3ea81c..d813205 100644 --- a/ProjectSourceCode/src/resources/routes/club-pages/generate-club-routes.js +++ b/ProjectSourceCode/src/resources/routes/club-pages/generate-club-routes.js @@ -9,9 +9,8 @@ module.exports = function generateClubRoutes(app) { app.get('/club/:clubID', (req, res) => { // Extract the league name from the URL parameters const clubID = req.params.clubID; - // Render the league page template using Handlebars - res.render('pages/club-page', { clubID: clubID }); + res.render('pages/club-page', { clubID: clubID, }); }); }; diff --git a/ProjectSourceCode/src/resources/routes/league-pages/generate-league-routes.js b/ProjectSourceCode/src/resources/routes/league-pages/generate-league-routes.js index 8dca5e8..ae7a9ff 100644 --- a/ProjectSourceCode/src/resources/routes/league-pages/generate-league-routes.js +++ b/ProjectSourceCode/src/resources/routes/league-pages/generate-league-routes.js @@ -9,9 +9,8 @@ module.exports = function generateLeagueRoutes(app) { app.get('/league/:leagueID', (req, res) => { // Extract the league name from the URL parameters const leagueID = req.params.leagueID; - // Render the league page template using Handlebars - res.render('pages/leagues-page', { leagueID: leagueID }); + res.render('pages/leagues-page', { leagueID: leagueID, user: user}); }); }; diff --git a/ProjectSourceCode/src/resources/routes/league-pages/get-team-names-for-registry.js b/ProjectSourceCode/src/resources/routes/league-pages/get-team-names-for-registry.js index 9f61dac..920bf33 100644 --- a/ProjectSourceCode/src/resources/routes/league-pages/get-team-names-for-registry.js +++ b/ProjectSourceCode/src/resources/routes/league-pages/get-team-names-for-registry.js @@ -9,7 +9,7 @@ const fetchTeamNames = async (selectedLeague) => { 'X-Auth-Token': '0aa1ed31245d4a36b1ef5a79150324b3', }, }); - + const teams = response.data.teams.map(team => team.name); return teams; } catch (error) { diff --git a/ProjectSourceCode/src/views/pages/clubs-page.hbs b/ProjectSourceCode/src/views/pages/clubs-page.hbs index fb35c60..ebb3aef 100644 --- a/ProjectSourceCode/src/views/pages/clubs-page.hbs +++ b/ProjectSourceCode/src/views/pages/clubs-page.hbs @@ -5,7 +5,21 @@

{{club.name}}

{{club.name}} Flag - Favorite Button + {{#if user}} + {{#if isFav}} + Favorite Button + {{else}} + Favorite Button + {{/if}} + {{else}} + Please sign in + {{/if}} + + + + + +
diff --git a/ProjectSourceCode/src/views/pages/home.hbs b/ProjectSourceCode/src/views/pages/home.hbs index 8fe6d22..23f8549 100644 --- a/ProjectSourceCode/src/views/pages/home.hbs +++ b/ProjectSourceCode/src/views/pages/home.hbs @@ -4,17 +4,17 @@ diff --git a/ProjectSourceCode/src/views/partials/navigation-bar/accountinfo.hbs b/ProjectSourceCode/src/views/partials/navigation-bar/accountinfo.hbs new file mode 100644 index 0000000..95a2e3d --- /dev/null +++ b/ProjectSourceCode/src/views/partials/navigation-bar/accountinfo.hbs @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/ProjectSourceCode/src/views/partials/navigation-bar/nav.hbs b/ProjectSourceCode/src/views/partials/navigation-bar/nav.hbs index 38357c1..66404ef 100644 --- a/ProjectSourceCode/src/views/partials/navigation-bar/nav.hbs +++ b/ProjectSourceCode/src/views/partials/navigation-bar/nav.hbs @@ -48,10 +48,12 @@
- +{{#if user.username}} +
+ {{> navigation-bar/accountinfo}} +
+{{else}}
{{> navigation-bar/login}}
- - - +{{/if}}