Skip to content

Commit 19bd63e

Browse files
authored
Merge pull request #335 from sammajayi/feat/quiz-retake-limit
feat/quiz-retake-limit
2 parents 8063aaf + 8178e66 commit 19bd63e

98 files changed

Lines changed: 2530 additions & 1331 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/eslint.config.mjs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,16 @@ export default tseslint.config(
2626
},
2727
{
2828
rules: {
29+
'no-unused-vars': 'off',
2930
'@typescript-eslint/no-explicit-any': 'off',
3031
'@typescript-eslint/no-floating-promises': 'warn',
32+
'@typescript-eslint/no-unused-vars': 'warn',
3133
'@typescript-eslint/no-unsafe-argument': 'warn',
3234
'@typescript-eslint/no-unsafe-assignment': 'off',
33-
'@typescript-eslint/no-unsafe-member-access': 'off',
3435
'@typescript-eslint/no-unsafe-call': 'off',
36+
'@typescript-eslint/no-unsafe-member-access': 'off',
3537
'@typescript-eslint/no-unsafe-return': 'off',
36-
},
37-
},
38-
{
39-
files: ['**/*.spec.ts', '**/*.e2e-spec.ts'],
40-
rules: {
38+
'@typescript-eslint/require-await': 'off',
4139
'@typescript-eslint/unbound-method': 'off',
4240
},
4341
},

