@@ -7,27 +7,37 @@ import {
7
7
forwardRef ,
8
8
} from '@nestjs/common' ;
9
9
import { Cron } from '@nestjs/schedule' ;
10
+ import { ConfigService } from '@nestjs/config' ;
10
11
import { CreateRegistrationDto } from './dto/create-registration.dto' ;
11
12
import { CreateRotationDto } from './dto/create-rotation.dto' ;
12
13
import { UpdateRotationDto } from './dto/update-rotation.dto' ;
13
14
import { RotationEntity } from './entity/rotation.entity' ;
14
15
import { RotationAttendeeEntity } from './entity/rotation-attendee.entity' ;
15
16
import { UserService } from 'src/user/user.service' ;
16
17
import { RotationRepository } from './repository/rotations.repository' ;
17
- import { getFourthWeekdaysOfMonth , getNextYearAndMonth , getTodayDate } from './utils/date' ;
18
+ import {
19
+ getFourthFridayOfMonth ,
20
+ getFourthMondayOfMonth ,
21
+ getFourthWeekdaysOfMonth ,
22
+ getNextYearAndMonth ,
23
+ getTodayDay ,
24
+ getTomorrowDate ,
25
+ } from './utils/date' ;
18
26
import { RotationAttendeeRepository } from './repository/rotation-attendees.repository' ;
19
27
import { DayObject , RotationAttendeeInfo } from './utils/types' ;
20
28
import { HolidayService } from 'src/holiday/holiday.service' ;
21
29
import { createRotation } from './utils/rotation' ;
30
+ import { SlackService } from 'nestjs-slack' ;
31
+ import { Message } from 'slack-block-builder' ;
22
32
import { FindTodayRotationDto } from './dto/find-today-rotation.dto' ;
23
33
import { FindRegistrationDto } from './dto/find-registration.dto' ;
24
34
import { FindAllRotationDto } from './dto/find-all-rotation.dto' ;
25
35
26
36
function getRotationCronTime ( ) {
27
37
if ( process . env . NODE_ENV === 'production' ) {
28
- return '59 23 * * 5 ' ;
38
+ return '42 4 27 * * ' ;
29
39
}
30
- return '0 0 * * 5 ' ;
40
+ return '0 0 27 * * ' ;
31
41
}
32
42
33
43
@Injectable ( )
@@ -39,8 +49,114 @@ export class RotationsService {
39
49
private rotationAttendeeRepository : RotationAttendeeRepository ,
40
50
@Inject ( forwardRef ( ( ) => UserService ) ) private userService : UserService ,
41
51
private holidayService : HolidayService ,
52
+ private slackService : SlackService ,
53
+ private configService : ConfigService ,
42
54
) { }
43
55
56
+ /*
57
+ * 매일 9시 42분에 내일 사서에게 메세지를 전송하는 cron job
58
+ */
59
+ @Cron ( '42 9 * * *' , {
60
+ name : 'sendMessageTomorrowLibrarian' ,
61
+ timeZone : 'Asia/Seoul' ,
62
+ } )
63
+ async sendMessageTomorrowLibrarian ( ) : Promise < void > {
64
+ const tomorrow = getTomorrowDate ( ) ;
65
+ const tomorrowYear = tomorrow . getFullYear ( ) ;
66
+ const tomorrowMonth = tomorrow . getMonth ( ) + 1 ;
67
+ const tomorrowDay = tomorrow . getDate ( ) ;
68
+
69
+ const tomorrowLibrarian = await this . rotationRepository . find ( {
70
+ where : {
71
+ year : tomorrowYear ,
72
+ month : tomorrowMonth ,
73
+ day : tomorrowDay ,
74
+ } ,
75
+ relations : [ 'user' ] ,
76
+ } ) ;
77
+
78
+ if ( tomorrowLibrarian . length === 0 ) {
79
+ return ;
80
+ }
81
+
82
+ for ( const item of tomorrowLibrarian ) {
83
+ if ( ! item . user ) {
84
+ this . logger . warn ( 'Failed to get tomorrow librarian information. User not found.' ) ;
85
+ return ;
86
+ }
87
+
88
+ const message = `[알림] 안녕하세요 ${ item . user . nickname } 님! 내일 사서 업무가 있습니다.` ;
89
+
90
+ try {
91
+ await this . slackService . postMessage (
92
+ Message ( {
93
+ text : message ,
94
+ channel : item . user . slackMemberId ,
95
+ } ) . buildToObject ( ) ,
96
+ ) ;
97
+ } catch ( error ) {
98
+ this . logger . error ( `Error sending message to ${ item . user . nickname } : ` + error ) ;
99
+ }
100
+ }
101
+ }
102
+
103
+ /*
104
+ * 매월 23일, 집현전 슬랙 채널에 메세지를 보내는 cron job
105
+ */
106
+ @Cron ( '42 15 23 * *' , {
107
+ name : 'sendMessageRotationDeadlineFirst' ,
108
+ timeZone : 'Asia/Seoul' ,
109
+ } )
110
+ async sendMessageRotationDeadlineFirst ( ) : Promise < void > {
111
+ const message =
112
+ '[알림] 4일 후 로테이션 신청이 마감됩니다.\n[신청하러 가기]: https://together.42jip.net/' ;
113
+
114
+ await this . slackService . postMessage (
115
+ Message ( {
116
+ text : message ,
117
+ channel : this . configService . get ( 'slack.jiphyeonjeonChannel' ) ,
118
+ } ) . buildToObject ( ) ,
119
+ ) ;
120
+ }
121
+
122
+ /*
123
+ * 매월 26일, 집현전 슬랙 채널에 메세지를 보내는 cron job
124
+ */
125
+ @Cron ( '42 15 26 * *' , {
126
+ name : 'sendMessageRotationDeadlineLast' ,
127
+ timeZone : 'Asia/Seoul' ,
128
+ } )
129
+ async sendMessageRotationDeadlineLast ( ) : Promise < void > {
130
+ const message =
131
+ '[알림] 로테이션 신청 기간이 내일 마감됩니다. 오늘까지 신청해주세요!\n[신청하러 가기]: https://together.42jip.net/' ;
132
+
133
+ await this . slackService . postMessage (
134
+ Message ( {
135
+ text : message ,
136
+ channel : this . configService . get ( 'slack.jiphyeonjeonChannel' ) ,
137
+ } ) . buildToObject ( ) ,
138
+ ) ;
139
+ }
140
+
141
+ /*
142
+ * 매월 27일 사서 로테이션 설정이 완료된 후, 집현전 슬랙 채널에 메세지를 보내는 cron job
143
+ */
144
+ @Cron ( '42 15 27 * *' , {
145
+ name : 'sendMessageRotationFinished' ,
146
+ timeZone : 'Asia/Seoul' ,
147
+ } )
148
+ async sendMessageRotationFinished ( ) : Promise < void > {
149
+ const message =
150
+ '[알림] 다음 달 로테이션이 확정되었습니다! 친바 사이트에서 확인해주세요!\n[확인하러 가기]: https://together.42jip.net/' ;
151
+
152
+ await this . slackService . postMessage (
153
+ Message ( {
154
+ text : message ,
155
+ channel : this . configService . get ( 'slack.jiphyeonjeonChannel' ) ,
156
+ } ) . buildToObject ( ) ,
157
+ ) ;
158
+ }
159
+
44
160
/*
45
161
* 4주차 월요일에 유저를 모두 DB에 담아놓는 작업 필요
46
162
* [update 20231219] - 매 달 1일에 유저를 모두 DB에 담아놓는 작업으로 변경
@@ -72,101 +188,98 @@ export class RotationsService {
72
188
* 매주 금요일을 체크하여, 만약 4주차 금요일인 경우,
73
189
* 23시 59분에 로테이션을 돌린다.
74
190
* 다음 달 로테이션 참석자를 바탕으로 로테이션 결과 반환
191
+ * [update 20240202] - 매월 4주차 금요일이 아닌, 매월 27일 새벽에 로테이션을 돌린다.
75
192
*/
76
193
@Cron ( `${ getRotationCronTime ( ) } ` , {
77
194
name : 'setRotation' ,
78
195
timeZone : 'Asia/Seoul' ,
79
196
} )
80
197
async setRotation ( ) : Promise < void > {
81
- if ( getFourthWeekdaysOfMonth ( ) . indexOf ( getTodayDate ( ) ) > 0 ) {
82
- this . logger . log ( 'Setting rotation...' ) ;
198
+ this . logger . log ( 'Setting rotation...' ) ;
83
199
84
- const { year, month } = getNextYearAndMonth ( ) ;
85
- const attendeeArray : Partial < RotationAttendeeEntity > [ ] = await this . getAllRegistration ( ) ;
86
- const monthArrayInfo : DayObject [ ] [ ] = await this . getInitMonthArray ( year , month ) ;
200
+ const { year, month } = getNextYearAndMonth ( ) ;
201
+ const attendeeArray : Partial < RotationAttendeeEntity > [ ] = await this . getAllRegistration ( ) ;
202
+ const monthArrayInfo : DayObject [ ] [ ] = await this . getInitMonthArray ( year , month ) ;
87
203
88
- if ( ! attendeeArray || attendeeArray . length === 0 ) {
89
- this . logger . warn ( 'No attendees participated in the rotation' ) ;
90
- return ;
91
- }
204
+ if ( ! attendeeArray || attendeeArray . length === 0 ) {
205
+ this . logger . warn ( 'No attendees participated in the rotation' ) ;
206
+ return ;
207
+ }
92
208
93
- const rotationAttendeeInfo : RotationAttendeeInfo [ ] = attendeeArray . map ( ( attendee ) => {
94
- const parsedAttendLimit : number [ ] = Array . isArray ( attendee . attendLimit )
95
- ? JSON . parse ( JSON . stringify ( attendee . attendLimit ) )
96
- : [ ] ;
97
- return {
98
- userId : attendee . userId ,
99
- year : attendee . year ,
100
- month : attendee . month ,
101
- attendLimit : parsedAttendLimit ,
102
- attended : 0 ,
103
- } ;
104
- } ) ;
209
+ const rotationAttendeeInfo : RotationAttendeeInfo [ ] = attendeeArray . map ( ( attendee ) => {
210
+ const parsedAttendLimit : number [ ] = Array . isArray ( attendee . attendLimit )
211
+ ? JSON . parse ( JSON . stringify ( attendee . attendLimit ) )
212
+ : [ ] ;
213
+ return {
214
+ userId : attendee . userId ,
215
+ year : attendee . year ,
216
+ month : attendee . month ,
217
+ attendLimit : parsedAttendLimit ,
218
+ attended : 0 ,
219
+ } ;
220
+ } ) ;
221
+
222
+ // 만약 year & month에 해당하는 로테이션 정보가 이미 존재한다면,
223
+ // 해당 로테이션 정보를 삭제하고 다시 생성한다.
224
+ const hasInfo = await this . rotationRepository . find ( {
225
+ where : {
226
+ year : year ,
227
+ month : month ,
228
+ } ,
229
+ } ) ;
230
+
231
+ if ( hasInfo . length > 0 ) {
232
+ this . logger . log ( 'Rotation info already exists. Deleting...' ) ;
233
+ await this . rotationRepository . softRemove ( hasInfo ) ;
234
+ }
105
235
106
- // 만약 year & month에 해당하는 로테이션 정보가 이미 존재한다면,
107
- // 해당 로테이션 정보를 삭제하고 다시 생성한다.
108
- const hasInfo = await this . rotationRepository . find ( {
236
+ const rotationResultArray : DayObject [ ] = createRotation ( rotationAttendeeInfo , monthArrayInfo ) ;
237
+
238
+ for ( const item of rotationResultArray ) {
239
+ const [ userId1 , userId2 ] = item . arr ;
240
+
241
+ const attendeeOneExist = await this . rotationRepository . findOne ( {
109
242
where : {
243
+ userId : userId1 ,
110
244
year : year ,
111
245
month : month ,
246
+ day : item . day ,
112
247
} ,
113
248
} ) ;
114
249
115
- if ( hasInfo . length > 0 ) {
116
- this . logger . log ( 'Rotation info already exists. Deleting...' ) ;
117
- await this . rotationRepository . softRemove ( hasInfo ) ;
118
- }
119
-
120
- const rotationResultArray : DayObject [ ] = createRotation ( rotationAttendeeInfo , monthArrayInfo ) ;
121
-
122
- for ( const item of rotationResultArray ) {
123
- const [ userId1 , userId2 ] = item . arr ;
250
+ if ( ! attendeeOneExist ) {
251
+ const rotation1 = new RotationEntity ( ) ;
252
+ rotation1 . userId = userId1 ;
253
+ rotation1 . updateUserId = userId1 ;
254
+ rotation1 . year = year ;
255
+ rotation1 . month = month ;
256
+ rotation1 . day = item . day ;
124
257
125
- const attendeeOneExist = await this . rotationRepository . findOne ( {
126
- where : {
127
- userId : userId1 ,
128
- year : year ,
129
- month : month ,
130
- day : item . day ,
131
- } ,
132
- } ) ;
133
-
134
- if ( ! attendeeOneExist ) {
135
- const rotation1 = new RotationEntity ( ) ;
136
- rotation1 . userId = userId1 ;
137
- rotation1 . updateUserId = userId1 ;
138
- rotation1 . year = year ;
139
- rotation1 . month = month ;
140
- rotation1 . day = item . day ;
141
-
142
- await this . rotationRepository . save ( rotation1 ) ;
143
- }
258
+ await this . rotationRepository . save ( rotation1 ) ;
259
+ }
144
260
145
- const attendeeTwoExist = await this . rotationRepository . findOne ( {
146
- where : {
147
- userId : userId2 ,
148
- year : year ,
149
- month : month ,
150
- day : item . day ,
151
- } ,
152
- } ) ;
261
+ const attendeeTwoExist = await this . rotationRepository . findOne ( {
262
+ where : {
263
+ userId : userId2 ,
264
+ year : year ,
265
+ month : month ,
266
+ day : item . day ,
267
+ } ,
268
+ } ) ;
153
269
154
- if ( ! attendeeTwoExist ) {
155
- const rotation2 = new RotationEntity ( ) ;
156
- rotation2 . userId = userId2 ;
157
- rotation2 . updateUserId = userId2 ;
158
- rotation2 . year = year ;
159
- rotation2 . month = month ;
160
- rotation2 . day = item . day ;
270
+ if ( ! attendeeTwoExist ) {
271
+ const rotation2 = new RotationEntity ( ) ;
272
+ rotation2 . userId = userId2 ;
273
+ rotation2 . updateUserId = userId2 ;
274
+ rotation2 . year = year ;
275
+ rotation2 . month = month ;
276
+ rotation2 . day = item . day ;
161
277
162
- await this . rotationRepository . save ( rotation2 ) ;
163
- }
278
+ await this . rotationRepository . save ( rotation2 ) ;
164
279
}
165
-
166
- this . logger . log ( 'Successfully set rotation!' ) ;
167
- } else {
168
- // skipped...
169
280
}
281
+
282
+ this . logger . log ( 'Successfully set rotation!' ) ;
170
283
}
171
284
172
285
/*
@@ -264,7 +377,7 @@ export class RotationsService {
264
377
const { year, month } = getNextYearAndMonth ( ) ;
265
378
266
379
/* 4주차인지 확인 */
267
- // if (getFourthWeekdaysOfMonth().indexOf(getTodayDate ()) < 0) {
380
+ // if (getFourthWeekdaysOfMonth().indexOfDay ()) < 0) {
268
381
// throw new BadRequestException(
269
382
// 'Invalid date: Today is not a fourth weekday of the month.',
270
383
// );
0 commit comments