Skip to content

Commit f738f10

Browse files
authored
Merge pull request #87 from teacoder-team/dev
refactor: course module relocation and infra updates
2 parents 31f6910 + cbfde8f commit f738f10

26 files changed

Lines changed: 174 additions & 394 deletions

.github/dependabot.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: 'npm'
4+
directory: '/'
5+
schedule:
6+
interval: 'weekly'
7+
day: 'monday'
8+
open-pull-requests-limit: 10
9+
labels:
10+
- 'dependencies'
11+
- 'npm'
12+
groups:
13+
dependencies:
14+
patterns:
15+
- '*'
16+
exclude-patterns:
17+
- 'generated/*'
18+
19+
- package-ecosystem: 'github-actions'
20+
directory: '/'
21+
schedule:
22+
interval: 'monthly'
23+
labels:
24+
- 'dependencies'
25+
- 'github-actions'

src/api/api.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { MfaModule } from './auth/mfa/mfa.module'
1818
import { PasskeyModule } from './auth/passkey/passkey.module'
1919
import { SessionModule } from './auth/session/session.module'
2020
import { SsoModule } from './auth/sso/sso.module'
21+
import { CourseModule } from './course/course.module'
2122
import { LessonModule } from './lesson/lesson.module'
2223
import { PaymentModule } from './payment/payment.module'
2324
import { ProgressModule } from './progress/progress.module'
@@ -53,7 +54,8 @@ import { UsersModule } from './users/users.module'
5354
LessonModule,
5455
ProgressModule,
5556
StatisticsModule,
56-
PaymentModule
57+
PaymentModule,
58+
CourseModule
5759
],
5860
providers: [
5961
{

src/domains/course/interfaces/rest/course.controller.ts renamed to src/api/course/course.controller.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,19 @@ import {
2727
} from '@/shared/decorators'
2828
import { Authorization } from '@/shared/decorators/auth.decorator'
2929

30-
import { CourseResponse } from '../../application/dto/course.response'
31-
import { CoursesResponse } from '../../application/dto/courses.response'
32-
import { CreateCourseRequest } from '../../application/dto/create-course.request'
33-
import { CreateCourseResponse } from '../../application/dto/create-course.response'
34-
import { GenerateDownloadLinkResponse } from '../../application/dto/generate-download-link.response'
35-
import { CourseApplicationService } from '../../application/services/course.application.service'
30+
import { CourseService } from './course.service'
31+
import { CourseResponse } from './dto/course.dto'
32+
import { CoursesResponse } from './dto/courses.dto'
33+
import {
34+
CreateCourseRequest,
35+
CreateCourseResponse
36+
} from './dto/create-course.dto'
37+
import { GenerateDownloadLinkResponse } from './dto/generate-download-link.dto'
3638

3739
@ApiTags('Course')
3840
@Controller('courses')
3941
export class CourseController {
40-
public constructor(private readonly app: CourseApplicationService) {}
42+
public constructor(private readonly courseService: CourseService) {}
4143

4244
@ApiOperation({
4345
summary: 'Fetch All Courses',
@@ -49,7 +51,7 @@ export class CourseController {
4951
@Get()
5052
@HttpCode(HttpStatus.OK)
5153
public async getAll() {
52-
return this.app.getAll()
54+
return this.courseService.getAll()
5355
}
5456

5557
@ApiOperation({
@@ -63,7 +65,7 @@ export class CourseController {
6365
@Get('popular')
6466
@HttpCode(HttpStatus.OK)
6567
public async getPopular() {
66-
return this.app.getPopular()
68+
return this.courseService.getPopular()
6769
}
6870

6971
@ApiOperation({
@@ -76,7 +78,7 @@ export class CourseController {
7678
@Get(':slug')
7779
@HttpCode(HttpStatus.OK)
7880
public async getBySlug(@Param('slug') slug: string) {
79-
return this.app.getBySlug(slug)
81+
return this.courseService.getBySlug(slug)
8082
}
8183

8284
@ApiOperation({
@@ -89,7 +91,7 @@ export class CourseController {
8991
@Get(':id/lessons')
9092
@HttpCode(HttpStatus.OK)
9193
public async getCourseLessons(@Param('id') id: string) {
92-
return this.app.getCourseLessons(id)
94+
return this.courseService.getCourseLessons(id)
9395
}
9496

9597
@ApiOperation({
@@ -102,7 +104,7 @@ export class CourseController {
102104
@Patch(':id/views')
103105
@HttpCode(HttpStatus.NO_CONTENT)
104106
public async incrementViews(@Param('id') id: string) {
105-
await this.app.incrementViews(id)
107+
await this.courseService.incrementViews(id)
106108
}
107109

108110
@ApiOperation({
@@ -120,7 +122,7 @@ export class CourseController {
120122
@Param('id') id: string,
121123
@Authorized() user: User
122124
) {
123-
return await this.app.generateDownloadLink(id, user)
125+
return await this.courseService.generateDownloadLink(id, user)
124126
}
125127

126128
@ApiOperation({
@@ -140,11 +142,17 @@ export class CourseController {
140142
@UserAgent() userAgent: string,
141143
@Res() res: Response
142144
) {
143-
const course = await this.app.resolveDownloadToken(token, ip, userAgent)
145+
const course = await this.courseService.resolveDownloadToken(
146+
token,
147+
ip,
148+
userAgent
149+
)
144150

145151
try {
146152
const { stream, contentType } =
147-
await this.app.fetchAttachmentStream(course.attachment)
153+
await this.courseService.fetchAttachmentStream(
154+
course.attachment
155+
)
148156

149157
res.setHeader(
150158
'Content-Disposition',
@@ -171,6 +179,6 @@ export class CourseController {
171179
@Post()
172180
@HttpCode(HttpStatus.OK)
173181
public async create(@Body() dto: CreateCourseRequest) {
174-
return this.app.create(dto)
182+
return this.courseService.create(dto)
175183
}
176184
}

src/api/course/course.module.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { HttpModule } from '@nestjs/axios'
2+
import { Module } from '@nestjs/common'
3+
4+
import { CourseController } from './course.controller'
5+
import { CourseRepository } from './course.repository'
6+
import { CourseService } from './course.service'
7+
8+
@Module({
9+
imports: [HttpModule.register({})],
10+
controllers: [CourseController],
11+
providers: [CourseService, CourseRepository]
12+
})
13+
export class CourseModule {}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Injectable } from '@nestjs/common'
2+
import { type Course, DownloadLog, Lesson, Prisma } from '@prisma/generated'
3+
4+
import { PrismaService } from '@/infra/prisma/prisma.service'
5+
6+
@Injectable()
7+
export class CourseRepository {
8+
public constructor(private readonly prismaService: PrismaService) {}
9+
10+
public async findAll(params: {
11+
skip?: number
12+
take?: number
13+
cursor?: Prisma.CourseWhereUniqueInput
14+
where?: Prisma.CourseWhereInput
15+
orderBy?: Prisma.CourseOrderByWithRelationInput
16+
}): Promise<Course[]> {
17+
return this.prismaService.course.findMany(params)
18+
}
19+
20+
public async findPopular(limit?: number): Promise<Course[]> {
21+
return this.prismaService.course.findMany({
22+
take: limit,
23+
orderBy: {
24+
views: 'desc'
25+
}
26+
})
27+
}
28+
29+
public async findBySlug(slug: string): Promise<Course | null> {
30+
return this.prismaService.course.findUnique({
31+
where: { slug }
32+
})
33+
}
34+
35+
public async findById(id: string): Promise<Course | null> {
36+
return this.prismaService.course.findUnique({
37+
where: { id }
38+
})
39+
}
40+
41+
public async findLessons(courseId: string): Promise<Lesson[]> {
42+
return this.prismaService.lesson.findMany({
43+
where: { courseId },
44+
orderBy: { position: 'asc' }
45+
})
46+
}
47+
48+
public async incrementViews(id: string): Promise<void> {
49+
await this.prismaService.course.update({
50+
where: { id },
51+
data: {
52+
views: { increment: 1 }
53+
}
54+
})
55+
}
56+
57+
public async create(data: Prisma.CourseCreateInput): Promise<Course> {
58+
return this.prismaService.course.create({
59+
data
60+
})
61+
}
62+
63+
public async logDownload(
64+
data: Prisma.DownloadLogUncheckedCreateInput
65+
): Promise<DownloadLog> {
66+
return this.prismaService.downloadLog.create({
67+
data
68+
})
69+
}
70+
}

src/domains/course/application/services/course.application.service.ts renamed to src/api/course/course.service.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,45 @@
11
import { HttpService } from '@nestjs/axios'
2-
import { Inject, Injectable, NotFoundException } from '@nestjs/common'
2+
import { Injectable, NotFoundException } from '@nestjs/common'
33
import { ConfigService } from '@nestjs/config'
44
import { User } from '@prisma/generated'
55
import { randomBytes } from 'crypto'
66
import { firstValueFrom } from 'rxjs'
77

8-
import type { AllConfigs } from '@/config/definitions'
98
import { RedisService } from '@/infra/redis/redis.service'
10-
import { slugify } from '@/shared/utils/slugify'
9+
import { slugify } from '@/shared/utils'
1110

12-
import { CourseRepository } from '../../domain/repositories/course.repository.interface'
13-
import { DownloadLogRepository } from '../../domain/repositories/download-log.repository.interface'
11+
import { CourseRepository } from './course.repository'
1412

1513
@Injectable()
16-
export class CourseApplicationService {
14+
export class CourseService {
1715
public constructor(
18-
@Inject('CourseRepository')
19-
private readonly courseRepo: CourseRepository,
20-
@Inject('DownloadLogRepository')
21-
private readonly downloadLogRepo: DownloadLogRepository,
16+
private readonly courseRepository: CourseRepository,
2217
private readonly redisService: RedisService,
23-
private readonly configService: ConfigService<AllConfigs>,
18+
private readonly configService: ConfigService,
2419
private readonly httpService: HttpService
2520
) {}
2621

2722
public async getAll() {
28-
return this.courseRepo.findAll({
29-
isPublished: true,
23+
return this.courseRepository.findAll({
24+
where: {
25+
isPublished: true
26+
},
3027
orderBy: {
31-
field: 'createdAt',
32-
direction: 'desc'
28+
createdAt: 'desc'
3329
}
3430
})
3531
}
3632

3733
public async getPopular() {
38-
return this.courseRepo.findPopular(4)
34+
return this.courseRepository.findPopular(4)
3935
}
4036

4137
public async getBySlug(slug: string) {
4238
const cached = await this.redisService.get(`courses:${slug}`)
4339

4440
if (cached) return JSON.parse(cached)
4541

46-
const course = await this.courseRepo.findBySlug(slug)
42+
const course = await this.courseRepository.findBySlug(slug)
4743

4844
if (!course || !course.isPublished)
4945
throw new NotFoundException('Course not found')
@@ -71,22 +67,22 @@ export class CourseApplicationService {
7167
}
7268

7369
public async getCourseLessons(id: string) {
74-
const course = await this.courseRepo.findById(id)
70+
const course = await this.courseRepository.findById(id)
7571

7672
if (!course || !course.isPublished)
7773
throw new NotFoundException('Course not found')
7874

79-
return this.courseRepo.findLessons(course.id)
75+
return this.courseRepository.findLessons(course.id)
8076
}
8177

8278
public async incrementViews(id: string) {
83-
await this.courseRepo.incrementViews(id)
79+
await this.courseRepository.incrementViews(id)
8480

8581
return true
8682
}
8783

8884
public async generateDownloadLink(id: string, user: User) {
89-
const course = await this.courseRepo.findById(id)
85+
const course = await this.courseRepository.findById(id)
9086

9187
if (!course || !course.attachment)
9288
throw new NotFoundException('Course not found')
@@ -116,11 +112,11 @@ export class CourseApplicationService {
116112

117113
const { courseId, userId } = JSON.parse(data)
118114

119-
const course = await this.courseRepo.findById(courseId)
115+
const course = await this.courseRepository.findById(courseId)
120116

121117
if (!course) throw new NotFoundException('Course not found')
122118

123-
await this.downloadLogRepo.logDownload({
119+
await this.courseRepository.logDownload({
124120
token,
125121
ip,
126122
userAgent,
@@ -155,7 +151,7 @@ export class CourseApplicationService {
155151
public async create(request: { title: string }) {
156152
const slug = slugify(request.title)
157153

158-
const created = await this.courseRepo.create({
154+
const created = await this.courseRepository.create({
159155
title: request.title,
160156
slug
161157
})

src/domains/course/application/dto/course.response.ts renamed to src/api/course/dto/course.dto.ts

File renamed without changes.

src/domains/course/application/dto/courses.response.ts renamed to src/api/course/dto/courses.dto.ts

File renamed without changes.

src/domains/course/application/dto/create-course.request.ts renamed to src/api/course/dto/create-course.dto.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ export class CreateCourseRequest {
1111
@MaxLength(100, { message: 'Название не должно превышать 100 символов' })
1212
public title: string
1313
}
14+
15+
export class CreateCourseResponse {
16+
@ApiProperty({
17+
description: 'Unique identifier',
18+
example: 'd364587c-b690-43c5-8a7f-3c391eee5b97'
19+
})
20+
public id: string
21+
}

src/domains/course/application/dto/generate-download-link.response.ts renamed to src/api/course/dto/generate-download-link.dto.ts

File renamed without changes.

0 commit comments

Comments
 (0)