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..e2d1f19e 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 && credentials) { + 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/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', () => { 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 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 514bd851..9749630c 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/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 5ecd0c4f..b4a3e86d 100644 --- a/react-ystemandchess/src/environments/environment.js +++ b/react-ystemandchess/src/environments/environment.js @@ -1,12 +1,12 @@ export const environment = { - production: false, - agora: { - appId: '6b7772f2a76f406192d8167460181be0', - }, - urls: { - middlewareURL: 'http://localhost:8000', - stockfishServerURL: 'http://localhost:8080', - chessServerURL: 'http://localhost:3001', - }, - productionType: 'development', + production: false, + agora: { + appId: '6b7772f2a76f406192d8167460181be0', + }, + urls: { + middlewareURL: 'http://localhost:8000', + stockfishServerURL: 'http://localhost:8080', // unified naming + chessServerURL: 'http://localhost:3001', // removed trailing slash for consistency + }, + productionType: 'development', }; \ 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 51226f25..48125bc1 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 @@ -373,6 +373,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 e3ff8e1c..77ecaf95 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 + ) + } + + )}