From ed68a470c99fb2725ab6945a18d98361b2c1d369 Mon Sep 17 00:00:00 2001 From: Hank-Choi Date: Fri, 14 Feb 2025 14:39:28 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Revert=20"snutt-ev=20UnsupportedMediaTypeEx?= =?UTF-8?q?ception=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5e421dd8be1dfed8c4bae6824543e2873534c014. --- core/src/main/kotlin/evaluation/service/EvService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/kotlin/evaluation/service/EvService.kt b/core/src/main/kotlin/evaluation/service/EvService.kt index 19d8ec45..eef4f366 100644 --- a/core/src/main/kotlin/evaluation/service/EvService.kt +++ b/core/src/main/kotlin/evaluation/service/EvService.kt @@ -41,7 +41,6 @@ class EvService( val result: MutableMap? = snuttEvWebClient.method(method) .uri { builder -> builder.path(requestPath).queryParams(requestQueryParams).build() } - .contentType(MediaType.APPLICATION_JSON) .header("Snutt-User-Id", userId) .header(HttpHeaders.CONTENT_ENCODING, "UTF-8") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) From 70d057ff54f76feea8301bc1bea3a1ec690f5a5e Mon Sep 17 00:00:00 2001 From: Hank-Choi Date: Fri, 14 Feb 2025 15:05:16 +0900 Subject: [PATCH 2/4] =?UTF-8?q?CancellationException=20=EB=8F=84=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/main/kotlin/filter/ErrorWebFilter.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/main/kotlin/filter/ErrorWebFilter.kt b/api/src/main/kotlin/filter/ErrorWebFilter.kt index 966ee774..2e42e81e 100644 --- a/api/src/main/kotlin/filter/ErrorWebFilter.kt +++ b/api/src/main/kotlin/filter/ErrorWebFilter.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.wafflestudio.snutt.common.exception.ErrorType import com.wafflestudio.snutt.common.exception.EvServiceProxyException import com.wafflestudio.snutt.common.exception.SnuttException +import kotlinx.coroutines.CancellationException import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.HttpStatusCode @@ -46,9 +47,9 @@ class ErrorWebFilter( SnuttException(errorMessage = throwable.body.title ?: ErrorType.DEFAULT_ERROR.errorMessage), ) } - is AbortedException -> { - httpStatusCode = HttpStatus.GATEWAY_TIMEOUT - errorBody = makeErrorBody(SnuttException()) + is AbortedException, is CancellationException -> { + httpStatusCode = HttpStatus.NO_CONTENT + errorBody = emptyMap() } else -> { log.error(throwable.message, throwable) From 5376fc1c1022dc1115e336acc33b59a4a20ed45e Mon Sep 17 00:00:00 2001 From: Hankyeol Choi Date: Sat, 15 Feb 2025 21:04:06 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20snutt=20=EB=A0=88?= =?UTF-8?q?=EA=B1=B0=EC=8B=9C=20=EC=8A=A4=ED=8E=99=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?(#342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/src/main/kotlin/handler/DeviceHandler.kt | 11 -- api/src/main/kotlin/handler/FriendHandler.kt | 4 +- .../kotlin/handler/LectureSearchHandler.kt | 36 +----- api/src/main/kotlin/handler/UserHandler.kt | 2 +- .../users/job/UserNicknameCreateJobConfig.kt | 103 ------------------ .../kotlin/friend/service/FriendService.kt | 4 +- .../kotlin/lectures/dto/BookmarkLectureDto.kt | 5 - .../lectures/dto/ClassPlaceAndTimeDto.kt | 6 - .../main/kotlin/lectures/dto/LectureDto.kt | 5 - .../kotlin/lectures/service/LectureService.kt | 21 +--- .../kotlin/lectures/utils/ClassTimeUtils.kt | 15 --- .../timetables/dto/TimetableLectureDto.kt | 5 - .../ClassPlaceAndTimeLegacyRequestDto.kt | 29 +---- .../timetables/service/TimetableService.kt | 22 +--- core/src/main/kotlin/users/data/User.kt | 11 +- .../kotlin/users/data/UserEntityCallback.kt | 48 -------- .../main/kotlin/users/service/UserService.kt | 2 +- 17 files changed, 16 insertions(+), 313 deletions(-) delete mode 100644 batch/src/main/kotlin/users/job/UserNicknameCreateJobConfig.kt delete mode 100644 core/src/main/kotlin/users/data/UserEntityCallback.kt diff --git a/api/src/main/kotlin/handler/DeviceHandler.kt b/api/src/main/kotlin/handler/DeviceHandler.kt index d8d8bbfe..331483e2 100644 --- a/api/src/main/kotlin/handler/DeviceHandler.kt +++ b/api/src/main/kotlin/handler/DeviceHandler.kt @@ -3,7 +3,6 @@ package com.wafflestudio.snutt.handler import com.wafflestudio.snutt.common.dto.OkResponse import com.wafflestudio.snutt.middleware.SnuttRestApiDefaultMiddleware import com.wafflestudio.snutt.notification.service.DeviceService -import com.wafflestudio.snutt.users.data.User import com.wafflestudio.snutt.users.service.UserNicknameService import com.wafflestudio.snutt.users.service.UserService import org.springframework.stereotype.Component @@ -24,19 +23,9 @@ class DeviceHandler( val registrationId = req.pathVariable("id") deviceService.addRegistrationId(userId, registrationId, clientInfo) - updateIfUserNicknameNull(req.getContext().user!!) OkResponse() } - // TODO 회원가입 API (SNUTT -> SNU4T) 마이그레이션 완료 이후 삭제 - // context: https://wafflestudio.slack.com/archives/C0PAVPS5T/p1690711859658779 - private suspend fun updateIfUserNicknameNull(user: User) { - if (user.nickname.isNullOrEmpty()) { - val nickname = userNicknameService.generateUniqueRandomNickname() - userService.update(user.copy(nickname = nickname)) - } - } - suspend fun removeRegistrationId(req: ServerRequest) = handle(req) { val userId = req.userId diff --git a/api/src/main/kotlin/handler/FriendHandler.kt b/api/src/main/kotlin/handler/FriendHandler.kt index 09088cf7..ddfbdc97 100644 --- a/api/src/main/kotlin/handler/FriendHandler.kt +++ b/api/src/main/kotlin/handler/FriendHandler.kt @@ -32,7 +32,7 @@ class FriendHandler( id = friend.id!!, userId = partner.id!!, displayName = partnerDisplayName, - nickname = userNicknameService.getNicknameDto(partner.nickname!!), + nickname = userNicknameService.getNicknameDto(partner.nickname), createdAt = friend.createdAt, ) } @@ -104,7 +104,7 @@ class FriendHandler( id = friend.id!!, userId = partner.id!!, displayName = friend.getPartnerDisplayName(userId), - nickname = userNicknameService.getNicknameDto(partner.nickname!!), + nickname = userNicknameService.getNicknameDto(partner.nickname), createdAt = friend.createdAt, ) } diff --git a/api/src/main/kotlin/handler/LectureSearchHandler.kt b/api/src/main/kotlin/handler/LectureSearchHandler.kt index 5cc081f0..6a9b79de 100644 --- a/api/src/main/kotlin/handler/LectureSearchHandler.kt +++ b/api/src/main/kotlin/handler/LectureSearchHandler.kt @@ -1,7 +1,6 @@ package com.wafflestudio.snutt.handler import com.fasterxml.jackson.annotation.JsonProperty -import com.wafflestudio.snutt.common.enum.DayOfWeek import com.wafflestudio.snutt.common.enum.Semester import com.wafflestudio.snutt.lectures.dto.SearchDto import com.wafflestudio.snutt.lectures.dto.SearchTimeDto @@ -39,8 +38,6 @@ data class SearchQueryLegacy( val academicYear: List? = null, val department: List? = null, val category: List? = null, - @JsonProperty("time_mask") - val timeMask: List? = null, val times: List? = null, val timesToExclude: List? = null, val etc: List? = null, @@ -62,7 +59,7 @@ data class SearchQueryLegacy( department = department, category = category, etcTags = etc, - times = times?.takeIf { it.isNotEmpty() } ?: bitmaskToClassTime(timeMask), + times = times, timesToExclude = timesToExclude, page = page, offset = offset, @@ -71,35 +68,4 @@ data class SearchQueryLegacy( categoryPre2025 = categoryPre2025, ) } - - /* - 기존 timeMask 스펙을 대응하기 위한 코드 - 8시부터 23시까지 30분 단위로 강의가 존재하는 경우 1, 존재하지 않는 경우 0인 2진수가 전달된다. - 이를 searchTimeDto로 변환 - ex) 111000... -> 8:00~9:30 수업 -> startMinute = 480, endMinute: 570 - */ - private fun bitmaskToClassTime(timeMask: List?): List? = - timeMask?.flatMapIndexed { dayValue, mask -> - mask.toTimeMask().zip((mask shr 1).toTimeMask()) - .foldIndexed(emptyList()) { index, acc, (bit, bitBefore) -> - if (bit && !bitBefore) { - acc + - SearchTimeDto( - day = DayOfWeek.getOfValue(dayValue)!!, - startMinute = index * 30 + 8 * 60, - endMinute = index * 30 + 8 * 60 + 30, - ) - } else if (bit && bitBefore) { - val updated = acc.last().copy(endMinute = index * 30 + 8 * 60 + 30) - acc.dropLast(1) + updated - } else { - acc - } - } - } - - /* - ex) 258048 -> 2진수 00000000000000111111000000000000 -> [..., true, true, true, true, true, true, false, false, ...] - */ - private fun Int.toTimeMask(): List = this.toString(2).padStart(30, '0').map { it == '1' } } diff --git a/api/src/main/kotlin/handler/UserHandler.kt b/api/src/main/kotlin/handler/UserHandler.kt index 2f3d8dad..23f6d73c 100644 --- a/api/src/main/kotlin/handler/UserHandler.kt +++ b/api/src/main/kotlin/handler/UserHandler.kt @@ -183,6 +183,6 @@ class UserHandler( email = user.email, localId = user.credential.localId, fbName = user.credential.fbName, - nickname = userNicknameService.getNicknameDto(user.nickname!!), + nickname = userNicknameService.getNicknameDto(user.nickname), ) } diff --git a/batch/src/main/kotlin/users/job/UserNicknameCreateJobConfig.kt b/batch/src/main/kotlin/users/job/UserNicknameCreateJobConfig.kt deleted file mode 100644 index 1cebec42..00000000 --- a/batch/src/main/kotlin/users/job/UserNicknameCreateJobConfig.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.wafflestudio.snutt.users.job - -import com.wafflestudio.snutt.common.extension.isEqualTo -import com.wafflestudio.snutt.users.data.User -import com.wafflestudio.snutt.users.service.UserNicknameService -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.launch -import kotlinx.coroutines.reactive.asFlow -import kotlinx.coroutines.reactive.awaitSingle -import kotlinx.coroutines.runBlocking -import org.slf4j.LoggerFactory -import org.springframework.batch.core.Job -import org.springframework.batch.core.Step -import org.springframework.batch.core.job.builder.JobBuilder -import org.springframework.batch.core.repository.JobRepository -import org.springframework.batch.core.step.builder.StepBuilder -import org.springframework.batch.repeat.RepeatStatus -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.dao.DuplicateKeyException -import org.springframework.data.mongodb.core.ReactiveMongoTemplate -import org.springframework.data.mongodb.core.find -import org.springframework.data.mongodb.core.query.Query -import org.springframework.transaction.PlatformTransactionManager -import java.util.concurrent.atomic.AtomicInteger -import kotlin.time.ExperimentalTime -import kotlin.time.measureTimedValue - -@Configuration -class UserNicknameCreateJobConfig( - private val reactiveMongoTemplate: ReactiveMongoTemplate, - private val userNicknameService: UserNicknameService, -) { - private val log = LoggerFactory.getLogger(javaClass) - - private companion object { - const val JOB_NAME = "userNicknameCreateJob" - const val STEP_NAME = "userNicknameCreateStep" - } - - @Bean - fun userNicknameCreateJob( - jobRepository: JobRepository, - userNicknameCreateStep: Step, - ): Job { - return JobBuilder(JOB_NAME, jobRepository) - .start(userNicknameCreateStep) - .build() - } - - @Bean - @OptIn(ExperimentalTime::class) - fun userNicknameCreateStep( - jobRepository: JobRepository, - transactionManager: PlatformTransactionManager, - ): Step = - StepBuilder(STEP_NAME, jobRepository).tasklet( - { _, _ -> - val (updateCount, elapsedTime) = measureTimedValue { updateUserNickname() } - log.info("닉네임 생성 작업이 완료되었습니다. 총 ${updateCount}명 유저의 닉네임을 생성했습니다. (소요시간: ${elapsedTime.inWholeSeconds}초)") - RepeatStatus.FINISHED - }, - transactionManager, - ).build() - - private fun updateUserNickname(): Int = - runBlocking { - val updateCount = AtomicInteger() - val channel = Channel(capacity = 100) - - repeat(100) { - launch { - for (user in channel) { - tryToUpdateNickname(user) - updateCount.incrementAndGet() - } - } - } - - reactiveMongoTemplate.find(Query.query(User::nickname isEqualTo null)) - .asFlow() - .collect { channel.send(it) } - - channel.close() - updateCount.get() - } - - private suspend fun tryToUpdateNickname(user: User) { - val nickname = userNicknameService.generateRandomNickname() - val updated = user.copy(nickname = nickname) - try { - reactiveMongoTemplate.save(updated).awaitSingle() - log.info("유저(id: #${user.id}, email:#${user.email})의 닉네임을 ${updated.nickname}로 변경했습니다.") - } catch (e: DuplicateKeyException) { - log.info("유저(id: #${user.id}, email:#${user.email})의 닉네임 ${nickname}이 중복되어 재시도합니다.") - val unique = user.copy(nickname = userNicknameService.generateUniqueRandomNickname()) - reactiveMongoTemplate.save(unique).awaitSingle() - log.info("유저(id: #${user.id}, email:#${user.email})의 닉네임을 ${unique.nickname}로 변경했습니다.") - } catch (e: Exception) { - log.error("유저(id: #${user.id}, email:#${user.email})의 닉네임 ${user.nickname} 변경에 실패했습니다.") - } - } -} diff --git a/core/src/main/kotlin/friend/service/FriendService.kt b/core/src/main/kotlin/friend/service/FriendService.kt index d48a810f..8c396a89 100644 --- a/core/src/main/kotlin/friend/service/FriendService.kt +++ b/core/src/main/kotlin/friend/service/FriendService.kt @@ -133,7 +133,7 @@ class FriendServiceImpl( fromUser: User, toUserId: String, ) { - val fromUserNickname = userNicknameService.getNicknameDto(fromUser.nickname!!).nickname + val fromUserNickname = userNicknameService.getNicknameDto(fromUser.nickname).nickname val pushMessage = PushMessage( title = "친구 요청", @@ -164,7 +164,7 @@ class FriendServiceImpl( fromUserId: String, toUser: User, ) { - val toUserNickname = userNicknameService.getNicknameDto(toUser.nickname!!).nickname + val toUserNickname = userNicknameService.getNicknameDto(toUser.nickname).nickname val pushMessage = PushMessage( title = "친구 요청 수락", diff --git a/core/src/main/kotlin/lectures/dto/BookmarkLectureDto.kt b/core/src/main/kotlin/lectures/dto/BookmarkLectureDto.kt index a66e45e1..573b21bc 100644 --- a/core/src/main/kotlin/lectures/dto/BookmarkLectureDto.kt +++ b/core/src/main/kotlin/lectures/dto/BookmarkLectureDto.kt @@ -3,7 +3,6 @@ package com.wafflestudio.snutt.lectures.dto import com.fasterxml.jackson.annotation.JsonProperty import com.wafflestudio.snutt.evaluation.dto.SnuttEvLectureSummaryDto import com.wafflestudio.snutt.lectures.data.BookmarkLecture -import com.wafflestudio.snutt.lectures.utils.ClassTimeUtils data class BookmarkLectureDto( @JsonProperty("_id") @@ -27,9 +26,6 @@ data class BookmarkLectureDto( @JsonProperty("course_title") var courseTitle: String, val snuttEvLecture: SnuttEvLectureSummaryDto? = null, - // FIXME: 안드로이드 구버전 대응용 필드 1년 후 2024년에 삭제 (2023/06/26) - @JsonProperty("class_time_mask") - val classTimeMask: List = emptyList(), ) fun BookmarkLectureDto( @@ -52,5 +48,4 @@ fun BookmarkLectureDto( courseNumber = lecture.courseNumber, courseTitle = lecture.courseTitle, snuttEvLecture = snuttEvLecture, - classTimeMask = ClassTimeUtils.classTimeToBitmask(lecture.classPlaceAndTimes), ) diff --git a/core/src/main/kotlin/lectures/dto/ClassPlaceAndTimeDto.kt b/core/src/main/kotlin/lectures/dto/ClassPlaceAndTimeDto.kt index 565a8611..d4ffdce8 100644 --- a/core/src/main/kotlin/lectures/dto/ClassPlaceAndTimeDto.kt +++ b/core/src/main/kotlin/lectures/dto/ClassPlaceAndTimeDto.kt @@ -2,8 +2,6 @@ package com.wafflestudio.snutt.lectures.dto import com.fasterxml.jackson.annotation.JsonProperty import com.wafflestudio.snutt.common.enum.DayOfWeek -import com.wafflestudio.snutt.lecturebuildings.data.LectureBuilding -import com.wafflestudio.snutt.lecturebuildings.data.PlaceInfo import com.wafflestudio.snutt.lectures.data.ClassPlaceAndTime import com.wafflestudio.snutt.lectures.utils.endPeriod import com.wafflestudio.snutt.lectures.utils.minuteToString @@ -37,7 +35,6 @@ data class ClassPlaceAndTimeLegacyDto( val periodLength: Double, @JsonProperty("start") val startPeriod: Double, - var lectureBuildings: List? = null, ) fun ClassPlaceAndTimeLegacyDto(classPlaceAndTime: ClassPlaceAndTime): ClassPlaceAndTimeLegacyDto = @@ -51,6 +48,3 @@ fun ClassPlaceAndTimeLegacyDto(classPlaceAndTime: ClassPlaceAndTime): ClassPlace startPeriod = classPlaceAndTime.startPeriod, periodLength = classPlaceAndTime.endPeriod - classPlaceAndTime.startPeriod, ) - -val ClassPlaceAndTimeLegacyDto.placeInfos: List - get() = place?.let { PlaceInfo.getValuesOf(it) } ?: emptyList() diff --git a/core/src/main/kotlin/lectures/dto/LectureDto.kt b/core/src/main/kotlin/lectures/dto/LectureDto.kt index aa80478f..5344fd2a 100644 --- a/core/src/main/kotlin/lectures/dto/LectureDto.kt +++ b/core/src/main/kotlin/lectures/dto/LectureDto.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.wafflestudio.snutt.common.enum.Semester import com.wafflestudio.snutt.evaluation.dto.SnuttEvLectureSummaryDto import com.wafflestudio.snutt.lectures.data.Lecture -import com.wafflestudio.snutt.lectures.utils.ClassTimeUtils data class LectureDto( @JsonProperty("_id") @@ -33,9 +32,6 @@ data class LectureDto( val wasFull: Boolean, val snuttEvLecture: SnuttEvLectureSummaryDto? = null, val categoryPre2025: String?, - // FIXME: 안드로이드 구버전 대응용 필드 1년 후 2024년에 삭제 (2023/06/26) - @JsonProperty("class_time_mask") - val classTimeMask: List = emptyList(), ) fun LectureDto( @@ -63,5 +59,4 @@ fun LectureDto( wasFull = lecture.wasFull, snuttEvLecture = snuttevLecture, categoryPre2025 = lecture.categoryPre2025, - classTimeMask = ClassTimeUtils.classTimeToBitmask(lecture.classPlaceAndTimes), ) diff --git a/core/src/main/kotlin/lectures/service/LectureService.kt b/core/src/main/kotlin/lectures/service/LectureService.kt index 62c91cb8..308ed264 100644 --- a/core/src/main/kotlin/lectures/service/LectureService.kt +++ b/core/src/main/kotlin/lectures/service/LectureService.kt @@ -4,13 +4,10 @@ import com.wafflestudio.snutt.common.enum.Semester import com.wafflestudio.snutt.common.exception.EvDataNotFoundException import com.wafflestudio.snutt.evaluation.dto.SnuttEvLectureSummaryDto import com.wafflestudio.snutt.evaluation.repository.SnuttEvRepository -import com.wafflestudio.snutt.lecturebuildings.service.LectureBuildingService import com.wafflestudio.snutt.lectures.data.Lecture import com.wafflestudio.snutt.lectures.dto.LectureDto import com.wafflestudio.snutt.lectures.dto.SearchDto -import com.wafflestudio.snutt.lectures.dto.placeInfos import com.wafflestudio.snutt.lectures.repository.LectureRepository -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import org.springframework.stereotype.Service @@ -40,7 +37,6 @@ interface LectureService { class LectureServiceImpl( private val lectureRepository: LectureRepository, private val snuttEvRepository: SnuttEvRepository, - private val lectureBuildingService: LectureBuildingService, ) : LectureService { override fun findAll(): Flow = lectureRepository.findAll() @@ -63,25 +59,10 @@ class LectureServiceImpl( return lectures.map { lecture -> val snuttEvLecture = snuttIdToEvLectureMap[lecture.id] LectureDto(lecture, snuttEvLecture) - }.addLectureBuildings() + } } override suspend fun getEvSummary(lectureId: String): SnuttEvLectureSummaryDto { return snuttEvRepository.getSummariesByIds(listOf(lectureId)).firstOrNull() ?: throw EvDataNotFoundException } - - private suspend fun List.addLectureBuildings(): List = - coroutineScope { - val placeInfosAll = - flatMap { it.classPlaceAndTimes.flatMap { classPlaceAndTime -> classPlaceAndTime.placeInfos } }.distinct() - val buildings = lectureBuildingService.getLectureBuildings(placeInfosAll).associateBy { it.buildingNumber } - forEach { - it.classPlaceAndTimes.forEach { classPlaceAndTime -> - classPlaceAndTime.apply { - lectureBuildings = placeInfos.mapNotNull { placeInfo -> buildings[placeInfo.buildingNumber] } - } - } - } - this@addLectureBuildings - } } diff --git a/core/src/main/kotlin/lectures/utils/ClassTimeUtils.kt b/core/src/main/kotlin/lectures/utils/ClassTimeUtils.kt index 63ea237f..e4926968 100644 --- a/core/src/main/kotlin/lectures/utils/ClassTimeUtils.kt +++ b/core/src/main/kotlin/lectures/utils/ClassTimeUtils.kt @@ -5,21 +5,6 @@ import kotlin.math.ceil import kotlin.math.floor object ClassTimeUtils { - // FIXME: 안드로이드 구버전 대응용 1년 후 2024년에 삭제 (2023/06/26) - fun classTimeToBitmask(classPlaceAndTimes: List): List { - val bitTable = Array(7) { Array(30) { 0 } } - - classPlaceAndTimes.filter { it.startMinute >= 480 && it.endMinute <= 1380 }.map { classTime -> - val dayValue = classTime.day.value - val startPeriod = classTime.startPeriod - val endPeriod = classTime.endPeriod - for (i: Int in (startPeriod * 2).toInt() until (endPeriod * 2).toInt()) - bitTable[dayValue][i] = 1 - } - - return bitTable.map { day -> day.reduce { res, i -> res.shl(1) + i } } - } - fun timesOverlap(times: List) = times.indices.any { i -> times.subList(i + 1, times.size).any { comparedTime -> diff --git a/core/src/main/kotlin/timetables/dto/TimetableLectureDto.kt b/core/src/main/kotlin/timetables/dto/TimetableLectureDto.kt index c166d8db..5aec8716 100644 --- a/core/src/main/kotlin/timetables/dto/TimetableLectureDto.kt +++ b/core/src/main/kotlin/timetables/dto/TimetableLectureDto.kt @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.wafflestudio.snutt.evaluation.dto.SnuttEvLectureIdDto import com.wafflestudio.snutt.lectures.dto.ClassPlaceAndTimeDto import com.wafflestudio.snutt.lectures.dto.ClassPlaceAndTimeLegacyDto -import com.wafflestudio.snutt.lectures.utils.ClassTimeUtils import com.wafflestudio.snutt.theme.data.ColorSet import com.wafflestudio.snutt.timetables.data.TimetableLecture @@ -82,9 +81,6 @@ data class TimetableLectureLegacyDto( @JsonProperty("lecture_id") var lectureId: String? = null, val snuttEvLecture: SnuttEvLectureIdDto? = null, - // FIXME: 안드로이드 구버전 대응용 필드 1년 후 2024년에 삭제 (2023/06/26) - @JsonProperty("class_time_mask") - val classTimeMask: List = emptyList(), val categoryPre2025: String?, ) @@ -109,7 +105,6 @@ fun TimetableLectureLegacyDto( color = timetableLecture.color, colorIndex = timetableLecture.colorIndex, lectureId = timetableLecture.lectureId, - classTimeMask = ClassTimeUtils.classTimeToBitmask(timetableLecture.classPlaceAndTimes), snuttEvLecture = snuttEvLecture, categoryPre2025 = timetableLecture.categoryPre2025, ) diff --git a/core/src/main/kotlin/timetables/dto/request/ClassPlaceAndTimeLegacyRequestDto.kt b/core/src/main/kotlin/timetables/dto/request/ClassPlaceAndTimeLegacyRequestDto.kt index 0862055c..ce0b0e5e 100644 --- a/core/src/main/kotlin/timetables/dto/request/ClassPlaceAndTimeLegacyRequestDto.kt +++ b/core/src/main/kotlin/timetables/dto/request/ClassPlaceAndTimeLegacyRequestDto.kt @@ -1,6 +1,5 @@ package com.wafflestudio.snutt.timetables.dto.request -import com.fasterxml.jackson.annotation.JsonProperty import com.wafflestudio.snutt.common.enum.DayOfWeek import com.wafflestudio.snutt.common.exception.InvalidTimeException import com.wafflestudio.snutt.lectures.data.ClassPlaceAndTime @@ -10,27 +9,12 @@ data class ClassPlaceAndTimeLegacyRequestDto( val place: String?, val startMinute: Int?, val endMinute: Int?, - // FIXME: 아래 네 필드는 삭제 예정 - 구버전 앱 대응용 (2023.7) - @JsonProperty("start_time") - val startTime: String?, - @JsonProperty("end_time") - val endTime: String?, - @JsonProperty("len") - val periodLength: Double?, - @JsonProperty("start") - val startPeriod: Double?, ) { fun toClassPlaceAndTime(): ClassPlaceAndTime { val startMinute = - this.startMinute - ?: startTime?.let(::timeStringToMinute) - ?: startPeriod?.let(::periodToMinute) - ?: throw InvalidTimeException + this.startMinute ?: throw InvalidTimeException val endMinute = - this.endMinute - ?: endTime?.let(::timeStringToMinute) - ?: periodLength?.plus(startPeriod!!)?.let(::periodToMinute) - ?: throw InvalidTimeException + this.endMinute ?: throw InvalidTimeException // 23:55 이후에 끝나는 수업 if (endMinute > 23 * 60 + 55) throw InvalidTimeException // 5분 미만 수업 @@ -43,13 +27,4 @@ data class ClassPlaceAndTimeLegacyRequestDto( endMinute = endMinute, ) } - - private fun timeStringToMinute(time: String): Int { - val (hour, minute) = time.split(":") - return hour.toInt() * 60 + minute.toInt() - } - - private fun periodToMinute(period: Double): Int { - return (period * 60 + 8 * 60).toInt() - } } diff --git a/core/src/main/kotlin/timetables/service/TimetableService.kt b/core/src/main/kotlin/timetables/service/TimetableService.kt index 9cb9c841..ba5f3e57 100644 --- a/core/src/main/kotlin/timetables/service/TimetableService.kt +++ b/core/src/main/kotlin/timetables/service/TimetableService.kt @@ -13,8 +13,6 @@ import com.wafflestudio.snutt.common.exception.TimetableNotPrimaryException import com.wafflestudio.snutt.coursebook.data.CoursebookDto import com.wafflestudio.snutt.coursebook.service.CoursebookService import com.wafflestudio.snutt.evaluation.repository.SnuttEvRepository -import com.wafflestudio.snutt.lecturebuildings.service.LectureBuildingService -import com.wafflestudio.snutt.lectures.dto.placeInfos import com.wafflestudio.snutt.theme.service.TimetableThemeService import com.wafflestudio.snutt.theme.service.toBasicThemeType import com.wafflestudio.snutt.theme.service.toIdForTimetable @@ -25,7 +23,6 @@ import com.wafflestudio.snutt.timetables.dto.TimetableLectureLegacyDto import com.wafflestudio.snutt.timetables.dto.TimetableLegacyDto import com.wafflestudio.snutt.timetables.dto.request.TimetableAddRequestDto import com.wafflestudio.snutt.timetables.repository.TimetableRepository -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map @@ -115,7 +112,6 @@ class TimetableServiceImpl( private val coursebookService: CoursebookService, private val timetableThemeService: TimetableThemeService, private val timetableRepository: TimetableRepository, - private val lectureBuildingService: LectureBuildingService, private val evRepository: SnuttEvRepository, private val dynamicLinkClient: DynamicLinkClient, @Value("\${google.firebase.dynamic-link.link-prefix}") val linkPrefix: String, @@ -335,7 +331,7 @@ class TimetableServiceImpl( val evLectureIdMap = evRepository.getEvIdsBySnuttIds(timetable.lectures.mapNotNull { it.lectureId }).associateBy { it.snuttId } val timetableLectures = timetable.lectures.map { TimetableLectureLegacyDto(it, evLectureIdMap[it.lectureId]) } - return TimetableLegacyDto(timetable, timetableLectures).addLectureBuildings() + return TimetableLegacyDto(timetable, timetableLectures) } override suspend fun convertTimetableToTimetableDto(timetable: Timetable): TimetableDto { @@ -344,20 +340,4 @@ class TimetableServiceImpl( val timetableLectures = timetable.lectures.map { TimetableLectureDto(it, evLectureIdMap[it.lectureId]) } return TimetableDto(timetable, timetableLectures) } - - private suspend fun TimetableLegacyDto.addLectureBuildings(): TimetableLegacyDto = - coroutineScope { - val placeInfosAll = - lectures.flatMap { it.classPlaceAndTimes.flatMap { classPlaceAndTime -> classPlaceAndTime.placeInfos } } - .distinct() - val buildings = lectureBuildingService.getLectureBuildings(placeInfosAll).associateBy { it.buildingNumber } - lectures.forEach { - it.classPlaceAndTimes.forEach { classPlaceAndTime -> - classPlaceAndTime.apply { - lectureBuildings = placeInfos.mapNotNull { placeInfo -> buildings[placeInfo.buildingNumber] } - } - } - } - this@addLectureBuildings - } } diff --git a/core/src/main/kotlin/users/data/User.kt b/core/src/main/kotlin/users/data/User.kt index 34cdd999..60d266c3 100644 --- a/core/src/main/kotlin/users/data/User.kt +++ b/core/src/main/kotlin/users/data/User.kt @@ -11,9 +11,8 @@ data class User( @Id val id: String? = null, var email: String?, - // TODO: apple 로그인 마이그레이션 이후 nickname nullable 하지 않게 수정 @Indexed(unique = true, sparse = true) - var nickname: String?, + var nickname: String, var isEmailVerified: Boolean?, var credential: Credential, var credentialHash: String, @@ -27,14 +26,14 @@ data class User( var lastLoginTimestamp: Long = System.currentTimeMillis(), var notificationCheckedAt: LocalDateTime = LocalDateTime.now(), ) { - val nicknameTag: Int? + val nicknameTag: Int get() { - return nickname?.substringAfterLast(NICKNAME_TAG_DELIMITER)?.toInt() + return nickname.substringAfterLast(NICKNAME_TAG_DELIMITER).toInt() } - val nicknameWithoutTag: String? + val nicknameWithoutTag: String get() { - return nickname?.substringBeforeLast(NICKNAME_TAG_DELIMITER) + return nickname.substringBeforeLast(NICKNAME_TAG_DELIMITER) } companion object { diff --git a/core/src/main/kotlin/users/data/UserEntityCallback.kt b/core/src/main/kotlin/users/data/UserEntityCallback.kt deleted file mode 100644 index e5098d53..00000000 --- a/core/src/main/kotlin/users/data/UserEntityCallback.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.wafflestudio.snutt.users.data - -import com.wafflestudio.snutt.users.repository.UserRepository -import com.wafflestudio.snutt.users.service.UserNicknameService -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.reactor.mono -import org.bson.Document -import org.reactivestreams.Publisher -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Lazy -import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterConvertCallback -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono - -@Component -class UserEntityCallback - @Lazy - constructor( - private val userRepository: UserRepository, - private val userNicknameService: UserNicknameService, - ) : ReactiveAfterConvertCallback { - private val log = LoggerFactory.getLogger(javaClass) - - override fun onAfterConvert( - entity: User, - document: Document, - collection: String, - ): Publisher { - if (entity.nickname != null) { - return Mono.just(entity) - } - - log.info("empty nickname found. user: $entity") - return backFillNickname(entity) - } - - private fun backFillNickname(user: User): Mono = - mono(Dispatchers.Unconfined) { - try { - val result = userRepository.save(user.copy(nickname = userNicknameService.generateUniqueRandomNickname())) - log.info("[BACKFILL] updated nickname of USER: $result") - result - } catch (e: Exception) { - log.error("[BACKFILL] failed to update nickname of USER: $user", e) - throw e - } - } - } diff --git a/core/src/main/kotlin/users/service/UserService.kt b/core/src/main/kotlin/users/service/UserService.kt index ccf52dec..df899583 100644 --- a/core/src/main/kotlin/users/service/UserService.kt +++ b/core/src/main/kotlin/users/service/UserService.kt @@ -161,7 +161,7 @@ class UserServiceImpl( with(userPatchRequest) { nickname?.trim()?.let { - val prevNickname = userNicknameService.getNicknameDto(user.nickname!!).nickname + val prevNickname = userNicknameService.getNicknameDto(user.nickname).nickname if (it != prevNickname) { user.nickname = userNicknameService.appendNewTag(it) } From ee3dbb04252d196fba728cd67e863a1dbe415a2b Mon Sep 17 00:00:00 2001 From: Hankyeol Choi Date: Sat, 15 Feb 2025 21:04:42 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=EA=B5=AC=20=EA=B5=90=EC=96=91=EC=98=81?= =?UTF-8?q?=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20=ED=95=A8=EC=88=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#343)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CategoryPre2025FetchService.kt | 41 ++++++++++--------- .../common/service/SugangSnuFetchService.kt | 4 ++ .../job/sync/service/SugangSnuSyncService.kt | 14 ------- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt b/batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt index 5d6e0d29..703ab3b7 100644 --- a/batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt +++ b/batch/src/main/kotlin/pre2025category/service/CategoryPre2025FetchService.kt @@ -2,6 +2,7 @@ package com.wafflestudio.snutt.pre2025category.service import com.wafflestudio.snutt.pre2025category.repository.CategoryPre2025Repository import org.apache.poi.ss.usermodel.WorkbookFactory +import org.springframework.core.io.buffer.PooledDataBuffer import org.springframework.stereotype.Service @Service @@ -9,26 +10,26 @@ class CategoryPre2025FetchService( private val categoryPre2025Repository: CategoryPre2025Repository, ) { suspend fun getCategoriesPre2025(): Map { - val oldCategoriesXlsx = categoryPre2025Repository.fetchCategoriesPre2025() - val workbook = WorkbookFactory.create(oldCategoriesXlsx.asInputStream()) - return workbook.sheetIterator().asSequence() - .flatMap { sheet -> - sheet.rowIterator().asSequence() - .drop(4) - .map { row -> - try { - val currentCourseNumber = row.getCell(7).stringCellValue - val oldCategory = row.getCell(1).stringCellValue - if (currentCourseNumber.isBlank() || oldCategory.isBlank()) { - return@map null - } - currentCourseNumber to oldCategory - } catch (e: Exception) { - null + val oldCategoriesXlsx: PooledDataBuffer = categoryPre2025Repository.fetchCategoriesPre2025() + + try { + val workbook = WorkbookFactory.create(oldCategoriesXlsx.asInputStream()) + return workbook.sheetIterator().asSequence() + .flatMap { sheet -> + sheet.rowIterator().asSequence() + .drop(4) + .mapNotNull { row -> + runCatching { + val currentCourseNumber = row.getCell(7).stringCellValue + val oldCategory = row.getCell(1).stringCellValue + check(currentCourseNumber.isNotBlank() && oldCategory.isNotBlank()) + currentCourseNumber to oldCategory + }.getOrNull() } - } - .filterNotNull() - } - .toMap() + } + .toMap() + } finally { + oldCategoriesXlsx.release() + } } } diff --git a/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt b/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt index 284caefd..0c134030 100644 --- a/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt +++ b/batch/src/main/kotlin/sugangsnu/common/service/SugangSnuFetchService.kt @@ -2,6 +2,7 @@ package com.wafflestudio.snutt.sugangsnu.common.service import com.wafflestudio.snutt.common.enum.Semester import com.wafflestudio.snutt.lectures.data.Lecture +import com.wafflestudio.snutt.pre2025category.service.CategoryPre2025FetchService import com.wafflestudio.snutt.sugangsnu.common.SugangSnuRepository import com.wafflestudio.snutt.sugangsnu.common.utils.SugangSnuClassTimeUtils import org.apache.poi.hssf.usermodel.HSSFWorkbook @@ -19,6 +20,7 @@ interface SugangSnuFetchService { @Service class SugangSnuFetchServiceImpl( private val sugangSnuRepository: SugangSnuRepository, + private val categoryPre2025FetchService: CategoryPre2025FetchService, ) : SugangSnuFetchService { private val log = LoggerFactory.getLogger(javaClass) private val quotaRegex = """(?\d+)(\s*\((?\d+)\))?""".toRegex() @@ -27,6 +29,7 @@ class SugangSnuFetchServiceImpl( year: Int, semester: Semester, ): List { + val courseNumberCategoryPre2025Map = categoryPre2025FetchService.getCategoriesPre2025() val koreanLectureXlsx = sugangSnuRepository.getSugangSnuLectures(year, semester, "ko") val englishLectureXlsx = sugangSnuRepository.getSugangSnuLectures(year, semester, "en") val koreanSheet = HSSFWorkbook(koreanLectureXlsx.asInputStream()).getSheetAt(0) @@ -69,6 +72,7 @@ class SugangSnuFetchServiceImpl( department = extraDepartment ?: department quota = extraLectureInfo.subInfo.quota ?: quota remark = extraLectureInfo.subInfo.remark ?: remark + categoryPre2025 = courseNumberCategoryPre2025Map[lecture.courseNumber] } } } diff --git a/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt b/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt index 7fcb837a..5846d5df 100644 --- a/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt +++ b/batch/src/main/kotlin/sugangsnu/job/sync/service/SugangSnuSyncService.kt @@ -9,7 +9,6 @@ import com.wafflestudio.snutt.lecturebuildings.service.LectureBuildingService import com.wafflestudio.snutt.lectures.data.Lecture import com.wafflestudio.snutt.lectures.service.LectureService import com.wafflestudio.snutt.lectures.utils.ClassTimeUtils -import com.wafflestudio.snutt.pre2025category.service.CategoryPre2025FetchService import com.wafflestudio.snutt.sugangsnu.common.SugangSnuRepository import com.wafflestudio.snutt.sugangsnu.common.data.SugangSnuCoursebookCondition import com.wafflestudio.snutt.sugangsnu.common.service.SugangSnuFetchService @@ -47,7 +46,6 @@ interface SugangSnuSyncService { @Service class SugangSnuSyncServiceImpl( private val sugangSnuFetchService: SugangSnuFetchService, - private val categoryPre2025FetchService: CategoryPre2025FetchService, private val lectureService: LectureService, private val timeTableRepository: TimetableRepository, private val sugangSnuRepository: SugangSnuRepository, @@ -59,14 +57,8 @@ class SugangSnuSyncServiceImpl( private val log = LoggerFactory.getLogger(javaClass) override suspend fun updateCoursebook(coursebook: Coursebook): List { - val courseNumberCategoryPre2025Map = categoryPre2025FetchService.getCategoriesPre2025() val newLectures = sugangSnuFetchService.getSugangSnuLectures(coursebook.year, coursebook.semester) - .map { lecture -> - lecture.apply { - categoryPre2025 = courseNumberCategoryPre2025Map[lecture.courseNumber] - } - } val oldLectures = lectureService.getLecturesByYearAndSemesterAsFlow(coursebook.year, coursebook.semester).toList() val compareResult = compareLectures(newLectures, oldLectures) @@ -81,14 +73,8 @@ class SugangSnuSyncServiceImpl( } override suspend fun addCoursebook(coursebook: Coursebook) { - val courseNumberCategoryPre2025Map = categoryPre2025FetchService.getCategoriesPre2025() val newLectures = sugangSnuFetchService.getSugangSnuLectures(coursebook.year, coursebook.semester) - .map { lecture -> - lecture.apply { - categoryPre2025 = courseNumberCategoryPre2025Map[lecture.courseNumber] - } - } lectureService.upsertLectures(newLectures) syncTagList(coursebook, newLectures)