-
Notifications
You must be signed in to change notification settings - Fork 100
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
feat(agreement): change to manage agreements dynamically #747
Changes from 13 commits
464708e
e0cf3e8
2cfed39
f285eb9
1a8544e
a39f54e
0c060df
2c00ba5
2fc734b
54c91aa
6f577e4
aa9f16c
2b82d49
ca4b82f
366067a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import axios from "axios"; | ||
|
||
export type AgreementResponse = { | ||
id: number; | ||
version: number; | ||
content: string; | ||
}; | ||
|
||
export const fetchAgreement = () => axios.get<AgreementResponse>("/api/agreements/latest"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
= 동의서 관련 API | ||
|
||
== 최신 버전의 동의서 조회 | ||
|
||
operation::agreement-latest-get[snippets='http-request,http-response'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package apply.application | ||
|
||
import apply.domain.agreement.Agreement | ||
|
||
data class AgreementResponse(val id: Long, val version: Int, val content: String) { | ||
constructor(agreement: Agreement) : this(agreement.id, agreement.version, agreement.content) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package apply.application | ||
|
||
import apply.domain.agreement.AgreementRepository | ||
import apply.domain.agreement.getFirstByOrderByVersionDesc | ||
import org.springframework.stereotype.Service | ||
|
||
@Service | ||
class AgreementService( | ||
private val agreementRepository: AgreementRepository, | ||
) { | ||
fun latest(): AgreementResponse { | ||
return agreementRepository.getFirstByOrderByVersionDesc().let(::AgreementResponse) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package apply.domain.agreement | ||
|
||
import support.domain.BaseRootEntity | ||
import java.time.format.DateTimeFormatter | ||
import javax.persistence.Column | ||
import javax.persistence.Entity | ||
|
||
@Entity | ||
class Agreement( | ||
@Column(nullable = false) | ||
val version: Int, | ||
|
||
@Column(nullable = false, length = 5000) | ||
val content: String, | ||
id: Long = 0L, | ||
) : BaseRootEntity<Agreement>(id) { | ||
init { | ||
runCatching { ISO_BASIC.parse(version.toString()) } | ||
.also { require(it.isSuccess) { "버전 형식이 일치하지 않습니다." } } | ||
} | ||
|
||
companion object { | ||
private val ISO_BASIC: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package apply.domain.agreement | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository | ||
|
||
fun AgreementRepository.getFirstByOrderByVersionDesc(): Agreement = findFirstByOrderByVersionDesc() | ||
?: throw NoSuchElementException("동의서가 존재하지 않습니다.") | ||
|
||
interface AgreementRepository : JpaRepository<Agreement, Long> { | ||
fun findFirstByOrderByVersionDesc(): Agreement? | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package apply.ui.api | ||
|
||
import apply.application.AgreementResponse | ||
import apply.application.AgreementService | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.web.bind.annotation.GetMapping | ||
import org.springframework.web.bind.annotation.RequestMapping | ||
import org.springframework.web.bind.annotation.RestController | ||
|
||
@RequestMapping("/api/agreements") | ||
@RestController | ||
class AgreementRestController( | ||
private val agreementService: AgreementService, | ||
) { | ||
@GetMapping("/latest") | ||
fun latest(): ResponseEntity<ApiResponse<AgreementResponse>> { | ||
return ResponseEntity.ok(ApiResponse.success(agreementService.latest())) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package support | ||
|
||
/** | ||
* Inside [trimMargin], "\n" is used so there is no need to distinguish between operating systems. | ||
*/ | ||
fun String.flattenByMargin(marginPrefix: String = "|"): String = trimMargin(marginPrefix).replace("\n", "") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
create table agreement | ||
( | ||
id bigint not null auto_increment, | ||
content varchar(5000) not null, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a: 만약 5000 넘어갈 일이 있다면 있을 것 같으면 미리 TEXT로 만들어둬도 좋겠네요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #738 (comment) 에서 실제로 사용되는 동의서는 636자, 1,180바이트이네요. 1,000자를 넘을 가능성은 거의 없어 보이지만, 레코드가 약 8,000바이트 정도를 넘어야 길이가 긴 컬럼을 off-page에 저장한다고 하니 지금은 걱정하지 않아도 될 것 같아요. |
||
version integer not null, | ||
primary key (id) | ||
) engine = InnoDB | ||
default charset = utf8mb4; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package apply | ||
|
||
import apply.application.AgreementResponse | ||
import apply.domain.agreement.Agreement | ||
import support.flattenByMargin | ||
|
||
private val AGREEMENT_CONTENT: String = | ||
""" | ||
|<p>(주)우아한형제들은 아래와 같이 지원자의 개인정보를 수집 및 이용합니다.</p> | ||
|<br> | ||
|<p><strong>보유 및 이용기간</strong> : <strong><span style="font-size:1.2rem">탈퇴 시 또는 이용목적 달성 시 파기</span></strong>(단, 관련법령 및 회사정책에 의해 보관이 필요한 경우 해당기간 동안 보관)</p> | ||
""".flattenByMargin() | ||
|
||
fun createAgreement( | ||
version: Int = 20240416, | ||
content: String = AGREEMENT_CONTENT, | ||
id: Long = 0L, | ||
): Agreement { | ||
return Agreement(version, content, id) | ||
} | ||
|
||
fun createAgreementResponse( | ||
version: Int = 20240416, | ||
content: String = AGREEMENT_CONTENT, | ||
id: Long = 0L, | ||
): AgreementResponse { | ||
return AgreementResponse(id, version, content) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package apply.domain.agreement | ||
|
||
import apply.createAgreement | ||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.core.spec.style.ExpectSpec | ||
import io.kotest.extensions.spring.SpringTestExtension | ||
import io.kotest.extensions.spring.SpringTestLifecycleMode | ||
import io.kotest.matchers.shouldBe | ||
import support.test.RepositoryTest | ||
|
||
@RepositoryTest | ||
class AgreementRepositoryTest( | ||
private val agreementRepository: AgreementRepository, | ||
) : ExpectSpec({ | ||
extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) | ||
|
||
context("동의서 조회") { | ||
agreementRepository.saveAll( | ||
listOf( | ||
createAgreement(version = 20240416), | ||
createAgreement(version = 20240506), | ||
) | ||
) | ||
|
||
expect("가장 최신 버전의 동의서를 조회한다") { | ||
val actual = agreementRepository.getFirstByOrderByVersionDesc() | ||
actual.version shouldBe 20240506 | ||
} | ||
} | ||
|
||
context("동의서 조회 예외") { | ||
expect("동의서가 없으면 예외가 발생한다") { | ||
shouldThrow<NoSuchElementException> { | ||
agreementRepository.getFirstByOrderByVersionDesc() | ||
} | ||
} | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package apply.domain.agreement | ||
|
||
import apply.createAgreement | ||
import io.kotest.assertions.throwables.shouldNotThrowAny | ||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.core.spec.style.StringSpec | ||
|
||
class AgreementTest : StringSpec({ | ||
"동의서 버전은 yyyyMMdd 형식으로 관리한다" { | ||
shouldNotThrowAny { createAgreement(version = 20240416) } | ||
shouldThrow<IllegalArgumentException> { createAgreement(version = 1) } | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c: href 없는 a tag로 바꾸면서 cursor가 pointer가 아닌 text 가 되었네요.
css 설정을 하거나 href="#" 넣고 이벤트를 막는 처리가 들어가면 좋을 것 같습니다! (어떤게 정석이죠?ㅋㅋ)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오! 좋습니다. 제가 놓친 부분이에요 ㅎㅎ
여러 부분 고민했는데 Link에도
href="#"
를 넣고 사용할 수 있네요 ㅎㅎ확인해 보니 브라우저 접근성 측면에서도
href="#"
처리가 좀 더 나아 보입니다!일관성을 맞추기 위해서 이 부분 코드 수정해서 반영해둘게요!