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
34 changes: 19 additions & 15 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
# πŸ“Œ Pull Request Title

## Description

<!-- Briefly describe the changes made in this PR -->
<!-- Provide a brief summary of the changes in this PR. -->

## Type of Change
## Related Issues

- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
<!-- Link related issues using `Closes #issue_number` or `Fixes #issue_number` -->

## Checklist
## Changes Made

- [ ] List key changes made in this PR.

## How to Test

- [ ] Lint passes (`npm run lint` in both backend and frontend)
- [ ] Build passes (`npm run build` in both backend and frontend)
- [ ] Tests pass (`npm run test` in backend)
- [ ] E2E tests pass (`npm run test:e2e` in backend)
- [ ] No new TypeScript errors
- [ ] No console errors or warnings
<!-- Describe how a reviewer can test these changes. -->

## Related Issue
## Screenshots (if applicable)

<!-- Upload screenshots for UI-related changes. -->

## Checklist

Closes #<!-- issue number -->
- [ ] My code follows the project's coding style.
- [ ] I have tested these changes locally.
- [ ] Documentation has been updated where necessary.
27 changes: 27 additions & 0 deletions .github/workflows/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# πŸ“Œ Pull Request Title

## Description

<!-- Provide a brief summary of the changes in this PR. -->

## Related Issues

<!-- Link related issues using `Closes #issue_number` or `Fixes #issue_number` -->

## Changes Made

- [ ] List key changes made in this PR.

## How to Test

<!-- Describe how a reviewer can test these changes. -->

## Screenshots (if applicable)

<!-- Upload screenshots for UI-related changes. -->

## Checklist

- [ ] My code follows the project's coding style.
- [ ] I have tested these changes locally.
- [ ] Documentation has been updated where necessary.
Binary file added backend/lint_output.txt
Binary file not shown.
40 changes: 40 additions & 0 deletions backend/src/admin/admin-courses.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { UserRole } from '../users/entities/user.entity';
import { CreateCourseDto } from '../courses/dto/create-course.dto';
import { UpdateCourseDto } from '../courses/dto/update-course.dto';
import { CourseResponseDto } from '../courses/dto/course-response.dto';
import { LessonResponseDto } from '../lessons/dto/lesson-response.dto';
import { ReorderLessonsDto } from './dto/reorder-lessons.dto';
import { PaginatedResult } from '../common/services/pagination.service';

Expand Down Expand Up @@ -143,4 +144,43 @@ export class AdminCoursesController {
async unpublish(@Param('id') id: string): Promise<CourseResponseDto> {
return this.coursesService.unpublishCourse(id);
}

@Get(':id/lessons')
@ApiOperation({ summary: 'List all lessons for a course (admin)' })
async findAllLessons(
@Param('id') courseId: string,
@Query('page') page = 1,
@Query('limit') limit = 10,
): Promise<PaginatedResult<LessonResponseDto>> {
const result = await this.lessonsService.findAllByCoursePaginated(
courseId,
Number(page),
Number(limit),
false, // Fetch all regardless of published status
);
return {
...result,
data: result.data.map((lesson) => new LessonResponseDto(lesson)),
};
}

@Patch(':id/lessons/:lessonId/publish')
@ApiOperation({ summary: 'Publish a lesson (admin)' })
async publishLesson(
@Param('id') courseId: string,
@Param('lessonId') lessonId: string,
): Promise<LessonResponseDto> {
const lesson = await this.lessonsService.setPublished(lessonId, true);
return new LessonResponseDto(lesson);
}

@Patch(':id/lessons/:lessonId/unpublish')
@ApiOperation({ summary: 'Unpublish a lesson (admin)' })
async unpublishLesson(
@Param('id') courseId: string,
@Param('lessonId') lessonId: string,
): Promise<LessonResponseDto> {
const lesson = await this.lessonsService.setPublished(lessonId, false);
return new LessonResponseDto(lesson);
}
}
1 change: 0 additions & 1 deletion backend/src/admin/admin-dao.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
Query,
Request,
ParseIntPipe,
NotFoundException,
BadRequestException,
} from '@nestjs/common';
import {
Expand Down
8 changes: 4 additions & 4 deletions backend/src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import {
ApiResponse,
ApiBearerAuth,
} from '@nestjs/swagger';
import { Roles } from '../common/decorators/roles.decorator';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { RolesGuard } from '../common/guards/roles.guard';
import { UserRole } from '../common/enums/user-role.enum';
import { AnalyticsService } from './analytics.service';
import {
AnalyticsOverviewDto,
CoursePerformanceDto,
LearnerActivityPointDto,
TopLearnerDto,
} from './dto/analytics-response.dto';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { RolesGuard } from '../common/guards/roles.guard';
import { Roles } from '../common/decorators/roles.decorator';
import { UserRole } from '../common/enums/user-role.enum';

@ApiTags('Analytics')
@ApiBearerAuth('access-token')
Expand Down
1 change: 0 additions & 1 deletion backend/src/auth/dto/create-auth.dto.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
import { ApiProperty } from '@nestjs/swagger';
export class CreateAuthDto {}
1 change: 0 additions & 1 deletion backend/src/auth/dto/update-auth.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateAuthDto } from './create-auth.dto';
import { ApiProperty } from '@nestjs/swagger';

