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
21 changes: 21 additions & 0 deletions .github/workflows/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Summary

Describe the purpose of this PR.

## Changes

-
-
-

## Verification

- [ ] pnpm lint
- [ ] pnpm typecheck
- [ ] pnpm build

## Manual Smoke Tests

- [ ] auth flow
- [ ] onboarding flow
- [ ] dashboard flow
36 changes: 18 additions & 18 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Code of Conduct
## Our Standards
Contributors should:
* be respectful
* provide constructive feedback
* collaborate professionally
* avoid harassment or discrimination
## Expectations
This repository is intended for professional startup-grade software development.
## Enforcement
Project maintainers may remove comments, pull requests, or contributions that violate these expectations.
# Code of Conduct

## Our Standards

Contributors should:

- be respectful
- provide constructive feedback
- collaborate professionally
- avoid harassment or discrimination

## Expectations

This repository is intended for professional startup-grade software development.

## Enforcement

Project maintainers may remove comments, pull requests, or contributions that violate these expectations.
94 changes: 47 additions & 47 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
# Contributing
Thank you for contributing to LearnDojoWorld.
## Workflow
1. Create a branch from `main`
2. Make focused changes
3. Run quality checks
4. Open a pull request
5. Wait for CI checks before merge
## Branch Naming
* `feature/*`
* `fix/*`
* `chore/*`
* `docs/*`
## Required Checks
Before pushing:
```bash
pnpm lint
pnpm typecheck
pnpm build
```
## Pull Requests
PRs should:
* remain focused
* avoid unrelated changes
* include smoke-test notes
* pass CI checks
## Security
Never commit:
* `.env`
* API keys
* tokens
* passwords
* production secrets
# Contributing

Thank you for contributing to LearnDojoWorld.

## Workflow

1. Create a branch from `main`
2. Make focused changes
3. Run quality checks
4. Open a pull request
5. Wait for CI checks before merge

## Branch Naming

- `feature/*`
- `fix/*`
- `chore/*`
- `docs/*`

## Required Checks

Before pushing:

```bash
pnpm lint
pnpm typecheck
pnpm build
```

## Pull Requests

PRs should:

- remain focused
- avoid unrelated changes
- include smoke-test notes
- pass CI checks

## Security

Never commit:

- `.env`
- API keys
- tokens
- passwords
- production secrets
2 changes: 2 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CoursesModule } from "./modules/courses/courses.module";
import { DashboardModule } from "./modules/dashboard/dashboard.module";
import { HealthModule } from "./modules/health/health.module";
import { LearningModule } from "./modules/learning/learning.module";
import { MemoryModule } from "./modules/memory/memory.module";
import { OnboardingModule } from "./modules/onboarding/onboarding.module";
import { ProfilesModule } from "./modules/profiles/profiles.module";
import { UsersModule } from "./modules/users/users.module";
Expand All @@ -26,6 +27,7 @@ import { UsersModule } from "./modules/users/users.module";
DashboardModule,
HealthModule,
LearningModule,
MemoryModule,
OnboardingModule,
ProfilesModule,
UsersModule,
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/modules/learning/learning.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller, Get, Param, UseGuards } from "@nestjs/common";
import { Controller, Get, UseGuards } from "@nestjs/common";

