-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 회원 탈퇴 및 비밀번호 변경 API 추가와 정책 반영 #150
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
Changes from 16 commits
f3dc95d
f943630
e5fe86d
41648b8
4648067
b89adee
42ce328
1717967
e0e6823
4daaafc
d304244
c4e89a1
df50b7a
bb04463
944fb44
23342f8
9c64472
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,18 @@ | ||
| package com.slatto.domain.auth.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Pattern; | ||
|
|
||
| public record PasswordChangeRequest( | ||
|
|
||
| @NotBlank(message = "현재 비밀번호는 필수입니다.") | ||
| String currentPassword, | ||
|
|
||
| @NotBlank(message = "새 비밀번호는 필수입니다.") | ||
| @Pattern( | ||
| regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[^A-Za-z0-9]).{8,64}$", | ||
| message = "비밀번호는 영문·숫자·특수문자를 포함해 8자 이상 64자 이하로 입력해야 합니다." | ||
| ) | ||
| String newPassword | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |||||||||||||||||||||||||||||||||||
| import com.slatto.domain.recruitment.enums.RecruitmentApplicationStatus; | ||||||||||||||||||||||||||||||||||||
| import com.slatto.domain.recruitment.enums.RecruitmentSortType; | ||||||||||||||||||||||||||||||||||||
| import com.slatto.domain.recruitment.enums.RecruitmentStatus; | ||||||||||||||||||||||||||||||||||||
| import com.slatto.domain.recruitment.exception.RecruitmentErrorCode; | ||||||||||||||||||||||||||||||||||||
| import com.slatto.domain.recruitment.repository.RecruitmentApplicationRepository; | ||||||||||||||||||||||||||||||||||||
| import com.slatto.domain.recruitment.repository.RecruitmentBookmarkRepository; | ||||||||||||||||||||||||||||||||||||
| import com.slatto.domain.recruitment.repository.RecruitmentRepository; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -107,6 +108,12 @@ public RecruitmentDetailResponse updateRecruitment( | |||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| validateWriter(recruitment, currentUserId); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // 마감된 공고는 내용을 수정할 수 없다. 다만 다시 모집중으로 되돌리는 요청은 통과시킨다. | ||||||||||||||||||||||||||||||||||||
| // 전면 차단하면 상태 변경도 같은 API 를 쓰므로 수동 마감을 취소할 방법이 사라진다. | ||||||||||||||||||||||||||||||||||||
| if (isClosed(recruitment) && !isReopening(recruitment, request)) { | ||||||||||||||||||||||||||||||||||||
| throw new BaseException(RecruitmentErrorCode.RECRUITMENT_CLOSED_NOT_EDITABLE); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+111
to
+115
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 자동 마감 공고의 재개 조건을 보완해야 합니다. 현재 조건은
수정 예시+ LocalDate today = recruitmentConverter.currentDate();
+ LocalDate effectiveDeadline = request.getDeadline() != null
+ ? request.getDeadline()
+ : recruitment.getDeadline();
+
- if (isClosed(recruitment) && request.getStatus() != RecruitmentStatus.RECRUITING) {
+ if (isClosed(recruitment)
+ && (request.getStatus() != RecruitmentStatus.RECRUITING
+ || (effectiveDeadline != null && effectiveDeadline.isBefore(today)))) {
throw new BaseException(RecruitmentErrorCode.RECRUITMENT_CLOSED_NOT_EDITABLE);
}자동 마감 공고에서 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| recruitment.update( | ||||||||||||||||||||||||||||||||||||
| request.getTitle(), | ||||||||||||||||||||||||||||||||||||
| request.getCategory(), | ||||||||||||||||||||||||||||||||||||
|
|
@@ -399,6 +406,29 @@ private RoleName getPrimaryRole(Long userId) { | |||||||||||||||||||||||||||||||||||
| return roles.isEmpty() ? null : roles.get(0).getRoleName(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // status 만 RECRUITING 으로 바꿔도 마감일이 과거면 여전히 마감이다. 그 상태로 통과시키면 | ||||||||||||||||||||||||||||||||||||
| // 같은 요청에 실린 내용 변경까지 반영돼 마감 공고 수정 금지가 우회된다. | ||||||||||||||||||||||||||||||||||||
| // 적용 후 실제로 모집중이 되는 요청만 재개로 인정한다. | ||||||||||||||||||||||||||||||||||||
| private boolean isReopening(Recruitment recruitment, RecruitmentUpdateRequest request) { | ||||||||||||||||||||||||||||||||||||
| if (request.getStatus() != RecruitmentStatus.RECRUITING) { | ||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| LocalDate appliedDeadline = request.getDeadline() != null | ||||||||||||||||||||||||||||||||||||
| ? request.getDeadline() | ||||||||||||||||||||||||||||||||||||
| : recruitment.getDeadline(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return appliedDeadline == null || !appliedDeadline.isBefore(recruitmentConverter.currentDate()); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private boolean isClosed(Recruitment recruitment) { | ||||||||||||||||||||||||||||||||||||
| return recruitmentConverter.resolveStatus( | ||||||||||||||||||||||||||||||||||||
| recruitment.getClosedManually(), | ||||||||||||||||||||||||||||||||||||
| recruitment.getDeadline(), | ||||||||||||||||||||||||||||||||||||
| recruitmentConverter.currentDate() | ||||||||||||||||||||||||||||||||||||
| ) == RecruitmentStatus.CLOSED; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private List<RegionName> getUserRegions(Long userId) { | ||||||||||||||||||||||||||||||||||||
| return locationRepository.findAllByUserIdAndRecruitmentIsNullOrderByIdAsc(userId) | ||||||||||||||||||||||||||||||||||||
| .stream() | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
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.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: SLAT-TO/SLATE-TO-BE
Length of output: 50375
🏁 Script executed:
Repository: SLAT-TO/SLATE-TO-BE
Length of output: 21131
🏁 Script executed:
Repository: SLAT-TO/SLATE-TO-BE
Length of output: 10805
동시 탈퇴와 공고 수정의 race를 막기 위해
deletedAt도 함께 잠금하세요.@DynamicUpdate는 비dirty한 컬럼을 exclude하지만, 벌크 UPDATE 이후 같은 트랜잭션에서 엔티티를 load하고 수정하면 flush 때deletedAt이 null로 다시 덮어써질 수 있습니다.recruitment에 적어도 DB 잠금이나deletedAt를 가진 optimistic lock/check 조건을 포함해 두 경로를 직렬화하거나 soft delete 조건을 보호하세요.🤖 Prompt for AI Agents