From f5e8fa36c3555fd8b1fe143d5e69d6374656868b Mon Sep 17 00:00:00 2001 From: Brandon Yu Date: Sat, 27 Dec 2025 22:30:49 -0800 Subject: [PATCH 1/6] merged activities-tracking with new react chess client --- chessServer/package-lock.json | 2 +- chessServer/src/managers/EventHandlers.js | 56 +++++++++++++++-- chessServer/src/managers/GameManager.js | 63 ++++++++++++++++--- middlewareNode/src/routes/activities.js | 60 +++++++----------- middlewareNode/src/routes/badges.js | 2 +- middlewareNode/src/server.js | 2 +- .../src/core/environments/environment.js | 13 ++++ .../src/core/environments/environment.prod.js | 12 ++++ react-ystemandchess/src/core/types/chess.d.ts | 3 + .../src/core/utils/activityNames.ts | 8 ++- .../src/environments/environment.js | 2 +- .../lesson-overlay/hooks/useChessSocket.ts | 3 + .../features/puzzles/puzzles-page/Puzzles.tsx | 3 +- .../Modals/ActivitiesModal.tsx | 21 ++++--- 14 files changed, 185 insertions(+), 65 deletions(-) create mode 100644 react-ystemandchess/src/core/environments/environment.js create mode 100644 react-ystemandchess/src/core/environments/environment.prod.js diff --git a/chessServer/package-lock.json b/chessServer/package-lock.json index f56886b0..adad4171 100644 --- a/chessServer/package-lock.json +++ b/chessServer/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "chess.js": "^1.0.0-beta.8", + "chess.js": "^1.0.0z", "dotenv": "^8.6.0", "express": "^4.21.0", "jest": "^29.7.0", diff --git a/chessServer/src/managers/EventHandlers.js b/chessServer/src/managers/EventHandlers.js index 95093b7f..16ac4aa8 100644 --- a/chessServer/src/managers/EventHandlers.js +++ b/chessServer/src/managers/EventHandlers.js @@ -45,12 +45,14 @@ const registerSocketHandlers = (socket, io) => { socket.on("newPuzzle", (msg) => { try { const parsed = JSON.parse(msg); + console.log('data',parsed, msg); // create the new puzzle gameManager.createOrJoinPuzzle({ student: parsed.student, mentor: parsed.mentor, role: parsed.role, - socketId: socket.id + socketId: socket.id, + credentials: parsed.credentials, }, io); } catch (err) { @@ -63,17 +65,59 @@ const registerSocketHandlers = (socket, io) => { * Handles player move request * Expected payload: { from, to } */ - socket.on("move", (msg) => { + socket.on("move", async (msg) => { try { - const { from, to } = JSON.parse(msg); - const result = gameManager.makeMove(socket.id, from, to); - gameManager.broadcastBoardState(result, io); + const { from, to, computerMove, username, credentials } = JSON.parse(msg); + const res = await gameManager.makeMove(socket.id, from, to); + const state = res.result; + gameManager.broadcastBoardState(res.result, io); + console.log('Move: ', res); + if(!computerMove) { + const activityEvents = res.activityEvents; + if (activityEvents && activityEvents.length > 0) { + const studentId = state.studentId; + const payload = { + activities: activityEvents, + lastMove: { from, to, san: state.move?.san } + }; + console.log('Payload', payload); + const studentSocket = io.sockets.sockets.get(studentId); + //console.log('student socket', studentSocket); + if (studentSocket) { + try { + console.log('route:', `${process.env.MIDDLEWARE_URL}/activities/${username}/activity`); + const response = await fetch(`${process.env.MIDDLEWARE_URL}/activities/${username}/activity`, { + method: "PUT", + headers: { + 'Content-Type': 'application/json', + 'Authentication' : `Bearer ${credentials}`, + }, + body: JSON.stringify({ + activityName: payload.activities[0].name, + }) + }); + console.log('response',response); + socket.emit("completeActivity"); + } catch (e) { + console.log('Error: ', e); + } + } + } + + } + + /* + + */ } catch (err) { socket.emit("error", err.message); - console.log("move error") + console.log('error thrown', err) } }); + socket.on("completeActivity", () => { + console.log('activity completed'); + }); /** * Handles undo move request diff --git a/chessServer/src/managers/GameManager.js b/chessServer/src/managers/GameManager.js index 32169f01..c926ba1b 100644 --- a/chessServer/src/managers/GameManager.js +++ b/chessServer/src/managers/GameManager.js @@ -73,7 +73,7 @@ class GameManager { * @param {Object} param0 - Contains student, mentor, role, socketId * @returns {Object} Game object, assigned color, and new game status */ - createOrJoinPuzzle({ student, mentor, role, socketId }, io) { + createOrJoinPuzzle({ student, mentor, role, socketId, credentials }, io) { let game = this.ongoingGames.find( (g) => g.student.username === student || g.mentor.username === mentor ); @@ -131,7 +131,8 @@ class GameManager { student: { username: student, id: role === "student" ? socketId : null, - color: studentColor + color: studentColor, + credentials: credentials, }, mentor: { username: mentor, @@ -170,8 +171,10 @@ class GameManager { } const board = game.boardState; - - const moveResult = board.move({ from: moveFrom, to: moveTo }); + const move = {from: moveFrom, to: moveTo}; + //console.log(move, typeof(move), typeof(move)==='object'); + const moveResult = board.move(move); + console.log(moveResult); if (!moveResult) { throw new Error("Invalid move!"); @@ -180,11 +183,53 @@ class GameManager { // Save board state game.pastStates.push(board.fen()) - return { - boardState: board.fen(), - move: moveResult, - studentId: game.student.id, - mentorId: game.mentor.id + const flags = moveResult.flags || ""; // e.g., 'c' capture, 'k'/'q' castle, 'e' en passant, 'p' promotion + const activityEvents = []; + + const captureMap = { + q: "captureQueen", + r: "captureRook", + n: "captureKnight", + b: "captureBishop", + p: "capturePawn" + }; + + // Capture (including en passant) + if (flags.includes("c") || flags.includes("e")) { + const capLetter = moveResult.captured; // 'q','r','n','b','p' + const name = capLetter ? captureMap[capLetter] : null; + if (name) { + activityEvents.push({ + name, + meta: { + from: moveResult.from, + to: moveResult.to, + san: moveResult.san + }, + at: Date.now() + }); + } + } + + // Castling + if (flags.includes("k") || flags.includes("q")) { + activityEvents.push({ + name: "performCastle", + meta: { san: moveResult.san }, + at: Date.now() + }); + } + //console.log(activityEvents); + //console.log('student info',game.student); + return { + result: { + boardState: board.fen(), + move: moveResult, + studentId: game.student.id, + mentorId: game.mentor.id, + studentUsername: game.student.username, + }, + activityEvents: activityEvents }; } diff --git a/middlewareNode/src/routes/activities.js b/middlewareNode/src/routes/activities.js index b085d8dd..9310a029 100644 --- a/middlewareNode/src/routes/activities.js +++ b/middlewareNode/src/routes/activities.js @@ -43,8 +43,8 @@ async function getUserId(db, username) { { username }, ); if(!currentUser) { - return res.status(404).json({error: "User not found!"}); - } + return; + } const userId = currentUser._id; return userId; } @@ -53,14 +53,14 @@ async function getUserId(db, username) { * GET /activities * Retrieves all daily activities for a user */ -router.get("/", async (req, res) => { +router.get("/:username", async (req, res) => { try { const db = await getDb(); const { username } = req.params; - if(!username) { - return res.status(401).json({error:'Authentication required'}); + const userId = await getUserId(db, username); + if(!userId) { + return res.status(404).json({error:'User not found'}); } - const userId = getUserId(db, username); const activities = db.collection("activities"); const userActivities = await activities.findOne( { userId }, { projection: {activities: 1, _id: 0}} @@ -73,14 +73,14 @@ router.get("/", async (req, res) => { } }) -router.get("/dates", async (req, res) => { +router.get("/:username/dates", async (req, res) => { try { const db = await getDb(); const { username } = req.params; - if(!username) { - return res.status(401).json({error: "User not found"}); + const userId = await getUserId(db, username); + if(!userId) { + return res.status(404).json({error: "User not found"}); } - const userId = getUserId(db, username); const activities = db.collection("activities"); const completedDates = await activities.findOne( { userId }, {projection: {_id: 0, completedDates: 1}} @@ -93,45 +93,33 @@ router.get("/dates", async (req, res) => { }); -router.put("/activity/:activityName", async (req, res) => { +router.put("/:username/activity", async (req, res) => { try { const db = await getDb(); - const { username, activityName } = req.params; - if(!username) { - return res.status(401).json({error:'Authentication required'}); + const { username } = req.params; + const { activityName } = req.body; + const userId = await getUserId(db, username); + if(!userId) { + return res.status(404).json({error:'User not found'}); } - const userId = getUserId(db, username); const activities = db.collection("activities"); + const activityIncomplete = await activities.findOne( + { userId, "activities.name": activityName }, + { activities: {$elemMatch: { name: activityName }}, _id:0}, + ); + if(activityIncomplete) { + console.log('incomplete activity: ', activityName); + } await activities.updateOne( { userId, "activities.name": activityName }, { $set: { "activities.$.completed": true } } ); - return res.status(200); + return res.status(200).json({message:'success'}); } catch (err) { console.error('Error updating activities: ', err); return res.status(500).json({error: 'Server error'}); } }) -router.put("/activity/check", async (req, res) => { - try { - const db = await getDb(); - const { username } = req.params; - const { moveData } = req.body; - if(!username) { - return res.status(401).json({error:'Authentication required'}); - } - const userId = getUserId(db, username); - const activities = db.collection("activities"); - const userActivities = await activities.findOne( - { userId }, { projection: {activities: 1, _id: 0}} - ); - //compare move data with activities - } - catch (err) { - console.error('Error checking move: ', err); - res.status(500).json({error: 'Server error'}); - } -}) module.exports = router; \ No newline at end of file diff --git a/middlewareNode/src/routes/badges.js b/middlewareNode/src/routes/badges.js index a1a691a3..0a2f2968 100644 --- a/middlewareNode/src/routes/badges.js +++ b/middlewareNode/src/routes/badges.js @@ -12,7 +12,7 @@ const express = require("express"); const { BADGE_CATALOG } = require("../badges/catalog"); -const { getEarned, awardIfEligible } = require("../Badges/service"); +const { getEarned, awardIfEligible } = require("../badges/service"); const router = express.Router(); diff --git a/middlewareNode/src/server.js b/middlewareNode/src/server.js index 71762b72..cec78345 100644 --- a/middlewareNode/src/server.js +++ b/middlewareNode/src/server.js @@ -43,7 +43,7 @@ app.use("/auth", require("./routes/auth")); app.use("/timeTracking", require("./routes/timeTracking")); app.use("/puzzles", require("./routes/puzzles")); app.use("/lessons", require("./routes/lessons")); -app.use("/activities/:username", require("./routes/activities")); +app.use("/activities", require("./routes/activities")); app.use('/streak', streakRoutes); app.use("/badges", require("./routes/badges")); diff --git a/react-ystemandchess/src/core/environments/environment.js b/react-ystemandchess/src/core/environments/environment.js new file mode 100644 index 00000000..db409461 --- /dev/null +++ b/react-ystemandchess/src/core/environments/environment.js @@ -0,0 +1,13 @@ +export const environment = { + production: false, + agora: { + appId: '6b7772f2a76f406192d8167460181be0', + }, + urls: { + middlewareURL: 'http://localhost:8000', + chessClientURL: 'http://localhost:8081', + stockFishURL: 'http://localhost:8080/stockfishserver/', + chessServer: 'http://localhost:3001/', + }, + productionType: 'development', // development/production +}; \ No newline at end of file diff --git a/react-ystemandchess/src/core/environments/environment.prod.js b/react-ystemandchess/src/core/environments/environment.prod.js new file mode 100644 index 00000000..f86e300f --- /dev/null +++ b/react-ystemandchess/src/core/environments/environment.prod.js @@ -0,0 +1,12 @@ +export const environment = { + production: false, + agora: { + appId: '6c368b93b82a4b3e9fb8e57da830f2a4', + }, + urls: { + middlewareURL: 'http://localhost/middleware/', + chessClientURL: 'http://localhost/chessclient/', + stockFishURL: 'http://localhost/stockfishserver/', + chessServer: 'http://localhost/chessserver/', + }, +}; \ No newline at end of file diff --git a/react-ystemandchess/src/core/types/chess.d.ts b/react-ystemandchess/src/core/types/chess.d.ts index f865a6ac..721e83c4 100644 --- a/react-ystemandchess/src/core/types/chess.d.ts +++ b/react-ystemandchess/src/core/types/chess.d.ts @@ -9,6 +9,9 @@ export interface Move { piece?: string; captured?: string; flags?: string; + computerMove?: boolean; + username?: string; + credentials?: string; } export interface GameConfig { diff --git a/react-ystemandchess/src/core/utils/activityNames.ts b/react-ystemandchess/src/core/utils/activityNames.ts index 7a33a064..1d6007c8 100644 --- a/react-ystemandchess/src/core/utils/activityNames.ts +++ b/react-ystemandchess/src/core/utils/activityNames.ts @@ -42,8 +42,12 @@ const activityNameMap: Record = { * @param {Array} names - Array of activity objects * @returns {Array} Array of display names for the activities */ -export const parseActivities = (names: Array): Array => { - const namesArray = names.map((activity) => activityNameMap[activity.name] || activity.name); +export const parseActivities = (names: Array): Array => { + const namesArray = names.map((activity) => ({ + name: activityNameMap[activity.name] || activity.name, + type: activity.type, + completed: activity.completed, + })); return namesArray; // TODO: Consider making API call to fetch display names dynamically } diff --git a/react-ystemandchess/src/environments/environment.js b/react-ystemandchess/src/environments/environment.js index 2a84a330..7a0e2b5a 100644 --- a/react-ystemandchess/src/environments/environment.js +++ b/react-ystemandchess/src/environments/environment.js @@ -10,4 +10,4 @@ export const environment = { chessServer: 'http://localhost:3001/', }, productionType: 'development', // development/production -}; \ No newline at end of file +}; diff --git a/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessSocket.ts b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessSocket.ts index b8b1ef23..ed247d17 100644 --- a/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessSocket.ts +++ b/react-ystemandchess/src/features/lessons/piece-lessons/lesson-overlay/hooks/useChessSocket.ts @@ -377,6 +377,9 @@ export const useChessSocket = ({ from: move.from, to: move.to, promotion: move.promotion, + computerMove: move.computerMove, + username: move.username, + credentials: move.credentials, }; console.log("Sending move:", data); socketRef.current?.emit("move", JSON.stringify(data)); diff --git a/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.tsx b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.tsx index 100fbf4f..443b59d7 100644 --- a/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.tsx +++ b/react-ystemandchess/src/features/puzzles/puzzles-page/Puzzles.tsx @@ -276,7 +276,8 @@ const Puzzles: React.FC = ({ if (newFen) { setCurrentFEN(newFen); } - + move.username = username; + move.credentials = cookies.login; socket.sendMove(move); socket.sendLastMove(move.from, move.to); diff --git a/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.tsx b/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.tsx index f7b26883..c16e6c51 100644 --- a/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.tsx +++ b/react-ystemandchess/src/features/student/student-profile/Modals/ActivitiesModal.tsx @@ -56,7 +56,6 @@ const ActivitiesModal = ({ onClose, username }: { onClose: () => void; username: 'Authorization': `Bearer ${cookies.login}`, 'Content-Type': 'application/json', }, - credentials: 'include' }) const json = await response.json(); const data = json.activities.activities; @@ -110,12 +109,20 @@ const ActivitiesModal = ({ onClose, username }: { onClose: () => void; username: {/* Stack of daily activity buttons. Activities are hard coded for now.*/}
- {activities.map((activity, idx) => ( - - ))} + {activities.map((activity, idx) => { + //Use activity.completed to change visual for complete or incomplete task + return ( + + //check completed status, display conditional, then complete task in puzzles to check + ) + } + + )}
From a38475f7e6b1a49d552da8b329d62abb5ab4bad8 Mon Sep 17 00:00:00 2001 From: Brandon Yu Date: Sat, 27 Dec 2025 22:56:19 -0800 Subject: [PATCH 2/6] imported users into passportjs --- middlewareNode/src/config/passport.js | 1 + 1 file changed, 1 insertion(+) diff --git a/middlewareNode/src/config/passport.js b/middlewareNode/src/config/passport.js index 6aebe6de..5554961d 100644 --- a/middlewareNode/src/config/passport.js +++ b/middlewareNode/src/config/passport.js @@ -3,6 +3,7 @@ const JwtStrategy = require("passport-jwt").Strategy; const ExtractJwt = require("passport-jwt").ExtractJwt; const passport = require("passport"); const config = require("config"); +const users = require("../models/users.js") /** * Serializes user for session storage From bb2856556012c6fb74b4c05574274e34cb101a70 Mon Sep 17 00:00:00 2001 From: Brandon Yu Date: Sat, 27 Dec 2025 23:00:10 -0800 Subject: [PATCH 3/6] merged new environment file --- react-ystemandchess/src/environments/environment.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/react-ystemandchess/src/environments/environment.js b/react-ystemandchess/src/environments/environment.js index 7a0e2b5a..86930d17 100644 --- a/react-ystemandchess/src/environments/environment.js +++ b/react-ystemandchess/src/environments/environment.js @@ -5,9 +5,8 @@ export const environment = { }, urls: { middlewareURL: 'http://localhost:8000', - chessClientURL: 'http://localhost', - stockFishURL: 'http://localhost:8080/stockfishserver/', - chessServer: 'http://localhost:3001/', + stockFishURL: 'http://localhost:8080', + chessServerURL: 'http://localhost:3001/', }, - productionType: 'development', // development/production + productionType: 'development', }; From 723047ecbdc3554be08863a540b4511b0a5d53c9 Mon Sep 17 00:00:00 2001 From: Brandon Yu Date: Sat, 27 Dec 2025 23:07:54 -0800 Subject: [PATCH 4/6] added checking for logged in user before making requests for activitie --- chessServer/src/managers/EventHandlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chessServer/src/managers/EventHandlers.js b/chessServer/src/managers/EventHandlers.js index 16ac4aa8..e2d1f19e 100644 --- a/chessServer/src/managers/EventHandlers.js +++ b/chessServer/src/managers/EventHandlers.js @@ -72,7 +72,7 @@ const registerSocketHandlers = (socket, io) => { const state = res.result; gameManager.broadcastBoardState(res.result, io); console.log('Move: ', res); - if(!computerMove) { + if(!computerMove && credentials) { const activityEvents = res.activityEvents; if (activityEvents && activityEvents.length > 0) { const studentId = state.studentId; From 7e6d6e3146e8673a0a692feccc03f522c55acf61 Mon Sep 17 00:00:00 2001 From: Brandon Yu Date: Mon, 29 Dec 2025 22:56:35 -0800 Subject: [PATCH 5/6] removed old environment files --- .../src/core/environments/environment.js | 13 ------------- .../src/core/environments/environment.prod.js | 12 ------------ 2 files changed, 25 deletions(-) delete mode 100644 react-ystemandchess/src/core/environments/environment.js delete mode 100644 react-ystemandchess/src/core/environments/environment.prod.js diff --git a/react-ystemandchess/src/core/environments/environment.js b/react-ystemandchess/src/core/environments/environment.js deleted file mode 100644 index db409461..00000000 --- a/react-ystemandchess/src/core/environments/environment.js +++ /dev/null @@ -1,13 +0,0 @@ -export const environment = { - production: false, - agora: { - appId: '6b7772f2a76f406192d8167460181be0', - }, - urls: { - middlewareURL: 'http://localhost:8000', - chessClientURL: 'http://localhost:8081', - stockFishURL: 'http://localhost:8080/stockfishserver/', - chessServer: 'http://localhost:3001/', - }, - productionType: 'development', // development/production -}; \ No newline at end of file diff --git a/react-ystemandchess/src/core/environments/environment.prod.js b/react-ystemandchess/src/core/environments/environment.prod.js deleted file mode 100644 index f86e300f..00000000 --- a/react-ystemandchess/src/core/environments/environment.prod.js +++ /dev/null @@ -1,12 +0,0 @@ -export const environment = { - production: false, - agora: { - appId: '6c368b93b82a4b3e9fb8e57da830f2a4', - }, - urls: { - middlewareURL: 'http://localhost/middleware/', - chessClientURL: 'http://localhost/chessclient/', - stockFishURL: 'http://localhost/stockfishserver/', - chessServer: 'http://localhost/chessserver/', - }, -}; \ No newline at end of file From 52af2b80a46a69cbe1dfda160ca7f82070e81284 Mon Sep 17 00:00:00 2001 From: Brandon Yu Date: Mon, 29 Dec 2025 23:06:08 -0800 Subject: [PATCH 6/6] updated GameManager expected output --- chessServer/src/tests/GameManager.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chessServer/src/tests/GameManager.test.js b/chessServer/src/tests/GameManager.test.js index fa955e1a..977faccc 100644 --- a/chessServer/src/tests/GameManager.test.js +++ b/chessServer/src/tests/GameManager.test.js @@ -50,8 +50,8 @@ describe('GameManager', () => { game.mentor.id = 'socket2'; const moveResult = gameManager.makeMove('socket1', 'e2', 'e4'); - expect(moveResult.move.from).toBe('e2'); - expect(moveResult.move.to).toBe('e4'); + expect(moveResult.result.move.from).toBe('e2'); + expect(moveResult.result.move.to).toBe('e4'); }); test('throws error for invalid move', () => {