Skip to content
Merged

D2M #141

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
01d786b
feat: 문항 수에 따른 프로모션 가격 분기
Mar 10, 2026
21fe7b0
refactor: promotion tier 분기 로직 분리
Mar 10, 2026
472e1b0
refactor: promotion 분기 결정하는 문항 수 min, max yml로 관리
Mar 10, 2026
d5bb9b6
Merge pull request #126 from On-Survey/feat/OMF-257
KJaeKwan Mar 10, 2026
8f9e0b9
Merge pull request #128 from On-Survey/main
wonjuneee Mar 11, 2026
5a1443d
fix: yml파일 인덴트로 인해 설정이 누락된 케이스 수정
wonjuneee Mar 11, 2026
b3bb2b4
chore: 오토리뷰 스킵 타이틀 키워드 추가
wonjuneee Mar 11, 2026
cf98e0b
Merge pull request #129 from On-Survey/feat/OMF-263
wonjuneee Mar 15, 2026
b210d20
feat: 학회별 인증 코드 관리, 설문 등록시 코드 추가
Mar 15, 2026
6c0c5bd
Merge pull request #130 from On-Survey/feat/OMF-273
KJaeKwan Mar 15, 2026
caa7a86
fix: isResponded가 true인 response만 조회되도록 수정
KJaeKwan Mar 16, 2026
7aba3e6
chore: 주석 정리
wonjuneee Mar 19, 2026
e60552a
refactor: 폼 발행 흐름 변경
Mar 21, 2026
8cd7383
refactor: 할인 코드 만료시간 추가
Mar 21, 2026
4fb6033
fix: dto에 validation 추가
Mar 21, 2026
74d8960
feat: 구글폼 링크 유효성 검사 로직 구현
wonjuneee Mar 21, 2026
207e30a
Merge remote-tracking branch 'refs/remotes/origin/OMF-250' into feat/…
wonjuneee Mar 21, 2026
f03b8bc
feat: 변환된 설문 자동 발행 로직 추가
wonjuneee Mar 22, 2026
4f6da1e
feat: 제목 및 설명 문항 추가 구현
wonjuneee Mar 22, 2026
0156a57
mod: 설문 관심사 설정 가능하도록 수정
wonjuneee Mar 22, 2026
20a42bd
fix: 코드리뷰 반영
wonjuneee Mar 22, 2026
e8b719c
chore: 폼링크 스키마 예제 수정
wonjuneee Mar 22, 2026
b814ae8
fix: 관심사 수정 시, Set 객체 내 데이터만 수정
wonjuneee Mar 22, 2026
05bd99a
Merge pull request #132 from On-Survey/OMF-250
KJaeKwan Mar 22, 2026
79da7ca
Merge pull request #133 from On-Survey/feat/OMF-274
wonjuneee Mar 22, 2026
88cc245
fix; swagger dev 주소 수정
Mar 22, 2026
80994c6
fix: surveyInfo에 상세 가격 request 삭제 및 response 기본 값 설정
Mar 22, 2026
54954af
Merge pull request #134 from On-Survey/feat/OMF-281
KJaeKwan Mar 22, 2026
02dbf9a
Merge pull request #131 from On-Survey/feat/OMF-272
wonjuneee Mar 22, 2026
24713e9
fix: 필수 문항에 대한 답변 검증
KJaeKwan Mar 25, 2026
67b691d
Merge pull request #135 from On-Survey/feat/OMF-294
KJaeKwan Mar 25, 2026
c9428ae
mod: 변환결과 DTO 분리
wonjuneee Mar 25, 2026
ed9c7a2
feat: 변환된 설문 미리보기를 위한 대응 로직 반영
wonjuneee Mar 26, 2026
8d7f3c6
feat: 설문 변환 실패 후, 운영팀에 도움 요청 알림 전송
Mar 26, 2026
1c50a89
mod: 결제 후 변환 설문폼 저장 로직 플로우에 맞게 수정
wonjuneee Mar 26, 2026
92e023a
fix: 예외처리 수정 및 디스코드 알림 수정
wonjuneee Mar 27, 2026
a42c97f
fix: 포맷 인잭션 방지
KJaeKwan Mar 27, 2026
3b00f84
fix: 중복 요청을 방지하기 위해 30초 대기 설정
KJaeKwan Mar 27, 2026
bf2d173
Merge pull request #136 from On-Survey/feat/OMF-296
KJaeKwan Mar 27, 2026
be9fa52
Merge pull request #137 from On-Survey/feat/OMF-278
wonjuneee Mar 27, 2026
71357b9
fix: 오타 수정
wonjuneee Mar 27, 2026
4a86461
feat: 폼링크 유효성 검사 중복요청 방지 및 이메일 전송 한도 설정
wonjuneee Mar 28, 2026
9c24e6c
fix: 요청 필드 `isEmailRequired` NPE 방어로직 추가
wonjuneee Mar 28, 2026
4599b87
fix: 로깅 분기 수정
wonjuneee Mar 28, 2026
f134d09
docs: 폼 링크 유효성 검사 예외처리 및 문서 추가
wonjuneee Mar 28, 2026
657f206
fix: 람다 응답 필드명 오타 수정
wonjuneee Mar 28, 2026
f78d6f4
feat: 이메일 본문을 위해 사용자 이름 페이로드로 추가
wonjuneee Mar 29, 2026
c60623d
mod: 폼변환 링크 유효성 검사 이메일 전송 선택 요소 제거 및 한도 수정
wonjuneee Mar 29, 2026
9f925bf
feat: bo에 할인코드 관리 추가
Mar 29, 2026
c6688c9
feat: bo에 설문별 프로모션 status 조회
Mar 29, 2026
cbddd0a
feat: 할인 코드 상태별 정렬 추가
Mar 29, 2026
2230bf8
mod: 이메일 전송 여부를 전송량으로 수정
wonjuneee Mar 29, 2026
88e14f4
feat: 수집 중인 설문 정보 조회
Mar 29, 2026
3b26620
Merge pull request #138 from On-Survey/feat/OMF-297
wonjuneee Mar 29, 2026
5eead4e
fix: 코드리뷰 반영
Mar 29, 2026
c1a7d8c
Revert "fix: 필수 문항에 대한 답변 검증"
KJaeKwan Apr 3, 2026
b1c16e5
mod: 미지원 문항 원본순서 반환하도록 수정
wonjuneee Apr 4, 2026
c0a3107
Merge pull request #140 from On-Survey/feat/OMF-300
wonjuneee Apr 4, 2026
00b1101
mod: 미지원 문항의 기존 섹션 정보를 포함하도록 필드 추가
wonjuneee Apr 6, 2026
0941f6b
feat: 거주지 필터 추가, bo에 반영
Apr 6, 2026
b4b50c9
fix: 거주지 저장 안 되는 문제 해결
Apr 6, 2026
163872d
Merge pull request #142 from On-Survey/feat/OMF-301
KJaeKwan Apr 6, 2026
1c34e70
fix: 기존 설문 데이터(residences 없음) 필터링
KJaeKwan Apr 7, 2026
fc782f3
Merge pull request #143 from On-Survey/feat/OMF-302
KJaeKwan Apr 7, 2026
b1314d4
fix: bo UI 수정
KJaeKwan Apr 7, 2026
15859f3
Merge branch 'feat/OMF-302' into develop
KJaeKwan Apr 7, 2026
1a972ec
fix: 설문 조회 권한 이슈 - ageAlias 추가
KJaeKwan Apr 7, 2026
8e86730
merge: OMF-273 <- develop
Apr 7, 2026
a702e03
Merge pull request #139 from On-Survey/OMF-273
KJaeKwan Apr 7, 2026
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
44 changes: 21 additions & 23 deletions .coderabbit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,39 +34,37 @@ reviews:
"Docs",
"Documentation",
"Style",
"Refactor",
"Test",
"Tests",
"Typo",
"Merge branch",
"Revert",
"의존성",
"D2R",
"R2D",
"R2M",
"M2R"
"D2R", "D2M",
"R2D", "R2M",
"M2D", "M2R"
]

tools:
# Backend
pmd: # Java
enabled: true
sqlfluff: # SQL
enabled: true
tools:
# Backend
pmd: # Java
enabled: true
sqlfluff: # SQL
enabled: true

# Security
gitleaks:
enabled: true
semgrep:
enabled: true
# Security
gitleaks:
enabled: true
semgrep:
enabled: true

# Infrastructure
actionlint: # GitHub Actions
enabled: true
hadolint: # Dockerfile
enabled: true
yamllint: # YAML
enabled: true
# Infrastructure
actionlint: # GitHub Actions
enabled: true
hadolint: # Dockerfile
enabled: true
yamllint: # YAML
enabled: true

chat:
auto_reply: true
18 changes: 18 additions & 0 deletions src/main/java/OneQ/OnSurvey/domain/admin/api/AdminController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import OneQ.OnSurvey.domain.admin.api.dto.response.AdminSurveyDetailResponse;
import OneQ.OnSurvey.domain.admin.api.dto.response.MemberSearchResponse;
import OneQ.OnSurvey.domain.admin.api.dto.response.AdminSurveyIntroItem;
import OneQ.OnSurvey.domain.admin.api.dto.response.OngoingSurveyResponse;
import OneQ.OnSurvey.domain.admin.api.dto.response.SurveyGrantStatsResponse;
import OneQ.OnSurvey.domain.admin.application.AdminFacade;
import OneQ.OnSurvey.domain.admin.domain.model.member.AdminMemberView;
import OneQ.OnSurvey.global.common.response.PageResponse;
Expand Down Expand Up @@ -59,6 +61,22 @@ public SuccessResponse<AdminSurveyDetailResponse> getQuestionsCompleted(
return SuccessResponse.ok(adminFacade.getSurveyDetail(surveyId));
}

