Skip to content

OMF-235 폼 변환 자동화 작업#121

Merged
wonjuneee merged 13 commits intodevelopfrom
fix/OMF-235
Mar 6, 2026
Merged

OMF-235 폼 변환 자동화 작업#121
wonjuneee merged 13 commits intodevelopfrom
fix/OMF-235

Conversation

@wonjuneee
Copy link
Collaborator

@wonjuneee wonjuneee commented Mar 6, 2026

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • Google 폼을 설문으로 변환하는 기능 추가
    • 이미지형 질문 유형 지원
  • 변경 사항

    • 설문 및 섹션의 이미지 URL 필드 제거
    • 관리자 API 엔드포인트 경로 변경 (/{surveyId}/owner/surveys/{surveyId}/owner)
    • 질문의 섹션 이동 기능 관련 필드 정리

@wonjuneee wonjuneee changed the title OMF-235 OMF-235 폼 변환 자동화 작업 OMF-235 폼 변환 자동화 작업 Mar 6, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 751200d4-4c48-47ef-a124-aaa31792e234

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

.coderabbit.yml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'tools'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

워크스루

설문 도메인의 imageUrl 필드를 제거하고, nextSection 파라미터를 질문 타입들에서 제거한 뒤, 새로운 IMAGE 질문 타입을 추가했습니다. Google 폼 변환 기능을 구현하고, 관리자 API 엔드포인트 경로를 업데이트했으며, contenteditable 기반 UI 편집 기능을 추가했습니다.

변경 사항

