1
- import { Injectable } from '@nestjs/common' ;
1
+ import { Inject , Injectable } from '@nestjs/common' ;
2
2
import { AuthorizedUserDto } from 'src/auth/dto/authorized-user-dto' ;
3
3
import { CreateCourseReviewRequestDto } from './dto/create-course-review-request.dto' ;
4
4
import { CourseReviewResponseDto } from './dto/course-review-response.dto' ;
@@ -9,15 +9,22 @@ import {
9
9
ReviewDto ,
10
10
} from './dto/get-course-reviews-response.dto' ;
11
11
import { GetCourseReviewSummaryResponseDto } from './dto/get-course-review-summary-response.dto' ;
12
- import { EntityManager , Repository } from 'typeorm' ;
12
+ import { Brackets , EntityManager , Repository } from 'typeorm' ;
13
13
import { CourseReviewRecommendEntity } from 'src/entities/course-review-recommend.entity' ;
14
14
import { CourseReviewEntity } from 'src/entities/course-review.entity' ;
15
15
import { CourseReviewsFilterDto } from './dto/course-reviews-filter.dto' ;
16
16
import { CourseService } from 'src/course/course.service' ;
17
17
import { InjectRepository } from '@nestjs/typeorm' ;
18
18
import { PointService } from 'src/user/point.service' ;
19
19
import { throwKukeyException } from 'src/utils/exception.util' ;
20
-
20
+ import { SearchCourseReviewsWithKeywordRequest } from './dto/search-course-reviews-with-keyword-request.dto' ;
21
+ import { SearchCourseReviewsWithKeywordResponse } from './dto/search-course-reviews-with-keyword-response.dto' ;
22
+ import { PaginatedCourseReviewsDto } from './dto/paginated-course-reviews.dto' ;
23
+ import { CourseEntity } from 'src/entities/course.entity' ;
24
+ import { CourseReviewCriteriaStrategy } from './strategy/course-review-criteria-strategy' ;
25
+ import { GetCoursesWithCourseReviewsRequestDto } from './dto/get-courses-with-course-reviews-request.dto' ;
26
+ import { CourseReviewCriteria } from 'src/enums/course-review-criteria.enum' ;
27
+ import { GetCoursesWithCourseReviewsResponseDto } from './dto/get-courses-with-course-reviews-response.dto' ;
21
28
@Injectable ( )
22
29
export class CourseReviewService {
23
30
constructor (
@@ -28,6 +35,8 @@ export class CourseReviewService {
28
35
private readonly userService : UserService ,
29
36
private readonly pointService : PointService ,
30
37
private readonly courseService : CourseService ,
38
+ @Inject ( 'CourseReviewCriteriaStrategy' )
39
+ private readonly strategies : CourseReviewCriteriaStrategy [ ] ,
31
40
) { }
32
41
33
42
async createCourseReview (
@@ -179,6 +188,100 @@ export class CourseReviewService {
179
188
) ;
180
189
}
181
190
191
+ async getCourseReviewsWithKeyword (
192
+ searchCourseReviewsWithKeywordRequest : SearchCourseReviewsWithKeywordRequest ,
193
+ ) : Promise < PaginatedCourseReviewsDto > {
194
+ const courses = await this . courseService . searchCoursesWithOnlyKeyword (
195
+ searchCourseReviewsWithKeywordRequest ,
196
+ ) ;
197
+
198
+ if ( courses . length === 0 ) {
199
+ return new PaginatedCourseReviewsDto ( [ ] ) ;
200
+ }
201
+
202
+ const courseGroupMap = new Map <
203
+ string ,
204
+ {
205
+ id : number ;
206
+ courseCode : string ;
207
+ professorName : string ;
208
+ courseName : string ;
209
+ totalRate : number ;
210
+ }
211
+ > ( ) ;
212
+
213
+ for ( const course of courses ) {
214
+ const key = `${ course . courseCode } _${ course . professorName } ` ;
215
+ if ( ! courseGroupMap . has ( key ) ) {
216
+ courseGroupMap . set ( key , {
217
+ id : course . id ,
218
+ courseCode : course . courseCode ,
219
+ professorName : course . professorName ,
220
+ courseName : course . courseName ,
221
+ totalRate : course . totalRate ,
222
+ } ) ;
223
+ }
224
+ }
225
+ const courseGroups = Array . from ( courseGroupMap . values ( ) ) ;
226
+
227
+ const reviewQueryBuilder = this . courseReviewRepository
228
+ . createQueryBuilder ( 'review' )
229
+ . select ( [
230
+ 'MIN(review.id) AS id' ,
231
+ 'review.courseCode AS courseCode' ,
232
+ 'review.professorName AS professorName' ,
233
+ 'COUNT(review.id) AS reviewCount' ,
234
+ ] )
235
+ . groupBy ( 'courseCode' )
236
+ . addGroupBy ( 'professorName' ) ;
237
+
238
+ reviewQueryBuilder . where (
239
+ new Brackets ( ( qb ) => {
240
+ courseGroups . forEach ( ( group , index ) => {
241
+ const condition = `review.courseCode = :courseCode${ index } AND review.professorName = :professorName${ index } ` ;
242
+ if ( index === 0 ) {
243
+ qb . where ( condition , {
244
+ [ `courseCode${ index } ` ] : group . courseCode ,
245
+ [ `professorName${ index } ` ] : group . professorName ,
246
+ } ) ;
247
+ } else {
248
+ qb . orWhere ( condition , {
249
+ [ `courseCode${ index } ` ] : group . courseCode ,
250
+ [ `professorName${ index } ` ] : group . professorName ,
251
+ } ) ;
252
+ }
253
+ } ) ;
254
+ } ) ,
255
+ ) ;
256
+
257
+ const reviewAggregates = await reviewQueryBuilder . getRawMany ( ) ;
258
+
259
+ const reviewMap = new Map < string , { reviewCount : number } > ( ) ;
260
+ reviewAggregates . forEach ( ( item ) => {
261
+ const key = `${ item . courseCode } _${ item . professorName } ` ;
262
+ reviewMap . set ( key , {
263
+ reviewCount : item . reviewCount ? parseInt ( item . reviewCount , 10 ) : 0 ,
264
+ } ) ;
265
+ } ) ;
266
+
267
+ const responses : SearchCourseReviewsWithKeywordResponse [ ] =
268
+ courseGroups . map ( ( group ) => {
269
+ const key = `${ group . courseCode } _${ group . professorName } ` ;
270
+ const reviewData = reviewMap . get ( key ) || {
271
+ reviewCount : 0 ,
272
+ } ;
273
+ return {
274
+ id : group . id ,
275
+ courseName : group . courseName ,
276
+ professorName : group . professorName ,
277
+ totalRate : group . totalRate ,
278
+ reviewCount : reviewData . reviewCount ,
279
+ } ;
280
+ } ) ;
281
+
282
+ return new PaginatedCourseReviewsDto ( responses ) ;
283
+ }
284
+
182
285
async getCourseReviews (
183
286
user : AuthorizedUserDto ,
184
287
getCourseReviewsRequestDto : GetCourseReviewsRequestDto ,
@@ -323,4 +426,54 @@ export class CourseReviewService {
323
426
324
427
return ! ! courseReview ;
325
428
}
429
+
430
+ async getCoursesWithCourseReviews (
431
+ getCoursesWithCourseReviewsRequestDto : GetCoursesWithCourseReviewsRequestDto ,
432
+ ) : Promise < GetCoursesWithCourseReviewsResponseDto [ ] > {
433
+ const { criteria, limit } = getCoursesWithCourseReviewsRequestDto ;
434
+
435
+ const courseReviewCriteria =
436
+ await this . findCourseReviewCriteriaStrategy ( criteria ) ;
437
+
438
+ let mainQuery = this . courseReviewRepository
439
+ . createQueryBuilder ( 'courseReview' )
440
+ . select ( 'courseReview.courseCode' , 'courseCode' )
441
+ . addSelect ( 'courseReview.professorName' , 'professorName' )
442
+ . groupBy ( 'courseReview.courseCode' )
443
+ . addGroupBy ( 'courseReview.professorName' ) ;
444
+
445
+ mainQuery = await courseReviewCriteria . buildQuery ( mainQuery ) ;
446
+
447
+ const courseReviews = await mainQuery . take ( limit ) . getRawMany ( ) ;
448
+
449
+ const courses : CourseEntity [ ] = [ ] ;
450
+ for ( const review of courseReviews ) {
451
+ const foundCourses =
452
+ await this . courseService . searchCoursesByCourseCodeAndProfessorName (
453
+ review . courseCode ,
454
+ review . professorName ,
455
+ review . year ,
456
+ review . semester ,
457
+ ) ;
458
+ courses . push ( ...foundCourses ) ;
459
+ }
460
+
461
+ return courses . map ( ( course ) => {
462
+ return new GetCoursesWithCourseReviewsResponseDto ( course ) ;
463
+ } ) ;
464
+ }
465
+
466
+ private async findCourseReviewCriteriaStrategy (
467
+ criteria : CourseReviewCriteria ,
468
+ ) : Promise < CourseReviewCriteriaStrategy > {
469
+ const courseReviewCriteria = this . strategies . find ( ( strategy ) =>
470
+ strategy . supports ( criteria ) ,
471
+ ) ;
472
+
473
+ if ( ! courseReviewCriteria ) {
474
+ throwKukeyException ( 'COURSE_REVIEW_CRITERIA_NOT_FOUND' ) ;
475
+ }
476
+
477
+ return courseReviewCriteria ;
478
+ }
326
479
}
0 commit comments