backend/src/admin/admin-courses.controller.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,27 @@ export class AdminCoursesController {
118118

119119
@Patch(':id/publish')
120120
@ApiOperation({ summary: 'Publish a course (admin)' })
121-
@ApiResponse({ status: 200, description: 'Course published successfully', type: CourseResponseDto })
122-
@ApiResponse({ status: 400, description: 'Cannot publish a course with no lessons' })
121+
@ApiResponse({
122+
status: 200,
123+
description: 'Course published successfully',
124+
type: CourseResponseDto,
125+
})
126+
@ApiResponse({
127+
status: 400,
128+
description: 'Cannot publish a course with no lessons',
129+
})
123130
@ApiResponse({ status: 404, description: 'Course not found' })
124131
async publish(@Param('id') id: string): Promise<CourseResponseDto> {
125132
return this.coursesService.publishCourse(id);
126133
}
127134

128135
@Patch(':id/unpublish')
129136
@ApiOperation({ summary: 'Unpublish a course (admin)' })
130-
@ApiResponse({ status: 200, description: 'Course unpublished successfully', type: CourseResponseDto })
137+
@ApiResponse({
138+
status: 200,
139+
description: 'Course unpublished successfully',
140+
type: CourseResponseDto,
141+
})
131142
@ApiResponse({ status: 404, description: 'Course not found' })
132143
async unpublish(@Param('id') id: string): Promise<CourseResponseDto> {
133144
return this.coursesService.unpublishCourse(id);

backend/src/admin/admin-dao.controller.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import {
1919
ApiQuery,
2020
} from '@nestjs/swagger';
2121
import { DAOService } from '../dao/dao.service';
22-
import { DAOProposal, ProposalStatus } from '../dao/entities/dao-proposal.entity';
22+
import {
23+
DAOProposal,
24+
ProposalStatus,
25+
} from '../dao/entities/dao-proposal.entity';
2326
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
2427
import { RolesGuard } from '../common/guards/roles.guard';
2528
import { Roles } from '../common/decorators/roles.decorator';
@@ -52,14 +55,16 @@ export class AdminDAOController {
5255
) {}
5356

5457
@Get('proposals')
55-
@ApiOperation({ summary: 'Get all proposals with moderation details (admin)' })
58+
@ApiOperation({
59+
summary: 'Get all proposals with moderation details (admin)',
60+
})
5661
@ApiQuery({ name: 'page', required: false, type: Number })
5762
@ApiQuery({ name: 'limit', required: false, type: Number })
58-
@ApiQuery({
59-
name: 'status',
60-
required: false,
63+
@ApiQuery({
64+
name: 'status',
65+
required: false,
6166
enum: ProposalStatus,
62-
description: 'Filter by proposal status'
67+
description: 'Filter by proposal status',
6368
})
6469
@ApiResponse({ status: 200, description: 'Proposals retrieved successfully' })
6570
@ApiResponse({ status: 403, description: 'Admin access required' })
@@ -83,7 +88,7 @@ export class AdminDAOController {
8388

8489
const [proposals, total] = await query.getManyAndCount();
8590

86-
const proposalsWithEmails = proposals.map(proposal => ({
91+
const proposalsWithEmails = proposals.map((proposal) => ({
8792
...proposal,
8893
proposerEmail: proposal.proposer?.email || 'Unknown',
8994
moderatorEmail: proposal.moderator?.email || undefined,

backend/src/admin/admin.module.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import { TypeOrmModule } from '@nestjs/typeorm';
88
import { User } from '../users/entities/user.entity';
99

1010
@Module({
11-
imports: [CoursesModule, LessonsModule, DAOModule, TypeOrmModule.forFeature([User])],
11+
imports: [
12+
CoursesModule,
13+
LessonsModule,
14+
DAOModule,
15+
TypeOrmModule.forFeature([User]),
16+
],
1217
controllers: [AdminCoursesController, AdminDAOController],
1318
})
1419
export class AdminModule {}

backend/src/admin/dto/reorder-lessons.dto.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { IsArray, IsString, ArrayNotEmpty } from 'class-validator';
22
import { ApiProperty } from '@nestjs/swagger';
33

44
export class ReorderLessonsDto {
5-
@ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', description: 'orderedIds field' })
5+
@ApiProperty({
6+
example: '123e4567-e89b-12d3-a456-426614174000',
7+
description: 'orderedIds field',
8+
})
69
@IsArray()
710
@ArrayNotEmpty()
811
@IsString({ each: true })
912
orderedIds: string[];
1013
}
11-

backend/src/analytics/analytics.controller.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import { Controller, Get, UseGuards } from '@nestjs/common';
2+
import {
3+
ApiTags,
4+
ApiOperation,
5+
ApiResponse,
6+
ApiBearerAuth,
7+
} from '@nestjs/swagger';
18
import {
29
Controller,
310
Get,
@@ -33,36 +40,64 @@ export class AnalyticsController {
3340

3441
@Get('overview')
3542
@ApiOperation({ summary: 'Get analytics overview' })
36-
@ApiResponse({ status: 200, description: 'Analytics overview retrieved successfully', type: AnalyticsOverviewDto })
43+
@ApiResponse({
44+
status: 200,
45+
description: 'Analytics overview retrieved successfully',
46+
type: AnalyticsOverviewDto,
47+
})
3748
@ApiResponse({ status: 401, description: 'Unauthorized' })
38-
@ApiResponse({ status: 403, description: 'Forbidden - Admin access required' })
49+
@ApiResponse({
50+
status: 403,
51+
description: 'Forbidden - Admin access required',
52+
})
3953
async getOverview(): Promise<AnalyticsOverviewDto> {
4054
return this.analyticsService.getOverview();
4155
}
4256

4357
@Get('course-performance')
4458
@ApiOperation({ summary: 'Get course performance analytics' })
45-
@ApiResponse({ status: 200, description: 'Course performance data retrieved successfully', type: [CoursePerformanceDto] })
59+
@ApiResponse({
60+
status: 200,
61+
description: 'Course performance data retrieved successfully',
62+
type: [CoursePerformanceDto],
63+
})
4664
@ApiResponse({ status: 401, description: 'Unauthorized' })
47-
@ApiResponse({ status: 403, description: 'Forbidden - Admin access required' })
65+
@ApiResponse({
66+
status: 403,
67+
description: 'Forbidden - Admin access required',
68+
})
4869
async getCoursePerformance(): Promise<CoursePerformanceDto[]> {
4970
return this.analyticsService.getCoursePerformance();
5071
}
5172

5273
@Get('learner-activity')
5374
@ApiOperation({ summary: 'Get learner activity analytics' })
54-
@ApiResponse({ status: 200, description: 'Learner activity data retrieved successfully', type: [LearnerActivityPointDto] })
75+
@ApiResponse({
76+
status: 200,
77+
description: 'Learner activity data retrieved successfully',
78+
type: [LearnerActivityPointDto],
79+
})
5580
@ApiResponse({ status: 401, description: 'Unauthorized' })
56-
@ApiResponse({ status: 403, description: 'Forbidden - Admin access required' })
81+
@ApiResponse({
82+
status: 403,
83+
description: 'Forbidden - Admin access required',
84+
})
5785
async getLearnerActivity(): Promise<LearnerActivityPointDto[]> {
5886
return this.analyticsService.getLearnerActivity();
5987
}
6088

6189
@Get('top-learners')
6290
@ApiOperation({ summary: 'Get top learners analytics' })
63-
@ApiResponse({ status: 200, description: 'Top learners data retrieved successfully', type: [TopLearnerDto] })
91+
@ApiResponse({
92+
status: 200,
93+
description: 'Top learners data retrieved successfully',
94+
type: [TopLearnerDto],
95+
})
6496
@ApiResponse({ status: 401, description: 'Unauthorized' })
65-
@ApiResponse({ status: 403, description: 'Forbidden - Admin access required' })
97+
@ApiResponse({
98+
status: 403,
99+
description: 'Forbidden - Admin access required',
100+
})
66101
async getTopLearners(): Promise<TopLearnerDto[]> {
67102
return this.analyticsService.getTopLearners();
68103
}

backend/src/analytics/dto/analytics-response.dto.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export class AnalyticsOverviewDto {
1515
}
1616

1717
export class CoursePerformanceDto {
18-
@ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', description: 'courseId field' })
18+
@ApiProperty({
19+
example: '123e4567-e89b-12d3-a456-426614174000',
20+
description: 'courseId field',
21+
})
1922
courseId: string;
2023
@ApiProperty({ example: 'Intro to Blockchain', description: 'title field' })
2124
title: string;
@@ -37,7 +40,10 @@ export class LearnerActivityPointDto {
3740
}
3841

3942
export class TopLearnerDto {
40-
@ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', description: 'userId field' })
43+
@ApiProperty({
44+
example: '123e4567-e89b-12d3-a456-426614174000',
45+
description: 'userId field',
46+
})
4147
userId: string;
4248
@ApiProperty({ example: 'Jane Doe', description: 'username field' })
4349
username: string | null;
@@ -48,4 +54,3 @@ export class TopLearnerDto {
4854
@ApiProperty({ example: 1, description: 'certificatesEarned field' })
4955
certificatesEarned: number;
5056
}
51-

backend/src/app.module.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ import { WebhooksModule } from './webhooks/webhooks.module';
3333
ConfigModule.forRoot({
3434
isGlobal: true,
3535
validationSchema: Joi.object({
36-
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
36+
NODE_ENV: Joi.string()
37+
.valid('development', 'production', 'test')
38+
.default('development'),
3739
PORT: Joi.number().default(3001),
3840

3941
JWT_SECRET: Joi.string().min(32).required(),
@@ -56,7 +58,9 @@ import { WebhooksModule } from './webhooks/webhooks.module';
5658
SMTP_USER: Joi.string().optional().allow(''),
5759
SMTP_PASS: Joi.string().optional().allow(''),
5860
SMTP_FROM_NAME: Joi.string().default('ByteChain Academy'),
59-
SMTP_FROM_EMAIL: Joi.string().email().default('noreply@bytechain.academy'),
61+
SMTP_FROM_EMAIL: Joi.string()
62+
.email()
63+
.default('noreply@bytechain.academy'),
6064

6165
AVATAR_UPLOAD_PATH: Joi.string().default('uploads/avatars'),
6266
MAX_AVATAR_SIZE_MB: Joi.number().default(2),

backend/src/auth/auth.controller.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
22
import { Throttle } from '@nestjs/throttler';
3-
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
3+
import {
4+
ApiTags,
5+
ApiOperation,
6+
ApiResponse,
7+
ApiBearerAuth,
8+
} from '@nestjs/swagger';
49

510
import { AuthService } from './auth.service';
611
import { RegisterDto } from './dto/register.dto';
@@ -29,9 +34,18 @@ export class AuthController {
2934
@Throttle({ default: { limit: 5, ttl: 900 } })
3035
@HttpCode(HttpStatus.OK)
3136
@ApiOperation({ summary: 'Authenticate user and return JWT token' })
32-
@ApiResponse({ status: 200, description: 'Login successful, returns JWT token' })
33-
@ApiResponse({ status: 400, description: 'Bad request - invalid credentials' })
34-
@ApiResponse({ status: 401, description: 'Unauthorized - invalid credentials' })
37+
@ApiResponse({
38+
status: 200,
39+
description: 'Login successful, returns JWT token',
40+
})
41+
@ApiResponse({
42+
status: 400,
43+
description: 'Bad request - invalid credentials',
44+
})
45+
@ApiResponse({
46+
status: 401,
47+
description: 'Unauthorized - invalid credentials',
48+
})
3549
async login(@Body() loginDto: LoginDto) {
3650
return this.authService.login(loginDto);
3751
}
@@ -62,8 +76,14 @@ export class AuthController {
6276
@HttpCode(HttpStatus.OK)
6377
@ApiOperation({ summary: 'Reset user password using reset token' })
6478
@ApiResponse({ status: 200, description: 'Password reset successfully' })
65-
@ApiResponse({ status: 400, description: 'Bad request - invalid token or password' })
66-
@ApiResponse({ status: 401, description: 'Unauthorized - invalid reset token' })
79+
@ApiResponse({
80+
status: 400,
81+
description: 'Bad request - invalid token or password',
82+
})
83+
@ApiResponse({
84+
status: 401,
85+
description: 'Unauthorized - invalid reset token',
86+
})
6787
async resetPassword(@Body() resetPasswordDto: ResetPasswordDto) {
6888
return this.authService.resetPassword(resetPasswordDto);
6989
}

backend/src/auth/auth.service.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { JwtService } from '@nestjs/jwt';
3-
import { ConflictException, UnauthorizedException, NotFoundException } from '@nestjs/common';
3+
import {
4+
ConflictException,
5+
UnauthorizedException,
6+
NotFoundException,
7+
} from '@nestjs/common';
48
import { ConfigService } from '@nestjs/config';
59
import { AuthService } from './auth.service';
610
import { UserService } from '../users/users.service';

0 commit comments

Comments
 (0)