Skip to content

Commit 4df91a2

Browse files
authored
Merge pull request #17 from pixie-git/feature/PIX-30_lobby-canvas-db
Feature/pix 30 lobby canvas db
2 parents e301fae + 0c6b6f5 commit 4df91a2

15 files changed

Lines changed: 325 additions & 40 deletions

File tree

client/src/services/socket.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ class SocketService {
1717
this.socket?.on('PIXEL_UPDATE', cb);
1818
}
1919

20-
emitDraw(payload: { lobbyId: string; x: number; y: number; color: number }) {
20+
emitDraw(payload: { lobbyName: string; x: number; y: number; color: number }) {
2121
this.socket?.emit('DRAW', payload);
2222
}
2323

24-
emitJoinLobby(lobbyId: string) {
25-
this.socket?.emit('JOIN_LOBBY', lobbyId);
24+
emitJoinLobby(lobbyName: string) {
25+
this.socket?.emit('JOIN_LOBBY', lobbyName);
2626
}
2727
}
2828

client/src/stores/editor.store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export const useEditorStore = defineStore('editor', () => {
6868

6969
// Notify Server
7070
socketService.emitDraw({
71-
lobbyId: 'default',
71+
lobbyName: 'Default Lobby',
7272
x,
7373
y,
7474
color: selectedColorIndex.value
@@ -85,7 +85,7 @@ export const useEditorStore = defineStore('editor', () => {
8585

8686
function init() {
8787
socketService.connect();
88-
socketService.emitJoinLobby('default');
88+
socketService.emitJoinLobby('Default Lobby');
8989
isConnected.value = true;
9090

9191
// Logic to bind Model events to ViewModel state

server/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
export const CONFIG = {
22
// Server settings
33
PORT: process.env.PORT || 3000,
4+
MONGO_URI: process.env.MONGO_URI || "mongodb://localhost:27017/pixie",
5+
CLIENT_ORIGIN: process.env.CLIENT_ORIGIN || "http://localhost:5173",
6+
JWT: {
7+
SECRET: process.env.JWT_SECRET || "dev-key",
8+
EXPIRES_IN: "7d",
9+
},
410

511
// Game Logic settings
612
CANVAS: {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Request, Response } from 'express';
2+
import { LobbyService } from '../services/lobby.service.js';
3+
4+
export class LobbyController {
5+
6+
// POST /api/lobbies
7+
static async create(req: Request, res: Response) {
8+
try {
9+
const { name, ownerId } = req.body;
10+
11+
if (!name) {
12+
return res.status(400).json({ error: 'Lobby name is required' });
13+
}
14+
15+
const lobby = await LobbyService.create(name, ownerId);
16+
17+
return res.status(201).json(lobby);
18+
19+
} catch (error: any) {
20+
// Duplicate name
21+
if (error.code === 11000) {
22+
return res.status(409).json({ error: 'Lobby name already taken' });
23+
}
24+
25+
console.error('[LobbyController] Create Error:', error);
26+
return res.status(500).json({ error: 'Internal Server Error' });
27+
}
28+
}
29+
30+
// GET /api/lobbies
31+
static async getAll(req: Request, res: Response) {
32+
try {
33+
const lobbies = await LobbyService.getAll();
34+
return res.json(lobbies);
35+
} catch (error) {
36+
console.error('[LobbyController] GetAll Error:', error);
37+
return res.status(500).json({ error: 'Internal Server Error' });
38+
}
39+
}
40+
}

server/src/db/connect.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import mongoose from "mongoose"
2-
import { seedUsers } from "./seed.js"
2+
import { seedUsers, seedLobbies } from "./seed.js"
33

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

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

1819
await seedUsers()
20+
await seedLobbies()
1921
} catch (err) {
2022
const error = err as Error
2123
console.error("[ERROR] MongoDB Connection Error:", error.message)

server/src/db/seed.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { User } from "../models/User.js"
2+
import { Lobby, ILobby } from "../models/Lobby.js"
3+
import { Canvas } from "../models/Canvas.js"
4+
import { CONFIG } from "../config.js"
25

36
export const seedUsers = async (): Promise<void> => {
47
const users = [
@@ -20,3 +23,45 @@ export const seedUsers = async (): Promise<void> => {
2023
console.error("Seed failed:", error)
2124
}
2225
}
26+
27+
export const seedLobbies = async (): Promise<void> => {
28+
try {
29+
console.log("Checking lobby seeds...")
30+
const lobbyName = "Default Lobby"
31+
let lobby: ILobby | null = await Lobby.findOne({ name: lobbyName })
32+
33+
if (!lobby) {
34+
console.log(`Creating '${lobbyName}'...`)
35+
lobby = await Lobby.createWithCanvas(lobbyName)
36+
} else {
37+
console.log(`'${lobbyName}' already exists`)
38+
}
39+
40+
if (lobby) {
41+
// Get the canvas to check/draw pixels
42+
const canvas = await Canvas.findById(lobby.canvas)
43+
if (canvas) {
44+
const hasContent = canvas.data.some((pixel) => pixel !== 0)
45+
46+
if (!hasContent) {
47+
console.log(`Canvas for '${lobbyName}' is empty. Seeding pattern...`)
48+
// Draw a red diagonal line
49+
const width = CONFIG.CANVAS.WIDTH
50+
const height = CONFIG.CANVAS.HEIGHT
51+
const color = 4 // Red
52+
53+
for (let i = 0; i < Math.min(width, height); i++) {
54+
const index = i * width + i
55+
canvas.data[index] = color
56+
}
57+
58+
canvas.markModified("data")
59+
await canvas.save()
60+
console.log(`Seeded '${lobbyName}' with a diagonal pattern`)
61+
}
62+
}
63+
}
64+
} catch (error) {
65+
console.error("Lobby seed failed:", error)
66+
}
67+
}

server/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import router from "./routes/index.js";
77
import { setupSocket } from "./sockets/index.js"; // Import the Socket Manager
88
import { CONFIG } from "./config.js";
99

10-
const PORT = process.env.PORT || 3000;
10+
const PORT = CONFIG.PORT;
1111

1212
// Configuration
1313
const app = express();
@@ -18,7 +18,7 @@ const httpServer = createServer(app);
1818
// Middleware
1919
app.use(
2020
cors({
21-
origin: "*",
21+
origin: CONFIG.CLIENT_ORIGIN,
2222
methods: ["GET", "POST", "PUT", "DELETE"],
2323
allowedHeaders: ["Content-Type", "Authorization"],
2424
})
@@ -45,7 +45,7 @@ app.use(errorHandler);
4545
// --- SOCKET.IO SETUP (Real-time) ---
4646
const io = new Server(httpServer, {
4747
cors: {
48-
origin: "*", // Allow all origins for development
48+
origin: CONFIG.CLIENT_ORIGIN, // Allow all origins for development
4949
methods: ["GET", "POST"]
5050
}
5151
});

server/src/models/Canvas.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Schema, model, Document, Types } from 'mongoose';
2+
import { CONFIG } from '../config.js';
3+
4+
export interface ICanvas extends Document {
5+
lobby: Types.ObjectId; // Back-link to Lobby (useful for maintenance)
6+
width: number;
7+
height: number;
8+
data: Buffer;
9+
lastModified: Date;
10+
}
11+
12+
const canvasSchema = new Schema<ICanvas>({
13+
lobby: { type: Schema.Types.ObjectId, ref: 'Lobby', required: true },
14+
width: { type: Number, default: CONFIG.CANVAS.WIDTH },
15+
height: { type: Number, default: CONFIG.CANVAS.HEIGHT },
16+
data: { type: Buffer, required: true },
17+
lastModified: { type: Date, default: Date.now }
18+
});
19+
20+
export const Canvas = model<ICanvas>('Canvas', canvasSchema);

server/src/models/Lobby.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Schema, model, Document, Types, Model } from 'mongoose';
2+
import { Canvas } from './Canvas.js';
3+
import { CONFIG } from '../config.js';
4+
5+
export interface ILobby extends Document {
6+
name: string;
7+
owner?: Types.ObjectId; // Links to your User schema
8+
canvas: Types.ObjectId; // Links to Canvas schema
9+
allowedUsers: Types.ObjectId[];
10+
bannedUsers: Types.ObjectId[];
11+
createdAt: Date;
12+
updatedAt: Date;
13+
}
14+
15+
// Static method interface
16+
interface LobbyModel extends Model<ILobby> {
17+
createWithCanvas(name: string, ownerId?: string): Promise<ILobby>;
18+
}
19+
20+
const lobbySchema = new Schema<ILobby>({
21+
name: {
22+
type: String,
23+
required: true,
24+
unique: true,
25+
trim: true
26+
},
27+
owner: { type: Schema.Types.ObjectId, ref: 'User' },
28+
canvas: { type: Schema.Types.ObjectId, ref: 'Canvas', required: true },
29+
allowedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }],
30+
bannedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
31+
}, { timestamps: true });
32+
33+
// --- FACTORY METHOD: Creates Lobby + Empty Canvas atomically ---
34+
lobbySchema.statics.createWithCanvas = async function (name: string, ownerId?: string) {
35+
const lobbyId = new Types.ObjectId();
36+
const canvasId = new Types.ObjectId();
37+
38+
// Initialized to 0 (Transparent/White depending on palette)
39+
const emptyBuffer = Buffer.alloc(CONFIG.CANVAS.WIDTH * CONFIG.CANVAS.HEIGHT, 0);
40+
41+
const canvas = new Canvas({
42+
_id: canvasId,
43+
lobby: lobbyId,
44+
width: CONFIG.CANVAS.WIDTH,
45+
height: CONFIG.CANVAS.HEIGHT,
46+
data: emptyBuffer
47+
});
48+
49+
const lobby = new this({
50+
_id: lobbyId,
51+
name,
52+
owner: ownerId ? new Types.ObjectId(ownerId) : undefined,
53+
canvas: canvasId
54+
});
55+
56+
// Save both
57+
await Promise.all([canvas.save(), lobby.save()]);
58+
return lobby;
59+
};
60+
61+
export const Lobby = model<ILobby, LobbyModel>('Lobby', lobbySchema);

server/src/routes/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import express, { Router } from "express"
22
import { LoginController } from "../controllers/LoginController.js"
33
import { UserController } from "../controllers/UserController.js"
44
import { authenticateToken } from "../middlewares/authMiddleware.js"
5+
import { LobbyController } from "../controllers/lobby.controller.js"
56

67
const router: Router = express.Router()
78

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

1415
// Test Error
1516

17+
// Lobbies
18+
router.post("/lobbies", LobbyController.create)
19+
router.get("/lobbies", LobbyController.getAll)
20+
1621
export default router

0 commit comments

Comments
 (0)