Skip to content
Open
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
14 changes: 14 additions & 0 deletions backend/migrations/015_study_set_exam_dates.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Study Set Exam Dates table
-- Allows students to set exam dates for study sets so the SRS can optimize review scheduling

CREATE TABLE IF NOT EXISTS study_set_exam_dates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
study_set_id UUID NOT NULL REFERENCES study_sets(id) ON DELETE CASCADE,
exam_date DATE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE (user_id, study_set_id)
);

CREATE INDEX idx_study_set_exam_dates_user_id ON study_set_exam_dates(user_id);
CREATE INDEX idx_study_set_exam_dates_exam_date ON study_set_exam_dates(exam_date);
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { SubscriptionModule } from './modules/subscription';
import { AnalyticsModule } from './modules/analytics';
import { NotificationsModule } from './modules/notifications';
import { BlogModule } from './modules/blog';
import { StudyPlannerModule } from './modules/study-planner';

// Common
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
Expand Down Expand Up @@ -97,6 +98,7 @@ import { HealthController } from './health.controller';
AnalyticsModule,
NotificationsModule,
BlogModule,
StudyPlannerModule,
],
controllers: [HealthController],
providers: [
Expand Down
3 changes: 3 additions & 0 deletions backend/src/modules/study-planner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './study-planner.module';
export * from './study-planner.service';
export * from './study-planner.controller';
75 changes: 75 additions & 0 deletions backend/src/modules/study-planner/study-planner.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
Controller,
Get,
Post,
Body,
Query,
UseGuards,
Header,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { CurrentUser, JwtPayload } from '../../common';
import { StudyPlannerService } from './study-planner.service';

@ApiTags('Study Planner')
@Controller('study-planner')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class StudyPlannerController {
constructor(private readonly studyPlannerService: StudyPlannerService) {}

@Get('upcoming')
@ApiOperation({ summary: 'Get upcoming flashcard reviews' })
@ApiQuery({ name: 'days', required: false, type: Number, description: 'Number of days to look ahead (default 14)' })
@ApiResponse({ status: 200, description: 'Upcoming reviews grouped by date' })
async getUpcomingReviews(
@CurrentUser() user: JwtPayload,
@Query('days') days?: string,
) {
const numDays = days ? parseInt(days, 10) : 14;
return this.studyPlannerService.getUpcomingReviews(user.sub, numDays);
}

@Get('daily')
@ApiOperation({ summary: 'Get daily study plan' })
@ApiQuery({ name: 'date', required: false, type: String, description: 'Date in YYYY-MM-DD format (default today)' })
@ApiResponse({ status: 200, description: 'Daily study plan with prioritized items' })
async getDailyPlan(
@CurrentUser() user: JwtPayload,
@Query('date') date?: string,
) {
const planDate = date || new Date().toISOString().split('T')[0];
return this.studyPlannerService.getDailyPlan(user.sub, planDate);
}

@Post('exam-date')
@ApiOperation({ summary: 'Set an exam date for a study set' })
@ApiResponse({ status: 201, description: 'Exam date set' })
async setExamDate(
@CurrentUser() user: JwtPayload,
@Body() body: { studySetId: string; examDate: string },
) {
return this.studyPlannerService.setExamDate(
user.sub,
body.studySetId,
body.examDate,
);
}

@Get('streak')
@ApiOperation({ summary: 'Get study streak and heatmap data' })
@ApiResponse({ status: 200, description: 'Current streak, longest streak, and 90-day heatmap' })
async getStudyStreak(@CurrentUser() user: JwtPayload) {
return this.studyPlannerService.getStudyStreak(user.sub);
}

@Get('export/ical')
@ApiOperation({ summary: 'Export study schedule as iCal file' })
@ApiResponse({ status: 200, description: 'iCal file content' })
@Header('Content-Type', 'text/calendar; charset=utf-8')
@Header('Content-Disposition', 'attachment; filename="studyield-schedule.ics"')
async exportIcal(@CurrentUser() user: JwtPayload) {
return this.studyPlannerService.exportToIcal(user.sub);
}
}
12 changes: 12 additions & 0 deletions backend/src/modules/study-planner/study-planner.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module';
import { StudyPlannerService } from './study-planner.service';
import { StudyPlannerController } from './study-planner.controller';

@Module({
imports: [AuthModule],
controllers: [StudyPlannerController],
providers: [StudyPlannerService],
exports: [StudyPlannerService],
})
export class StudyPlannerModule {}
Loading
Loading