export class UpdateAuthDto extends PartialType(CreateAuthDto) {}
2 changes: 1 addition & 1 deletion backend/src/auth/strategies/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
}

async validate(payload: any) {
async validate(payload: { sub: string; email: string; role: string }) {
const user = await this.userService.findById(payload.sub);

if (!user) {
Expand Down
1 change: 0 additions & 1 deletion backend/src/certificates/certificates.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ const makeUserRepo = () => ({
const makeCourseRepo = () => ({
findOneBy: jest.fn(),
});

describe('CertificateService', () => {
let service: CertificateService;
let certRepo: ReturnType<typeof makeCertRepo>;
Expand Down
10 changes: 2 additions & 8 deletions backend/src/certificates/certificates.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,18 +326,12 @@ export class CertificateService {
certificateData,
} = issueCertificateDto;

const hashPayload = {
recipientName,
recipientEmail,
courseOrProgram,
issuedAt,
timestamp: Date.now(),
};
const issuedAtDate = new Date(issuedAt);

const certificateHash = this.generateCertificateHash(
recipientName + recipientEmail,
courseOrProgram,
new Date(issuedAt),
issuedAtDate,
);

const existing = await this.certificateRepository.findOne({
Expand Down
1 change: 0 additions & 1 deletion backend/src/certificates/dto/create-certificate.dto.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
import { ApiProperty } from '@nestjs/swagger';
export class CreateCertificateDto {}
1 change: 0 additions & 1 deletion backend/src/certificates/dto/update-certificate.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateCertificateDto } from './create-certificate.dto';
import { ApiProperty } from '@nestjs/swagger';

export class UpdateCertificateDto extends PartialType(CreateCertificateDto) {}
1 change: 0 additions & 1 deletion backend/src/common/dto/paggination.dto.ts

This file was deleted.

1 change: 0 additions & 1 deletion backend/src/courses/courses.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { AuthGuard } from '@nestjs/passport';

import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { RolesGuard } from '../common/guards/roles.guard';
import { PaginationDto } from '../common/dto/pagination.dto';
import { CourseFilterDto } from './dto/course-filter.dto';
import { CoursesService } from './courses.service';
import { UserRole } from '../common/enums/user-role.enum';
Expand Down
3 changes: 0 additions & 3 deletions backend/src/courses/courses.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
import { Course } from './entities/course.entity';
import { CourseRegistration } from './entities/course-registration.entity';
import { PaginationService } from '../common/services/pagination.service';
import { Lesson } from '../lessons/entities/lesson.entity';
import { Progress } from '../progress/entities/progress.entity';
import { NotificationsService } from '../notifications/notifications.service';

const now = new Date();

Expand Down Expand Up @@ -111,11 +108,11 @@
useValue: regRepo,
},
{
provide: getRepositoryToken(Lesson),

Check warning on line 111 in backend/src/courses/courses.service.spec.ts

View workflow job for this annotation

GitHub Actions / Backend

Unsafe argument of type error typed assigned to a parameter of type `EntityClassOrSchema`
useValue: lessonRepo,
},
{
provide: getRepositoryToken(Progress),

Check warning on line 115 in backend/src/courses/courses.service.spec.ts

View workflow job for this annotation

GitHub Actions / Backend

Unsafe argument of type error typed assigned to a parameter of type `EntityClassOrSchema`
useValue: progressRepo,
},
{
Expand Down
26 changes: 18 additions & 8 deletions backend/src/courses/dto/course-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,54 @@ export class CourseResponseDto {
@IsNotEmpty()
id: string;

@ApiProperty()
@ApiProperty({ example: 'Intro to Blockchain', description: 'title field' })
@IsString()
@IsNotEmpty()
title: string;

@ApiProperty({
example: 'A concise description of the resource.',
example: 'A concise description of the course.',
description: 'description field',
})
@IsString()
@IsNotEmpty()
description: string;

@ApiProperty()
@ApiProperty({ example: true, required: false })
@IsBoolean()
published: boolean;
@IsOptional()
published?: boolean;

@ApiProperty({ example: 'Beginner', required: false, nullable: true })
@ApiProperty({
example: 'Beginner',
description: 'difficulty field',
required: false,
nullable: true,
})
@IsOptional()
@IsString()
difficulty: string | null;

@ApiProperty({ example: ['bitcoin', 'defi'], type: [String] })
@ApiProperty({
example: ['blockchain', 'tech'],
description: 'tags field',
type: [String],
})
@IsArray()
@IsString({ each: true })
tags: string[];

@ApiProperty({
example: 'https://example.com/thumb.jpg',
description: 'thumbnailUrl field',
required: false,
nullable: true,
})
@IsOptional()
@IsUrl()
thumbnailUrl: string | null;

@ApiProperty({ example: 42, description: 'Number of enrolled users' })
@ApiProperty({ example: 120, description: 'enrollmentCount field' })
@IsInt()
enrollmentCount: number;

Expand All @@ -73,7 +84,6 @@ export class CourseResponseDto {
@IsDate()
updatedAt: Date;

/** Present on GET /courses when the request includes a valid JWT */
@ApiProperty({
example: true,
description: 'isEnrolled field',
Expand Down
5 changes: 1 addition & 4 deletions backend/src/courses/dto/create-course.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ export class CreateCourseDto {
@IsNotEmpty()
title: string;

@ApiProperty({
example: 'A concise description of the resource.',
description: 'description field',
})
@ApiProperty({ example: 'A concise description of the course.' })
@IsString()
@IsNotEmpty()
description: string;
Expand Down
1 change: 0 additions & 1 deletion backend/src/courses/dto/update-course.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateCourseDto } from './create-course.dto';
import { ApiProperty } from '@nestjs/swagger';

export class UpdateCourseDto extends PartialType(CreateCourseDto) {}
4 changes: 2 additions & 2 deletions backend/src/currencies/currencies.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import { Repository } from 'typeorm';
import { CurrencyEntry, CurrencyType } from './entities/currency-entry.entity';
import {
CreateCurrencyDto,
Expand Down Expand Up @@ -43,7 +43,7 @@

const formattedItems = items.map((item) => {
// Omit detailed historical data in find all list to reduce payload
const { historicalData, ...rest } = item;
const { historicalData: _historicalData, ...rest } = item;

Check warning on line 46 in backend/src/currencies/currencies.service.ts

View workflow job for this annotation

GitHub Actions / Backend

'_historicalData' is assigned a value but never used
return rest;
});

Expand Down
12 changes: 11 additions & 1 deletion backend/src/lessons/dto/create-lesson.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IsNumber,
Min,
IsUrl,
IsBoolean,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

Expand All @@ -30,7 +31,16 @@ export class CreateLessonDto {
videoUrl?: string;

@ApiProperty({
example: '123e4567-e89b-12d3-a456-426614174000',
example: true,
description: 'published field',
required: false,
})
@IsBoolean()
@IsOptional()
published?: boolean;

@ApiProperty({
example: 0,
description: 'videoStartTimestamp field',
required: false,
})
Expand Down
7 changes: 5 additions & 2 deletions backend/src/lessons/dto/lesson-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ export class LessonResponseDto {
title: string;
@ApiProperty({ example: 'example', description: 'content field' })
content: string;
@ApiProperty({ example: true, description: 'published field' })
published: boolean;
@ApiProperty({
example: '123e4567-e89b-12d3-a456-426614174000',
example: 'https://example.com/video.mp4',
description: 'videoUrl field',
})
videoUrl: string | null;
@ApiProperty({
example: '123e4567-e89b-12d3-a456-426614174000',
example: 0,
description: 'videoStartTimestamp field',
})
videoStartTimestamp: number | null;
Expand Down Expand Up @@ -61,6 +63,7 @@ export class LessonResponseDto {
this.id = lesson.id;
this.title = lesson.title;
this.content = lesson.content;
this.published = lesson.published !== undefined ? lesson.published : true;
this.videoUrl = lesson.videoUrl || null;
this.videoStartTimestamp = lesson.videoStartTimestamp || null;
this.order = lesson.order;
Expand Down
1 change: 0 additions & 1 deletion backend/src/lessons/dto/update-lesson.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateLessonDto } from './create-lesson.dto';
import { ApiProperty } from '@nestjs/swagger';

export class UpdateLessonDto extends PartialType(CreateLessonDto) {}
Loading
Loading