Skip to content
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
6 changes: 3 additions & 3 deletions client/src/services/socket.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ class SocketService {
this.socket?.on('PIXEL_UPDATE', cb);
}

emitDraw(payload: { lobbyId: string; x: number; y: number; color: number }) {
emitDraw(payload: { lobbyName: string; x: number; y: number; color: number }) {
this.socket?.emit('DRAW', payload);
}

emitJoinLobby(lobbyId: string) {
this.socket?.emit('JOIN_LOBBY', lobbyId);
emitJoinLobby(lobbyName: string) {
this.socket?.emit('JOIN_LOBBY', lobbyName);
}
}

Expand Down
4 changes: 2 additions & 2 deletions client/src/stores/editor.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const useEditorStore = defineStore('editor', () => {

// Notify Server
socketService.emitDraw({
lobbyId: 'default',
lobbyName: 'Default Lobby',
x,
y,
color: selectedColorIndex.value
Expand All @@ -85,7 +85,7 @@ export const useEditorStore = defineStore('editor', () => {

function init() {
socketService.connect();
socketService.emitJoinLobby('default');
socketService.emitJoinLobby('Default Lobby');
isConnected.value = true;

// Logic to bind Model events to ViewModel state
Expand Down
6 changes: 6 additions & 0 deletions server/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export const CONFIG = {
// Server settings
PORT: process.env.PORT || 3000,
MONGO_URI: process.env.MONGO_URI || "mongodb://localhost:27017/pixie",
CLIENT_ORIGIN: process.env.CLIENT_ORIGIN || "http://localhost:5173",
JWT: {
SECRET: process.env.JWT_SECRET || "dev-key",
EXPIRES_IN: "7d",
},

// Game Logic settings
CANVAS: {
Expand Down
40 changes: 40 additions & 0 deletions server/src/controllers/lobby.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Request, Response } from 'express';
import { LobbyService } from '../services/lobby.service.js';

export class LobbyController {

// POST /api/lobbies
static async create(req: Request, res: Response) {
try {
const { name, ownerId } = req.body;

if (!name) {
return res.status(400).json({ error: 'Lobby name is required' });
}

const lobby = await LobbyService.create(name, ownerId);

return res.status(201).json(lobby);

} catch (error: any) {
// Duplicate name
if (error.code === 11000) {
return res.status(409).json({ error: 'Lobby name already taken' });
}

console.error('[LobbyController] Create Error:', error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}

// GET /api/lobbies
static async getAll(req: Request, res: Response) {
try {
const lobbies = await LobbyService.getAll();
return res.json(lobbies);
} catch (error) {
console.error('[LobbyController] GetAll Error:', error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}
}
6 changes: 4 additions & 2 deletions server/src/db/connect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import mongoose from "mongoose"
import { seedUsers } from "./seed.js"
import { seedUsers, seedLobbies } from "./seed.js"

const MONGO_URI = process.env.MONGO_URI || "mongodb://localhost:27017/pixie"
import { CONFIG } from "../config.js"
const MONGO_URI = CONFIG.MONGO_URI

export const connectDB = async (): Promise<void> => {
try {
Expand All @@ -16,6 +17,7 @@ export const connectDB = async (): Promise<void> => {
console.log("[INFO] MongoDB is ready")

await seedUsers()
await seedLobbies()
} catch (err) {
const error = err as Error
console.error("[ERROR] MongoDB Connection Error:", error.message)
Expand Down
45 changes: 45 additions & 0 deletions server/src/db/seed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { User } from "../models/User.js"
import { Lobby, ILobby } from "../models/Lobby.js"
import { Canvas } from "../models/Canvas.js"
import { CONFIG } from "../config.js"

export const seedUsers = async (): Promise<void> => {
const users = [
Expand All @@ -20,3 +23,45 @@ export const seedUsers = async (): Promise<void> => {
console.error("Seed failed:", error)
}
}

export const seedLobbies = async (): Promise<void> => {
try {
console.log("Checking lobby seeds...")
const lobbyName = "Default Lobby"
let lobby: ILobby | null = await Lobby.findOne({ name: lobbyName })

if (!lobby) {
console.log(`Creating '${lobbyName}'...`)
lobby = await Lobby.createWithCanvas(lobbyName)
} else {
console.log(`'${lobbyName}' already exists`)
}

if (lobby) {
// Get the canvas to check/draw pixels
const canvas = await Canvas.findById(lobby.canvas)
if (canvas) {
const hasContent = canvas.data.some((pixel) => pixel !== 0)

if (!hasContent) {
console.log(`Canvas for '${lobbyName}' is empty. Seeding pattern...`)
// Draw a red diagonal line
const width = CONFIG.CANVAS.WIDTH
const height = CONFIG.CANVAS.HEIGHT
const color = 4 // Red

for (let i = 0; i < Math.min(width, height); i++) {
const index = i * width + i
canvas.data[index] = color
}

canvas.markModified("data")
await canvas.save()
console.log(`Seeded '${lobbyName}' with a diagonal pattern`)
}
}
}
} catch (error) {
console.error("Lobby seed failed:", error)
}
}
6 changes: 3 additions & 3 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import router from "./routes/index.js";
import { setupSocket } from "./sockets/index.js"; // Import the Socket Manager
import { CONFIG } from "./config.js";

const PORT = process.env.PORT || 3000;
const PORT = CONFIG.PORT;

// Configuration
const app = express();
Expand All @@ -18,7 +18,7 @@ const httpServer = createServer(app);
// Middleware
app.use(
cors({
origin: "*",
origin: CONFIG.CLIENT_ORIGIN,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
})
Expand All @@ -45,7 +45,7 @@ app.use(errorHandler);
// --- SOCKET.IO SETUP (Real-time) ---
const io = new Server(httpServer, {
cors: {
origin: "*", // Allow all origins for development
origin: CONFIG.CLIENT_ORIGIN, // Allow all origins for development
methods: ["GET", "POST"]
}
});
Expand Down
20 changes: 20 additions & 0 deletions server/src/models/Canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Schema, model, Document, Types } from 'mongoose';
import { CONFIG } from '../config.js';

export interface ICanvas extends Document {
lobby: Types.ObjectId; // Back-link to Lobby (useful for maintenance)
width: number;
height: number;
data: Buffer;
lastModified: Date;
}

const canvasSchema = new Schema<ICanvas>({
lobby: { type: Schema.Types.ObjectId, ref: 'Lobby', required: true },
width: { type: Number, default: CONFIG.CANVAS.WIDTH },
height: { type: Number, default: CONFIG.CANVAS.HEIGHT },
data: { type: Buffer, required: true },
lastModified: { type: Date, default: Date.now }
});

export const Canvas = model<ICanvas>('Canvas', canvasSchema);
61 changes: 61 additions & 0 deletions server/src/models/Lobby.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Schema, model, Document, Types, Model } from 'mongoose';
import { Canvas } from './Canvas.js';
import { CONFIG } from '../config.js';

export interface ILobby extends Document {
name: string;
owner?: Types.ObjectId; // Links to your User schema
canvas: Types.ObjectId; // Links to Canvas schema
allowedUsers: Types.ObjectId[];
bannedUsers: Types.ObjectId[];
createdAt: Date;
updatedAt: Date;
}

// Static method interface
interface LobbyModel extends Model<ILobby> {
createWithCanvas(name: string, ownerId?: string): Promise<ILobby>;
}

const lobbySchema = new Schema<ILobby>({
name: {
type: String,
required: true,
unique: true,
trim: true
},
owner: { type: Schema.Types.ObjectId, ref: 'User' },
canvas: { type: Schema.Types.ObjectId, ref: 'Canvas', required: true },
allowedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }],
bannedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
}, { timestamps: true });

// --- FACTORY METHOD: Creates Lobby + Empty Canvas atomically ---
lobbySchema.statics.createWithCanvas = async function (name: string, ownerId?: string) {
const lobbyId = new Types.ObjectId();
const canvasId = new Types.ObjectId();

// Initialized to 0 (Transparent/White depending on palette)
const emptyBuffer = Buffer.alloc(CONFIG.CANVAS.WIDTH * CONFIG.CANVAS.HEIGHT, 0);

const canvas = new Canvas({
_id: canvasId,
lobby: lobbyId,
width: CONFIG.CANVAS.WIDTH,
height: CONFIG.CANVAS.HEIGHT,
data: emptyBuffer
});

const lobby = new this({
_id: lobbyId,
name,
owner: ownerId ? new Types.ObjectId(ownerId) : undefined,
canvas: canvasId
});

// Save both
await Promise.all([canvas.save(), lobby.save()]);
return lobby;
};

export const Lobby = model<ILobby, LobbyModel>('Lobby', lobbySchema);
5 changes: 5 additions & 0 deletions server/src/routes/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import express, { Router } from "express"
import { LoginController } from "../controllers/LoginController.js"
import { UserController } from "../controllers/UserController.js"
import { authenticateToken } from "../middlewares/authMiddleware.js"
import { LobbyController } from "../controllers/lobby.controller.js"

const router: Router = express.Router()

Expand All @@ -13,4 +14,8 @@ router.get("/users", authenticateToken, UserController.getAll)

// Test Error

// Lobbies
router.post("/lobbies", LobbyController.create)
router.get("/lobbies", LobbyController.getAll)

export default router
5 changes: 3 additions & 2 deletions server/src/services/UserService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import jwt from "jsonwebtoken"
import { User } from "../models/User.js"
import { CONFIG } from "../config.js"

interface LoginResponse {
username: string
Expand All @@ -19,7 +20,7 @@ export class UserService {
* Handle user login/registration and token generation
*/
static async login(username: string): Promise<LoginResponse> {
const JWT_SECRET = process.env.JWT_SECRET || "dev-key"
const JWT_SECRET = CONFIG.JWT.SECRET

let user = await User.findOne({ username })
let isNewUser = false
Expand All @@ -29,7 +30,7 @@ export class UserService {
isNewUser = true
}

const token = jwt.sign({ id: user._id, username: user.username }, JWT_SECRET, { expiresIn: "7d" })
const token = jwt.sign({ id: user._id, username: user.username }, JWT_SECRET, { expiresIn: CONFIG.JWT.EXPIRES_IN as any })

return {
username: user.username,
Expand Down
Loading