Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

출석 정보 가져오기 구현 #1015

Open
wants to merge 17 commits into
base: feature/refactor-attendance-screen
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package org.sopt.official.data.repository.attendance

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.sopt.official.data.model.attendance.AttendanceHistoryResponse
import org.sopt.official.data.model.attendance.AttendanceHistoryResponse.AttendanceResponse
import org.sopt.official.data.model.attendance.RequestAttendanceCode
import org.sopt.official.data.model.attendance.SoptEventResponse
import org.sopt.official.data.service.attendance.AttendanceService
import org.sopt.official.domain.entity.attendance.Attendance
import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType
import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance
import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance.RoundAttendanceState
import org.sopt.official.domain.entity.attendance.Attendance.Session
import org.sopt.official.domain.entity.attendance.Attendance.User.AttendanceLog.AttendanceState
import org.sopt.official.domain.entity.attendance.ConfirmAttendanceCodeResult
import org.sopt.official.domain.entity.attendance.FetchAttendanceCurrentRoundResult
import org.sopt.official.domain.repository.attendance.NewAttendanceRepository
import retrofit2.HttpException
import java.time.LocalDateTime
import javax.inject.Inject

class DefaultAttendanceRepository @Inject constructor(
private val attendanceService: AttendanceService,
private val json: Json
) : NewAttendanceRepository {
override suspend fun fetchAttendanceInfo(): Attendance {
val soptEventResponse: SoptEventResponse? = runCatching { attendanceService.getSoptEvent().data }.getOrNull()
val attendanceHistoryResponse: AttendanceHistoryResponse? =
runCatching { attendanceService.getAttendanceHistory().data }.getOrNull()
return Attendance(
sessionId = soptEventResponse?.id ?: Attendance.UNKNOWN_SESSION_ID,
user = Attendance.User(
name = attendanceHistoryResponse?.name ?: Attendance.User.UNKNOWN_NAME,
generation = attendanceHistoryResponse?.generation ?: Attendance.User.UNKNOWN_GENERATION,
part = Attendance.User.Part.valueOf(attendanceHistoryResponse?.part ?: Attendance.User.UNKNOWN_PART),
attendanceScore = attendanceHistoryResponse?.score ?: 0.0,
attendanceCount = Attendance.User.AttendanceCount(
attendanceCount = attendanceHistoryResponse?.attendanceCount?.normal ?: 0,
lateCount = attendanceHistoryResponse?.attendanceCount?.late ?: 0,
absenceCount = attendanceHistoryResponse?.attendanceCount?.abnormal ?: 0,
),
attendanceHistory = attendanceHistoryResponse?.attendances?.map { attendanceResponse: AttendanceResponse ->
Attendance.User.AttendanceLog(
sessionName = attendanceResponse.eventName,
date = attendanceResponse.date,
attendanceState = AttendanceState.valueOf(attendanceResponse.attendanceState)
)
} ?: emptyList(),
),
attendanceDayType = when (soptEventResponse?.type) {
"NO_SESSION" -> {
AttendanceDayType.NoSession
}

"HAS_ATTENDANCE" -> {
val firstAttendanceResponse: SoptEventResponse.AttendanceResponse? = soptEventResponse.attendances.firstOrNull()
val secondAttendanceResponse: SoptEventResponse.AttendanceResponse? = soptEventResponse.attendances.lastOrNull()
Comment on lines +59 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 lastOrNull로 가도 괜찮을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예시 보니깐 attendances를 반드시 2개로 주긴 하더라구요. 더 좋은 방법이 있는지 생각해보겠습니다. 감사합니다!

AttendanceDayType.HasAttendance(
session = Session(
name = soptEventResponse.eventName,
location = soptEventResponse.location.ifBlank { null },
startAt = LocalDateTime.parse(soptEventResponse.startAt),
endAt = LocalDateTime.parse(soptEventResponse.endAt),
),
firstRoundAttendance = RoundAttendance(
state = if (firstAttendanceResponse == null) RoundAttendanceState.NOT_YET else RoundAttendanceState.valueOf(
firstAttendanceResponse.status
),
attendedAt = LocalDateTime.parse(firstAttendanceResponse?.attendedAt),
),
secondRoundAttendance = RoundAttendance(
state = if (secondAttendanceResponse == null) RoundAttendanceState.NOT_YET else RoundAttendanceState.valueOf(
secondAttendanceResponse.status
),
attendedAt = LocalDateTime.parse(secondAttendanceResponse?.attendedAt),
),
)
}

"NO_ATTENDANCE" -> {
AttendanceDayType.NoAttendance(
session = Session(
name = soptEventResponse.eventName,
location = soptEventResponse.location.ifBlank { null },
startAt = LocalDateTime.parse(soptEventResponse.startAt),
endAt = LocalDateTime.parse(soptEventResponse.endAt),
)
)
}

else -> {
AttendanceDayType.NoSession
}
},
)
}

override suspend fun fetchAttendanceCurrentRound(lectureId: Long): FetchAttendanceCurrentRoundResult {
return runCatching { attendanceService.getAttendanceRound(lectureId).data }.fold(
onSuccess = { FetchAttendanceCurrentRoundResult.Success(it?.round) },
onFailure = { error: Throwable ->
if (error !is HttpException) return FetchAttendanceCurrentRoundResult.Failure(null)

val errorBodyString: String =
error.response()?.errorBody()?.string() ?: return FetchAttendanceCurrentRoundResult.Failure(null)

val jsonObject = json.parseToJsonElement(errorBodyString).jsonObject
val errorMessage = jsonObject["message"]?.jsonPrimitive?.contentOrNull

FetchAttendanceCurrentRoundResult.Failure(errorMessage)
},
)
}

override suspend fun confirmAttendanceCode(
subLectureId: Long,
code: String
): ConfirmAttendanceCodeResult {
return runCatching {
attendanceService.confirmAttendanceCode(RequestAttendanceCode(subLectureId = subLectureId, code = code))
}.fold(
onSuccess = { ConfirmAttendanceCodeResult.Success },
onFailure = { error: Throwable ->
if (error !is HttpException) return ConfirmAttendanceCodeResult.Failure(null)

val errorBodyString: String =
error.response()?.errorBody()?.string() ?: return ConfirmAttendanceCodeResult.Failure(null)

val jsonObject = json.parseToJsonElement(errorBodyString).jsonObject
val errorMessage = jsonObject["message"]?.jsonPrimitive?.contentOrNull

ConfirmAttendanceCodeResult.Failure(errorMessage)
},
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import org.sopt.official.common.di.OperationRetrofit
import org.sopt.official.data.repository.attendance.AttendanceRepositoryImpl
import org.sopt.official.data.repository.attendance.DefaultAttendanceRepository
import org.sopt.official.data.service.attendance.AttendanceService
import org.sopt.official.domain.repository.attendance.AttendanceRepository
import org.sopt.official.domain.repository.attendance.NewAttendanceRepository
import retrofit2.Retrofit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -43,6 +45,10 @@ abstract class AttendanceBindsModule {
@Singleton
abstract fun bindAttendanceRepository(attendanceRepositoryImpl: AttendanceRepositoryImpl): AttendanceRepository

@Binds
@Singleton
abstract fun bindDefaultAttendanceRepository(defaultAttendanceRepository: DefaultAttendanceRepository): NewAttendanceRepository

companion object {
@Provides
@Singleton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.sopt.official.domain.entity.attendance

import java.time.LocalDateTime

data class Attendance(
val sessionId: Int,
val user: User,
val attendanceDayType: AttendanceDayType,
) {
data class User(
val name: String,
val generation: Int,
val part: Part,
val attendanceScore: Number,
val attendanceCount: AttendanceCount,
val attendanceHistory: List<AttendanceLog>
) {
enum class Part(val partName: String) {
PLAN("기획"),
DESIGN("디자인"),
ANDROID("안드로이드"),
IOS("iOS"),
WEB("웹"),
SERVER("서버"),
UNKNOWN("")
}

data class AttendanceCount(
/** 출석 전체 횟수 */
val attendanceCount: Int,
/** 지각 전체 횟수 */
val lateCount: Int,
/** 결석 전체 횟수 */
val absenceCount: Int,
) {
/** 전체 횟수 */
val totalCount: Int
get() = attendanceCount + lateCount + absenceCount
}

data class AttendanceLog(
val sessionName: String,
val date: String,
val attendanceState: AttendanceState
) {
enum class AttendanceState {
/** 참여(출석 체크 X)*/
PARTICIPATE,

/** 출석 */
ATTENDANCE,

/** 지각 */
TARDY,

/** 결석 */
ABSENT
}
}

companion object {
const val UNKNOWN_NAME = "회원"
const val UNKNOWN_GENERATION = -1
const val UNKNOWN_PART = "UNKNOWN"
}
}

sealed interface AttendanceDayType {

/** 일정이 없는 날 */
data object NoSession : AttendanceDayType

/** 일정이 있고, 출석 체크가 있는 날 */
data class HasAttendance(
val session: Session,
val firstRoundAttendance: RoundAttendance,
val secondRoundAttendance: RoundAttendance
) : AttendanceDayType {
/** n차 출석에 관한 정보 */
data class RoundAttendance(
val state: RoundAttendanceState,
val attendedAt: LocalDateTime?
) {
/** n차 출석 상태 */
enum class RoundAttendanceState {
ABSENT, ATTENDANCE, NOT_YET,
}
}
}

/** 일정이 있고, 출석 체크가 없는 날 */
data class NoAttendance(val session: Session) : AttendanceDayType
}

/** 솝트의 세션에 관한 정보
* @property name 세션 이름 (OT, 1차 세미나, 솝커톤 등)
* @property location 세션 장소, 정해진 장소가 없을 경우(온라인) null
* @property startAt 세션 시작 시각
* @property endAt 세션 종료 시각
* */
data class Session(
val name: String,
val location: String?,
val startAt: LocalDateTime,
val endAt: LocalDateTime,
)

companion object {
const val UNKNOWN_SESSION_ID = -1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.sopt.official.domain.entity.attendance

sealed interface ConfirmAttendanceCodeResult {
data object Success : ConfirmAttendanceCodeResult
data class Failure(val errorMessage: String?) : ConfirmAttendanceCodeResult
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.sopt.official.domain.entity.attendance

sealed interface FetchAttendanceCurrentRoundResult {
data class Success(val round: Int?) : FetchAttendanceCurrentRoundResult
data class Failure(val errorMessage: String?) : FetchAttendanceCurrentRoundResult
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sopt.official.domain.repository.attendance

import org.sopt.official.domain.entity.attendance.Attendance
import org.sopt.official.domain.entity.attendance.ConfirmAttendanceCodeResult
import org.sopt.official.domain.entity.attendance.FetchAttendanceCurrentRoundResult

interface NewAttendanceRepository {
suspend fun fetchAttendanceInfo(): Attendance
suspend fun fetchAttendanceCurrentRound(lectureId: Long): FetchAttendanceCurrentRoundResult
suspend fun confirmAttendanceCode(subLectureId: Long, code: String): ConfirmAttendanceCodeResult
}
Loading