코호트 / 파일 요약
설문 및 섹션 엔티티 정리
src/main/java/OneQ/OnSurvey/domain/survey/entity/Survey.java, src/main/java/OneQ/OnSurvey/domain/question/entity/Section.java
imageUrl 필드 및 매핑 제거, updateSurvey/updateSection 메서드 시그니처에서 imageUrl 파라미터 제거
질문 엔티티 및 타입 변경
src/main/java/OneQ/OnSurvey/domain/question/entity/Question.java, src/main/java/OneQ/OnSurvey/domain/question/entity/question/*.java, src/main/java/OneQ/OnSurvey/domain/question/model/QuestionType.java
모든 질문 타입(Choice, DateAnswer, Image, LongAnswer, NPS, NumberAnswer, Rating, ShortAnswer)에서 nextSection 파라미터 제거, 새로운 IMAGE 타입 추가
DTO 및 응답 객체 정리
src/main/java/OneQ/OnSurvey/domain/question/model/dto/..., src/main/java/OneQ/OnSurvey/domain/survey/model/dto/..., src/main/java/OneQ/OnSurvey/domain/survey/model/response/*.java
ParticipationQuestionResponse, SurveyDetailResponse, SurveyFormResponse, ParticipationInfoResponse 등에서 imageUrl 필드 제거, nextSection 매핑 제거
Google 폼 변환 기능
src/main/java/OneQ/OnSurvey/domain/survey/model/formRequest/*.java, src/main/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormCommandService.java, src/main/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormRequestLambda.java
FormConversionPayload, FormConversionResponse, FormRequestConversionEvent 신규 추가, 폼 변환 비동기 처리 로직 구현
Discord 알림 확장
src/main/java/OneQ/OnSurvey/global/infra/discord/*.java, src/main/java/OneQ/OnSurvey/global/infra/discord/notifier/dto/SurveyConversionAlert.java
설문 변환 결과 알림을 위한 SurveyConversionAlert 및 인터페이스 메서드 추가, 다중 notifier 구현체 업데이트
서비스 및 비즈니스 로직
src/main/java/OneQ/OnSurvey/domain/question/service/QuestionCommandService.java, src/main/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandService.java, src/main/java/OneQ/OnSurvey/domain/survey/service/query/SurveyQueryService.java
nextSection 및 imageUrl 처리 제거, Google 폼 변환 후 설문 생성 로직 추가
관리자 API 및 설정
src/main/java/OneQ/OnSurvey/domain/admin/api/AdminController.java, src/main/java/OneQ/OnSurvey/domain/admin/api/dto/response/AdminSurveyDetailResponse.java, src/main/java/OneQ/OnSurvey/domain/admin/domain/model/survey/*.java, src/main/java/OneQ/OnSurvey/domain/survey/SurveyErrorCode.java, src/main/java/OneQ/OnSurvey/global/common/config/WebClientConfig.java
관리자 엔드포인트 경로를 /surveys/{surveyId}/owner로 변경, 응답 DTO에서 imageUrl 제거, FORM_CONVERSION_FAILED 에러 코드 추가, Lambda용 WebClient Bean 추가
인프라 및 유틸리티
src/main/java/OneQ/OnSurvey/global/infra/transaction/TransactionHandler.java, src/main/java/OneQ/OnSurvey/domain/question/repository/section/SectionRepositoryImpl.java, src/main/java/OneQ/OnSurvey/domain/survey/repository/SurveyRepositoryImpl.java
트랜잭션 처리 컴포넌트 신규 추가, SectionDto 조회 쿼리에서 imageUrl 제거
요청 및 폼 모델
src/main/java/OneQ/OnSurvey/domain/survey/model/request/*.java
FreeSurveyFormRequest, SectionRequest, SurveyFormCreateRequest, SurveyFormRequest에서 imageUrl 필드 제거
관리 화면 UI
src/main/resources/templates/bo/survey.html, src/main/resources/static/bo/scripts/form-request.js, src/main/resources/templates/bo/form-request.html
contenteditable 기반 인라인 편집 지원, 이미지 업로드/미리보기 기능, 하이퍼링크 편집 팝오버, 폼 변환 요청 입력 UI 추가

시퀀스 다이어그램

sequenceDiagram
    participant Client as 클라이언트
    participant FormCmd as FormCommandService
    participant EventPub as EventPublisher
    participant Lambda as FormRequestLambda
    participant ExtAPI as AWS Lambda<br/>(Google Form API)
    participant SurveyService as SurveyCommandService<br/>/QuestionService
    participant DB as 데이터베이스
    participant Discord as DiscordAlarmService

    Client->>FormCmd: POST /form-requests<br/>(form URL)
    FormCmd->>DB: FormRequest 저장
    DB-->>FormCmd: FormRequest ID 반환
    FormCmd->>EventPub: FormRequestConversionEvent 발행
    EventPub->>Lambda: convertGoogleFormIntoSurvey()

    Lambda->>Lambda: FormConversionPayload 구성
    Lambda->>ExtAPI: WebClient POST<br/>(timeout, retry 설정)
    ExtAPI-->>Lambda: FormConversionResponse 반환

    Lambda->>DB: 요청자 이메일로 멤버 조회
    
    loop 변환 결과 반복
        alt 성공
            Lambda->>SurveyService: 설문 생성<br/>(제목, 섹션, 질문)
            SurveyService->>DB: 설문/섹션/질문 저장
            DB-->>SurveyService: surveyId 반환
            Lambda->>Lambda: 성공 상세 정보 기록
        else 실패
            Lambda->>Lambda: 실패 상세 정보 기록
        end
    end
    
    Lambda->>Discord: SurveyConversionAlert 발행
    Discord->>Discord: Discord 메시지 구성<br/>(성공/실패 여부)
    Discord-->>Client: Discord Webhook 전송
Loading

예상 코드 리뷰 노력

🎯 4 (복잡) | ⏱️ ~75분

관련 가능성 있는 PR

제안 레이블

🌟feature

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명은 Jira 링크와 간단한 설명만 포함하며, 제공된 템플릿의 필수 섹션들(Task Details, Review Requirements 등)이 누락되어 있습니다. 제공된 템플릿에 따라 Task Details 섹션에서 구체적인 작업 항목들을 체크박스로 나열하고, 필요시 Review Requirements를 추가하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.02% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 'OMF-235 폼 변환 자동화 작업'이며, 이는 Jira 이슈를 참조하고 주요 변경 사항인 폼 변환 자동화 기능을 명확하게 설명합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/OMF-235

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/main/resources/static/bo/scripts/form-request.js (1)

145-152: ⚠️ Potential issue | 🟠 Major

XSS 취약점: formLink 값이 이스케이프 없이 onclick 핸들러에 삽입됨

req.formLink에 작은따옴표가 포함되면 JavaScript 문자열이 깨지며 코드 인젝션이 가능합니다. 예: https://evil.com'); alert('XSS

🛡️ 제안된 수정
                 ${req.isRegistered && req.registeredSurveyId
                     ? `<button onclick="openSurveyEditor(${req.registeredSurveyId}, ${req.id})" class="px-3 py-1 bg-indigo-600 text-white rounded text-xs font-bold hover:bg-indigo-700">
                         <i class="fas fa-eye mr-1"></i>설문 조회
                        </button>`
-                    : `<button onclick="createSurveyFromRequest(${req.id}, '${req.formLink}')" class="px-3 py-1 bg-green-600 text-white rounded text-xs font-bold hover:bg-green-700">
+                    : `<button onclick="createSurveyFromRequest(${req.id}, '${req.formLink.replace(/'/g, "\\'")}')" class="px-3 py-1 bg-green-600 text-white rounded text-xs font-bold hover:bg-green-700">
                         <i class="fas fa-plus mr-1"></i>설문 생성
                        </button>`
                 }

또는 data 속성과 이벤트 리스너를 사용하는 것이 더 안전합니다:

: `<button data-request-id="${req.id}" data-form-link="${encodeURIComponent(req.formLink)}" class="create-survey-btn ...">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/static/bo/scripts/form-request.js` around lines 145 - 152,
The inline onclick currently injects req.formLink into a JS string (in the
createSurveyFromRequest call) causing XSS when formLink contains quotes; replace
the inline onclick approach by rendering buttons with safe data attributes
(e.g., data-request-id and data-form-link where formLink is URL-encoded via
encodeURIComponent) for both branches and remove the onclick; then attach a
delegated event listener in your script that reads dataset.requestId and decodes
dataset.formLink before calling createSurveyFromRequest(requestId,
decodedFormLink) (and similarly call openSurveyEditor using dataset.requestId
for the registeredSurveyId button) so no unescaped user data is placed into
inline JavaScript.
src/main/resources/templates/bo/form-request.html (1)

7-7: 🧹 Nitpick | 🔵 Trivial

프로덕션 환경에서 Tailwind CDN 사용 검토 필요

cdn.tailwindcss.com은 개발 환경용으로 권장됩니다. 프로덕션에서는 빌드된 CSS 파일 사용을 고려해 주세요. 성능 및 가용성 측면에서 외부 CDN 의존성을 줄일 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/bo/form-request.html` at line 7, The template
currently includes the Tailwind CDN script ("https://cdn.tailwindcss.com") which
is intended for development; replace this by linking to a built, versioned CSS
asset and remove the CDN script in form-request.html: add a <link> to the
compiled Tailwind output (e.g., /assets/css/tailwind.min.css), update your build
pipeline to run Tailwind (e.g., npx tailwindcss build -> generate the compiled
CSS into your static assets) and ensure the template references that compiled
file (and optionally include cache-busting/versioning).
src/main/java/OneQ/OnSurvey/domain/survey/model/response/SurveyDetailResponse.java (1)

10-28: ⚠️ Potential issue | 🟠 Major

v1 응답에서 필드를 제거하면 기존 클라이언트 계약이 깨집니다.

imageUrl을 응답 DTO에서 바로 제거하면, 그 필드를 기대하던 기존 클라이언트는 파싱 실패나 UI 누락을 일으킬 수 있습니다. 클라이언트 마이그레이션이 끝날 때까지는 deprecated 필드로 유지하거나, 호환되지 않는 변경이면 새 버전 응답으로 분리하는 게 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/OneQ/OnSurvey/domain/survey/model/response/SurveyDetailResponse.java`
around lines 10 - 28, SurveyDetailResponse currently removes the imageUrl field
which breaks v1 client contracts; restore backward compatibility by either
reintroducing imageUrl as a deprecated field on SurveyDetailResponse (and
populate it in the static factory SurveyDetailResponse.from(Survey, SurveyInfo))
or create a new versioned DTO (e.g., SurveyDetailResponseV2) and leave the
current SurveyDetailResponse unchanged so existing clients keep receiving
imageUrl until migration completes.
src/main/java/OneQ/OnSurvey/domain/admin/api/AdminController.java (1)

62-72: ⚠️ Potential issue | 🟠 Major

기존 어드민 API 경로를 그대로 깨뜨립니다.

이 변경으로 /v1/admin/{surveyId}/owner를 호출하던 기존 클라이언트는 바로 404를 받습니다. 이미 배포된 소비자가 있다면 이전 경로를 함께 유지하거나, 버전 업으로 분리한 뒤 클라이언트 전환이 끝난 다음 제거하는 편이 안전합니다.

호환 경로를 함께 유지하는 최소 수정안
-    `@PatchMapping`("/surveys/{surveyId}/owner")
+    `@PatchMapping`({"/surveys/{surveyId}/owner", "/{surveyId}/owner"})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/OneQ/OnSurvey/domain/admin/api/AdminController.java` around
lines 62 - 72, The AdminController changeSurveyOwner endpoint changed its route
and broke existing clients; restore backward compatibility by adding the old
route mapping alongside the new one (either use `@PatchMapping`(paths =
{"/surveys/{surveyId}/owner","/v1/admin/{surveyId}/owner"}) on the
changeSurveyOwner method or add a small delegating method annotated with
`@PatchMapping`("/v1/admin/{surveyId}/owner") that calls
AdminController.changeSurveyOwner(surveyId, request)); ensure you keep the
current method name changeSurveyOwner and the adminFacade.changeSurveyOwner call
unchanged and do not remove the new path.
src/main/java/OneQ/OnSurvey/domain/question/service/QuestionCommandService.java (1)

211-313: 🧹 Nitpick | 🔵 Trivial

[선택사항] 질문 타입별 if-else 체인을 팩토리 패턴으로 리팩토링 고려

createQuestion 메서드의 if-else 체인이 8개의 질문 타입을 처리하며 길어지고 있습니다. 새로운 질문 타입이 추가될 때마다 이 메서드를 수정해야 합니다.

QuestionType에 팩토리 메서드를 두거나 별도의 팩토리 클래스를 사용하면 OCP(Open-Closed Principle)를 더 잘 준수할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/OneQ/OnSurvey/domain/question/service/QuestionCommandService.java`
around lines 211 - 313, The createQuestion method in QuestionCommandService
contains a long if-else chain over QuestionType; extract this logic into a
factory to follow OCP: implement a QuestionFactory (or add a static factory
method on QuestionType) with a single method like
QuestionFactory.createQuestion(Long surveyId, QuestionUpsertDto.UpsertInfo
upsertInfo) that switches on type once and returns the appropriate Question
subtype (DateAnswer, NPS, Rating, Choice, ShortAnswer, LongAnswer, NumberAnswer,
Image); replace the body of createQuestion to delegate to that factory and
remove the big if-else so adding new QuestionType only requires extending the
factory, not changing createQuestion.
src/main/resources/templates/bo/survey.html (1)

1694-1729: 🛠️ Refactor suggestion | 🟠 Major

sanitizeDescHtml 함수는 적절하게 구현되어 있음

이 함수는 표시용 HTML sanitizer로 잘 구현되어 있습니다. 서버에서 데이터를 로드하여 contenteditable이나 innerHTML에 삽입할 때 이 함수를 일관되게 사용하면 XSS 위험을 방지할 수 있습니다.

위에서 지적한 여러 위치에서 이 함수를 활용하시기 바랍니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/bo/survey.html` around lines 1694 - 1729, The
review notes that sanitizeDescHtml is correctly implemented as an HTML sanitizer
for rendering/preview; ensure all places that insert server-loaded HTML into the
DOM (especially where using innerHTML or contenteditable) call sanitizeDescHtml
first; locate usages that set innerHTML or assign HTML to editable fields and
replace direct insertion with sanitized content via sanitizeDescHtml(html)
(reference function sanitizeDescHtml and its wrapper/resulting innerHTML) so
server data is always sanitized before DOM insertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/OneQ/OnSurvey/domain/survey/model/response/ParticipationQuestionResponse.java`:
- Around line 33-34: The ternary on the second element uses currSection != null
? nextSection : 0 which can return a null nextSection and cause an NPE; in
ParticipationQuestionResponse (constructor/factory/builder where currSection and
nextSection are used) change the condition to check nextSection instead—e.g.,
use nextSection != null ? nextSection : 0 or
Objects.requireNonNullElse(nextSection, 0)—so you return a safe int when
nextSection is null.

In
`@src/main/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormRequestLambda.java`:
- Around line 95-97: The code currently constructs a SurveyConversionAlert with
new ArrayList<>() and then mutates it via alert.details().add(), violating
record immutability; instead, in FormRequestLambda collect
SurveyConversionAlert.SurveyDetails into a local List (e.g., detailsList) while
iterating response.results(), then call
SurveyConversionAlert.success(response.totalCount(), response.successCount(),
detailsList) and pass that immutable-ready alert to
alertNotifier.sendSurveyConversionAsync; remove any later calls that mutate
alert.details() and ensure all additions happen before creating the alert.
- Around line 43-45: The class-level `@Qualifier` on FormRequestLambda is
ineffective for constructor injection with `@RequiredArgsConstructor`; move the
qualifier onto the injected web client field/constructor parameter so Spring can
pick the "lambdaWebClient" bean (i.e., annotate the lambdaWebClient field or its
constructor parameter with `@Qualifier`("lambdaWebClient") in the
FormRequestLambda class and ensure the `@Qualifier` import is present).
- Around line 283-298: The mapQuestionType method currently misses the
"DROPDOWN" mapping causing dropdown questions to return null; update the switch
in mapQuestionType to include case "DROPDOWN" -> QuestionType.CHOICE (alongside
"MULTIPLE_CHOICE", "CHECKBOX") so dropdowns are mapped to Choice like maxChoice
logic expects.
- Around line 76-83: The reactive chain in FormRequestLambda currently throws a
CustomException inside doOnError (which is a side-effect operator), so the
thrown exception won't replace the stream error; instead, keep the
logging/alerting in doOnError (call alertNotifier.sendSurveyConversionAsync with
SurveyConversionAlert) and then append onErrorMap(e -> new
CustomException(SurveyErrorCode.FORM_CONVERSION_FAILED)) to convert the original
error into the desired CustomException (or use onErrorResume to return an
alternative Mono if appropriate) before the terminal .block(); ensure you
reference the existing doOnError, alertNotifier.sendSurveyConversionAsync,
SurveyConversionAlert, onErrorMap/onErrorResume, CustomException and
SurveyErrorCode symbols when making the change.

In `@src/main/java/OneQ/OnSurvey/domain/survey/SurveyErrorCode.java`:
- Around line 32-33: The enum entry FORM_CONVERSION_FAILED in SurveyErrorCode
currently uses the incorrect errorCode "FORM_REQUEST_001"; update its errorCode
to follow the project's TYPE_HTTP_STATUS convention (e.g.,
"FORM_CONVERSION_400") to match HttpStatus.BAD_REQUEST and the pattern used by
other entries like FORM_REQUEST_NOT_FOUND; ensure only the string value for
FORM_CONVERSION_FAILED is changed and keep the existing message and
HttpStatus.BAD_REQUEST.

In `@src/main/java/OneQ/OnSurvey/global/common/config/WebClientConfig.java`:
- Around line 36-46: The method formatting for creating the HttpClient and
returning the WebClient is inconsistent with the existing webClient method chain
style; reformat the block that constructs HttpClient (symbols:
HttpClient.create(), ChannelOption.CONNECT_TIMEOUT_MILLIS, responseTimeout,
doOnConnected, ReadTimeoutHandler, WriteTimeoutHandler) and the return builder
(WebClient.builder(), ReactorClientHttpConnector) to match the deeper
method-chaining indentation used elsewhere in the webClient method for
consistent style across the class.

In `@src/main/java/OneQ/OnSurvey/global/infra/discord/DiscordAlarmService.java`:
- Around line 121-162: sendSurveyConversionAlert builds a potentially very long
description with StringBuilder (symbols: sendSurveyConversionAlert, desc,
SurveyConversionAlert.SurveyDetails) but doesn’t enforce the embed description
length limit; mirror the sendErrorAlert approach by truncating desc.toString()
to MAX_EMBED_DESC (use the existing MAX_EMBED_DESC constant) before calling
post(url, title, ...), optionally appending an ellipsis or summary suffix to
indicate truncation so the resulting payload to post(...) never exceeds Discord
embed limits.

In `@src/main/resources/static/bo/scripts/form-request.js`:
- Around line 53-62: The try/catch should validate the API response more
robustly and surface errors to the user: replace the loose check (response !==
undefined) with a proper validation of the returned object (e.g., ensure
response is not null and contains expected success flag or HTTP status) inside
the block that calls apiCall('/form-requests', 'POST', requestData), and only
call showToast('폼 변환 요청이 등록되었습니다.'), clearFormRequestInputs(), and
loadFormRequests() when that validation passes; also enhance the catch block to
call showToast with a user-friendly error message and log the detailed error via
console.error so users receive feedback if apiCall fails.

In `@src/main/resources/templates/bo/survey.html`:
- Around line 2570-2578: The section template inserts server-side
title/description directly into contenteditable divs (section-title-input,
section-desc-input) similar to addSectionWithoutSave, creating XSS risk; update
the template and the addSectionWithoutSave code path to sanitize or escape the
values before inserting into the contenteditable elements (use the existing
escapeHtml helper or a dedicated sanitize function) or prefer assigning via
textContent/createTextNode instead of raw HTML so that title/description cannot
inject markup; ensure both the hidden inputs (section-title-value,
section-desc-value) and visible divs receive the sanitized/escaped values
consistently.
- Around line 3135-3143: The contenteditable title/description are being set
with raw HTML in addNewQuestion, creating XSS risk; before inserting into the
contenteditable divs (or before calling addNewQuestion in
renderLoadedQuestions), run the incoming q.title and q.description through
sanitizeDescHtml (or a similar sanitizer) and pass the sanitized strings to
addNewQuestion so that the .question-title-input / .question-desc-input
innerHTML is safe; keep escapeHtml for hidden inputs but ensure sanitizeDescHtml
is applied to any value rendered into contenteditable elements (or alternatively
call sanitizeDescHtml inside addNewQuestion right before assigning the
innerHTML).
- Around line 2425-2432: The edit mode assigns untrusted server data directly to
innerHTML for titleDiv and descDiv (and also sets hidden inputs), creating an
XSS risk; change these assignments to use textContent (or run the incoming data
through a strict HTML sanitizer like DOMPurify before assigning) for
document.getElementById('survey-title') and
document.getElementById('survey-description'), keep setting
document.getElementById('survey-title-value').value and
'survey-description-value'.value as-is, and if setupEditableLinkHandlers expects
HTML, ensure you pass sanitized HTML rather than raw data.
- Around line 2406-2414: The code assigns server data directly to innerHTML
(titleDiv.innerHTML and descDiv.innerHTML), risking XSS; instead sanitize or
escape before inserting: for plain text use
titleDiv.textContent/descDiv.textContent, and if rich HTML is required run the
trusted sanitizer (e.g., sanitizeHtmlForSave or a DOMPurify call) on data.title
and data.description before assigning to titleDiv.innerHTML/descDiv.innerHTML;
also ensure the hidden inputs (survey-title-value, survey-description-value)
receive the same sanitized or escaped values and keep calling
setupEditableLinkHandlers(titleDiv) and setupEditableLinkHandlers(descDiv) after
sanitization.
- Around line 1939-1951: The popover position calculation uses
popover.offsetHeight while the element is still hidden, causing offsetHeight to
be 0; change the flow in the click/positioning handler (references: linkElement,
popover, popoverRect, getBoundingClientRect, offsetHeight) so you first make the
popover measurable (e.g., remove the 'hidden' class or temporarily set
visibility/display so it can be measured) then call getBoundingClientRect() and
use popoverRect.height/width (not offsetHeight) to compute top/left, and finally
apply any adjustments and ensure the intended visibility state (re-hide if
needed) to avoid measuring a hidden element.

---

Outside diff comments:
In `@src/main/java/OneQ/OnSurvey/domain/admin/api/AdminController.java`:
- Around line 62-72: The AdminController changeSurveyOwner endpoint changed its
route and broke existing clients; restore backward compatibility by adding the
old route mapping alongside the new one (either use `@PatchMapping`(paths =
{"/surveys/{surveyId}/owner","/v1/admin/{surveyId}/owner"}) on the
changeSurveyOwner method or add a small delegating method annotated with
`@PatchMapping`("/v1/admin/{surveyId}/owner") that calls
AdminController.changeSurveyOwner(surveyId, request)); ensure you keep the
current method name changeSurveyOwner and the adminFacade.changeSurveyOwner call
unchanged and do not remove the new path.

In
`@src/main/java/OneQ/OnSurvey/domain/question/service/QuestionCommandService.java`:
- Around line 211-313: The createQuestion method in QuestionCommandService
contains a long if-else chain over QuestionType; extract this logic into a
factory to follow OCP: implement a QuestionFactory (or add a static factory
method on QuestionType) with a single method like
QuestionFactory.createQuestion(Long surveyId, QuestionUpsertDto.UpsertInfo
upsertInfo) that switches on type once and returns the appropriate Question
subtype (DateAnswer, NPS, Rating, Choice, ShortAnswer, LongAnswer, NumberAnswer,
Image); replace the body of createQuestion to delegate to that factory and
remove the big if-else so adding new QuestionType only requires extending the
factory, not changing createQuestion.

In
`@src/main/java/OneQ/OnSurvey/domain/survey/model/response/SurveyDetailResponse.java`:
- Around line 10-28: SurveyDetailResponse currently removes the imageUrl field
which breaks v1 client contracts; restore backward compatibility by either
reintroducing imageUrl as a deprecated field on SurveyDetailResponse (and
populate it in the static factory SurveyDetailResponse.from(Survey, SurveyInfo))
or create a new versioned DTO (e.g., SurveyDetailResponseV2) and leave the
current SurveyDetailResponse unchanged so existing clients keep receiving
imageUrl until migration completes.

In `@src/main/resources/static/bo/scripts/form-request.js`:
- Around line 145-152: The inline onclick currently injects req.formLink into a
JS string (in the createSurveyFromRequest call) causing XSS when formLink
contains quotes; replace the inline onclick approach by rendering buttons with
safe data attributes (e.g., data-request-id and data-form-link where formLink is
URL-encoded via encodeURIComponent) for both branches and remove the onclick;
then attach a delegated event listener in your script that reads
dataset.requestId and decodes dataset.formLink before calling
createSurveyFromRequest(requestId, decodedFormLink) (and similarly call
openSurveyEditor using dataset.requestId for the registeredSurveyId button) so
no unescaped user data is placed into inline JavaScript.

In `@src/main/resources/templates/bo/form-request.html`:
- Line 7: The template currently includes the Tailwind CDN script
("https://cdn.tailwindcss.com") which is intended for development; replace this
by linking to a built, versioned CSS asset and remove the CDN script in
form-request.html: add a <link> to the compiled Tailwind output (e.g.,
/assets/css/tailwind.min.css), update your build pipeline to run Tailwind (e.g.,
npx tailwindcss build -> generate the compiled CSS into your static assets) and
ensure the template references that compiled file (and optionally include
cache-busting/versioning).

In `@src/main/resources/templates/bo/survey.html`:
- Around line 1694-1729: The review notes that sanitizeDescHtml is correctly
implemented as an HTML sanitizer for rendering/preview; ensure all places that
insert server-loaded HTML into the DOM (especially where using innerHTML or
contenteditable) call sanitizeDescHtml first; locate usages that set innerHTML
or assign HTML to editable fields and replace direct insertion with sanitized
content via sanitizeDescHtml(html) (reference function sanitizeDescHtml and its
wrapper/resulting innerHTML) so server data is always sanitized before DOM
insertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 84892ee4-5140-4d17-a7fa-f236cc138f6f

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2df10 and 419fa92.

📒 Files selected for processing (54)
  • src/main/java/OneQ/OnSurvey/domain/admin/api/AdminController.java
  • src/main/java/OneQ/OnSurvey/domain/admin/api/dto/response/AdminSurveyDetailResponse.java
  • src/main/java/OneQ/OnSurvey/domain/admin/domain/model/survey/SurveySection.java
  • src/main/java/OneQ/OnSurvey/domain/admin/domain/model/survey/SurveySingleViewInfo.java
  • src/main/java/OneQ/OnSurvey/domain/admin/infra/mapper/AdminSurveyMapper.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/Question.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/Section.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/Choice.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/DateAnswer.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/Image.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/LongAnswer.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/NPS.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/NumberAnswer.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/Rating.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/question/ShortAnswer.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/QuestionType.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/QuestionUpsertDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/SectionDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/ChoiceDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/DateDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/DefaultQuestionDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/RatingDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/repository/section/SectionRepositoryImpl.java
  • src/main/java/OneQ/OnSurvey/domain/question/service/QuestionCommandService.java
  • src/main/java/OneQ/OnSurvey/domain/survey/SurveyErrorCode.java
  • src/main/java/OneQ/OnSurvey/domain/survey/entity/Survey.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/dto/SurveyDetailData.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/formRequest/FormConversionPayload.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/formRequest/FormConversionResponse.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/formRequest/event/FormRequestConversionEvent.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/request/FreeSurveyFormRequest.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/request/SectionRequest.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/request/SurveyFormCreateRequest.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/request/SurveyFormRequest.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/response/ParticipationInfoResponse.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/response/ParticipationQuestionResponse.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/response/SurveyDetailResponse.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/response/SurveyFormResponse.java
  • src/main/java/OneQ/OnSurvey/domain/survey/repository/SurveyRepositoryImpl.java
  • src/main/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandService.java
  • src/main/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormCommandService.java
  • src/main/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormRequestLambda.java
  • src/main/java/OneQ/OnSurvey/domain/survey/service/query/SurveyQueryService.java
  • src/main/java/OneQ/OnSurvey/global/common/config/WebClientConfig.java
  • src/main/java/OneQ/OnSurvey/global/infra/discord/DiscordAlarmAsyncFacade.java
  • src/main/java/OneQ/OnSurvey/global/infra/discord/DiscordAlarmService.java
  • src/main/java/OneQ/OnSurvey/global/infra/discord/notifier/AlertNotifier.java
  • src/main/java/OneQ/OnSurvey/global/infra/discord/notifier/DiscordAlertNotifier.java
  • src/main/java/OneQ/OnSurvey/global/infra/discord/notifier/NoOpAlertNotifier.java
  • src/main/java/OneQ/OnSurvey/global/infra/discord/notifier/dto/SurveyConversionAlert.java
  • src/main/java/OneQ/OnSurvey/global/infra/transaction/TransactionHandler.java
  • src/main/resources/static/bo/scripts/form-request.js
  • src/main/resources/templates/bo/form-request.html
  • src/main/resources/templates/bo/survey.html
💤 Files with no reviewable changes (9)
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/DefaultQuestionDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/DateDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/entity/Question.java
  • src/main/java/OneQ/OnSurvey/domain/survey/repository/SurveyRepositoryImpl.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/QuestionUpsertDto.java
  • src/main/java/OneQ/OnSurvey/domain/survey/model/dto/SurveyDetailData.java
  • src/main/java/OneQ/OnSurvey/domain/admin/domain/model/survey/SurveySingleViewInfo.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/ChoiceDto.java
  • src/main/java/OneQ/OnSurvey/domain/question/model/dto/type/RatingDto.java

@wonjuneee wonjuneee merged commit 68f8b10 into develop Mar 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant