Skip to content
Merged
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
Expand Up @@ -2,26 +2,25 @@ package core.application.afterParty.application.service

import core.application.afterParty.application.exception.AfterPartyNotFoundException
import core.application.afterParty.application.exception.InviteTagNameNotFoundException
import core.application.member.application.service.authority.MemberAuthorityService
import core.application.afterParty.presentation.response.AfterPartyInviteeCompensationResponse
import core.application.member.application.exception.MemberNotFoundException
import core.application.member.application.service.authority.MemberAuthorityService
import core.domain.afterParty.aggregate.AfterParty
import core.domain.afterParty.aggregate.AfterPartyInviteTag
import core.domain.afterParty.aggregate.AfterPartyInvitee
import core.domain.afterParty.enums.AfterPartyInviteTagEnum
import core.domain.afterParty.port.inbound.AfterPartyCommandUseCase
import core.domain.afterParty.port.inbound.AfterPartyInviteTagQueryUseCase
import core.domain.afterParty.port.inbound.AfterPartyInviteeCommandUseCase
import core.domain.afterParty.port.outbound.AfterPartyInviteePersistencePort
import core.domain.afterParty.port.outbound.AfterPartyInviteTagPersistencePort
import core.domain.afterParty.port.outbound.AfterPartyInviteePersistencePort
import core.domain.afterParty.port.outbound.AfterPartyPersistencePort
import core.domain.authorization.vo.RoleType
import core.domain.cohort.port.outbound.CohortPersistencePort
import core.domain.cohort.vo.CohortId
import core.domain.member.aggregate.Member
import core.domain.member.aggregate.MemberCohort
import core.domain.member.port.inbound.MemberQueryUseCase
import core.domain.member.enums.MemberStatus
import core.domain.member.port.inbound.MemberQueryUseCase
import core.domain.member.vo.MemberId
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import core.domain.authorization.vo.RoleId
import core.domain.cohort.port.inbound.CohortQueryUseCase
import core.domain.cohort.vo.CohortId
import core.domain.member.aggregate.Member
import core.domain.member.port.outbound.query.MemberOverviewQueryModel
import core.domain.member.port.inbound.MemberQueryByRoleUseCase
import core.domain.member.port.inbound.MemberQueryUseCase
import core.domain.member.port.outbound.MemberPersistencePort
import core.domain.member.port.outbound.query.MemberNameRoleQueryModel
import core.domain.member.port.outbound.query.MemberOverviewQueryModel
import core.domain.member.vo.MemberId
import core.domain.team.vo.TeamId
import org.springframework.beans.factory.annotation.Value
Expand Down Expand Up @@ -68,8 +68,7 @@ class MemberQueryService(
memberPersistencePort
.findAllByCohort(value)

fun getMembersByCohortId(cohortId: CohortId): List<MemberId> =
memberPersistencePort.findAllByCohortId(cohortId)
fun getMembersByCohortId(cohortId: CohortId): List<MemberId> = memberPersistencePort.findAllByCohortId(cohortId)

/**
* 멤버의 식별자를 기반으로 해당 멤버의 팀 번호를 조회함.
Expand All @@ -83,9 +82,7 @@ class MemberQueryService(
memberPersistencePort.findMemberTeamByMemberId(memberId)
?: defaultTeamId

fun checkWhiteList(
email: String,
): Member? = memberPersistencePort.findBySignupEmail(email)
fun checkWhiteList(email: String): Member? = memberPersistencePort.findBySignupEmail(email)

fun getMembersOverview(latest: Boolean?): List<MemberOverviewQueryModel> =
memberPersistencePort.findAllOrderedByHighestCohortAndStatus(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import core.application.security.annotation.CurrentMemberId
import core.domain.cohort.vo.CohortId
import core.domain.member.vo.MemberId
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.parameters.RequestBody
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,10 @@ class SessionQueryService(
sessionPersistencePort.findSessionById(sessionId.value)
?: throw SessionNotFoundException()

fun getAttendanceTime(sessionId: SessionId): Instant =
fun getAttendancePolicy(sessionId: SessionId): AttendancePolicy =
sessionPersistencePort
.findSessionById(sessionId.value)
?.attendancePolicy
?.attendanceStart
?: throw SessionNotFoundException()

fun getSessionWeeks(): List<SessionWeekQueryModel> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class SessionQueryController(
): CustomResponse<AttendanceTimeResponse> {
val response =
sessionQueryService
.getAttendanceTime(sessionId)
.getAttendancePolicy(sessionId)
.let { SessionMapper.toAttendanceTimeResponse(it) }

return CustomResponse.ok(response)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import core.domain.session.port.inbound.command.SessionAttendancePolicyCommand
import core.domain.session.port.inbound.command.SessionCreateCommand
import core.domain.session.port.inbound.command.SessionUpdateCommand
import core.domain.session.port.inbound.query.SessionWeekQueryModel
import core.domain.session.vo.AttendancePolicy
import core.domain.session.vo.SessionId
import java.time.Instant
import java.time.LocalDateTime

object SessionMapper {
Expand Down Expand Up @@ -73,10 +73,12 @@ object SessionMapper {
)
}

fun toAttendanceTimeResponse(attendanceStartTime: Instant) =
fun toAttendanceTimeResponse(attendancePolicy: AttendancePolicy) =
AttendanceTimeResponse(
attendanceStartTime =
instantToLocalDateTime(attendanceStartTime),
instantToLocalDateTime(attendancePolicy.attendanceStart),
attendanceLateTime = instantToLocalDateTime(attendancePolicy.lateStart),
attendanceAbsentTime = instantToLocalDateTime(attendancePolicy.absentStart),
)

fun toSessionCreateCommand(request: SessionCreateRequest) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import java.time.LocalDateTime

data class AttendanceTimeResponse(
val attendanceStartTime: LocalDateTime,
val attendanceLateTime: LocalDateTime,
val attendanceAbsentTime: LocalDateTime,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package core.domain.member.port.outbound
import core.domain.authorization.vo.RoleId
import core.domain.cohort.vo.CohortId
import core.domain.member.aggregate.Member
import core.domain.member.port.outbound.query.MemberOverviewQueryModel
import core.domain.member.port.outbound.query.MemberNameRoleQueryModel
import core.domain.member.port.outbound.query.MemberOverviewQueryModel
import core.domain.member.vo.MemberId

interface MemberPersistencePort {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import core.domain.session.vo.AttendancePolicy
import core.domain.session.vo.SessionId
import java.time.Instant
import java.time.ZoneId
import java.time.temporal.ChronoUnit
import kotlin.random.Random

/**
Expand Down Expand Up @@ -65,10 +64,8 @@ class Session(
private fun determineAttendanceStatus(now: Instant): AttendanceResult =
when {
now.isBefore(attendancePolicy.attendanceStart) -> AttendanceResult.TooEarly
now.isBefore(attendancePolicy.attendanceStart.plus(16, ChronoUnit.MINUTES)) ->
AttendanceResult.Success(AttendanceStatus.PRESENT)
now.isBefore(attendancePolicy.attendanceStart.plus(31, ChronoUnit.MINUTES)) ->
AttendanceResult.Success(AttendanceStatus.LATE)
now.isBefore(attendancePolicy.lateStart) -> AttendanceResult.Success(AttendanceStatus.PRESENT)
now.isBefore(attendancePolicy.absentStart) -> AttendanceResult.Success(AttendanceStatus.LATE)
else -> AttendanceResult.Success(AttendanceStatus.ABSENT)
Comment on lines +67 to 69
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

출석 정책 시간 순서 불변식 검증이 없어 상태 판정이 깨질 수 있습니다.

Line 67-69는 lateStart, absentStart를 직접 신뢰합니다.
그런데 현재 생성/수정 경로에서 attendanceStart < lateStart < absentStart 보장이 없어서, 잘못된 정책값이 들어오면 출석 상태가 역전되거나 즉시 결석 처리될 수 있습니다.

💡 제안 수정안
 class Session(
@@
     deletedAt: Instant? = null,
 ) {
+    init {
+        validateAttendancePolicy(attendancePolicy)
+    }
+
+    private fun validateAttendancePolicy(policy: AttendancePolicy) {
+        require(policy.attendanceStart.isBefore(policy.lateStart)) {
+            "attendanceStart must be before lateStart"
+        }
+        require(policy.lateStart.isBefore(policy.absentStart)) {
+            "lateStart must be before absentStart"
+        }
+    }
@@
     fun updateAttendanceStartTime(newStartTime: Instant) {
-        this.attendancePolicy =
-            attendancePolicy.copy(
-                attendanceStart = newStartTime,
-            )
+        val newPolicy = attendancePolicy.copy(attendanceStart = newStartTime)
+        validateAttendancePolicy(newPolicy)
+        this.attendancePolicy = newPolicy
     }
@@
     fun updateSession(command: SessionUpdateCommand) {
@@
-        this.attendancePolicy =
-            AttendancePolicy(
-                attendanceStart = command.attendanceStart,
-                lateStart = command.lateStart,
-                absentStart = command.absentStart,
-                attendanceCode = this.attendancePolicy.attendanceCode,
-            )
+        val newPolicy =
+            AttendancePolicy(
+                attendanceStart = command.attendanceStart,
+                lateStart = command.lateStart,
+                absentStart = command.absentStart,
+                attendanceCode = this.attendancePolicy.attendanceCode,
+            )
+        validateAttendancePolicy(newPolicy)
+        this.attendancePolicy = newPolicy
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@domain/src/main/kotlin/core/domain/session/aggregate/Session.kt` around lines
67 - 69, The attendance-time ordering (attendanceStart < lateStart <
absentStart) is not being validated, so malformed AttendancePolicy values can
invert status decisions; update the code that consumes or constructs
attendancePolicy (e.g., the Session aggregate constructor/factory or the method
using attendancePolicy in Session.kt where
now.isBefore(attendancePolicy.lateStart) is evaluated) to validate the invariant
and fail fast: verify attendancePolicy.attendanceStart <
attendancePolicy.lateStart && attendancePolicy.lateStart <
attendancePolicy.absentStart and either throw a clear domain error / return a
failure result before performing the
AttendanceResult.Success(AttendanceStatus...) logic, or normalize/reject
incoming policy updates to ensure the invariant always holds.

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package core.domain.session.event

import core.domain.session.vo.SessionId
import core.domain.cohort.vo.CohortId
import core.domain.session.vo.SessionId

data class SessionCreateEvent(
val sessionId: SessionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import core.domain.member.aggregate.Member
import core.domain.member.enums.MemberPart
import core.domain.member.enums.MemberStatus
import core.domain.member.port.outbound.MemberPersistencePort
import core.domain.member.port.outbound.query.MemberOverviewQueryModel
import core.domain.member.port.outbound.query.MemberNameRoleQueryModel
import core.domain.member.port.outbound.query.MemberOverviewQueryModel
import core.domain.member.vo.MemberId
import core.entity.member.MemberEntity
import org.jooq.DSLContext
Expand All @@ -18,13 +18,13 @@ import org.jooq.dsl.tables.references.MEMBER_ROLES
import org.jooq.dsl.tables.references.MEMBER_TEAMS
import org.jooq.dsl.tables.references.ROLES
import org.jooq.dsl.tables.references.TEAMS
import org.jooq.impl.DSL.field
import org.jooq.impl.DSL.exists
import org.jooq.impl.DSL.field
import org.jooq.impl.DSL.max
import org.jooq.impl.DSL.name
import org.jooq.impl.DSL.selectOne
import org.jooq.impl.DSL.`when`
import org.jooq.impl.DSL.table
import org.jooq.impl.DSL.`when`
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
import java.time.ZoneId
Expand Down
Loading