import { CurrentUser } from "../auth/decorators/current-user.decorator";
import { JwtAuthGuard } from "../auth/guards/jwt-auth.guard";
Expand Down
46 changes: 25 additions & 21 deletions apps/api/src/modules/learning/learning.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import {
ConflictException,
ForbiddenException,
Injectable,
NotFoundException,
} from "@nestjs/common";
import { ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
import { Prisma } from "@prisma/client";

import { PrismaService } from "../../lib/prisma/prisma.service";

Expand Down Expand Up @@ -46,10 +42,6 @@ export class LearningService {
async startLesson(userId: string, lessonId: string) {
const lesson = await this.ensureLessonAccess(userId, lessonId);

const existing = await this.prisma.lessonProgress.findUnique({
where: { userId_lessonId: { lessonId, userId } },
});

const progress = await this.prisma.lessonProgress.upsert({
create: {
courseId: lesson.module.courseId,
Expand Down Expand Up @@ -81,7 +73,8 @@ export class LearningService {

const safeWatchedSec = Math.max(0, watchedSec);
const duration = lesson.durationSec ?? 0;
const computedProgress = duration > 0 ? Math.min(100, Math.round((safeWatchedSec / duration) * 100)) : 0;
const computedProgress =
duration > 0 ? Math.min(100, Math.round((safeWatchedSec / duration) * 100)) : 0;

const progress = await this.prisma.lessonProgress.upsert({
create: {
Expand All @@ -93,9 +86,13 @@ export class LearningService {
},
update: {
lastActivityAt: new Date(),
status: completed ? "COMPLETED" : current?.status === "COMPLETED" ? "COMPLETED" : "IN_PROGRESS",
status: completed
? "COMPLETED"
: current?.status === "COMPLETED"
? "COMPLETED"
: "IN_PROGRESS",
watchedSec: Math.max(current?.watchedSec ?? 0, safeWatchedSec),
completedAt: completed ? new Date() : current?.completedAt ?? null,
completedAt: completed ? new Date() : (current?.completedAt ?? null),
},
where: { userId_lessonId: { lessonId, userId } },
});
Expand Down Expand Up @@ -181,7 +178,8 @@ export class LearningService {
where: { courseId, userId },
});

const progressPercent = totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0;
const progressPercent =
totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0;

return {
completedLessons,
Expand Down Expand Up @@ -212,11 +210,16 @@ export class LearningService {
const totalLessons = await this.prisma.lesson.count({
where: { module: { courseId: latest.courseId } },
});
const progressPercent = totalLessons > 0
? Math.round((await this.prisma.lessonProgress.count({
where: { courseId: latest.courseId, status: "COMPLETED", userId },
}) / totalLessons) * 100)
: 0;
const progressPercent =
totalLessons > 0
? Math.round(
((await this.prisma.lessonProgress.count({
where: { courseId: latest.courseId, status: "COMPLETED", userId },
})) /
totalLessons) *
100,
)
: 0;

return {
courseId: latest.courseId,
Expand Down Expand Up @@ -270,7 +273,8 @@ export class LearningService {
where: { courseId, status: "COMPLETED", userId },
});

const progressPercent = totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0;
const progressPercent =
totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0;

await this.prisma.enrollment.updateMany({
data: { progressPercent },
Expand All @@ -295,7 +299,7 @@ export class LearningService {
await this.prisma.learningActivity.create({
data: {
lessonId,
metadata,
metadata: (metadata ?? null) as Prisma.InputJsonValue,
type,
userId,
courseId,
Expand Down
5 changes: 1 addition & 4 deletions apps/api/src/modules/learning/progress.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ export class ProgressController {

@UseGuards(JwtAuthGuard)
@Get("courses/:courseId")
getCourseProgress(
@CurrentUser() user: AuthenticatedUser,
@Param("courseId") courseId: string,
) {
getCourseProgress(@CurrentUser() user: AuthenticatedUser, @Param("courseId") courseId: string) {
return this.learningService.getCourseProgress(user.id, courseId);
}
}
77 changes: 77 additions & 0 deletions apps/api/src/modules/memory/memory.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Body, Controller, Get, Param, Post, Query, UseGuards } from "@nestjs/common";

import { CurrentUser } from "../auth/decorators/current-user.decorator";
import { JwtAuthGuard } from "../auth/guards/jwt-auth.guard";
import type { AuthenticatedUser } from "../auth/types/authenticated-user.type";
import { MemoryService } from "./memory.service";

@Controller("")
export class MemoryController {
constructor(private readonly memoryService: MemoryService) {}

@UseGuards(JwtAuthGuard)
@Get("quizzes")
getQuizzes() {
return this.memoryService.getQuizzes();
}

@UseGuards(JwtAuthGuard)
@Get("quizzes/:id")
getQuiz(@Param("id") id: string) {
return this.memoryService.getQuiz(id);
}

@UseGuards(JwtAuthGuard)
@Post("quizzes/:id/attempts")
submitAttempt(
@CurrentUser() user: AuthenticatedUser,
@Param("id") id: string,
@Body("answers") answers: Record<string, unknown>,
) {
return this.memoryService.submitAttempt(user.id, id, answers ?? {});
}

@UseGuards(JwtAuthGuard)
@Get("quizzes/:id/results")
getResults(@CurrentUser() user: AuthenticatedUser, @Param("id") id: string) {
return this.memoryService.getResults(user.id, id);
}

@UseGuards(JwtAuthGuard)
@Get("flashcards/me")
getMyFlashcards(@CurrentUser() user: AuthenticatedUser) {
return this.memoryService.getMyFlashcards(user.id);
}

@UseGuards(JwtAuthGuard)
@Post("flashcards")
createFlashcard(
@CurrentUser() user: AuthenticatedUser,
@Body()
body: { front: string; back: string; tags?: string[]; lessonId?: string; courseId?: string },
) {
return this.memoryService.createFlashcard(user.id, body);
}

@UseGuards(JwtAuthGuard)
@Post("flashcards/:id/review")
reviewFlashcard(
@CurrentUser() user: AuthenticatedUser,
@Param("id") id: string,
@Body("difficulty") difficulty: "FORGOT" | "HARD" | "GOOD" | "EASY",
) {
return this.memoryService.reviewFlashcard(user.id, id, difficulty);
}

@UseGuards(JwtAuthGuard)
@Get("flashcards/review-due")
getReviewDue(@CurrentUser() user: AuthenticatedUser, @Query("limit") limit?: string) {
return this.memoryService.getReviewDue(user.id, Number(limit ?? 10));
}

@UseGuards(JwtAuthGuard)
@Get("revision/dashboard")
getRevisionDashboard(@CurrentUser() user: AuthenticatedUser) {
return this.memoryService.getRevisionDashboard(user.id);
}
}
12 changes: 12 additions & 0 deletions apps/api/src/modules/memory/memory.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";

import { PrismaModule } from "../../lib/prisma/prisma.module";
import { MemoryController } from "./memory.controller";
import { MemoryService } from "./memory.service";

@Module({
imports: [PrismaModule],
controllers: [MemoryController],
providers: [MemoryService],
})
export class MemoryModule {}
Loading
Loading