2026-03-06 19:17:03 -08:00
|
|
|
import {
|
|
|
|
|
Body,
|
|
|
|
|
Controller,
|
|
|
|
|
Delete,
|
|
|
|
|
Get,
|
|
|
|
|
HttpCode,
|
|
|
|
|
HttpStatus,
|
|
|
|
|
Param,
|
|
|
|
|
Patch,
|
|
|
|
|
Post,
|
|
|
|
|
Query,
|
|
|
|
|
} from '@nestjs/common';
|
|
|
|
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
|
|
|
|
import { LearningService } from './learning.service';
|
|
|
|
|
import { CreateLearningPathDto } from './dto/create-learning-path.dto';
|
|
|
|
|
import { UpdateLearningPathDto } from './dto/update-learning-path.dto';
|
|
|
|
|
import { CreateLessonDto } from './dto/create-lesson.dto';
|
|
|
|
|
import { UpdateLessonDto } from './dto/update-lesson.dto';
|
|
|
|
|
import { RecordReviewDto } from './dto/record-review.dto';
|
|
|
|
|
import { QueryLearningPathsDto } from './dto/query-learning-paths.dto';
|
|
|
|
|
import { QueryLessonsDto } from './dto/query-lessons.dto';
|
|
|
|
|
import type { LearningPath } from './entities/learning-path.entity';
|
|
|
|
|
import type { Lesson } from './entities/lesson.entity';
|
|
|
|
|
import type { LessonProgress } from './entities/lesson-progress.entity';
|
|
|
|
|
import type { ReviewLog } from './entities/review-log.entity';
|
|
|
|
|
import type { PaginatedResponse } from '@common/pagination.dto';
|
2026-03-17 17:51:05 -07:00
|
|
|
import type { LearningDueResponse, LearningPathStats } from '@life-platform/shared';
|
2026-03-06 19:17:03 -08:00
|
|
|
|
|
|
|
|
@ApiTags('Learning')
|
|
|
|
|
@Controller('learning')
|
|
|
|
|
export class LearningController {
|
|
|
|
|
constructor(private readonly learningService: LearningService) {}
|
|
|
|
|
|
|
|
|
|
// --- Paths ---
|
|
|
|
|
|
|
|
|
|
@Get('paths')
|
|
|
|
|
@ApiOperation({ summary: 'List learning paths' })
|
|
|
|
|
findAllPaths(@Query() query: QueryLearningPathsDto): Promise<PaginatedResponse<LearningPath>> {
|
|
|
|
|
return this.learningService.findAllPaths(query as QueryLearningPathsDto & Record<string, unknown>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('paths/:id')
|
|
|
|
|
@ApiOperation({ summary: 'Get learning path with lessons and progress' })
|
|
|
|
|
findOnePath(@Param('id') id: string): Promise<LearningPath> {
|
|
|
|
|
return this.learningService.findOnePath(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post('paths')
|
|
|
|
|
@ApiOperation({ summary: 'Create learning path' })
|
|
|
|
|
createPath(@Body() dto: CreateLearningPathDto): Promise<LearningPath> {
|
|
|
|
|
return this.learningService.createPath(dto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Patch('paths/:id')
|
|
|
|
|
@ApiOperation({ summary: 'Update learning path' })
|
|
|
|
|
updatePath(@Param('id') id: string, @Body() dto: UpdateLearningPathDto): Promise<LearningPath> {
|
|
|
|
|
return this.learningService.updatePath(id, dto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Delete('paths/:id')
|
|
|
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
|
|
|
@ApiOperation({ summary: 'Delete learning path' })
|
|
|
|
|
removePath(@Param('id') id: string): Promise<void> {
|
|
|
|
|
return this.learningService.removePath(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('paths/:id/stats')
|
|
|
|
|
@ApiOperation({ summary: 'Get learning path completion stats' })
|
|
|
|
|
getPathStats(@Param('id') id: string): Promise<LearningPathStats> {
|
|
|
|
|
return this.learningService.getPathStats(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('paths/:id/lessons')
|
|
|
|
|
@ApiOperation({ summary: 'List lessons in a learning path' })
|
|
|
|
|
findPathLessons(
|
|
|
|
|
@Param('id') id: string,
|
|
|
|
|
@Query() query: QueryLessonsDto,
|
|
|
|
|
): Promise<PaginatedResponse<Lesson>> {
|
|
|
|
|
return this.learningService.findLessons({
|
|
|
|
|
...query,
|
|
|
|
|
learningPathId: id,
|
|
|
|
|
} as QueryLessonsDto & Record<string, unknown>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Lessons ---
|
|
|
|
|
|
|
|
|
|
@Post('lessons')
|
|
|
|
|
@ApiOperation({ summary: 'Create lesson' })
|
|
|
|
|
createLesson(@Body() dto: CreateLessonDto): Promise<Lesson> {
|
|
|
|
|
return this.learningService.createLesson(dto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('lessons/:id')
|
|
|
|
|
@ApiOperation({ summary: 'Get lesson with progress' })
|
|
|
|
|
findOneLesson(@Param('id') id: string): Promise<Lesson> {
|
|
|
|
|
return this.learningService.findOneLesson(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Patch('lessons/:id')
|
|
|
|
|
@ApiOperation({ summary: 'Update lesson' })
|
|
|
|
|
updateLesson(@Param('id') id: string, @Body() dto: UpdateLessonDto): Promise<Lesson> {
|
|
|
|
|
return this.learningService.updateLesson(id, dto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Delete('lessons/:id')
|
|
|
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
|
|
|
@ApiOperation({ summary: 'Delete lesson' })
|
|
|
|
|
removeLesson(@Param('id') id: string): Promise<void> {
|
|
|
|
|
return this.learningService.removeLesson(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post('lessons/:id/review')
|
|
|
|
|
@ApiOperation({ summary: 'Record a review (SM-2 update)' })
|
|
|
|
|
recordReview(@Param('id') id: string, @Body() dto: RecordReviewDto): Promise<LessonProgress> {
|
|
|
|
|
return this.learningService.recordReview(id, dto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('lessons/:id/reviews')
|
|
|
|
|
@ApiOperation({ summary: 'Review history for a lesson' })
|
|
|
|
|
getReviewHistory(@Param('id') id: string): Promise<ReviewLog[]> {
|
|
|
|
|
return this.learningService.getReviewHistory(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Due / Stats ---
|
|
|
|
|
|
|
|
|
|
@Get('due')
|
|
|
|
|
@ApiOperation({ summary: "What's due for review now?" })
|
|
|
|
|
getDue(@Query('limit') limit?: string): Promise<LearningDueResponse> {
|
|
|
|
|
return this.learningService.getDue(limit ? parseInt(limit, 10) : undefined);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Get('stats')
|
|
|
|
|
@ApiOperation({ summary: 'Stats across all active learning paths' })
|
|
|
|
|
getAllStats(): Promise<LearningPathStats[]> {
|
|
|
|
|
return this.learningService.getAllPathStats();
|
|
|
|
|
}
|
|
|
|
|
}
|