Skip to content

feat: matchmaking game api #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 111 additions & 43 deletions ChromeExtension/Backend/controllers/gameController.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,144 @@
import Game from '../models/gameModel.js';
import User from '../models/userModel.js';
import InvitationCode from '../models/InvitationCodeModel.js';
// import { v4 as uuidv4 } from 'uuid';

// Get all games
const getAllGames = async (req, res) => {
export const getAllGames = async (req, res) => {
try {
const games = await Game.find().populate('player_1_id player_2_id invitation_code_id');
const games = await Game.find(); // Removed .populate()
res.status(200).json(games);
} catch (err) {
res.status(500).json({ message: err.message });
console.error("Error fetching games:", err);
res.status(500).json({ message: "Failed to retrieve games", error: err.message });
}
};

// Get one game by ID
const getGameById = async (req, res) => {
// Create a new game
export const createGame = async (req, res) => {
try {
const game = await Game.findById(req.params.id).populate('player_1_id player_2_id invitation_code_id');
if (!game) return res.status(404).json({ message: 'Game not found' });
const { invitation_code, username } = req.body;

res.status(200).json(game);
} catch (err) {
res.status(500).json({ message: err.message });
if (!invitation_code) {
return res.status(400).json({ message: 'invitation_code is required' });
}

// Check if invitation_code already exists
const existingGame = await Game.findOne({ invitation_code });
if (existingGame) {
return res.status(400).json({ message: 'Invitation code already in use' });
}

const newGame = new Game({
invitation_code,
player_1: username
});

await newGame.save();
res.status(201).json(newGame);
} catch (error) {
res.status(500).json({ message: 'Error creating game', error });
}
};

// Create a new game
const createGame = async (req, res) => {
const { player_1_id, player_2_id, invitation_code } = req.body;
// Join a game
export const joinGame = async (req, res) => {
try {
const { invitation_code, username } = req.body;

if (!invitation_code) {
return res.status(400).json({ message: 'invitation_code is required' });
}

const game = await Game.findOne({ invitation_code });

if (!game) {
return res.status(400).json({ message: 'Invalid invitation code' });
}

if (game.player_2) {
return res.status(400).json({ message: 'Player 2 already joined' });
}

if (game.player_1 === username) {
return res.status(400).json({ message: 'You are already in this game as Player 1' });
}

game.player_2 = username;
game.status = 'paired';
await game.save();

res.status(200).json(game);
} catch (error) {
res.status(500).json({ message: 'Error joining game', error });
}
};

// Update game
export const updateGame = async (req, res) => {
try {
const invitation = await InvitationCode.findOne({ invitation_code, status: 'active' });
if (!invitation) {
return res.status(400).json({ message: 'Invalid or inactive invitation code' });
const gameId = req.params.id;
const { player_1, player_2, invitation_code, status } = req.body;

const game = await Game.findById(gameId);
if (!game) {
return res.status(404).json({ message: 'Game not found' });
}

const newGame = new Game({
player_1_id,
player_2_id,
invitation_code_id: invitation._id
});
// Merge any fields provided in the request
if (player_1 !== undefined) game.player_1 = player_1;
if (player_2 !== undefined) game.player_2 = player_2;
if (invitation_code !== undefined) game.invitation_code = invitation_code;
if (status !== undefined) game.status = status;

await newGame.save();
await game.save();
res.status(200).json(game);
} catch (error) {
console.error('Error updating game:', error);
res.status(500).json({ message: 'Failed to update game', error });
}
};

invitation.status = 'inactive';
await invitation.save();

res.status(201).json(newGame);
} catch (err) {
res.status(500).json({ message: err.message });
// Update game status
export const updateGameStatus = async (req, res) => {
try {
const { status } = req.body; // Expect new status in body
const game = await Game.findById(req.params.id);

if (!game) {
return res.status(404).json({ message: 'Game not found' });
}

game.status = status; // Update the game status
await game.save();
res.status(200).json(game);
} catch (error) {
res.status(500).json({ message: 'Error updating game status' });
}
};

// Delete one game by ID
const deleteGameById = async (req, res) => {
// Get a game by invitation code
export const getGameByInvitationCode = async (req, res) => {
try {
const deletedGame = await Game.findByIdAndDelete(req.params.id);
if (!deletedGame) return res.status(404).json({ message: 'Game not found' });
const { invitation_code_id } = req.params; // Get the invitation code from URL params
const game = await Game.findOne({ invitation_code_id }).populate('player_1 player_2');

res.status(200).json({ message: 'Game deleted successfully' });
} catch (err) {
res.status(500).json({ message: err.message });
if (!game) {
return res.status(404).json({ message: 'Game not found' });
}

res.status(200).json(game); // Return the game details
} catch (error) {
res.status(500).json({ message: 'Error retrieving game by invitation code' });
}
};

// Delete all games
const deleteAllGames = async (req, res) => {
export const deleteAllGames = async (req, res) => {
try {
await Game.deleteMany();
res.status(200).json({ message: 'All games deleted successfully' });
} catch (err) {
res.status(500).json({ message: err.message });
const result = await Game.deleteMany({});
res.status(200).json({ message: `Deleted ${result.deletedCount} games successfully.` });
} catch (error) {
console.error("Error deleting games:", error);
res.status(500).json({ message: "Failed to delete games", error: error.message });
}
};

export { getAllGames, getGameById, createGame, deleteGameById, deleteAllGames };
66 changes: 0 additions & 66 deletions ChromeExtension/Backend/controllers/invitationCodeController.js

This file was deleted.

8 changes: 0 additions & 8 deletions ChromeExtension/Backend/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import User from '../models/userModel.js';
import { validateUser } from '../utils/leetcodeGraphQLQueries.js';

// Create a new user
const createUser = async (req, res) => {
const { username } = req.body;

try {
// Validate Username
const validUser = await validateUser(username);
console.log(username)
if (!validUser) {
return res.status(400).json({ message: 'Invalid LeetCode username.' });
}

const newUser = new User({ username });
await newUser.save();
res.status(201).json(newUser);
Expand Down
41 changes: 28 additions & 13 deletions ChromeExtension/Backend/models/gameModel.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,47 @@
import mongoose from 'mongoose';

const GameSchema = new mongoose.Schema({
invitation_code_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'InvitationCode',
invitation_code: {
type: String,
default: null
},
player_1_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
player_1: {
// type: mongoose.Schema.Types.ObjectId,
type: String,
// ref: 'User',
default: null
},
player_2_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
player_2: {
type: String,
// type: mongoose.Schema.Types.ObjectId,
// ref: 'User',
default: null
},
winner: {
type: String,
enum: ['player_1', 'player_2', 'draw', null],
// type: mongoose.Schema.Types.ObjectId,
// ref: 'User',
type:String,
default: null
},
status: {
type: String,
enum: ['waiting', 'paired', 'in_progress', 'completed'],
default: 'waiting'
},
code_expires_at: {
type: Date,
default: () => Date.now() + 15 * 60 * 1000 // Expires in 15 minutes
},
score: {
player_1: { type: Number, default: 0 },
player_2: { type: Number, default: 0 }
},
date: {
type: Date,
default: null
}
});
}, { timestamps: true });

const Game = mongoose.model('Game', GameSchema);

export default Game;
export default Game;
21 changes: 0 additions & 21 deletions ChromeExtension/Backend/models/invitationCodeModel.js

This file was deleted.

6 changes: 5 additions & 1 deletion ChromeExtension/Backend/models/userModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ const UserSchema = new mongoose.Schema({
gamesPlayed: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Game'
}]
}],
// elo: {
// type: Float32Array,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small thing, but the elo is an integer and would almost never exceed 4000, so change to:
type: Int16Array

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good catch. The elo is currently commented out, I think when the backend gets set up for recording scoring, then that change will be kept in mind.

// default:
// }
});

const User = mongoose.model('User', UserSchema);
Expand Down
4 changes: 3 additions & 1 deletion ChromeExtension/Backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"mongoose": "^8.10.1"
"mongoose": "^8.10.1",
"uuid": "^11.1.0",
"ws": "^8.18.1"
},
"devDependencies": {
"@babel/core": "^7.26.9",
Expand Down
Loading