From 7ce97ae4a08b91b00afd0b271885a407afb4e0ff Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 11 Jul 2024 08:47:48 +0900 Subject: [PATCH 01/26] feat: add property for HTML converted description --- src/main/kotlin/apply/domain/mission/Mission.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/kotlin/apply/domain/mission/Mission.kt b/src/main/kotlin/apply/domain/mission/Mission.kt index d1b11ef61..d81a7d3f3 100644 --- a/src/main/kotlin/apply/domain/mission/Mission.kt +++ b/src/main/kotlin/apply/domain/mission/Mission.kt @@ -3,6 +3,7 @@ package apply.domain.mission import org.hibernate.annotations.SQLDelete import org.hibernate.annotations.Where import support.domain.BaseEntity +import support.markdownToEmbeddedHtml import java.time.LocalDateTime import javax.persistence.Column import javax.persistence.Embedded @@ -40,6 +41,9 @@ class Mission( val isSubmitting: Boolean get() = status == MissionStatus.SUBMITTING + val formattedDescription: String + get() = markdownToEmbeddedHtml(description) + constructor( title: String, description: String, From 07d808d568162124cd64ea8b5bb77312776b5760 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 11 Jul 2024 08:55:58 +0900 Subject: [PATCH 02/26] feat: implement function to retrieve HTML-converted description --- .../kotlin/apply/application/MissionService.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/kotlin/apply/application/MissionService.kt b/src/main/kotlin/apply/application/MissionService.kt index 8dc5aee0a..e64807df1 100644 --- a/src/main/kotlin/apply/application/MissionService.kt +++ b/src/main/kotlin/apply/application/MissionService.kt @@ -120,4 +120,19 @@ class MissionService( ?.let(::EvaluationItemSelectData) ?: EvaluationItemSelectData() } + + fun parseDescription(missionData: MissionData): String { + val mission = Mission( + missionData.title, + missionData.description, + missionData.evaluation.id, + missionData.startDateTime, + missionData.endDateTime, + missionData.submittable, + missionData.hidden, + missionData.id + ) + + return mission.formattedDescription + } } From b08248f107dcca6e76938e6aa79286583aacf228 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Mon, 8 Jul 2024 15:32:31 +0900 Subject: [PATCH 03/26] feat: add preview to check parsed description --- .../ui/admin/mission/MissionPreviewDialog.kt | 55 +++++++++++++++++++ .../ui/admin/mission/MissionsFormView.kt | 44 ++++++++++----- 2 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt diff --git a/src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt b/src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt new file mode 100644 index 000000000..4c3bbd9a1 --- /dev/null +++ b/src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt @@ -0,0 +1,55 @@ +package apply.ui.admin.mission + +import com.vaadin.flow.component.Component +import com.vaadin.flow.component.Html +import com.vaadin.flow.component.button.Button +import com.vaadin.flow.component.dialog.Dialog +import com.vaadin.flow.component.html.H2 +import com.vaadin.flow.component.orderedlayout.FlexComponent +import com.vaadin.flow.component.orderedlayout.HorizontalLayout +import com.vaadin.flow.component.orderedlayout.VerticalLayout +import support.views.createContrastButton + +class MissionPreviewDialog( + htmlString: String +) : Dialog() { + init { + add(createHeader(), createHtmlComponentFrom(htmlString), createButtons()) + width = "700px" + height = "800px" + open() + } + + private fun createHeader(): VerticalLayout { + return VerticalLayout(H2("과제 설명 미리보기")).apply { + isPadding = false + val style = element.style + style.set("margin-bottom", "10px") + style["margin-bottom"] = "10px" + element.style["margin-bottom"] = "10px" + } + } + + private fun createHtmlComponentFrom(htmlString: String): Component { + val wrappedHtmlString = "
$htmlString
" + return Html(wrappedHtmlString).apply { + element.style["display"] = "block" + element.style["height"] = "600px" + element.style["overflow"] = "auto" + } + } + + private fun createButtons(): Component { + return HorizontalLayout(createCloseButton()).apply { + setWidthFull() + justifyContentMode = FlexComponent.JustifyContentMode.CENTER + element.style["margin-top"] = "20px" + } + } + + private fun createCloseButton(): Button { + return createContrastButton("닫기") { + close() + } + } +} diff --git a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt index 4d3c5f437..e8ef4722c 100644 --- a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt +++ b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt @@ -1,6 +1,7 @@ package apply.ui.admin.mission import apply.application.EvaluationService +import apply.application.MissionData import apply.application.MissionService import apply.ui.admin.BaseLayout import com.vaadin.flow.component.Component @@ -22,6 +23,7 @@ import support.views.createPrimaryButton import support.views.toDisplayName private val MISSION_FORM_URL_PATTERN: Regex = Regex("^(\\d*)/?(\\d*)/?($NEW_VALUE|$EDIT_VALUE)$") +private const val DATA_NOT_BIND_MESSAGE: String = "모든 항목이 잘 입력되었는지 확인해 주세요." @Route(value = "admin/missions", layout = BaseLayout::class) class MissionsFormView( @@ -54,29 +56,43 @@ class MissionsFormView( submitButton.text = displayName } - private fun createSubmitButton(): Button { - return createPrimaryButton { - missionForm.bindOrNull()?.let { - try { - missionService.save(it) - UI.getCurrent().navigate(MissionsView::class.java, recruitmentId) - } catch (e: Exception) { - createNotification(e.localizedMessage) - } - } - } - } - private fun createButtons(): Component { - return HorizontalLayout(submitButton, createCancelButton()).apply { + return HorizontalLayout(submitButton, createCancelButton(), createPreviewButton()).apply { setSizeFull() justifyContentMode = FlexComponent.JustifyContentMode.CENTER } } + private fun createSubmitButton(): Button { + return createPrimaryButton { + val missionData = getDataFromMissionForm() + try { + missionService.save(missionData) + UI.getCurrent().navigate(MissionsView::class.java, recruitmentId) + } catch (e: Exception) { + createNotification(e.localizedMessage) + } + } + } + private fun createCancelButton(): Button { return createContrastButton("취소") { UI.getCurrent().navigate(MissionsView::class.java, recruitmentId) } } + + private fun createPreviewButton(): Button { + return createContrastButton("미리보기") { + val result = getDataFromMissionForm() + val body = missionService.parseDescription(result) + MissionPreviewDialog(body) + } + } + + private fun getDataFromMissionForm(): MissionData { + return missionForm.bindOrNull() ?: run { + createNotification(DATA_NOT_BIND_MESSAGE) + throw IllegalArgumentException(DATA_NOT_BIND_MESSAGE) + } + } } From dc028b7a91cd86b9f0c48ef1b496bfbfe65e3667 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Mon, 8 Jul 2024 15:43:57 +0900 Subject: [PATCH 04/26] =?UTF-8?q?feat:=20=EA=B3=BC=EC=A0=9C=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=EC=9D=98=20DB=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/domain/mission/Mission.kt | 2 ++ src/main/resources/db/migration/V5_1__Alter_mission_table.sql | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 src/main/resources/db/migration/V5_1__Alter_mission_table.sql diff --git a/src/main/kotlin/apply/domain/mission/Mission.kt b/src/main/kotlin/apply/domain/mission/Mission.kt index d81a7d3f3..44941f6f6 100644 --- a/src/main/kotlin/apply/domain/mission/Mission.kt +++ b/src/main/kotlin/apply/domain/mission/Mission.kt @@ -8,6 +8,7 @@ import java.time.LocalDateTime import javax.persistence.Column import javax.persistence.Embedded import javax.persistence.Entity +import javax.persistence.Lob @SQLDelete(sql = "update mission set deleted = true where id = ?") @Where(clause = "deleted = false") @@ -17,6 +18,7 @@ class Mission( val title: String, @Column(nullable = false) + @Lob val description: String, @Column(nullable = false) diff --git a/src/main/resources/db/migration/V5_1__Alter_mission_table.sql b/src/main/resources/db/migration/V5_1__Alter_mission_table.sql new file mode 100644 index 000000000..aa5e65392 --- /dev/null +++ b/src/main/resources/db/migration/V5_1__Alter_mission_table.sql @@ -0,0 +1,2 @@ +alter table mission + modify description longtext not null; From 75c8893ffc5bd5ab07cd94e12620629964eb4d38 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Mon, 8 Jul 2024 15:44:31 +0900 Subject: [PATCH 05/26] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EC=9D=98=20=EA=B3=BC=EC=A0=9C=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=EA=B0=92=20=EB=A7=88=ED=81=AC=EB=8B=A4=EC=9A=B4?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apply/config/DatabaseInitializer.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/apply/config/DatabaseInitializer.kt b/src/main/kotlin/apply/config/DatabaseInitializer.kt index a3a7347de..36d1e1db5 100644 --- a/src/main/kotlin/apply/config/DatabaseInitializer.kt +++ b/src/main/kotlin/apply/config/DatabaseInitializer.kt @@ -412,7 +412,15 @@ class DatabaseInitializer( val missions = listOf( Mission( title = "1주 차 프리코스 - 숫자 야구 게임", - description = "https://github.com/woowacourse/java-baseball-precourse", + description = """ + # 미션 - 숫자 야구 게임 + + ## 🔍 진행 방식 + + - 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. + - 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. + - 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. + """.trimIndent(), evaluationId = 2L, startDateTime = createLocalDateTime(2020, 11, 24, 15), endDateTime = createLocalDateTime(2120, 12, 1, 0), @@ -421,7 +429,15 @@ class DatabaseInitializer( ), Mission( title = "2주 차 프리코스 - 자동차 경주 게임", - description = "https://github.com/woowacourse/java-racingcar-precourse", + description = """ + # 미션 - 자동차 경주 게임 + + ## 🔍 진행 방식 + + - 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. + - 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. + - 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. + """.trimIndent(), evaluationId = 3L, startDateTime = createLocalDateTime(2020, 12, 1, 15), endDateTime = createLocalDateTime(2120, 12, 8, 0), From 371c953ef7d5f6e6db56d6117fc38a9b51587e6b Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 11 Jul 2024 09:19:39 +0900 Subject: [PATCH 06/26] refactor: edit name of dto using my missions response feat(mission): refactor name of dto using my missions response --- src/main/kotlin/apply/application/MissionDtos.kt | 2 +- .../kotlin/apply/application/MyMissionService.kt | 12 ++++++------ .../kotlin/apply/ui/api/MissionRestController.kt | 4 ++-- src/test/kotlin/apply/MissionFixtures.kt | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/apply/application/MissionDtos.kt b/src/main/kotlin/apply/application/MissionDtos.kt index e815b4833..2b4da5aca 100644 --- a/src/main/kotlin/apply/application/MissionDtos.kt +++ b/src/main/kotlin/apply/application/MissionDtos.kt @@ -95,7 +95,7 @@ data class MissionResponse( ) } -data class MyMissionResponse( +data class MyMissionAndJudgementResponse( val id: Long, val title: String, val description: String, diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index 9b53a78be..a23c406c6 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -25,12 +25,12 @@ class MyMissionService( private val assignmentRepository: AssignmentRepository, private val judgmentRepository: JudgmentRepository ) { - fun findAllByMemberIdAndRecruitmentId(memberId: Long, recruitmentId: Long): List { + fun findAllByMemberIdAndRecruitmentId(memberId: Long, recruitmentId: Long): List { val missions = findMissions(memberId, recruitmentId) if (missions.isEmpty()) return emptyList() val assignments = assignmentRepository.findAllByMemberId(memberId) - if (assignments.isEmpty()) return missions.map(::MyMissionResponse) + if (assignments.isEmpty()) return missions.map(::MyMissionAndJudgementResponse) val judgmentItems = judgmentItemRepository.findAllByMissionIdIn(missions.map { it.id }) if (judgmentItems.isEmpty()) return missions.mapBy(assignments) @@ -46,10 +46,10 @@ class MyMissionService( return missionRepository.findAllByEvaluationIdIn(targets.map { it.id }).filterNot { it.hidden } } - private fun List.mapBy(assignments: List): List { + private fun List.mapBy(assignments: List): List { return map { mission -> val assignment = assignments.find { it.missionId == mission.id } - MyMissionResponse(mission, assignment != null) + MyMissionAndJudgementResponse(mission, assignment != null) } } @@ -57,12 +57,12 @@ class MyMissionService( assignments: List, judgmentItems: List, judgments: List - ): List { + ): List { return map { mission -> val assignment = assignments.find { it.missionId == mission.id } val judgmentItem = judgmentItems.find { it.missionId == mission.id } val judgment = judgments.findLastJudgment(assignment, judgmentItem) - MyMissionResponse( + MyMissionAndJudgementResponse( mission = mission, submitted = assignment != null, runnable = assignment != null && judgmentItem != null, diff --git a/src/main/kotlin/apply/ui/api/MissionRestController.kt b/src/main/kotlin/apply/ui/api/MissionRestController.kt index cb5eb48ab..9620fe713 100644 --- a/src/main/kotlin/apply/ui/api/MissionRestController.kt +++ b/src/main/kotlin/apply/ui/api/MissionRestController.kt @@ -4,7 +4,7 @@ import apply.application.MissionAndEvaluationResponse import apply.application.MissionData import apply.application.MissionResponse import apply.application.MissionService -import apply.application.MyMissionResponse +import apply.application.MyMissionAndJudgementResponse import apply.application.MyMissionService import apply.domain.member.Member import apply.security.LoginMember @@ -58,7 +58,7 @@ class MissionRestController( fun findMyMissionsByRecruitmentId( @PathVariable recruitmentId: Long, @LoginMember member: Member - ): ResponseEntity>> { + ): ResponseEntity>> { val responses = missionQueryService.findAllByMemberIdAndRecruitmentId(member.id, recruitmentId) return ResponseEntity.ok(ApiResponse.success(responses)) } diff --git a/src/test/kotlin/apply/MissionFixtures.kt b/src/test/kotlin/apply/MissionFixtures.kt index 8601415c8..c1369dba5 100644 --- a/src/test/kotlin/apply/MissionFixtures.kt +++ b/src/test/kotlin/apply/MissionFixtures.kt @@ -5,7 +5,7 @@ import apply.application.JudgmentItemData import apply.application.LastJudgmentResponse import apply.application.MissionData import apply.application.MissionResponse -import apply.application.MyMissionResponse +import apply.application.MyMissionAndJudgementResponse import apply.domain.mission.Mission import apply.domain.mission.MissionStatus import java.time.LocalDateTime @@ -83,8 +83,8 @@ fun createMyMissionResponse( runnable: Boolean = true, judgment: LastJudgmentResponse? = createLastJudgmentResponse(), id: Long = 0L -): MyMissionResponse { - return MyMissionResponse( +): MyMissionAndJudgementResponse { + return MyMissionAndJudgementResponse( id, title, description, From 55d19db93adcc1b7a8b2454505c4a58e569425b6 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Mon, 8 Jul 2024 17:41:38 +0900 Subject: [PATCH 07/26] =?UTF-8?q?feat:=20=EC=B0=B8=EC=97=AC=20=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EC=A7=91=EC=9D=98=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/apply/application/MissionDtos.kt | 22 +++++ .../apply/application/MyMissionService.kt | 16 ++++ .../apply/application/MyMissionServiceTest.kt | 94 +++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 src/test/kotlin/apply/application/MyMissionServiceTest.kt diff --git a/src/main/kotlin/apply/application/MissionDtos.kt b/src/main/kotlin/apply/application/MissionDtos.kt index 2b4da5aca..2477b65c4 100644 --- a/src/main/kotlin/apply/application/MissionDtos.kt +++ b/src/main/kotlin/apply/application/MissionDtos.kt @@ -126,6 +126,28 @@ data class MyMissionAndJudgementResponse( ) } +data class MyMissionResponse( + val id: Long, + val title: String, + val description: String, + val submittable: Boolean, + val startDateTime: LocalDateTime, + val endDateTime: LocalDateTime, + val status: MissionStatus, + val submitted: Boolean, +) { + constructor(mission: Mission, submitted: Boolean) : this( + mission.id, + mission.title, + mission.formattedDescription, + mission.submittable, + mission.period.startDateTime, + mission.period.endDateTime, + mission.status, + submitted + ) +} + data class JudgmentItemData( var id: Long = 0L, var testName: String = "", diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index a23c406c6..b21776b97 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -12,6 +12,7 @@ import apply.domain.judgmentitem.JudgmentItem import apply.domain.judgmentitem.JudgmentItemRepository import apply.domain.mission.Mission import apply.domain.mission.MissionRepository +import apply.domain.mission.getOrThrow import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -94,4 +95,19 @@ class MyMissionService( judgmentRecord = judgment?.lastRecord ) } + + fun findByUserIdAndMissionId(memberId: Long, missionId: Long): MyMissionResponse { + val mission = missionRepository.getOrThrow(missionId) + val evaluationTarget = evaluationTargetRepository.findByEvaluationIdAndMemberId(mission.evaluationId, memberId) + ?: throw NoSuchElementException("과제 참여 대상자가 아닙니다.") + + check(!mission.hidden) { "비공개 상태의 과제입니다." } + + val assignment = assignmentRepository.findByMemberIdAndMissionId(memberId, missionId) + + return MyMissionResponse( + mission = mission, + submitted = assignment != null, + ) + } } diff --git a/src/test/kotlin/apply/application/MyMissionServiceTest.kt b/src/test/kotlin/apply/application/MyMissionServiceTest.kt new file mode 100644 index 000000000..91ed65b95 --- /dev/null +++ b/src/test/kotlin/apply/application/MyMissionServiceTest.kt @@ -0,0 +1,94 @@ +package apply.application + +import apply.createAssignment +import apply.createEvaluationTarget +import apply.createMission +import apply.createMember +import apply.domain.assignment.AssignmentRepository +import apply.domain.evaluation.EvaluationRepository +import apply.domain.evaluationtarget.EvaluationTargetRepository +import apply.domain.judgment.JudgmentRepository +import apply.domain.judgmentitem.JudgmentItemRepository +import apply.domain.mission.MissionRepository +import apply.domain.mission.getOrThrow +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.booleans.shouldBeTrue +import io.kotest.matchers.nulls.shouldNotBeNull +import io.mockk.every +import io.mockk.mockk + +class MyMissionServiceTest : BehaviorSpec({ + val evaluationRepository = mockk() + val evaluationTargetRepository = mockk() + val missionRepository = mockk() + val judgmentItemRepository = mockk() + val assignmentRepository = mockk() + val judgmentRepository = mockk() + + val myMissionService = MyMissionService( + evaluationRepository, + evaluationTargetRepository, + missionRepository, + judgmentItemRepository, + assignmentRepository, + judgmentRepository + ) + + Given("공개 상태의 과제가 있는 경우") { + val participatedUser = createMember(id = 1L) + val notParticipatedUser = createMember(id = 2L) + val mission = createMission() + val target = createEvaluationTarget(evaluationId = mission.evaluationId, memberId = participatedUser.id) + + every { missionRepository.getOrThrow(any()) } returns mission + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), eq(participatedUser.id)) } returns target + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), neq(participatedUser.id)) } returns null + every { assignmentRepository.findByMemberIdAndMissionId(eq(participatedUser.id), any()) } returns createAssignment() + + When("요청한 유저가 과제 참여자이면") { + val actual = myMissionService.findByUserIdAndMissionId(participatedUser.id, mission.id) + + Then("과제의 상세 내용을 확인할 수 있다") { + actual.shouldNotBeNull() + actual.description.shouldNotBeNull() + actual.submitted.shouldBeTrue() + } + } + + When("요청한 유저가 과제 참여자가 아니면") { + Then("과제의 상세 내용을 확인할 수 없다") { + shouldThrow { + myMissionService.findByUserIdAndMissionId(notParticipatedUser.id, mission.id) + } + } + } + } + + Given("비공개 상태의 과제가 있는 경우") { + val participatedUser = createMember(id = 1L) + val notParticipatedUser = createMember(id = 2L) + val mission = createMission(hidden = true) + val target = createEvaluationTarget(evaluationId = mission.evaluationId, memberId = participatedUser.id) + + every { missionRepository.getOrThrow(any()) } returns mission + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), eq(participatedUser.id)) } returns target + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), neq(participatedUser.id)) } returns null + + When("요청한 유저가 과제 참여자이면") { + Then("과제 상태로 인한 예외로 상세 내용을 확인할 수 없다") { + shouldThrow { + myMissionService.findByUserIdAndMissionId(participatedUser.id, mission.id) + } + } + } + + When("요청한 유저가 과제 참여자가 아니면") { + Then("참여 대상자가 아니기 때문에 발생한 예외로 상세 내용을 확인할 수 없다") { + shouldThrow { + myMissionService.findByUserIdAndMissionId(notParticipatedUser.id, mission.id) + } + } + } + } +}) From 6570b52f0539fabe00e086e2682b906fd957a440 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Mon, 8 Jul 2024 17:42:13 +0900 Subject: [PATCH 08/26] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=98=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=83=81=EC=84=B8=20=EB=B3=B4=EA=B8=B0=20endpoint?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/ui/api/MissionRestController.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/kotlin/apply/ui/api/MissionRestController.kt b/src/main/kotlin/apply/ui/api/MissionRestController.kt index 9620fe713..529b03be1 100644 --- a/src/main/kotlin/apply/ui/api/MissionRestController.kt +++ b/src/main/kotlin/apply/ui/api/MissionRestController.kt @@ -5,6 +5,7 @@ import apply.application.MissionData import apply.application.MissionResponse import apply.application.MissionService import apply.application.MyMissionAndJudgementResponse +import apply.application.MyMissionResponse import apply.application.MyMissionService import apply.domain.member.Member import apply.security.LoginMember @@ -63,6 +64,16 @@ class MissionRestController( return ResponseEntity.ok(ApiResponse.success(responses)) } + @GetMapping("/{missionId}/me") + fun findMyMission( + @PathVariable recruitmentId: Long, + @PathVariable missionId: Long, + @LoginMember member: Member + ): ResponseEntity> { + val response = missionQueryService.findByUserIdAndMissionId(member.id, missionId) + return ResponseEntity.ok(ApiResponse.success(response)) + } + @DeleteMapping("/{missionId}") fun deleteById( @PathVariable recruitmentId: Long, From c12b6c5db88ca3095bfb45999e8d774fc6fa21d9 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Wed, 17 Jul 2024 14:31:52 +0900 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20=EB=82=B4=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=ED=98=84=ED=99=A9=20=EB=AA=A9=EB=A1=9D=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C=EC=97=90=EC=84=9C=20=EB=AF=B8=EC=85=98=EC=9D=98=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/application/MissionDtos.kt | 2 -- src/test/kotlin/apply/MissionFixtures.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/main/kotlin/apply/application/MissionDtos.kt b/src/main/kotlin/apply/application/MissionDtos.kt index 2477b65c4..d53a09ad5 100644 --- a/src/main/kotlin/apply/application/MissionDtos.kt +++ b/src/main/kotlin/apply/application/MissionDtos.kt @@ -98,7 +98,6 @@ data class MissionResponse( data class MyMissionAndJudgementResponse( val id: Long, val title: String, - val description: String, val submittable: Boolean, val submitted: Boolean, val startDateTime: LocalDateTime, @@ -115,7 +114,6 @@ data class MyMissionAndJudgementResponse( ) : this( mission.id, mission.title, - mission.description, mission.submittable, submitted, mission.period.startDateTime, diff --git a/src/test/kotlin/apply/MissionFixtures.kt b/src/test/kotlin/apply/MissionFixtures.kt index c1369dba5..be0d1e5f4 100644 --- a/src/test/kotlin/apply/MissionFixtures.kt +++ b/src/test/kotlin/apply/MissionFixtures.kt @@ -74,7 +74,6 @@ fun createMissionResponse( fun createMyMissionResponse( title: String = MISSION_TITLE, - description: String = MISSION_DESCRIPTION, submittable: Boolean = true, submitted: Boolean = true, startDateTime: LocalDateTime = START_DATE_TIME, @@ -87,7 +86,6 @@ fun createMyMissionResponse( return MyMissionAndJudgementResponse( id, title, - description, submittable, submitted, startDateTime, From 3ae10d31579212926e231187caf751a08bf0a836 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Wed, 17 Jul 2024 14:34:13 +0900 Subject: [PATCH 10/26] refactor: rename fixture function responding to its name of data --- src/test/kotlin/apply/MissionFixtures.kt | 2 +- src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/apply/MissionFixtures.kt b/src/test/kotlin/apply/MissionFixtures.kt index be0d1e5f4..92fae5e46 100644 --- a/src/test/kotlin/apply/MissionFixtures.kt +++ b/src/test/kotlin/apply/MissionFixtures.kt @@ -72,7 +72,7 @@ fun createMissionResponse( ) } -fun createMyMissionResponse( +fun createMyMissionAndJudgementResponse( title: String = MISSION_TITLE, submittable: Boolean = true, submitted: Boolean = true, diff --git a/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt b/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt index f3bc94b67..db4a508bf 100644 --- a/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt +++ b/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt @@ -8,7 +8,7 @@ import apply.createLastJudgmentResponse import apply.createMission import apply.createMissionData import apply.createMissionResponse -import apply.createMyMissionResponse +import apply.createMyMissionAndJudgementResponse import apply.domain.judgment.JudgmentStatus import com.ninjasquad.springmockk.MockkBean import io.mockk.Runs @@ -76,9 +76,9 @@ class MissionRestControllerTest : RestControllerTest() { @Test fun `나의 과제들을 조회한다`() { val responses = listOf( - createMyMissionResponse(id = 1L, runnable = false, judgment = null), - createMyMissionResponse(id = 2L, runnable = true, judgment = createLastJudgmentResponse()), - createMyMissionResponse( + createMyMissionAndJudgementResponse(id = 1L, runnable = false, judgment = null), + createMyMissionAndJudgementResponse(id = 2L, runnable = true, judgment = createLastJudgmentResponse()), + createMyMissionAndJudgementResponse( id = 3L, runnable = true, judgment = createLastJudgmentResponse(passCount = 9, totalCount = 10, status = JudgmentStatus.SUCCEEDED) From fdb29b2dbcbad3ccb86de5f90e1d325ce974c3dc Mon Sep 17 00:00:00 2001 From: woowabrie Date: Wed, 17 Jul 2024 14:50:17 +0900 Subject: [PATCH 11/26] =?UTF-8?q?refactor:=20=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20>=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0?= =?UTF-8?q?=EC=99=80=20=EB=8F=99=EC=9D=BC=ED=95=9C=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=AF=B8=EC=85=98=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EA=B0=80=20=EB=8F=99=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/admin/mission/MissionsFormView.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt index e8ef4722c..9ca50f42f 100644 --- a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt +++ b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt @@ -65,12 +65,13 @@ class MissionsFormView( private fun createSubmitButton(): Button { return createPrimaryButton { - val missionData = getDataFromMissionForm() - try { - missionService.save(missionData) - UI.getCurrent().navigate(MissionsView::class.java, recruitmentId) - } catch (e: Exception) { - createNotification(e.localizedMessage) + handleMissionData { mission -> + try { + missionService.save(mission) + UI.getCurrent().navigate(MissionsView::class.java, recruitmentId) + } catch (e: Exception) { + createNotification(e.localizedMessage) + } } } } @@ -83,16 +84,19 @@ class MissionsFormView( private fun createPreviewButton(): Button { return createContrastButton("미리보기") { - val result = getDataFromMissionForm() - val body = missionService.parseDescription(result) - MissionPreviewDialog(body) + handleMissionData { mission -> + val body = missionService.parseDescription(mission) + MissionPreviewDialog(body) + } } } - private fun getDataFromMissionForm(): MissionData { - return missionForm.bindOrNull() ?: run { + private fun handleMissionData(action: (MissionData) -> Unit) { + val result = missionForm.bindOrNull() + if (result == null) { createNotification(DATA_NOT_BIND_MESSAGE) - throw IllegalArgumentException(DATA_NOT_BIND_MESSAGE) + } else { + action(result) } } } From 186185579090a8989bff448ced80c24699547f7c Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 08:44:35 +0900 Subject: [PATCH 12/26] =?UTF-8?q?feat:=20Mission=EC=9D=98=20description=20?= =?UTF-8?q?=EB=A7=8C=20=EC=9D=B8=EC=9E=90=EB=A1=9C=20=EB=B0=9B=EC=95=84=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/apply/application/MissionService.kt | 16 +++------------- .../apply/ui/admin/mission/MissionsFormView.kt | 4 ++-- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/apply/application/MissionService.kt b/src/main/kotlin/apply/application/MissionService.kt index e64807df1..588c12f21 100644 --- a/src/main/kotlin/apply/application/MissionService.kt +++ b/src/main/kotlin/apply/application/MissionService.kt @@ -11,6 +11,7 @@ import apply.domain.mission.getOrThrow import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import support.markdownToEmbeddedHtml @Transactional @Service @@ -121,18 +122,7 @@ class MissionService( ?: EvaluationItemSelectData() } - fun parseDescription(missionData: MissionData): String { - val mission = Mission( - missionData.title, - missionData.description, - missionData.evaluation.id, - missionData.startDateTime, - missionData.endDateTime, - missionData.submittable, - missionData.hidden, - missionData.id - ) - - return mission.formattedDescription + fun parseDescription(description: String): String { + return markdownToEmbeddedHtml(description) } } diff --git a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt index 9ca50f42f..13cb8f2f1 100644 --- a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt +++ b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt @@ -85,8 +85,8 @@ class MissionsFormView( private fun createPreviewButton(): Button { return createContrastButton("미리보기") { handleMissionData { mission -> - val body = missionService.parseDescription(mission) - MissionPreviewDialog(body) + val formattedDescription = missionService.parseDescription(mission.description) + MissionPreviewDialog(formattedDescription) } } } From e7174d8b9a12f642d51f60c7f1bef3eb24f6df9c Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 08:48:55 +0900 Subject: [PATCH 13/26] =?UTF-8?q?refactor:=20Service=EC=97=90=EC=84=9C=20M?= =?UTF-8?q?ission=20description=EC=9D=84=20=EB=B3=80=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/application/MissionDtos.kt | 4 ++-- src/main/kotlin/apply/application/MyMissionService.kt | 2 ++ src/main/kotlin/apply/domain/mission/Mission.kt | 4 ---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/apply/application/MissionDtos.kt b/src/main/kotlin/apply/application/MissionDtos.kt index d53a09ad5..2acaddcf4 100644 --- a/src/main/kotlin/apply/application/MissionDtos.kt +++ b/src/main/kotlin/apply/application/MissionDtos.kt @@ -134,10 +134,10 @@ data class MyMissionResponse( val status: MissionStatus, val submitted: Boolean, ) { - constructor(mission: Mission, submitted: Boolean) : this( + constructor(mission: Mission, submitted: Boolean, formattedDescription: String) : this( mission.id, mission.title, - mission.formattedDescription, + formattedDescription, mission.submittable, mission.period.startDateTime, mission.period.endDateTime, diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index b21776b97..1baec1453 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -15,6 +15,7 @@ import apply.domain.mission.MissionRepository import apply.domain.mission.getOrThrow import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import support.markdownToEmbeddedHtml @Transactional(readOnly = true) @Service @@ -108,6 +109,7 @@ class MyMissionService( return MyMissionResponse( mission = mission, submitted = assignment != null, + markdownToEmbeddedHtml(mission.description), ) } } diff --git a/src/main/kotlin/apply/domain/mission/Mission.kt b/src/main/kotlin/apply/domain/mission/Mission.kt index 44941f6f6..c98c82419 100644 --- a/src/main/kotlin/apply/domain/mission/Mission.kt +++ b/src/main/kotlin/apply/domain/mission/Mission.kt @@ -3,7 +3,6 @@ package apply.domain.mission import org.hibernate.annotations.SQLDelete import org.hibernate.annotations.Where import support.domain.BaseEntity -import support.markdownToEmbeddedHtml import java.time.LocalDateTime import javax.persistence.Column import javax.persistence.Embedded @@ -43,9 +42,6 @@ class Mission( val isSubmitting: Boolean get() = status == MissionStatus.SUBMITTING - val formattedDescription: String - get() = markdownToEmbeddedHtml(description) - constructor( title: String, description: String, From 010b2e78fcef028669bfff2436db1a5100977123 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 08:50:09 +0900 Subject: [PATCH 14/26] =?UTF-8?q?refactor:=20User=20->=20Member=20?= =?UTF-8?q?=EC=A0=84=ED=99=98=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/application/MyMissionService.kt | 2 +- src/main/kotlin/apply/ui/api/MissionRestController.kt | 2 +- src/test/kotlin/apply/application/MyMissionServiceTest.kt | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index 1baec1453..d5993bd41 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -97,7 +97,7 @@ class MyMissionService( ) } - fun findByUserIdAndMissionId(memberId: Long, missionId: Long): MyMissionResponse { + fun findByMemberIdAndMissionId(memberId: Long, missionId: Long): MyMissionResponse { val mission = missionRepository.getOrThrow(missionId) val evaluationTarget = evaluationTargetRepository.findByEvaluationIdAndMemberId(mission.evaluationId, memberId) ?: throw NoSuchElementException("과제 참여 대상자가 아닙니다.") diff --git a/src/main/kotlin/apply/ui/api/MissionRestController.kt b/src/main/kotlin/apply/ui/api/MissionRestController.kt index 529b03be1..77c95196f 100644 --- a/src/main/kotlin/apply/ui/api/MissionRestController.kt +++ b/src/main/kotlin/apply/ui/api/MissionRestController.kt @@ -70,7 +70,7 @@ class MissionRestController( @PathVariable missionId: Long, @LoginMember member: Member ): ResponseEntity> { - val response = missionQueryService.findByUserIdAndMissionId(member.id, missionId) + val response = missionQueryService.findByMemberIdAndMissionId(member.id, missionId) return ResponseEntity.ok(ApiResponse.success(response)) } diff --git a/src/test/kotlin/apply/application/MyMissionServiceTest.kt b/src/test/kotlin/apply/application/MyMissionServiceTest.kt index 91ed65b95..84bce8b4f 100644 --- a/src/test/kotlin/apply/application/MyMissionServiceTest.kt +++ b/src/test/kotlin/apply/application/MyMissionServiceTest.kt @@ -47,7 +47,7 @@ class MyMissionServiceTest : BehaviorSpec({ every { assignmentRepository.findByMemberIdAndMissionId(eq(participatedUser.id), any()) } returns createAssignment() When("요청한 유저가 과제 참여자이면") { - val actual = myMissionService.findByUserIdAndMissionId(participatedUser.id, mission.id) + val actual = myMissionService.findByMemberIdAndMissionId(participatedUser.id, mission.id) Then("과제의 상세 내용을 확인할 수 있다") { actual.shouldNotBeNull() @@ -59,7 +59,7 @@ class MyMissionServiceTest : BehaviorSpec({ When("요청한 유저가 과제 참여자가 아니면") { Then("과제의 상세 내용을 확인할 수 없다") { shouldThrow { - myMissionService.findByUserIdAndMissionId(notParticipatedUser.id, mission.id) + myMissionService.findByMemberIdAndMissionId(notParticipatedUser.id, mission.id) } } } @@ -78,7 +78,7 @@ class MyMissionServiceTest : BehaviorSpec({ When("요청한 유저가 과제 참여자이면") { Then("과제 상태로 인한 예외로 상세 내용을 확인할 수 없다") { shouldThrow { - myMissionService.findByUserIdAndMissionId(participatedUser.id, mission.id) + myMissionService.findByMemberIdAndMissionId(participatedUser.id, mission.id) } } } @@ -86,7 +86,7 @@ class MyMissionServiceTest : BehaviorSpec({ When("요청한 유저가 과제 참여자가 아니면") { Then("참여 대상자가 아니기 때문에 발생한 예외로 상세 내용을 확인할 수 없다") { shouldThrow { - myMissionService.findByUserIdAndMissionId(notParticipatedUser.id, mission.id) + myMissionService.findByMemberIdAndMissionId(notParticipatedUser.id, mission.id) } } } From f557d2fc5421cf4eaf8e03a0e7bce6f244279237 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 08:50:41 +0900 Subject: [PATCH 15/26] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=95=88=ED=95=98=EB=8A=94=20=EB=B3=80=EC=88=98=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/application/MyMissionService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index d5993bd41..663a3d319 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -99,7 +99,7 @@ class MyMissionService( fun findByMemberIdAndMissionId(memberId: Long, missionId: Long): MyMissionResponse { val mission = missionRepository.getOrThrow(missionId) - val evaluationTarget = evaluationTargetRepository.findByEvaluationIdAndMemberId(mission.evaluationId, memberId) + evaluationTargetRepository.findByEvaluationIdAndMemberId(mission.evaluationId, memberId) ?: throw NoSuchElementException("과제 참여 대상자가 아닙니다.") check(!mission.hidden) { "비공개 상태의 과제입니다." } From 02213d438af251d7ffd2cb2432e62ed9d5027dcf Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 09:12:13 +0900 Subject: [PATCH 16/26] =?UTF-8?q?feat:=20Mission=20=EA=B8=B0=EA=B0=84=20?= =?UTF-8?q?=EB=82=B4=EC=97=90=EB=A7=8C=20=EA=B3=BC=EC=A0=9C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EB=B3=B4=EA=B8=B0=EA=B0=80=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/application/MyMissionService.kt | 7 +++++-- src/main/kotlin/apply/domain/mission/Mission.kt | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index 663a3d319..cfc673d36 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -99,10 +99,13 @@ class MyMissionService( fun findByMemberIdAndMissionId(memberId: Long, missionId: Long): MyMissionResponse { val mission = missionRepository.getOrThrow(missionId) + evaluationTargetRepository.findByEvaluationIdAndMemberId(mission.evaluationId, memberId) - ?: throw NoSuchElementException("과제 참여 대상자가 아닙니다.") + ?: throw NoSuchElementException("과제가 존재하지 않습니다. id: $missionId") - check(!mission.hidden) { "비공개 상태의 과제입니다." } + if (!mission.isDescriptionViewable) { + throw NoSuchElementException("과제가 존재하지 않습니다. id: $missionId") + } val assignment = assignmentRepository.findByMemberIdAndMissionId(memberId, missionId) diff --git a/src/main/kotlin/apply/domain/mission/Mission.kt b/src/main/kotlin/apply/domain/mission/Mission.kt index c98c82419..ceade4426 100644 --- a/src/main/kotlin/apply/domain/mission/Mission.kt +++ b/src/main/kotlin/apply/domain/mission/Mission.kt @@ -42,6 +42,9 @@ class Mission( val isSubmitting: Boolean get() = status == MissionStatus.SUBMITTING + val isDescriptionViewable: Boolean + get() = !hidden && (status == MissionStatus.SUBMITTING || status == MissionStatus.UNSUBMITTABLE) + constructor( title: String, description: String, From e2b13db912d8b590816b4f2ff22f8d415a0a99a3 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 09:33:34 +0900 Subject: [PATCH 17/26] =?UTF-8?q?refactor:=20admin=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=A0=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20dialog=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailPreviewDialog.kt => PreviewDialog.kt} | 6 +- .../apply/ui/admin/mail/MailsFormView.kt | 3 +- .../ui/admin/mission/MissionPreviewDialog.kt | 55 ------------------- .../ui/admin/mission/MissionsFormView.kt | 3 +- 4 files changed, 7 insertions(+), 60 deletions(-) rename src/main/kotlin/apply/ui/admin/{mail/MailPreviewDialog.kt => PreviewDialog.kt} (92%) delete mode 100644 src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt diff --git a/src/main/kotlin/apply/ui/admin/mail/MailPreviewDialog.kt b/src/main/kotlin/apply/ui/admin/PreviewDialog.kt similarity index 92% rename from src/main/kotlin/apply/ui/admin/mail/MailPreviewDialog.kt rename to src/main/kotlin/apply/ui/admin/PreviewDialog.kt index 775454a4a..8e9450579 100644 --- a/src/main/kotlin/apply/ui/admin/mail/MailPreviewDialog.kt +++ b/src/main/kotlin/apply/ui/admin/PreviewDialog.kt @@ -1,4 +1,4 @@ -package apply.ui.admin.mail +package apply.ui.admin import com.vaadin.flow.component.Component import com.vaadin.flow.component.Html @@ -11,7 +11,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout import org.jsoup.Jsoup import support.views.createContrastButton -class MailPreviewDialog( +class PreviewDialog( htmlText: String ) : Dialog() { init { @@ -22,7 +22,7 @@ class MailPreviewDialog( } private fun createHeader(): VerticalLayout { - return VerticalLayout(H2("메일 미리 보기")).apply { + return VerticalLayout(H2("미리 보기")).apply { isPadding = false element.style["margin-bottom"] = "10px" } diff --git a/src/main/kotlin/apply/ui/admin/mail/MailsFormView.kt b/src/main/kotlin/apply/ui/admin/mail/MailsFormView.kt index 069afcefc..21a76d3b5 100644 --- a/src/main/kotlin/apply/ui/admin/mail/MailsFormView.kt +++ b/src/main/kotlin/apply/ui/admin/mail/MailsFormView.kt @@ -8,6 +8,7 @@ import apply.application.RecruitmentService import apply.application.mail.MailData import apply.application.mail.MailService import apply.ui.admin.BaseLayout +import apply.ui.admin.PreviewDialog import com.vaadin.flow.component.Component import com.vaadin.flow.component.UI import com.vaadin.flow.component.button.Button @@ -82,7 +83,7 @@ class MailsFormView( private fun createPreviewButton(): Button { return createContrastButton("미리 보기") { - handleMailData { mail -> MailPreviewDialog(mailService.generateMailBody(mail)) } + handleMailData { mail -> PreviewDialog(mailService.generateMailBody(mail)) } } } diff --git a/src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt b/src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt deleted file mode 100644 index 4c3bbd9a1..000000000 --- a/src/main/kotlin/apply/ui/admin/mission/MissionPreviewDialog.kt +++ /dev/null @@ -1,55 +0,0 @@ -package apply.ui.admin.mission - -import com.vaadin.flow.component.Component -import com.vaadin.flow.component.Html -import com.vaadin.flow.component.button.Button -import com.vaadin.flow.component.dialog.Dialog -import com.vaadin.flow.component.html.H2 -import com.vaadin.flow.component.orderedlayout.FlexComponent -import com.vaadin.flow.component.orderedlayout.HorizontalLayout -import com.vaadin.flow.component.orderedlayout.VerticalLayout -import support.views.createContrastButton - -class MissionPreviewDialog( - htmlString: String -) : Dialog() { - init { - add(createHeader(), createHtmlComponentFrom(htmlString), createButtons()) - width = "700px" - height = "800px" - open() - } - - private fun createHeader(): VerticalLayout { - return VerticalLayout(H2("과제 설명 미리보기")).apply { - isPadding = false - val style = element.style - style.set("margin-bottom", "10px") - style["margin-bottom"] = "10px" - element.style["margin-bottom"] = "10px" - } - } - - private fun createHtmlComponentFrom(htmlString: String): Component { - val wrappedHtmlString = "
$htmlString
" - return Html(wrappedHtmlString).apply { - element.style["display"] = "block" - element.style["height"] = "600px" - element.style["overflow"] = "auto" - } - } - - private fun createButtons(): Component { - return HorizontalLayout(createCloseButton()).apply { - setWidthFull() - justifyContentMode = FlexComponent.JustifyContentMode.CENTER - element.style["margin-top"] = "20px" - } - } - - private fun createCloseButton(): Button { - return createContrastButton("닫기") { - close() - } - } -} diff --git a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt index 13cb8f2f1..b3a3f5eed 100644 --- a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt +++ b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt @@ -4,6 +4,7 @@ import apply.application.EvaluationService import apply.application.MissionData import apply.application.MissionService import apply.ui.admin.BaseLayout +import apply.ui.admin.PreviewDialog import com.vaadin.flow.component.Component import com.vaadin.flow.component.UI import com.vaadin.flow.component.button.Button @@ -86,7 +87,7 @@ class MissionsFormView( return createContrastButton("미리보기") { handleMissionData { mission -> val formattedDescription = missionService.parseDescription(mission.description) - MissionPreviewDialog(formattedDescription) + PreviewDialog("
$formattedDescription
") } } } From e545e26c61828a4ef65023c89fb8f321d9550101 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 09:36:43 +0900 Subject: [PATCH 18/26] =?UTF-8?q?refactor:=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=84=B8=ED=8C=85=20=EC=8B=9C=20?= =?UTF-8?q?trimIndent()=20=EB=8C=80=EC=8B=A0=20trimMargin()=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apply/config/DatabaseInitializer.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/apply/config/DatabaseInitializer.kt b/src/main/kotlin/apply/config/DatabaseInitializer.kt index 36d1e1db5..a72914b1d 100644 --- a/src/main/kotlin/apply/config/DatabaseInitializer.kt +++ b/src/main/kotlin/apply/config/DatabaseInitializer.kt @@ -413,14 +413,14 @@ class DatabaseInitializer( Mission( title = "1주 차 프리코스 - 숫자 야구 게임", description = """ - # 미션 - 숫자 야구 게임 - - ## 🔍 진행 방식 - - - 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. - - 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. - - 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. - """.trimIndent(), + |# 미션 - 숫자 야구 게임 + | + |## 🔍 진행 방식 + | + |- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. + |- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. + |- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. + """.trimMargin(), evaluationId = 2L, startDateTime = createLocalDateTime(2020, 11, 24, 15), endDateTime = createLocalDateTime(2120, 12, 1, 0), @@ -430,14 +430,14 @@ class DatabaseInitializer( Mission( title = "2주 차 프리코스 - 자동차 경주 게임", description = """ - # 미션 - 자동차 경주 게임 - - ## 🔍 진행 방식 - - - 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. - - 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. - - 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. - """.trimIndent(), + |# 미션 - 자동차 경주 게임 + | + |## 🔍 진행 방식 + | + |- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. + |- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. + |- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. + """.trimMargin(), evaluationId = 3L, startDateTime = createLocalDateTime(2020, 12, 1, 15), endDateTime = createLocalDateTime(2120, 12, 8, 0), From e4c50388857f723e24af202ac9513cfcc83dcff1 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 09:37:55 +0900 Subject: [PATCH 19/26] =?UTF-8?q?refactor:=20flyway=20script=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...5_1__Alter_mission_table.sql => V4_5__Alter_mission_table.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V5_1__Alter_mission_table.sql => V4_5__Alter_mission_table.sql} (100%) diff --git a/src/main/resources/db/migration/V5_1__Alter_mission_table.sql b/src/main/resources/db/migration/V4_5__Alter_mission_table.sql similarity index 100% rename from src/main/resources/db/migration/V5_1__Alter_mission_table.sql rename to src/main/resources/db/migration/V4_5__Alter_mission_table.sql From 75b7694528287e078d64f6eafc02858fc2480810 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Thu, 18 Jul 2024 10:07:10 +0900 Subject: [PATCH 20/26] =?UTF-8?q?test:=20=EB=82=98=EC=9D=98=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20Controlle?= =?UTF-8?q?rTest=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/mission.adoc | 6 ++++- src/test/kotlin/apply/MissionFixtures.kt | 23 +++++++++++++++++++ .../apply/ui/api/MissionRestControllerTest.kt | 17 ++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/mission.adoc b/src/docs/asciidoc/mission.adoc index da7b6efbc..50a153957 100644 --- a/src/docs/asciidoc/mission.adoc +++ b/src/docs/asciidoc/mission.adoc @@ -1,5 +1,9 @@ = 과제 관련 API -== 내 과제 조회 +== 내 과제 목록 조회 + +operation::mission-list-me-get[snippets='http-request,http-response'] + +== 내 과제 상세 조회 operation::mission-me-get[snippets='http-request,http-response'] diff --git a/src/test/kotlin/apply/MissionFixtures.kt b/src/test/kotlin/apply/MissionFixtures.kt index 92fae5e46..a7ccafcf4 100644 --- a/src/test/kotlin/apply/MissionFixtures.kt +++ b/src/test/kotlin/apply/MissionFixtures.kt @@ -6,6 +6,7 @@ import apply.application.LastJudgmentResponse import apply.application.MissionData import apply.application.MissionResponse import apply.application.MyMissionAndJudgementResponse +import apply.application.MyMissionResponse import apply.domain.mission.Mission import apply.domain.mission.MissionStatus import java.time.LocalDateTime @@ -95,3 +96,25 @@ fun createMyMissionAndJudgementResponse( judgment ) } + +fun createMyMissionResponse( + title: String = MISSION_TITLE, + description: String = MISSION_DESCRIPTION, + submittable: Boolean = true, + startDateTime: LocalDateTime = START_DATE_TIME, + endDateTime: LocalDateTime = END_DATE_TIME, + missionStatus: MissionStatus = MissionStatus.SUBMITTING, + submitted: Boolean = true, + id: Long = 0L, +): MyMissionResponse { + return MyMissionResponse( + id, + title, + description, + submittable, + startDateTime, + endDateTime, + missionStatus, + submitted, + ) +} diff --git a/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt b/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt index db4a508bf..018215d22 100644 --- a/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt +++ b/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt @@ -9,6 +9,7 @@ import apply.createMission import apply.createMissionData import apply.createMissionResponse import apply.createMyMissionAndJudgementResponse +import apply.createMyMissionResponse import apply.domain.judgment.JudgmentStatus import com.ninjasquad.springmockk.MockkBean import io.mockk.Runs @@ -92,6 +93,22 @@ class MissionRestControllerTest : RestControllerTest() { }.andExpect { status { isOk() } content { success(responses) } + }.andDo { + handle(document("mission-list-me-get")) + } + } + + @Test + fun `나의 과제를 상세 조회한다`() { + val response = createMyMissionResponse(id = 1L) + + every { missionQueryService.findByMemberIdAndMissionId(any(), any()) } returns response + + mockMvc.get("/api/recruitments/{recruitmentId}/missions/{missionId}/me", 1L, 1L) { + bearer("valid_token") + }.andExpect { + status { isOk() } + content { success(response) } }.andDo { handle(document("mission-me-get")) } From 15862ce2a9415b2bbb675ba4272738c6d00ebbd6 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Tue, 23 Jul 2024 16:09:01 +0900 Subject: [PATCH 21/26] =?UTF-8?q?refactor:=20=EB=82=B4=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=9C=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=20=EC=A0=81=ED=95=A9=ED=95=9C=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=9A=A9=EC=96=B4=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apply/application/MyMissionServiceTest.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/test/kotlin/apply/application/MyMissionServiceTest.kt b/src/test/kotlin/apply/application/MyMissionServiceTest.kt index 84bce8b4f..5bdadd2d0 100644 --- a/src/test/kotlin/apply/application/MyMissionServiceTest.kt +++ b/src/test/kotlin/apply/application/MyMissionServiceTest.kt @@ -35,7 +35,7 @@ class MyMissionServiceTest : BehaviorSpec({ judgmentRepository ) - Given("공개 상태의 과제가 있는 경우") { + Given("공개된 과제가 있는 경우") { val participatedUser = createMember(id = 1L) val notParticipatedUser = createMember(id = 2L) val mission = createMission() @@ -46,18 +46,18 @@ class MyMissionServiceTest : BehaviorSpec({ every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), neq(participatedUser.id)) } returns null every { assignmentRepository.findByMemberIdAndMissionId(eq(participatedUser.id), any()) } returns createAssignment() - When("요청한 유저가 과제 참여자이면") { + When("요청한 유저가 평가 대상자면") { val actual = myMissionService.findByMemberIdAndMissionId(participatedUser.id, mission.id) - Then("과제의 상세 내용을 확인할 수 있다") { + Then("과제 내용을 확인할 수 있다") { actual.shouldNotBeNull() actual.description.shouldNotBeNull() actual.submitted.shouldBeTrue() } } - When("요청한 유저가 과제 참여자가 아니면") { - Then("과제의 상세 내용을 확인할 수 없다") { + When("요청한 유저가 평가 대상자가 아니면") { + Then("과제 내용을 확인할 수 없다") { shouldThrow { myMissionService.findByMemberIdAndMissionId(notParticipatedUser.id, mission.id) } @@ -65,7 +65,7 @@ class MyMissionServiceTest : BehaviorSpec({ } } - Given("비공개 상태의 과제가 있는 경우") { + Given("비공개된 과제가 있는 경우") { val participatedUser = createMember(id = 1L) val notParticipatedUser = createMember(id = 2L) val mission = createMission(hidden = true) @@ -75,16 +75,16 @@ class MyMissionServiceTest : BehaviorSpec({ every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), eq(participatedUser.id)) } returns target every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), neq(participatedUser.id)) } returns null - When("요청한 유저가 과제 참여자이면") { - Then("과제 상태로 인한 예외로 상세 내용을 확인할 수 없다") { - shouldThrow { + When("요청한 유저가 평가 대상자면") { + Then("과제 내용을 확인할 수 없다") { + shouldThrow { myMissionService.findByMemberIdAndMissionId(participatedUser.id, mission.id) } } } - When("요청한 유저가 과제 참여자가 아니면") { - Then("참여 대상자가 아니기 때문에 발생한 예외로 상세 내용을 확인할 수 없다") { + When("요청한 유저가 평가 대상자가 아니면") { + Then("과제 내용을 확인할 수 없다") { shouldThrow { myMissionService.findByMemberIdAndMissionId(notParticipatedUser.id, mission.id) } From 4eedac403a6dff79c89b87340af7530e4988ccbe Mon Sep 17 00:00:00 2001 From: woowabrie Date: Tue, 23 Jul 2024 16:10:06 +0900 Subject: [PATCH 22/26] =?UTF-8?q?refactor:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20API=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EC=A0=9C=EB=AA=A9=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/mission.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/asciidoc/mission.adoc b/src/docs/asciidoc/mission.adoc index 50a153957..ffbfb480f 100644 --- a/src/docs/asciidoc/mission.adoc +++ b/src/docs/asciidoc/mission.adoc @@ -4,6 +4,6 @@ operation::mission-list-me-get[snippets='http-request,http-response'] -== 내 과제 상세 조회 +== 내 과제 조회 operation::mission-me-get[snippets='http-request,http-response'] From 01c82301be4f5f164efa25034997cd39df05f06a Mon Sep 17 00:00:00 2001 From: woowabrie Date: Tue, 23 Jul 2024 16:14:32 +0900 Subject: [PATCH 23/26] =?UTF-8?q?refactor:=20MyMissionResponse=EC=9D=98=20?= =?UTF-8?q?=EB=B6=80=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EB=A7=A4=EA=B0=9C?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=88=9C=EC=84=9C=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/application/MissionDtos.kt | 2 +- src/main/kotlin/apply/application/MyMissionService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/apply/application/MissionDtos.kt b/src/main/kotlin/apply/application/MissionDtos.kt index 2acaddcf4..83f0ae8d2 100644 --- a/src/main/kotlin/apply/application/MissionDtos.kt +++ b/src/main/kotlin/apply/application/MissionDtos.kt @@ -134,7 +134,7 @@ data class MyMissionResponse( val status: MissionStatus, val submitted: Boolean, ) { - constructor(mission: Mission, submitted: Boolean, formattedDescription: String) : this( + constructor(mission: Mission, formattedDescription: String, submitted: Boolean) : this( mission.id, mission.title, formattedDescription, diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index cfc673d36..1cccd5483 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -111,8 +111,8 @@ class MyMissionService( return MyMissionResponse( mission = mission, + formattedDescription = markdownToEmbeddedHtml(mission.description), submitted = assignment != null, - markdownToEmbeddedHtml(mission.description), ) } } From 48a795d7fee3aee9b3b056fd86c3fafcc9bbb128 Mon Sep 17 00:00:00 2001 From: woowabrie Date: Tue, 23 Jul 2024 16:16:02 +0900 Subject: [PATCH 24/26] =?UTF-8?q?refactor:=20=EB=82=B4=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=9C=20=EC=A1=B0=ED=9A=8C=20=EC=BD=94=EB=93=9C=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/apply/application/MyMissionService.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index 1cccd5483..9c9199441 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -99,16 +99,11 @@ class MyMissionService( fun findByMemberIdAndMissionId(memberId: Long, missionId: Long): MyMissionResponse { val mission = missionRepository.getOrThrow(missionId) - - evaluationTargetRepository.findByEvaluationIdAndMemberId(mission.evaluationId, memberId) - ?: throw NoSuchElementException("과제가 존재하지 않습니다. id: $missionId") - - if (!mission.isDescriptionViewable) { + val evaluationTarget = evaluationTargetRepository.findByEvaluationIdAndMemberId(mission.evaluationId, memberId) + if (!mission.isDescriptionViewable || evaluationTarget == null) { throw NoSuchElementException("과제가 존재하지 않습니다. id: $missionId") } - val assignment = assignmentRepository.findByMemberIdAndMissionId(memberId, missionId) - return MyMissionResponse( mission = mission, formattedDescription = markdownToEmbeddedHtml(mission.description), From 31f49b267aa968f185186ff422a5eaaee99725c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B8=8C=EB=A6=AC?= Date: Tue, 23 Jul 2024 16:18:59 +0900 Subject: [PATCH 25/26] =?UTF-8?q?refactor:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B0=80=EB=8F=85=EC=84=B1=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 박재성 --- src/main/kotlin/apply/domain/mission/Mission.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/apply/domain/mission/Mission.kt b/src/main/kotlin/apply/domain/mission/Mission.kt index ceade4426..746f8ea3c 100644 --- a/src/main/kotlin/apply/domain/mission/Mission.kt +++ b/src/main/kotlin/apply/domain/mission/Mission.kt @@ -43,7 +43,7 @@ class Mission( get() = status == MissionStatus.SUBMITTING val isDescriptionViewable: Boolean - get() = !hidden && (status == MissionStatus.SUBMITTING || status == MissionStatus.UNSUBMITTABLE) + get() = !hidden && status in listOf(MissionStatus.SUBMITTING, MissionStatus.UNSUBMITTABLE) constructor( title: String, From f7e5b18601e036cda5f0f7346425a04a0662e585 Mon Sep 17 00:00:00 2001 From: woowahan-pjs Date: Wed, 24 Jul 2024 16:05:31 +0900 Subject: [PATCH 26/26] test(mission): refine test scenarios --- .../kotlin/apply/application/MissionDtos.kt | 4 +- .../apply/application/MyMissionService.kt | 2 +- .../ui/admin/mission/MissionsFormView.kt | 3 +- src/test/kotlin/apply/MissionFixtures.kt | 13 ++++- .../apply/application/MyMissionServiceTest.kt | 56 +++++++++---------- .../apply/ui/api/MissionRestControllerTest.kt | 6 +- 6 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/apply/application/MissionDtos.kt b/src/main/kotlin/apply/application/MissionDtos.kt index 83f0ae8d2..9b0216384 100644 --- a/src/main/kotlin/apply/application/MissionDtos.kt +++ b/src/main/kotlin/apply/application/MissionDtos.kt @@ -134,10 +134,10 @@ data class MyMissionResponse( val status: MissionStatus, val submitted: Boolean, ) { - constructor(mission: Mission, formattedDescription: String, submitted: Boolean) : this( + constructor(mission: Mission, description: String, submitted: Boolean) : this( mission.id, mission.title, - formattedDescription, + description, mission.submittable, mission.period.startDateTime, mission.period.endDateTime, diff --git a/src/main/kotlin/apply/application/MyMissionService.kt b/src/main/kotlin/apply/application/MyMissionService.kt index 9c9199441..a5ebc59bb 100644 --- a/src/main/kotlin/apply/application/MyMissionService.kt +++ b/src/main/kotlin/apply/application/MyMissionService.kt @@ -106,7 +106,7 @@ class MyMissionService( val assignment = assignmentRepository.findByMemberIdAndMissionId(memberId, missionId) return MyMissionResponse( mission = mission, - formattedDescription = markdownToEmbeddedHtml(mission.description), + description = markdownToEmbeddedHtml(mission.description), submitted = assignment != null, ) } diff --git a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt index b3a3f5eed..e1a42c072 100644 --- a/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt +++ b/src/main/kotlin/apply/ui/admin/mission/MissionsFormView.kt @@ -24,7 +24,6 @@ import support.views.createPrimaryButton import support.views.toDisplayName private val MISSION_FORM_URL_PATTERN: Regex = Regex("^(\\d*)/?(\\d*)/?($NEW_VALUE|$EDIT_VALUE)$") -private const val DATA_NOT_BIND_MESSAGE: String = "모든 항목이 잘 입력되었는지 확인해 주세요." @Route(value = "admin/missions", layout = BaseLayout::class) class MissionsFormView( @@ -95,7 +94,7 @@ class MissionsFormView( private fun handleMissionData(action: (MissionData) -> Unit) { val result = missionForm.bindOrNull() if (result == null) { - createNotification(DATA_NOT_BIND_MESSAGE) + createNotification("모든 항목이 정확하게 입력되었는지 확인해 주세요.") } else { action(result) } diff --git a/src/test/kotlin/apply/MissionFixtures.kt b/src/test/kotlin/apply/MissionFixtures.kt index a7ccafcf4..756ed36cd 100644 --- a/src/test/kotlin/apply/MissionFixtures.kt +++ b/src/test/kotlin/apply/MissionFixtures.kt @@ -9,10 +9,21 @@ import apply.application.MyMissionAndJudgementResponse import apply.application.MyMissionResponse import apply.domain.mission.Mission import apply.domain.mission.MissionStatus +import support.flattenByMargin import java.time.LocalDateTime private const val MISSION_TITLE: String = "숫자야구게임" private const val MISSION_DESCRIPTION: String = "과제 설명입니다." +private val FORMATTED_MISSION_DESCRIPTION: String = + """ + |

미션 - 숫자 야구 게임

+ |

🔍 진행 방식

+ |
    + |
  • 미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.
  • + |
  • 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
  • + |
  • 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.
  • + |
+ """.flattenByMargin() private val START_DATE_TIME: LocalDateTime = LocalDateTime.now() private val END_DATE_TIME: LocalDateTime = LocalDateTime.now().plusDays(7L) @@ -99,7 +110,7 @@ fun createMyMissionAndJudgementResponse( fun createMyMissionResponse( title: String = MISSION_TITLE, - description: String = MISSION_DESCRIPTION, + description: String = FORMATTED_MISSION_DESCRIPTION, submittable: Boolean = true, startDateTime: LocalDateTime = START_DATE_TIME, endDateTime: LocalDateTime = END_DATE_TIME, diff --git a/src/test/kotlin/apply/application/MyMissionServiceTest.kt b/src/test/kotlin/apply/application/MyMissionServiceTest.kt index 5bdadd2d0..5d1d77738 100644 --- a/src/test/kotlin/apply/application/MyMissionServiceTest.kt +++ b/src/test/kotlin/apply/application/MyMissionServiceTest.kt @@ -1,9 +1,8 @@ package apply.application -import apply.createAssignment import apply.createEvaluationTarget -import apply.createMission import apply.createMember +import apply.createMission import apply.domain.assignment.AssignmentRepository import apply.domain.evaluation.EvaluationRepository import apply.domain.evaluationtarget.EvaluationTargetRepository @@ -13,7 +12,6 @@ import apply.domain.mission.MissionRepository import apply.domain.mission.getOrThrow import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec -import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.nulls.shouldNotBeNull import io.mockk.every import io.mockk.mockk @@ -35,58 +33,56 @@ class MyMissionServiceTest : BehaviorSpec({ judgmentRepository ) - Given("공개된 과제가 있는 경우") { - val participatedUser = createMember(id = 1L) - val notParticipatedUser = createMember(id = 2L) - val mission = createMission() - val target = createEvaluationTarget(evaluationId = mission.evaluationId, memberId = participatedUser.id) + Given("과제가 과제 기간 내에 있고 공개인 경우") { + val mission = createMission(hidden = false) + val evaluationTarget = createEvaluationTarget(evaluationId = mission.evaluationId, memberId = 1L) + val justMember = createMember(id = 2L) every { missionRepository.getOrThrow(any()) } returns mission - every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), eq(participatedUser.id)) } returns target - every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), neq(participatedUser.id)) } returns null - every { assignmentRepository.findByMemberIdAndMissionId(eq(participatedUser.id), any()) } returns createAssignment() + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), evaluationTarget.memberId) } returns evaluationTarget + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), justMember.id) } returns null + every { assignmentRepository.findByMemberIdAndMissionId(any(), any()) } returns null - When("요청한 유저가 평가 대상자면") { - val actual = myMissionService.findByMemberIdAndMissionId(participatedUser.id, mission.id) + When("평가 대상자가 과제를 조회하면") { + val actual = myMissionService.findByMemberIdAndMissionId(evaluationTarget.memberId, mission.id) - Then("과제 내용을 확인할 수 있다") { + Then("과제를 확인할 수 있다") { actual.shouldNotBeNull() actual.description.shouldNotBeNull() - actual.submitted.shouldBeTrue() } } - When("요청한 유저가 평가 대상자가 아니면") { - Then("과제 내용을 확인할 수 없다") { + When("평가 대상자가 아닌 회원이 과제를 조회하면") { + Then("예외가 발생한다") { shouldThrow { - myMissionService.findByMemberIdAndMissionId(notParticipatedUser.id, mission.id) + myMissionService.findByMemberIdAndMissionId(justMember.id, mission.id) } } } } - Given("비공개된 과제가 있는 경우") { - val participatedUser = createMember(id = 1L) - val notParticipatedUser = createMember(id = 2L) + Given("과제가 과제 기간 내에 있지만 비공개인 경우") { val mission = createMission(hidden = true) - val target = createEvaluationTarget(evaluationId = mission.evaluationId, memberId = participatedUser.id) + val evaluationTarget = createEvaluationTarget(evaluationId = mission.evaluationId, memberId = 1L) + val justMember = createMember(id = 2L) every { missionRepository.getOrThrow(any()) } returns mission - every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), eq(participatedUser.id)) } returns target - every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), neq(participatedUser.id)) } returns null + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), evaluationTarget.memberId) } returns evaluationTarget + every { evaluationTargetRepository.findByEvaluationIdAndMemberId(any(), justMember.id) } returns null + every { assignmentRepository.findByMemberIdAndMissionId(any(), any()) } returns null - When("요청한 유저가 평가 대상자면") { - Then("과제 내용을 확인할 수 없다") { + When("평가 대상자가 과제를 조회하면") { + Then("예외가 발생한다") { shouldThrow { - myMissionService.findByMemberIdAndMissionId(participatedUser.id, mission.id) + myMissionService.findByMemberIdAndMissionId(evaluationTarget.memberId, mission.id) } } } - When("요청한 유저가 평가 대상자가 아니면") { - Then("과제 내용을 확인할 수 없다") { + When("평가 대상자가 아닌 회원이 과제를 조회하면") { + Then("예외가 발생한다") { shouldThrow { - myMissionService.findByMemberIdAndMissionId(notParticipatedUser.id, mission.id) + myMissionService.findByMemberIdAndMissionId(justMember.id, mission.id) } } } diff --git a/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt b/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt index 018215d22..252f10de9 100644 --- a/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt +++ b/src/test/kotlin/apply/ui/api/MissionRestControllerTest.kt @@ -75,7 +75,7 @@ class MissionRestControllerTest : RestControllerTest() { } @Test - fun `나의 과제들을 조회한다`() { + fun `나의 과제 목록을 조회한다`() { val responses = listOf( createMyMissionAndJudgementResponse(id = 1L, runnable = false, judgment = null), createMyMissionAndJudgementResponse(id = 2L, runnable = true, judgment = createLastJudgmentResponse()), @@ -85,7 +85,6 @@ class MissionRestControllerTest : RestControllerTest() { judgment = createLastJudgmentResponse(passCount = 9, totalCount = 10, status = JudgmentStatus.SUCCEEDED) ) ) - every { missionQueryService.findAllByMemberIdAndRecruitmentId(any(), any()) } returns responses mockMvc.get("/api/recruitments/{recruitmentId}/missions/me", 1L) { @@ -99,9 +98,8 @@ class MissionRestControllerTest : RestControllerTest() { } @Test - fun `나의 과제를 상세 조회한다`() { + fun `나의 과제를 조회한다`() { val response = createMyMissionResponse(id = 1L) - every { missionQueryService.findByMemberIdAndMissionId(any(), any()) } returns response mockMvc.get("/api/recruitments/{recruitmentId}/missions/{missionId}/me", 1L, 1L) {