@GetMapping("/promotion-grants/survey-stats")
@Operation(summary = "설문별 리워드 지급 현황 조회", description = "설문 단위로 성공/실패/대기 건수를 집계하여 최신순으로 반환합니다.")
public SuccessResponse<List<SurveyGrantStatsResponse>> getSurveyGrantStats() {
return SuccessResponse.ok(adminFacade.getSurveyGrantStats());
}

@GetMapping("/dashboard/ongoing-surveys")
@Operation(summary = "수집중인 설문 현황 조회", description = "현재 ONGOING 상태인 설문의 ID, 제목, 현재응답수, 목표응답수를 반환합니다.")
public SuccessResponse<List<OngoingSurveyResponse>> getOngoingSurveys() {
return SuccessResponse.ok(
adminFacade.getOngoingSurveys().stream()
.map(OngoingSurveyResponse::from)
.toList()
);
}

@PatchMapping("/surveys/{surveyId}/owner")
@Operation(summary = "설문 소유자 변경 (어드민)", description = "어드민 권한으로 설문의 소유자를 변경합니다.")
public SuccessResponse<String> changeSurveyOwner(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public record SurveyInformationDto(
String deadline,
Set<String> ages,
String gender,
String residence,
Set<String> residences,
Set<String> interests,
Integer dueCount
) {
Expand All @@ -58,7 +58,7 @@ public static SurveyInformationDto from(SurveySingleViewInfo vo) {
vo.deadline() != null ? vo.deadline().toString() : null,
vo.ages(),
vo.gender(),
vo.residence(),
vo.residences(),
vo.interests(),
vo.dueCount()
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package OneQ.OnSurvey.domain.admin.api.dto.response;

import OneQ.OnSurvey.domain.admin.domain.model.survey.OngoingSurveyView;

public record OngoingSurveyResponse(
Long surveyId,
String title,
int completedCount,
int dueCount
) {
public static OngoingSurveyResponse from(OngoingSurveyView view) {
return new OngoingSurveyResponse(
view.surveyId(),
view.title(),
view.completedCount(),
view.dueCount()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package OneQ.OnSurvey.domain.admin.api.dto.response;

import OneQ.OnSurvey.global.promotion.PromotionGrantStatsProjection;

import java.time.LocalDateTime;

public record SurveyGrantStatsResponse(
Long surveyId,
long totalCount,
long successCount,
long failedCount,
long pendingCount,
LocalDateTime latestAt
) {
public static SurveyGrantStatsResponse from(PromotionGrantStatsProjection p) {
return new SurveyGrantStatsResponse(
p.surveyId(), p.totalCount(), p.successCount(),
p.failedCount(), p.pendingCount(), p.latestAt()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import OneQ.OnSurvey.domain.admin.api.dto.request.AdminSurveySearchQuery;
import OneQ.OnSurvey.domain.admin.api.dto.response.AdminSurveyDetailResponse;
import OneQ.OnSurvey.domain.admin.api.dto.response.AdminSurveyIntroItem;
import OneQ.OnSurvey.domain.admin.api.dto.response.SurveyGrantStatsResponse;
import OneQ.OnSurvey.domain.admin.domain.model.Admin;
import OneQ.OnSurvey.domain.admin.domain.model.AdminRole;
import OneQ.OnSurvey.domain.admin.domain.model.member.AdminMemberView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.AdminSurveyListView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.OngoingSurveyView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveySingleViewInfo;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveyQuestion;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveyScreening;
Expand All @@ -16,6 +18,7 @@
import OneQ.OnSurvey.domain.admin.domain.port.out.MemberPort;
import OneQ.OnSurvey.domain.admin.domain.port.out.SurveyPort;
import OneQ.OnSurvey.domain.admin.domain.repository.AdminRepository;
import OneQ.OnSurvey.global.promotion.port.out.PromotionGrantRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -34,6 +37,7 @@ public class AdminFacade implements AuthUseCase, AdminUseCase {
private final MemberPort memberPort;
private final SurveyPort surveyPort;
private final PasswordEncoder passwordEncoder;
private final PromotionGrantRepository promotionGrantRepository;

@Override
public String authenticate(String username, String rawPassword) {
Expand Down Expand Up @@ -100,4 +104,16 @@ public AdminSurveyDetailResponse getSurveyDetail(Long surveyId) {
public void changeSurveyOwner(Long surveyId, Long memberId) {
surveyPort.updateSurveyOwner(surveyId, memberId);
}

@Override
public List<SurveyGrantStatsResponse> getSurveyGrantStats() {
return promotionGrantRepository.findSurveyGrantStats().stream()
.map(SurveyGrantStatsResponse::from)
.toList();
}

@Override
public List<OngoingSurveyView> getOngoingSurveys() {
return surveyPort.findOngoingSurveys();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package OneQ.OnSurvey.domain.admin.domain.model.survey;

public record OngoingSurveyView(
Long surveyId,
String title,
int completedCount,
int dueCount
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public record SurveySingleViewInfo(

Set<String> ages,
String gender,
String residence,
Set<String> residences,
Set<String> interests,
Integer dueCount
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import OneQ.OnSurvey.domain.admin.api.dto.request.AdminSurveySearchQuery;
import OneQ.OnSurvey.domain.admin.api.dto.response.AdminSurveyDetailResponse;
import OneQ.OnSurvey.domain.admin.api.dto.response.AdminSurveyIntroItem;
import OneQ.OnSurvey.domain.admin.api.dto.response.SurveyGrantStatsResponse;
import OneQ.OnSurvey.domain.admin.domain.model.member.AdminMemberView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.OngoingSurveyView;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

Expand All @@ -19,4 +21,7 @@ public interface AdminUseCase {

void changeSurveyOwner(Long surveyId, Long newMemberId);

List<SurveyGrantStatsResponse> getSurveyGrantStats();

List<OngoingSurveyView> getOngoingSurveys();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import OneQ.OnSurvey.domain.admin.api.dto.request.AdminSurveySearchQuery;
import OneQ.OnSurvey.domain.admin.domain.model.survey.AdminSurveyListView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.OngoingSurveyView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveySingleViewInfo;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveyQuestion;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveyScreening;
Expand All @@ -24,4 +25,6 @@ public interface SurveyPort {
List<SurveySection> findSurveySectionsById(Long surveyId);

void updateSurveyOwner(Long surveyId, Long newMemberId);

List<OngoingSurveyView> findOngoingSurveys();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import OneQ.OnSurvey.domain.admin.api.dto.request.AdminSurveySearchQuery;
import OneQ.OnSurvey.domain.admin.domain.model.survey.AdminSurveyListView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.OngoingSurveyView;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveySingleViewInfo;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveyQuestion;
import OneQ.OnSurvey.domain.admin.domain.model.survey.SurveyScreening;
Expand All @@ -12,6 +13,7 @@
import OneQ.OnSurvey.domain.question.model.dto.type.DefaultQuestionDto;
import OneQ.OnSurvey.domain.question.service.QuestionQuery;
import OneQ.OnSurvey.domain.survey.model.dto.ScreeningViewData;
import OneQ.OnSurvey.domain.survey.model.dto.OngoingSurveyStats;
import OneQ.OnSurvey.domain.survey.model.dto.SurveyDetailData;
import OneQ.OnSurvey.domain.survey.model.dto.SurveyListView;
import OneQ.OnSurvey.domain.survey.model.dto.SurveyOwnerChangeDto;
Expand Down Expand Up @@ -81,6 +83,19 @@ public List<SurveySection> findSurveySectionsById(Long surveyId) {
.toList();
}

@Override
public List<OngoingSurveyView> findOngoingSurveys() {
List<OngoingSurveyStats> statsList = surveyQueryService.getOngoingSurveyStats();
return statsList.stream()
.map(s -> new OngoingSurveyView(
s.getSurveyId(),
s.getTitle(),
s.getCompletedCount() != null ? s.getCompletedCount() : 0,
s.getDueCount() != null ? s.getDueCount() : 0
))
.toList();
}

@Override
public void updateSurveyOwner(Long surveyId, Long memberId) {
SurveyOwnerChangeDto changeDto = SurveyOwnerChangeDto.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import OneQ.OnSurvey.domain.survey.model.dto.SurveyListView;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public final class AdminSurveyMapper {
Expand Down Expand Up @@ -56,7 +57,9 @@ public static SurveySingleViewInfo toSurveySingleViewInfo(SurveyDetailData surve
surveyDetailData.getDeadline() != null ? surveyDetailData.getDeadline().toLocalDate() : null,
surveyDetailData.getAges().stream().map(Enum::name).collect(Collectors.toSet()),
surveyDetailData.getGender() != null ? surveyDetailData.getGender().name() : null,
surveyDetailData.getResidence() != null ? surveyDetailData.getResidence().name() : null,
surveyDetailData.getResidences() != null
? surveyDetailData.getResidences().stream().map(Enum::name).collect(Collectors.toSet())
: Set.of(),
surveyDetailData.getInterests().stream().map(Enum::name).collect(Collectors.toSet()),
surveyDetailData.getDueCount()
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package OneQ.OnSurvey.domain.discount;

import OneQ.OnSurvey.global.common.exception.ApiErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum DiscountCodeErrorCode implements ApiErrorCode {

DISCOUNT_CODE_NOT_FOUND("DISCOUNT_404", "유효하지 않은 할인 코드입니다.", HttpStatus.NOT_FOUND),
DISCOUNT_CODE_EXPIRED("DISCOUNT_410", "만료된 할인 코드입니다.", HttpStatus.GONE);

private final String errorCode;
private final String message;
private final HttpStatus status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package OneQ.OnSurvey.domain.discount.controller;

import OneQ.OnSurvey.global.common.response.SuccessResponse;
import OneQ.OnSurvey.domain.discount.model.request.CreateDiscountCodeRequest;
import OneQ.OnSurvey.domain.discount.model.response.DiscountCodeResponse;
import OneQ.OnSurvey.domain.discount.model.response.ValidateDiscountCodeResponse;
import OneQ.OnSurvey.domain.discount.service.DiscountCodeCommandService;
import OneQ.OnSurvey.domain.discount.service.DiscountCodeQueryService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/v1/discount-codes")
@RequiredArgsConstructor
public class DiscountCodeController {

private final DiscountCodeQueryService discountCodeQueryService;
private final DiscountCodeCommandService discountCodeCommandService;

@GetMapping("/{code}")
@Operation(summary = "할인 코드를 검증하고 할인 정보를 반환합니다.")
public SuccessResponse<ValidateDiscountCodeResponse> validate(@PathVariable String code) {
return SuccessResponse.ok(discountCodeQueryService.validate(code));
}

@PostMapping
@Operation(summary = "할인 코드를 생성합니다.")
public SuccessResponse<DiscountCodeResponse> create(
@RequestBody @Valid CreateDiscountCodeRequest request
) {
return SuccessResponse.ok(discountCodeCommandService.create(request));
}

@GetMapping
@Operation(summary = "전체 할인 코드 목록을 조회합니다.")
public SuccessResponse<List<DiscountCodeResponse>> findAll() {
return SuccessResponse.ok(discountCodeQueryService.findAll());
}
}
Loading
Loading