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: 6 additions & 0 deletions shatter-backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions shatter-backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express';
import userRoutes from './routes/user_route'; // these routes define how to handle requests to /api/users
import authRoutes from './routes/auth_routes';
import eventRoutes from './routes/event_routes';

const app = express();

Expand All @@ -12,5 +13,6 @@ app.get('/', (_req, res) => {

app.use('/api/users', userRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/events', eventRoutes);

export default app;
63 changes: 63 additions & 0 deletions shatter-backend/src/controllers/event_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Request, Response } from "express";
import { Event } from "../models/event_model";
import "../models/participant_model";

import {generateEventId, generateJoinCode} from "../utils/event_utils";


export async function createEvent(req: Request, res: Response) {
try {
const { name, description, startDate, endDate, maxParticipant, currentState, createdBy } = req.body;

if (!createdBy) {
return res.status(400).json({ success: false, error: "createdBy email is required" });
}

const eventId = generateEventId();
const joinCode = generateJoinCode();

const event = new Event({
eventId,
name,
description,
joinCode,
startDate,
endDate,
maxParticipant,
participants: [],
currentState,
createdBy, // required email field
});

const savedEvent = await event.save();

res.status(201).json({ success: true, event: savedEvent });
} catch (err: any) {
res.status(500).json({ success: false, error: err.message });
}
}


export async function getEventByJoinCode(req: Request, res: Response) {
try {
const { joinCode } = req.params;

if (!joinCode) {
return res.status(400).json({ success: false, error: "joinCode is required" });
}

// Find event by joinCode and populate participants
const event = await Event.findOne({ joinCode }).populate("participants");

if (!event) {
return res.status(404).json({ success: false, error: "Event not found" });
}

res.status(200).json({
success: true,
event,
});
} catch (err: any) {
res.status(500).json({ success: false, error: err.message });
}
}
56 changes: 56 additions & 0 deletions shatter-backend/src/models/event_model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import mongoose, { Schema, model, Document, Types } from "mongoose";
import {User} from "../models/user_model";

import { IParticipant } from "./participant_model";

export interface IEvent extends Document {
eventId: string;
name: string;
description: string;
joinCode: string;
startDate: Date;
endDate: Date;
maxParticipant: number;
participants: mongoose.Types.DocumentArray<IParticipant>;
currentState: string;
createdBy: string;
}

const EventSchema = new Schema<IEvent>(
{
eventId: { type: String, required: true, unique: true },
name: { type: String, required: true },
description: { type: String, required: true },
joinCode: { type: String, required: true, unique: true },
startDate: { type: Date, required: true },
endDate: { type: Date, required: true },
maxParticipant: { type: Number, required: true },
participants: [{ type: Types.ObjectId, ref: "Participant" }],
currentState: { type: String, required: true },
createdBy: {
type: String,
required: true,
validate: {
validator: async function (email: string) {
const user = await User.findOne({ email });
return !!user; // true if user exists
},
message: "User with this email does not exist"
}
}
},
{
timestamps: true,
}
);

// Optional validation: ensure endDate is after startDate
EventSchema.pre("save", function (next) {
if (this.endDate <= this.startDate) {
next(new Error("endDate must be after startDate"));
} else {
next();
}
});

export const Event = model<IEvent>("Event", EventSchema);
26 changes: 26 additions & 0 deletions shatter-backend/src/models/participant_model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Schema, model, Document } from "mongoose";

export interface IParticipant extends Document {
participantId: string | null;
name: string;
eventId: string;
}

const ParticipantSchema = new Schema<IParticipant>({
participantId: {
type: String,
default: null,
},

name: {
type: String,
required: true,
},

eventId: {
type: String,
required: true,
},
});

export const Participant = model<IParticipant>("Participant", ParticipantSchema);
11 changes: 11 additions & 0 deletions shatter-backend/src/routes/event_routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Router } from 'express';
import { createEvent, getEventByJoinCode } from '../controllers/event_controller';

const router = Router();

// POST /api/events - create a new event
router.post('/createEvent', createEvent);
router.get("/event/:joinCode", getEventByJoinCode);


export default router;
18 changes: 18 additions & 0 deletions shatter-backend/src/utils/event_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import crypto from "crypto";

/**
* Generates a random hash string for eventId
* Example: 3f5a9c7d2e8b1a6f4c9d0e2b7a1c8f3d
*/
export function generateEventId(): string {
return crypto.randomBytes(16).toString("hex");
}

/**
* Generates a random 8-digit number string for joinCode
* Example: "48392017"
*/
export function generateJoinCode(): string {
const code = Math.floor(10000000 + Math.random() * 90000000);
return code.toString();
}