Skip to content

Commit 3562f0b

Browse files
authored
Merge pull request #27 from Code-the-Dream-School/feature/dashboard-login
Connect dashboard + quizzes + shared header
2 parents 98977ce + 01cdfb5 commit 3562f0b

22 files changed

+990
-279
lines changed

backend/src/controllers/dashboard.controller.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Request, Response } from 'express';
2-
import { buildDashboard } from '../services/dashboard.service.js';
2+
import { buildDashboard, buildActivityLog } from '../services/dashboard.service.js';
33
import { UnauthenticatedError } from '../errors/index.js';
44

55
export const getDashboardData = async (req: Request, res: Response) => {
@@ -18,3 +18,25 @@ export const getDashboardData = async (req: Request, res: Response) => {
1818
res.status(500).json({ message: 'Failed to load dashboard' });
1919
}
2020
};
21+
22+
export const getDashboardActivity = async (req: Request, res: Response) => {
23+
try {
24+
const userId = req.user?.id;
25+
26+
if (!userId) {
27+
throw new UnauthenticatedError('User not authenticated');
28+
}
29+
30+
const limitParam = typeof req.query.limit === 'string' ? Number(req.query.limit) : undefined;
31+
const limit = Number.isFinite(limitParam) ? limitParam : undefined;
32+
const activity = await buildActivityLog(
33+
userId,
34+
typeof limit === 'number' ? { limit } : {}
35+
);
36+
37+
res.json(activity);
38+
} catch (error) {
39+
console.error('Dashboard activity error:', error);
40+
res.status(500).json({ message: 'Failed to load dashboard activity' });
41+
}
42+
};

backend/src/controllers/flashcards.controller.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import mongoose from 'mongoose';
44
import { FlashcardSetModel } from '../models/FlashcardSet';
55
import { FlashcardModel } from '../models/Flashcard';
66
import { Resource } from '../models/Resource';
7+
import { FlashcardStudySessionModel } from '../models/FlashcardStudySession';
78
import { generateFlashcardsFromText } from '../services/flashcardsGenerator';
89

910
const getOwnerId = (req: Request): string | undefined =>
@@ -287,3 +288,63 @@ export const deleteFlashcardsSet = async (req: Request, res: Response) => {
287288
return res.status(500).json({ message: msg });
288289
}
289290
};
291+
292+
export const recordFlashcardStudySession = async (req: Request, res: Response) => {
293+
try {
294+
const ownerIdStr = getOwnerId(req);
295+
if (!ownerIdStr) {
296+
return res.status(401).json({ message: 'Unauthorized.' });
297+
}
298+
if (!mongoose.Types.ObjectId.isValid(ownerIdStr)) {
299+
return res.status(400).json({ message: 'Invalid ownerId.' });
300+
}
301+
302+
const ownerId = new mongoose.Types.ObjectId(ownerIdStr);
303+
304+
const setIdStr = req.params.setId;
305+
if (!setIdStr || !mongoose.Types.ObjectId.isValid(setIdStr)) {
306+
return res.status(400).json({ message: 'Invalid setId.' });
307+
}
308+
const setId = new mongoose.Types.ObjectId(setIdStr);
309+
310+
const { cardsReviewed, startedAt, finishedAt } = req.body as {
311+
cardsReviewed?: number;
312+
startedAt?: string;
313+
finishedAt?: string;
314+
};
315+
316+
if (typeof cardsReviewed !== 'number' || cardsReviewed < 0) {
317+
return res.status(400).json({ message: 'cardsReviewed must be a non-negative number.' });
318+
}
319+
320+
if (!startedAt || !finishedAt) {
321+
return res.status(400).json({ message: 'startedAt and finishedAt are required.' });
322+
}
323+
324+
const started = new Date(startedAt);
325+
const finished = new Date(finishedAt);
326+
if (Number.isNaN(started.getTime()) || Number.isNaN(finished.getTime())) {
327+
return res.status(400).json({ message: 'Invalid date format.' });
328+
}
329+
330+
const set = await FlashcardSetModel.findOne({ _id: setId, ownerId }).lean();
331+
if (!set) {
332+
return res.status(404).json({ message: 'Flashcard set not found.' });
333+
}
334+
335+
const session = await FlashcardStudySessionModel.create({
336+
ownerId,
337+
setId,
338+
cardsReviewed,
339+
startedAt: started,
340+
finishedAt: finished,
341+
});
342+
343+
return res.status(201).json({
344+
sessionId: session._id.toString(),
345+
});
346+
} catch (e) {
347+
const msg = e instanceof Error ? e.message : 'Unknown error';
348+
return res.status(500).json({ message: msg });
349+
}
350+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import mongoose, { Schema } from "mongoose";
2+
3+
export type FlashcardStudySession = {
4+
ownerId: mongoose.Types.ObjectId;
5+
setId: mongoose.Types.ObjectId;
6+
cardsReviewed: number;
7+
startedAt: Date;
8+
finishedAt: Date;
9+
createdAt: Date;
10+
};
11+
12+
const FlashcardStudySessionSchema = new Schema<FlashcardStudySession>(
13+
{
14+
ownerId: { type: Schema.Types.ObjectId, required: true, index: true, ref: "User" },
15+
setId: { type: Schema.Types.ObjectId, required: true, index: true, ref: "FlashcardSet" },
16+
cardsReviewed: { type: Number, required: true, min: 0 },
17+
startedAt: { type: Date, required: true },
18+
finishedAt: { type: Date, required: true },
19+
},
20+
{
21+
collection: "flashcard_study_sessions",
22+
timestamps: { createdAt: true, updatedAt: false },
23+
}
24+
);
25+
26+
FlashcardStudySessionSchema.pre("validate", function (next) {
27+
if (this.startedAt >= this.finishedAt) {
28+
return next(new Error("finishedAt must be after startedAt"));
29+
}
30+
next();
31+
});
32+
33+
FlashcardStudySessionSchema.index({ ownerId: 1, finishedAt: -1 });
34+
35+
export const FlashcardStudySessionModel: mongoose.Model<FlashcardStudySession> =
36+
mongoose.models.FlashcardStudySession
37+
? (mongoose.models.FlashcardStudySession as mongoose.Model<FlashcardStudySession>)
38+
: mongoose.model<FlashcardStudySession>("FlashcardStudySession", FlashcardStudySessionSchema);
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Router } from 'express';
2-
import { getDashboardData } from '../controllers/dashboard.controller';
2+
import { getDashboardActivity, getDashboardData } from '../controllers/dashboard.controller';
33

44
const router = Router();
55

66
router.get('/', getDashboardData);
7+
router.get('/activity', getDashboardActivity);
78

89
export default router;

backend/src/routes/flashcards.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
listFlashcardsSetsByResource,
66
getFlashcardsSet,
77
deleteFlashcardsSet,
8+
recordFlashcardStudySession,
89
} from '../controllers/flashcards.controller';
910

1011
const router = express.Router();
@@ -14,5 +15,6 @@ router.get('/flashcard-sets', listAllFlashcardSets);
1415
router.get('/resources/:resourceId/flashcard-sets', listFlashcardsSetsByResource);
1516
router.get('/flashcard-sets/:setId', getFlashcardsSet);
1617
router.delete('/flashcard-sets/:setId', deleteFlashcardsSet);
18+
router.post('/flashcard-sets/:setId/sessions', recordFlashcardStudySession);
1719

1820
export default router;

0 commit comments

Comments
 (0)