Skip to content

Commit 646e08b

Browse files
authored
Merge pull request #30 from techstartucalgary/feature/STR-56-DataModels
LGTM
2 parents 2e836e0 + 0d5f7c8 commit 646e08b

File tree

7 files changed

+182
-0
lines changed

7 files changed

+182
-0
lines changed

shatter-backend/package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shatter-backend/src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import express from 'express';
22
import userRoutes from './routes/user_route'; // these routes define how to handle requests to /api/users
33
import authRoutes from './routes/auth_routes';
4+
import eventRoutes from './routes/event_routes';
45

56
const app = express();
67

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

1314
app.use('/api/users', userRoutes);
1415
app.use('/api/auth', authRoutes);
16+
app.use('/api/events', eventRoutes);
1517

1618
export default app;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Request, Response } from "express";
2+
import { Event } from "../models/event_model";
3+
import "../models/participant_model";
4+
5+
import {generateEventId, generateJoinCode} from "../utils/event_utils";
6+
7+
8+
export async function createEvent(req: Request, res: Response) {
9+
try {
10+
const { name, description, startDate, endDate, maxParticipant, currentState, createdBy } = req.body;
11+
12+
if (!createdBy) {
13+
return res.status(400).json({ success: false, error: "createdBy email is required" });
14+
}
15+
16+
const eventId = generateEventId();
17+
const joinCode = generateJoinCode();
18+
19+
const event = new Event({
20+
eventId,
21+
name,
22+
description,
23+
joinCode,
24+
startDate,
25+
endDate,
26+
maxParticipant,
27+
participants: [],
28+
currentState,
29+
createdBy, // required email field
30+
});
31+
32+
const savedEvent = await event.save();
33+
34+
res.status(201).json({ success: true, event: savedEvent });
35+
} catch (err: any) {
36+
res.status(500).json({ success: false, error: err.message });
37+
}
38+
}
39+
40+
41+
export async function getEventByJoinCode(req: Request, res: Response) {
42+
try {
43+
const { joinCode } = req.params;
44+
45+
if (!joinCode) {
46+
return res.status(400).json({ success: false, error: "joinCode is required" });
47+
}
48+
49+
// Find event by joinCode and populate participants
50+
const event = await Event.findOne({ joinCode }).populate("participants");
51+
52+
if (!event) {
53+
return res.status(404).json({ success: false, error: "Event not found" });
54+
}
55+
56+
res.status(200).json({
57+
success: true,
58+
event,
59+
});
60+
} catch (err: any) {
61+
res.status(500).json({ success: false, error: err.message });
62+
}
63+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import mongoose, { Schema, model, Document, Types } from "mongoose";
2+
import {User} from "../models/user_model";
3+
4+
import { IParticipant } from "./participant_model";
5+
6+
export interface IEvent extends Document {
7+
eventId: string;
8+
name: string;
9+
description: string;
10+
joinCode: string;
11+
startDate: Date;
12+
endDate: Date;
13+
maxParticipant: number;
14+
participants: mongoose.Types.DocumentArray<IParticipant>;
15+
currentState: string;
16+
createdBy: string;
17+
}
18+
19+
const EventSchema = new Schema<IEvent>(
20+
{
21+
eventId: { type: String, required: true, unique: true },
22+
name: { type: String, required: true },
23+
description: { type: String, required: true },
24+
joinCode: { type: String, required: true, unique: true },
25+
startDate: { type: Date, required: true },
26+
endDate: { type: Date, required: true },
27+
maxParticipant: { type: Number, required: true },
28+
participants: [{ type: Types.ObjectId, ref: "Participant" }],
29+
currentState: { type: String, required: true },
30+
createdBy: {
31+
type: String,
32+
required: true,
33+
validate: {
34+
validator: async function (email: string) {
35+
const user = await User.findOne({ email });
36+
return !!user; // true if user exists
37+
},
38+
message: "User with this email does not exist"
39+
}
40+
}
41+
},
42+
{
43+
timestamps: true,
44+
}
45+
);
46+
47+
// Optional validation: ensure endDate is after startDate
48+
EventSchema.pre("save", function (next) {
49+
if (this.endDate <= this.startDate) {
50+
next(new Error("endDate must be after startDate"));
51+
} else {
52+
next();
53+
}
54+
});
55+
56+
export const Event = model<IEvent>("Event", EventSchema);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Schema, model, Document } from "mongoose";
2+
3+
export interface IParticipant extends Document {
4+
participantId: string | null;
5+
name: string;
6+
eventId: string;
7+
}
8+
9+
const ParticipantSchema = new Schema<IParticipant>({
10+
participantId: {
11+
type: String,
12+
default: null,
13+
},
14+
15+
name: {
16+
type: String,
17+
required: true,
18+
},
19+
20+
eventId: {
21+
type: String,
22+
required: true,
23+
},
24+
});
25+
26+
export const Participant = model<IParticipant>("Participant", ParticipantSchema);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Router } from 'express';
2+
import { createEvent, getEventByJoinCode } from '../controllers/event_controller';
3+
4+
const router = Router();
5+
6+
// POST /api/events - create a new event
7+
router.post('/createEvent', createEvent);
8+
router.get("/event/:joinCode", getEventByJoinCode);
9+
10+
11+
export default router;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import crypto from "crypto";
2+
3+
/**
4+
* Generates a random hash string for eventId
5+
* Example: 3f5a9c7d2e8b1a6f4c9d0e2b7a1c8f3d
6+
*/
7+
export function generateEventId(): string {
8+
return crypto.randomBytes(16).toString("hex");
9+
}
10+
11+
/**
12+
* Generates a random 8-digit number string for joinCode
13+
* Example: "48392017"
14+
*/
15+
export function generateJoinCode(): string {
16+
const code = Math.floor(10000000 + Math.random() * 90000000);
17+
return code.toString();
18+
}

0 commit comments

Comments
 (0)