Skip to content

MOSU-86 refactor: 한국어 Enum 반환 시 한글 반환으로 변경#87

Merged
wlgns12370 merged 12 commits intodevelopfrom
feature/mosu-86
Jul 13, 2025
Merged

MOSU-86 refactor: 한국어 Enum 반환 시 한글 반환으로 변경#87
wlgns12370 merged 12 commits intodevelopfrom
feature/mosu-86

Conversation

@wlgns12370
Copy link
Contributor

@wlgns12370 wlgns12370 commented Jul 11, 2025

✨ 구현한 기능

  • CustomerKey 반환 기능
  • 한국어 Enum 반환 시 한글 반환으로 변경

📢 논의하고 싶은 내용

  • x

🎸 기타

  • Swagger local 테스트 설정 추가

Summary by CodeRabbit

  • New Features

    • Added a REST API endpoint to retrieve a user's customer key by user ID.
    • Introduced a utility for generating unique customer keys.
    • Added a database initializer to populate sample data for users, profiles, schools, applications, boards, and events.
  • Improvements

    • Updated API responses and admin views to display gender, education, and grade as user-friendly strings.
    • Enhanced profile registration to validate user existence and update user state.
    • Improved OAuth user handling to update user details and set default roles and agreements.
  • Refactor

    • Renamed repository interfaces across FAQs, inquiries, notices, and attachments for consistency.
    • Simplified and unified database initialization by consolidating multiple initializers.
  • Bug Fixes

    • Adjusted Swagger documentation examples for clarity and localization.
  • Chores

    • Disabled certain security and OAuth filters.
    • Removed unused or redundant entity and initializer classes.

@coderabbitai
Copy link

coderabbitai bot commented Jul 11, 2025

Walkthrough

This update standardizes repository interface naming by renaming all *Repository interfaces to *JpaRepository throughout the codebase, updating all service and usage references accordingly. It introduces new user-related service and controller classes, enhances OAuth user update logic, consolidates database initialization, and improves enum-to-string mapping for API responses.

Changes

File(s) Change Summary
.../faq/FaqAttachmentService.java, .../faq/FaqService.java, .../inquiry/InquiryAnswerAttachmentService.java, .../inquiry/InquiryAnswerService.java, .../inquiry/InquiryAttachmentService.java, .../inquiry/InquiryService.java, .../notice/NoticeAttachmentService.java, .../notice/NoticeService.java All usages and imports of *Repository interfaces replaced with *JpaRepository equivalents; variable names and types updated accordingly.
.../faq/FaqAttachmentJpaRepository.java, .../faq/FaqJpaRepository.java, .../inquiry/InquiryAttachmentJpaRepository.java, .../inquiry/InquiryJpaRepository.java, .../inquiryAnswer/InquiryAnswerAttachmentJpaRepository.java, .../inquiryAnswer/InquiryAnswerJpaRepository.java, .../notice/NoticeAttachmentJpaRepository.java, .../notice/NoticeJpaRepository.java Repository interfaces renamed from *Repository to *JpaRepository.
.../user/UserJpaEntity.java User entity constructor now generates customerKey internally; adds updateOAuthUser and registerProfile methods.
.../oauth/OAuthUserService.java Updates OAuth user handling: updates existing users and sets defaults; new users start with ROLE_PENDING.
.../profile/ProfileService.java Adds user existence validation and calls registerProfile() on user before profile creation.
.../user/UserService.java, .../user/UserController.java, .../user/CustomerKeyResponse.java Adds new service, controller, and response record for retrieving customer keys by user ID.
.../admin/ApplicationQueryRepositoryImpl.java, .../admin/dto/ApplicationListResponse.java, .../profile/dto/ProfileDetailResponse.java, .../admin/dto/StudentListResponse.java Enum-typed fields in API responses changed to String; mapping updated to use enum display names.
.../serviceterm/ServiceTermAgreementJpaEntity.java, .../serviceterm/ServiceTermJpaEntity.java, .../initializer/SchoolInitializer.java, .../initializer/UserAndProfileInitializer.java Removes service term and old data initializer entity/classes.
.../initializer/DatabaseInitializer.java Adds comprehensive database initializer for users, profiles, schools, applications, boards, and events.
.../global/util/KeyGeneratorUtil.java Adds utility class for generating UUID customer keys.
.../global/config/SecurityConfig.java, .../presentation/oauth/AccessTokenFilter.java, .../presentation/oauth/TokenExceptionFilter.java Removes and comments out token-related security filters and their registration.
.../presentation/oauth/OAuthController.java Refactors OAuth endpoint: now handles /authorize-code with query params and redirects using HTTP 302.
.../domain/application/Lunch.java, .../domain/event/EventJpaEntity.java Minor: removes a comment and extra blank lines.
.../infra/config/SwaggerConfig.java Adds local development server entry to OpenAPI documentation.
.../FaqServiceTest.java Updates test to use FaqJpaRepository instead of FaqRepository.

Sequence Diagram(s)

sequenceDiagram
  participant Frontend
  participant UserController
  participant UserService
  participant UserJpaRepository
  Frontend->>UserController: GET /user/customer-key?userId=...
  UserController->>UserService: getCustomerKey(userId)
  UserService->>UserJpaRepository: findById(userId)
  UserJpaRepository-->>UserService: UserJpaEntity or not found
  UserService-->>UserController: customerKey or error
  UserController-->>Frontend: ApiResponseWrapper<CustomerKeyResponse>
Loading
sequenceDiagram
  participant OAuthProvider
  participant Frontend
  participant OAuthController
  OAuthProvider-->>Frontend: Redirect with code (& state)
  Frontend->>OAuthController: GET /authorize-code?code=...&state=...
  OAuthController->>Frontend: HTTP 302 redirect to /api/v1/oauth2/callback/kakao?code=...&state=...
Loading

Possibly related PRs

  • #61: Removes AccessTokenFilter and TokenExceptionFilter from SecurityConfig, matching the current PR's removal and commenting out of these filters.
  • #58: Refactors FaqAttachmentService to use a helper for saving attachments; both PRs modify repository usage in this service.
  • #73: Modifies internal methods and documentation in FaqAttachmentService and FaqService, which are also updated in this PR for repository interface changes.

Suggested reviewers

  • jbh010204

Poem

In the warren where code bunnies hop,
We renamed our Repos—no need to stop!
With JPA in tow and keys that are new,
Our user and OAuth flows hop right through.
Spring fields are tidy, the tests still run—
🐇 This refactor’s finished, and now we’re done!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@wlgns12370 wlgns12370 added this to the 모수 MVP API 구현 milestone Jul 11, 2025
@wlgns12370 wlgns12370 self-assigned this Jul 11, 2025
@wlgns12370 wlgns12370 linked an issue Jul 11, 2025 that may be closed by this pull request
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @wlgns12370, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request implements significant refactoring and introduces new features to enhance the mosuserver application. The core changes involve localizing Enum values to Korean for better user readability, standardizing repository naming conventions, and introducing a new API for retrieving user CustomerKeys. The changes also refine OAuth user management, streamline the security filter chain, and improve the development environment setup with a new database initializer and updated Swagger configuration.

Highlights

  • Enum Localization: Changed various Enum fields (e.g., Gender, Education, Grade) in DTOs and query results to return their Korean string representations instead of English Enum names, improving user experience in Korean interfaces. This impacts admin application lists, student lists, and user profile details.
  • Repository Naming Refactor: Standardized the naming convention for JPA repository interfaces across multiple domains (FAQ, Inquiry, Notice, Inquiry Answer) by renaming them from *Repository to *JpaRepository. This is a widespread refactoring for consistency and clarity.
  • CustomerKey Feature: Implemented a new feature allowing retrieval of a user's unique CustomerKey. This includes a new UserService method (getCustomerKey) and a corresponding REST endpoint (/user/customer-key) in UserController.
  • OAuth User Management Enhancements: Improved the logic for creating and updating OAuth users. New OAuth users now have their customerKey automatically generated, their initial userRole set to ROLE_PENDING, and their agreement to terms/privacy policies set by default. Existing OAuth users' gender, name, and birth information can now be updated.
  • Profile Registration Flow Update: Modified the profile registration process to automatically update a user's role from ROLE_PENDING to ROLE_USER upon successful profile completion, ensuring correct user status management.
  • Security Filter Streamlining: Removed the AccessTokenFilter and TokenExceptionFilter from the Spring Security configuration and commented out their source files. This indicates a simplification or re-evaluation of the token validation and exception handling strategy.
  • Development Setup Improvements: Introduced a DatabaseInitializer component to automatically populate the database with dummy data on application startup, facilitating easier development and testing. Additionally, a local development server URL (http://localhost:8080/api/v1) was added to the Swagger configuration for convenience.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors repository naming conventions, changes Enum return types to Korean strings in DTOs, and updates the OAuth and customer key functionalities. The changes are well-aligned with the PR's goals.

My review highlights several points to enhance correctness and maintainability. I've identified an incorrect error code, a critical data integrity issue in the new database initializer, and some hardcoded values that could cause issues in production. Addressing these points will improve the overall quality and robustness of the code.

Comment on lines +219 to +231
for (int i = 1; i <= 10; i++) {
InquiryJpaEntity inquiryToAnswer = inquiries.get(random.nextInt(inquiries.size()));

UserJpaEntity answerer = users.get(random.nextInt(users.size()));
inquiryAnswerJpaRepository.save(InquiryAnswerJpaEntity.builder()
.title("Re: " + inquiryToAnswer.getTitle())
.content("문의하신 내용에 대한 답변입니다. " + i + "번째 답변입니다.")
.inquiryId(inquiryToAnswer.getId())
.userId(answerer.getId())
.build());
inquiryToAnswer.updateStatusToComplete();
inquiryJpaRepository.save(inquiryToAnswer);
}

Choose a reason for hiding this comment

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

critical

The current logic for creating inquiry answers can assign multiple answers to the same inquiry because it randomly picks an inquiry from the list in a loop. This violates the business logic in InquiryAnswerService, which ensures only one answer per inquiry. This could lead to data integrity issues.

To fix this, ensure each inquiry is answered at most once. A good approach is to shuffle the list of inquiries and then iterate through a portion of it to create answers.

        // Create answers for 5 random inquiries to avoid multiple answers for one inquiry
        Collections.shuffle(inquiries, random);
        for (int i = 0; i < 5; i++) {
            InquiryJpaEntity inquiryToAnswer = inquiries.get(i);

            UserJpaEntity answerer = users.get(random.nextInt(users.size()));
            inquiryAnswerJpaRepository.save(InquiryAnswerJpaEntity.builder()
                    .title("Re: " + inquiryToAnswer.getTitle())
                    .content("문의하신 내용에 대한 답변입니다. " + (i + 1) + "번째 답변입니다.")
                    .inquiryId(inquiryToAnswer.getId())
                    .userId(answerer.getId())
                    .build());
            inquiryToAnswer.updateStatusToComplete();
            inquiryJpaRepository.save(inquiryToAnswer);
        }

Comment on lines 53 to 63
final UserJpaEntity newUser = UserJpaEntity.builder()
.loginId(info.email())
.gender(Gender.MALE)
.gender(info.gender() != null ? info.gender() : Gender.MALE)
.name(info.name())
.password("")
.birth(LocalDate.now())
.userRole(UserRole.ROLE_USER)
.birth(info.birthDay() != null ? info.birthDay()
: LocalDate.of(1900, 1, 1))
.userRole(UserRole.ROLE_PENDING)
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(false)
.build();

Choose a reason for hiding this comment

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

high

When creating a new UserJpaEntity for an OAuth user, the password field is not being set, which means it will be null. The previous implementation set it to an empty string (""). A null password could lead to NullPointerExceptions. It would be safer to initialize it to an empty string.

Suggested change
final UserJpaEntity newUser = UserJpaEntity.builder()
.loginId(info.email())
.gender(Gender.MALE)
.gender(info.gender() != null ? info.gender() : Gender.MALE)
.name(info.name())
.password("")
.birth(LocalDate.now())
.userRole(UserRole.ROLE_USER)
.birth(info.birthDay() != null ? info.birthDay()
: LocalDate.of(1900, 1, 1))
.userRole(UserRole.ROLE_PENDING)
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(false)
.build();
final UserJpaEntity newUser = UserJpaEntity.builder()
.loginId(info.email())
.gender(info.gender() != null ? info.gender() : Gender.MALE)
.name(info.name())
.password("") // It's safer to set an empty password for OAuth users
.birth(info.birthDay() != null ? info.birthDay()
: LocalDate.of(1900, 1, 1))
.userRole(UserRole.ROLE_PENDING)
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(false)
.build();

Comment on lines 65 to 67
public UserJpaEntity(String loginId, String password, Gender gender, String name,
LocalDate birth,
String customerKey, boolean agreedToTermsOfService, boolean agreedToPrivacyPolicy,
LocalDate birth, boolean agreedToTermsOfService, boolean agreedToPrivacyPolicy,
boolean agreedToMarketing, UserRole userRole) {

Choose a reason for hiding this comment

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

high

The constructor signature has changed to remove customerKey, which is now generated internally. However, the builder is still being used without providing a password, which will result in a null value for the password field. For OAuth users, it's safer to provide a default non-null value, like an empty string, to avoid potential NullPointerExceptions downstream.

public void deleteFaq(Long faqId) {
FaqJpaEntity faqEntity = faqRepository.findById(faqId)
FaqJpaEntity faqEntity = faqJpaRepository.findById(faqId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.FILE_NOT_FOUND));

Choose a reason for hiding this comment

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

medium

The error code ErrorCode.FILE_NOT_FOUND seems incorrect. Using ErrorCode.FAQ_NOT_FOUND would be clearer, as in getFaqDetail and update.

Suggested change
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.FILE_NOT_FOUND));
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.FAQ_NOT_FOUND));

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: 11

🔭 Outside diff range comments (1)
src/main/java/life/mosu/mosuserver/global/config/SecurityConfig.java (1)

77-84: Critical: All endpoints are publicly accessible.

The authorization configuration has all rules commented out, making every endpoint publicly accessible (anyRequest().permitAll()).

If this is intended for development/testing, consider:

  1. Adding a profile-specific configuration
  2. Documenting the security model being implemented
  3. Ensuring this doesn't reach production
 .authorizeHttpRequests(authorize -> authorize
-//                        .requestMatchers(
-//                                "/api/v1/profile/**",
-//                                "/api/v1/admin/**"
-//                        )
-//                        .hasRole("ADMIN")
+                        .requestMatchers(
+                                "/api/v1/profile/**",
+                                "/api/v1/admin/**"
+                        )
+                        .hasRole("ADMIN")
                         .anyRequest().permitAll()
 )
🧹 Nitpick comments (7)
src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java (1)

195-201: Enum-to-string conversion looks good but consider null safety.

The conversion to Korean strings using .getGenderName(), .getEducationName(), and .getGradeName() aligns well with the localization effort.

Consider adding null safety checks to prevent potential NPEs:

-                tuple.get(profile.gender).getGenderName(),
+                Optional.ofNullable(tuple.get(profile.gender))
+                    .map(Gender::getGenderName)
+                    .orElse("Unknown"),

Apply similar patterns for education and grade fields.

src/main/java/life/mosu/mosuserver/global/util/KeyGeneratorUtil.java (1)

5-10: Suggest minor improvements for utility class best practices.

The implementation is correct and functional. Consider these improvements:

  1. Make the class final and add a private constructor to prevent instantiation
  2. Consider a more concise method name
  3. Add documentation for the use case
+/**
+ * Utility class for generating unique keys for entities.
+ */
-public class KeyGeneratorUtil {
+public final class KeyGeneratorUtil {
+
+    private KeyGeneratorUtil() {
+        // Prevent instantiation
+    }

+    /**
+     * Generates a unique customer key using UUID.
+     * @return A unique UUID string for customer identification
+     */
     public static String generateUUIDCustomerKey() {
         return UUID.randomUUID().toString();
     }
 }
src/test/java/life/mosu/mosuserver/FaqServiceTest.java (1)

13-36: Test adapts to renamed repository – minor nitpick on naming

Variable faqJpaRepository is still mocked but the field name remains the same – 👍.
Optional: rename the parameter in the setUp() method (faqJpaRepository) to keep IDE “name shadowing” warnings away.

No functional issues.

Also applies to: 46-55

src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java (1)

53-64: Potential double query & redundant existence-check

inquiryAnswerJpaRepository.findByInquiryId(postId).ifPresent(...) performs an extra select before inquiryAnswerService.deleteInquiryAnswer(postId), which almost certainly queries again.
Either:

- inquiryAnswerJpaRepository.findByInquiryId(postId).ifPresent(answer -> {
-     inquiryAnswerService.deleteInquiryAnswer(postId);
- });
+ inquiryAnswerService.deleteInquiryAnswer(postId);   // make delete idempotent

or move the existence check inside InquiryAnswerService.

src/main/java/life/mosu/mosuserver/presentation/oauth/OAuthController.java (1)

26-26: Consider supporting multiple OAuth providers

The path is hardcoded to /api/v1/oauth2/callback/kakao, limiting support to only Kakao. Consider making the provider configurable or accepting it as a parameter to support multiple OAuth providers in the future.

src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java (1)

80-86: Consider adding validation to update method

The updateOAuthUser method should validate inputs to ensure data integrity.

 public void updateOAuthUser(
         Gender gender, String name,
         LocalDate birth) {
+    if (name == null || name.trim().isEmpty()) {
+        throw new IllegalArgumentException("Name cannot be null or empty");
+    }
     this.gender = gender;
     this.name = name;
     this.birth = birth;
 }
src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java (1)

60-65: Add environment check for additional safety

While the count check prevents re-initialization, consider adding an explicit environment check as an additional safety measure.

 @PostConstruct
 public void init() {
+    String activeProfile = System.getProperty("spring.profiles.active", "");
+    if (activeProfile.contains("prod")) {
+        log.warn("Database initializer is disabled in production environment");
+        return;
+    }
+    
     if (userRepository.count() > 0 || schoolRepository.count() > 0) {
         log.info("이미 더미 데이터가 존재하여 전체 초기화를 건너뜁니다.");
         return;
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd11919 and a938a26.

📒 Files selected for processing (40)
  • src/main/java/life/mosu/mosuserver/application/faq/FaqAttachmentService.java (4 hunks)
  • src/main/java/life/mosu/mosuserver/application/faq/FaqService.java (4 hunks)
  • src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java (4 hunks)
  • src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java (4 hunks)
  • src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java (5 hunks)
  • src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java (5 hunks)
  • src/main/java/life/mosu/mosuserver/application/notice/NoticeService.java (4 hunks)
  • src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/profile/ProfileService.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/application/user/UserService.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/application/Lunch.java (0 hunks)
  • src/main/java/life/mosu/mosuserver/domain/event/EventJpaEntity.java (0 hunks)
  • src/main/java/life/mosu/mosuserver/domain/faq/FaqAttachmentJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/faq/FaqJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryAttachmentJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/InquiryAnswerAttachmentJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/InquiryAnswerJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/notice/NoticeAttachmentJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/notice/NoticeJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/serviceterm/ServiceTermAgreementJpaEntity.java (0 hunks)
  • src/main/java/life/mosu/mosuserver/domain/serviceterm/ServiceTermJpaEntity.java (0 hunks)
  • src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/global/config/SecurityConfig.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/global/initializer/SchoolInitializer.java (0 hunks)
  • src/main/java/life/mosu/mosuserver/global/initializer/UserAndProfileInitializer.java (0 hunks)
  • src/main/java/life/mosu/mosuserver/global/util/KeyGeneratorUtil.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/infra/config/SwaggerConfig.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentListResponse.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/oauth/AccessTokenFilter.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/oauth/OAuthController.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/oauth/TokenExceptionFilter.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/profile/dto/ProfileDetailResponse.java (3 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/user/CustomerKeyResponse.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/user/UserController.java (1 hunks)
  • src/test/java/life/mosu/mosuserver/FaqServiceTest.java (4 hunks)
💤 Files with no reviewable changes (6)
  • src/main/java/life/mosu/mosuserver/domain/application/Lunch.java
  • src/main/java/life/mosu/mosuserver/domain/event/EventJpaEntity.java
  • src/main/java/life/mosu/mosuserver/domain/serviceterm/ServiceTermJpaEntity.java
  • src/main/java/life/mosu/mosuserver/domain/serviceterm/ServiceTermAgreementJpaEntity.java
  • src/main/java/life/mosu/mosuserver/global/initializer/SchoolInitializer.java
  • src/main/java/life/mosu/mosuserver/global/initializer/UserAndProfileInitializer.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java (1)
src/main/java/life/mosu/mosuserver/global/util/KeyGeneratorUtil.java (1)
  • KeyGeneratorUtil (5-10)
🔇 Additional comments (32)
src/main/java/life/mosu/mosuserver/presentation/admin/dto/StudentListResponse.java (1)

26-26: LGTM! Swagger example updated for better user experience.

The update from enum-like "HIGH_1" to Korean "고등학교 1학년" improves API documentation clarity and aligns with the localization effort.

src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java (1)

23-24: Field type changes and Swagger examples updated consistently.

The conversion of gender, educationLevel, and grade from enum types to String types with Korean examples improves API usability and aligns with the localization effort.

Also applies to: 35-36, 41-42

src/main/java/life/mosu/mosuserver/presentation/profile/dto/ProfileDetailResponse.java (1)

44-49: Factory method enum conversions implemented correctly.

The conversion to Korean strings using .getGenderName(), .getEducationName(), and .getGradeName() is consistent with the repository implementation and maintains the localization pattern.

src/main/java/life/mosu/mosuserver/domain/notice/NoticeAttachmentJpaRepository.java (1)

6-7: LGTM: Consistent interface renaming.

The renaming from NoticeAttachmentRepository to NoticeAttachmentJpaRepository aligns with the standardization effort mentioned in the PR summary. The interface functionality remains unchanged.

src/main/java/life/mosu/mosuserver/domain/notice/NoticeJpaRepository.java (1)

5-5: LGTM: Consistent interface renaming.

The renaming from NoticeRepository to NoticeJpaRepository follows the same standardization pattern as other repository interfaces in this PR.

src/main/java/life/mosu/mosuserver/presentation/oauth/TokenExceptionFilter.java (1)

1-29: Critical: Confirm OAuth exception handling strategy after removing TokenExceptionFilter

Our search shows that TokenExceptionFilter is fully commented out and no other *ExceptionFilter exists to catch CustomRuntimeException during the OAuth filter chain. While GlobalExceptionHandler handles CustomRuntimeException in controllers, exceptions thrown in servlet filters may bypass it.

Files needing your attention:

  • src/main/java/life/mosu/mosuserver/presentation/oauth/TokenExceptionFilter.java
  • src/main/java/life/mosu/mosuserver/global/exception/GlobalExceptionHandler.java

Please verify that:

  • The removal of TokenExceptionFilter is intentional (not a temporary change).
  • A replacement mechanism captures and converts OAuth-related token exceptions early in the filter chain.
  • Security and integration tests cover token errors to prevent raw stack traces or default HTTP 500 responses.
src/main/java/life/mosu/mosuserver/global/config/SecurityConfig.java (1)

45-45: Verify reliance on method-level security and intentional CORS disablement

It looks like you’ve switched all HTTP-level authorization to permitAll() and disabled CORS, but you still have:

  • @EnableWebSecurity and @EnableMethodSecurity in SecurityConfig
  • Controller-level @PreAuthorize on ProfileController and ApplicationController

This means you’re relying entirely on Spring’s method-level security for access control, and no filters will enforce token validation at the HTTP layer.

Please confirm:

• Is it intentional to leave the HTTP security chain fully open (no token filters or URL-based rules)?
• Are all secured endpoints covered by method-level annotations today, and will you add annotations for admin routes?
• Should CORS remain disabled, or do you plan to configure allowed origins?

src/main/java/life/mosu/mosuserver/domain/faq/FaqAttachmentJpaRepository.java (1)

6-9: Rename looks good – consider adding @Repository only if custom behaviour is added later

Spring Data will auto-detect because it extends JpaRepository; no functional issues observed.

src/main/java/life/mosu/mosuserver/domain/faq/FaqJpaRepository.java (1)

6-8: Consistent JPA suffix – OK

Interface rename aligns with the new convention; no further comments.

src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryJpaRepository.java (1)

5-7: Signature remains intact after rename

Extending InquiryQueryRepository is preserved; nothing else changed.

src/main/java/life/mosu/mosuserver/domain/inquiry/InquiryAttachmentJpaRepository.java (1)

6-9: Repository rename looks good

The rename to InquiryAttachmentJpaRepository cleanly conveys the persistence technology in use without altering behaviour. No issues spotted.

src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/InquiryAnswerJpaRepository.java (1)

6-9: Interface rename acknowledged

Consistent with the new naming convention, no functional or stylistic issues found.

src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/InquiryAnswerAttachmentJpaRepository.java (1)

6-9: No concerns

The JPA-specific interface is clear and concise.

src/main/java/life/mosu/mosuserver/application/notice/NoticeService.java (1)

35-42: Possibility of N+1 attachment queries

getNoticeWithAttachments maps each NoticeJpaEntity to toNoticeResponse, which in turn calls attachmentService.toAttachmentResponses. If attachments are lazily loaded, this loops N queries. Consider fetching notices with attachments in a single query or batching via join fetch.

src/main/java/life/mosu/mosuserver/infra/config/SwaggerConfig.java (1)

21-23: LGTM! Good addition for local development support.

The addition of a local development server configuration is a practical improvement that enhances the developer experience by allowing API testing through Swagger UI in local environments.

src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java (2)

6-6: LGTM! Repository interface renaming improves naming consistency.

The repository interface renaming from InquiryAnswerAttachmentRepository to InquiryAnswerAttachmentJpaRepository is part of a systematic refactoring effort to standardize repository naming across the codebase.


23-23: LGTM! Field declaration updated consistently.

The field declaration has been updated to use the renamed repository interface, maintaining consistency with the import change.

src/main/java/life/mosu/mosuserver/presentation/user/CustomerKeyResponse.java (1)

3-10: LGTM! Well-structured response record.

The CustomerKeyResponse record is well-designed with:

  • Appropriate use of Java records for data transfer
  • Clean static factory method following common patterns
  • Simple and focused structure
src/main/java/life/mosu/mosuserver/application/faq/FaqAttachmentService.java (3)

6-6: LGTM! Repository interface renaming maintains consistency.

The import change from FaqAttachmentRepository to FaqAttachmentJpaRepository is consistent with the systematic repository interface renaming across the codebase.


23-23: LGTM! Field declaration updated consistently.

The repository field has been updated to use the renamed interface, maintaining consistency with the import change.


34-34: LGTM! Repository usage updated consistently.

All repository method calls have been updated to use the renamed field consistently throughout the service.

src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java (3)

6-6: LGTM! Repository interface renaming maintains consistency.

The import change from NoticeAttachmentRepository to NoticeAttachmentJpaRepository completes the systematic repository interface renaming pattern across the attachment services.


24-24: LGTM! Field declaration updated consistently.

The repository field has been updated to use the renamed interface, maintaining consistency with the import change.


34-34: LGTM! Repository usage updated consistently.

All repository method calls throughout the service have been updated to use the renamed field consistently.

src/main/java/life/mosu/mosuserver/application/profile/ProfileService.java (3)

21-21: Good addition of user validation dependency.

The addition of UserJpaRepository dependency enables proper user existence validation before profile registration, which improves data integrity.


26-28: Excellent user validation implementation.

The user existence check with proper error handling using USER_NOT_FOUND error code follows consistent error handling patterns throughout the codebase.


33-33: Good integration of user state management.

The call to user.registerProfile() appropriately updates the user's state during profile registration, maintaining consistency between user and profile entities.

src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java (1)

6-6: Consistent repository interface refactoring.

The systematic replacement of InquiryAttachmentRepository with InquiryAttachmentJpaRepository across imports, field declarations, and method calls is well-executed and maintains all existing functionality.

Also applies to: 21-21, 31-31, 43-43, 45-45, 51-51

src/main/java/life/mosu/mosuserver/application/faq/FaqService.java (1)

5-5: Consistent repository interface refactoring.

The systematic replacement of FaqRepository with FaqJpaRepository across imports, field declarations, and method calls is well-executed and maintains all existing functionality.

Also applies to: 24-24, 30-30, 38-38, 47-47, 55-55, 59-59, 70-70

src/main/java/life/mosu/mosuserver/application/user/UserService.java (1)

1-22: Well-implemented new service class.

The UserService class provides a clean and efficient implementation for retrieving customer keys. The use of Optional.map() and orElseThrow() demonstrates good functional programming practices and proper error handling. This aligns well with the PR objectives for adding customer key return functionality.

src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java (1)

4-4: Consistent repository interface refactoring.

The systematic replacement of InquiryRepository and InquiryAnswerRepository with their JPA-specific counterparts across imports, field declarations, and method calls is well-executed and maintains all existing functionality.

Also applies to: 6-6, 23-24, 30-30, 34-34, 45-45, 48-48, 56-56, 68-68, 72-72, 79-79

src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java (1)

64-78: LGTM! Clean customer key generation

The automatic UUID generation for customer keys in the constructor is a good approach that ensures uniqueness and removes the burden from callers.

Comment on lines 28 to +29
@Schema(description = "학력 (예: ENROLLED, GRADUATED)", example = "ENROLLED")
Education education,
String education,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update Swagger examples for consistency.

The field type changes are correct, but the Swagger examples should be updated to Korean strings for consistency with other DTOs.

Update the Swagger examples to match the localization pattern:

-        @Schema(description = "학력 (예: ENROLLED, GRADUATED)", example = "ENROLLED")
+        @Schema(description = "학력 (예: 재학생, 졸업생)", example = "재학생")
         String education,

-        @Schema(description = "학년", example = "HIGH_1")
+        @Schema(description = "학년", example = "고등학교 1학년")
         String grade

Also applies to: 34-35

🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/presentation/profile/dto/ProfileDetailResponse.java
around lines 28-29 and 34-35, the Swagger @Schema example values for the
education field are currently in English ("ENROLLED"). Update these example
values to their Korean equivalents to maintain consistency with other DTOs and
the localization pattern used in the project.

Comment on lines +1 to +64
//package life.mosu.mosuserver.presentation.oauth;
//
//import static org.springframework.http.HttpHeaders.AUTHORIZATION;
//
//import jakarta.servlet.FilterChain;
//import jakarta.servlet.ServletException;
//import jakarta.servlet.http.HttpServletRequest;
//import jakarta.servlet.http.HttpServletResponse;
//import java.io.IOException;
//import life.mosu.mosuserver.application.auth.AccessTokenService;
//import lombok.RequiredArgsConstructor;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.security.core.Authentication;
//import org.springframework.security.core.context.SecurityContextHolder;
//import org.springframework.stereotype.Component;
//import org.springframework.util.ObjectUtils;
//import org.springframework.web.filter.OncePerRequestFilter;
//
//@Component
//@RequiredArgsConstructor
//public class AccessTokenFilter extends OncePerRequestFilter {
//
// private static final String TOKEN_PREFIX = "Bearer ";
// private final AccessTokenService accessTokenService;
//
// @Value("${endpoints.reissue}")
// private String reissueEndpoint;
//
// @Override
// protected void doFilterInternal(
// final HttpServletRequest request,
// final HttpServletResponse response,
// final FilterChain filterChain
// ) throws ServletException, IOException {
// if (request.getRequestURI().equals(reissueEndpoint)) {
// filterChain.doFilter(request, response);
// return;
// }
//
// if (request.getRequestURI().startsWith("/api/v1/oauth2")) {
// filterChain.doFilter(request, response);
// return;
// }
//
// final String accessToken = resolveToken(request);
// if (accessToken != null) {
// setAuthentication(accessToken);
// }
// filterChain.doFilter(request, response);
// }
//
// private void setAuthentication(final String accessToken) {
// final Authentication authentication = accessTokenService.getAuthentication(accessToken);
// SecurityContextHolder.getContext().setAuthentication(authentication);
// }
//
// private String resolveToken(final HttpServletRequest request) {
// final String token = request.getHeader(AUTHORIZATION);
// if (ObjectUtils.isEmpty(token) || !token.startsWith(TOKEN_PREFIX)) {
// return null;
// }
// return token.substring(TOKEN_PREFIX.length());
// }
//} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fully-commented source file introduces maintenance & security risks – delete or formally deprecate instead

The entire compilation unit now contains only comments.
Side effects:

  1. ✂️ No AccessTokenFilter class is produced, but the file is still compiled, creating dead weight in every build.
  2. 🛡 OAuth authentication previously enforced by this filter disappears silently; unless a replacement filter is configured the API is now publicly accessible.
  3. 🧭 Future readers waste time deciphering whether this filter is meant to be active.

Action items

-// (commented code)
+// @Deprecated – kept for reference only. Will be removed in MOSU-XXX.
+
+/*  If historical reference is really required, move the block to
+   docs/legacy/AccessTokenFilter.md and delete this .java file. */

Or simply delete the file altogether and rely on VCS history.

Also double-check SecurityConfig to confirm another filter (e.g. JwtAuthenticationFilter) now guards the protected endpoints.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/presentation/oauth/AccessTokenFilter.java
lines 1 to 64, the entire AccessTokenFilter class is commented out, leaving no
active code and causing potential security risks by removing OAuth enforcement
silently. To fix this, either delete the file entirely to remove dead code or
formally deprecate it with proper annotations and comments explaining its
status. Additionally, verify in SecurityConfig that another filter like
JwtAuthenticationFilter is properly configured to protect the endpoints before
removing this filter.

Comment on lines 24 to 28
private final InquiryAttachmentService inquiryAttachmentService;
private final InquiryRepository inquiryRepository;
private final InquiryJpaRepository inquiryJpaRepository;
private final InquiryAnswerService inquiryAnswerService;
private final InquiryAnswerRepository inquiryAnswerRepository;
private final InquiryAnswerJpaRepository inquiryAnswerJpaRepository;

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid mixing service & repository layers for the same aggregate

Injecting both InquiryAnswerService and InquiryAnswerJpaRepository in the same service breaks layer isolation and makes future refactors harder.
deleteInquiry only needs a presence-check before delegating to InquiryAnswerService. Expose that check via the service (or let the delete method be idempotent) and drop the direct repository dependency here.

🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java
around lines 24 to 28, avoid injecting both InquiryAnswerService and
InquiryAnswerJpaRepository to maintain layer isolation. Remove the direct
dependency on InquiryAnswerJpaRepository and instead expose a presence-check
method in InquiryAnswerService or make the deleteInquiry method idempotent so
that the repository is only accessed through the service layer.

Comment on lines 52 to 56
public void deleteNotice(Long noticeId) {
NoticeJpaEntity noticeEntity = getNoticeOrThrow(noticeId);
noticeRepository.delete(noticeEntity);
noticeJpaRepository.delete(noticeEntity);
attachmentService.deleteAttachment(noticeEntity);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Delete child entities before parent to avoid FK violations

noticeJpaRepository.delete(noticeEntity); executes before attachment deletion.
Unless the DB FK is ON DELETE CASCADE, this can break referential integrity. Safer order:

- noticeJpaRepository.delete(noticeEntity);
- attachmentService.deleteAttachment(noticeEntity);
+ attachmentService.deleteAttachment(noticeEntity);
+ noticeJpaRepository.delete(noticeEntity);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void deleteNotice(Long noticeId) {
NoticeJpaEntity noticeEntity = getNoticeOrThrow(noticeId);
noticeRepository.delete(noticeEntity);
noticeJpaRepository.delete(noticeEntity);
attachmentService.deleteAttachment(noticeEntity);
}
public void deleteNotice(Long noticeId) {
NoticeJpaEntity noticeEntity = getNoticeOrThrow(noticeId);
attachmentService.deleteAttachment(noticeEntity);
noticeJpaRepository.delete(noticeEntity);
}
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/notice/NoticeService.java
around lines 52 to 56, the current code deletes the parent notice entity before
deleting its child attachments, which can cause foreign key constraint
violations if the database does not have ON DELETE CASCADE enabled. To fix this,
reorder the deletion calls to first delete the attachments via
attachmentService.deleteAttachment(noticeEntity) and only then delete the notice
entity with noticeJpaRepository.delete(noticeEntity).

Comment on lines +68 to 69
FaqJpaEntity faqEntity = faqJpaRepository.findById(faqId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.FILE_NOT_FOUND));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistent error code usage.

The error code should be FAQ_NOT_FOUND instead of FILE_NOT_FOUND to be consistent with other methods in this service that use the correct FAQ-specific error code.

-        FaqJpaEntity faqEntity = faqJpaRepository.findById(faqId)
-                .orElseThrow(() -> new CustomRuntimeException(ErrorCode.FILE_NOT_FOUND));
+        FaqJpaEntity faqEntity = faqJpaRepository.findById(faqId)
+                .orElseThrow(() -> new CustomRuntimeException(ErrorCode.FAQ_NOT_FOUND));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FaqJpaEntity faqEntity = faqJpaRepository.findById(faqId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.FILE_NOT_FOUND));
FaqJpaEntity faqEntity = faqJpaRepository.findById(faqId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.FAQ_NOT_FOUND));
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/faq/FaqService.java at lines
68-69, the error code used when throwing the exception for a missing FAQ is
incorrectly set to FILE_NOT_FOUND. Change this error code to FAQ_NOT_FOUND to
maintain consistency with other methods in the service that handle FAQ-related
errors.

Comment on lines +20 to +28
@GetMapping("/customer-key")
public ResponseEntity<ApiResponseWrapper<CustomerKeyResponse>> getCustomerKey(
@RequestParam Long userId
) {
String customerKey = userService.getCustomerKey(userId);

return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "CustomerKey 조회 성공",
CustomerKeyResponse.from(customerKey)));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for userId parameter

The userId parameter should be validated to ensure it's not null and potentially check for positive values.

Apply this diff to add validation:

+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import org.springframework.validation.annotation.Validated;

+@Validated
 @RestController
 @RequestMapping("/user")
 @RequiredArgsConstructor
 public class UserController {

     private final UserService userService;

     @GetMapping("/customer-key")
     public ResponseEntity<ApiResponseWrapper<CustomerKeyResponse>> getCustomerKey(
-            @RequestParam Long userId
+            @RequestParam @NotNull @Positive Long userId
     ) {
         String customerKey = userService.getCustomerKey(userId);

         return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "CustomerKey 조회 성공",
                 CustomerKeyResponse.from(customerKey)));
     }
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/presentation/user/UserController.java
around lines 20 to 28, the userId parameter in the getCustomerKey method lacks
validation. Add validation annotations to ensure userId is not null and is a
positive value, such as using @NotNull and @Positive on the userId parameter.
Also, enable validation by adding @Validated on the controller class if not
already present.

return userRepository.findByLoginId(info.email())
.map(existingUser -> {
existingUser.updateOAuthUser(info.gender(), info.name(),
info.birthDay() != null ? info.birthDay() : LocalDate.of(1900, 1, 1));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using null instead of placeholder date

Using LocalDate.of(1900, 1, 1) as a default birth date could lead to incorrect age calculations or business logic issues. Consider keeping it null if the birth date is not provided.

Also applies to: 57-58

🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java at
lines 49 and 57-58, replace the placeholder date LocalDate.of(1900, 1, 1) with
null when the birthDay is not provided. This means changing the conditional
expressions to return null instead of the default date to avoid incorrect age
calculations or business logic errors.

.userRole(UserRole.ROLE_USER)
.birth(info.birthDay() != null ? info.birthDay()
: LocalDate.of(1900, 1, 1))
.userRole(UserRole.ROLE_PENDING)
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify ROLE_PENDING workflow

The change from ROLE_USER to ROLE_PENDING suggests users must complete additional steps before full access. Ensure:

  1. Security configurations properly handle ROLE_PENDING users
  2. There's a clear flow for users to transition from ROLE_PENDING to ROLE_USER
  3. Existing functionality expecting ROLE_USER is updated

🏁 Script executed:

#!/bin/bash
# Search for ROLE_PENDING and ROLE_USER usage to verify proper handling
echo "=== Searching for ROLE_PENDING usage ==="
rg "ROLE_PENDING" -A 3 -B 3

echo -e "\n=== Searching for security configurations ==="
fd -e java -e yml -e properties | xargs rg -l "hasRole|hasAuthority|SecurityConfig"

echo -e "\n=== Checking registerProfile usage (transitions to ROLE_USER) ==="
ast-grep --pattern 'registerProfile()'

Length of output: 3129


Fix ROLE_PENDING access and transition logic

It looks like we’re setting new users to ROLE_PENDING, but:

  • SecurityConfig.java doesn’t include ROLE_PENDING anywhere (all hasRole() checks still target USER/ADMIN).
  • There’s no code path that upgrades a pending user to ROLE_USER (we didn’t find any registerProfile() or similar transition method).
  • Controllers guarded by hasRole("USER") will reject all pending accounts.

Please update as follows:

• In global/config/SecurityConfig.java
– Define which endpoints pending users may access (e.g. profile completion, email verification)
– Ensure pending users are explicitly denied or redirected elsewhere for protected routes

• In the user‐management flow (e.g. OAuthUserService or a new service)
– Implement a clear “approve” or “complete profile” endpoint that updates UserRole.ROLE_PENDING → ROLE_USER
– Add and wire a method (e.g. approvePendingUser(userId)) and call it after required steps

• In any controllers currently using hasRole("USER")
– Consider adding a custom exception or handler for pending users to guide them through completion

🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java at
line 59 and related files, the issue is that new users are assigned ROLE_PENDING
but the security configuration and user flow do not handle this role properly.
To fix this, update SecurityConfig.java to define accessible endpoints for
ROLE_PENDING users and restrict protected routes accordingly. Implement a method
in OAuthUserService or a new service to transition users from ROLE_PENDING to
ROLE_USER after profile completion or verification, such as an
approvePendingUser(userId) method. Also, modify controllers using
hasRole("USER") to handle pending users gracefully, possibly by adding custom
exceptions or handlers to guide them through the completion process.

Comment on lines +60 to +62
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(false)
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify user consent for agreement flags

Hardcoding agreement flags (especially setting agreedToMarketing to false while others are true) may not accurately reflect user consent obtained during OAuth login. Consider:

  1. Obtaining these consents from the OAuth provider if available
  2. Deferring consent collection to a separate user flow
  3. Setting all to false initially and prompting users to agree

🏁 Script executed:

#!/bin/bash
# Show the beginning of OAuthUserService to understand how consents are set
sed -n '1,120p' src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java

# Locate all occurrences of consent flags in the file
rg -n "agreedTo" -A2 src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java

Length of output: 3417


Reconsider default consent flags in OAuthUserService

Hardcoding agreedToTermsOfService(true) and agreedToPrivacyPolicy(true)—and only setting marketing to false—risks recording consents that the user never explicitly granted. Update this logic to accurately capture or defer consent:

• File: src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java
Lines 60–62 currently read:
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(false)

• Suggested approaches:

  1. Pull consent flags from OAuthUserInfo if the provider returns them.
  2. Initialize all three flags to false by default, then prompt the user in a follow-up flow.
  3. If you do receive explicit consent via OAuth, map those values instead of hardcoding.

Example diff:

-   .agreedToTermsOfService(true)
-   .agreedToPrivacyPolicy(true)
-   .agreedToMarketing(false)
+   .agreedToTermsOfService(info.termsAccepted() != null
+           ? info.termsAccepted() : false)
+   .agreedToPrivacyPolicy(info.privacyAccepted() != null
+           ? info.privacyAccepted() : false)
+   .agreedToMarketing(info.marketingAccepted() != null
+           ? info.marketingAccepted() : false)

Or, if your provider doesn’t supply these fields, default all to false and implement a UI step to collect and persist user consent.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(false)
.agreedToTermsOfService(info.termsAccepted() != null
? info.termsAccepted() : false)
.agreedToPrivacyPolicy(info.privacyAccepted() != null
? info.privacyAccepted() : false)
.agreedToMarketing(info.marketingAccepted() != null
? info.marketingAccepted() : false)
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java
around lines 60 to 62, the consent flags for terms of service and privacy policy
are hardcoded to true, which may inaccurately record user consent. Modify the
code to either extract these consent flags from the OAuthUserInfo object if
available or default all consent flags (terms of service, privacy policy,
marketing) to false. Then ensure that explicit user consent is collected and
persisted in a subsequent user interaction flow rather than assuming consent at
this stage.

Comment on lines +44 to +47
@Slf4j
@Component
@RequiredArgsConstructor
public class DatabaseInitializer {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Restrict initializer to non-production environments

This database initializer should not run in production environments. Consider using Spring profiles to control when it's active.

Apply this change:

-@Component
+@Component
+@Profile({"dev", "test", "local"})
 @RequiredArgsConstructor
 public class DatabaseInitializer {

Or use a configuration property:

+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

 @Component
+@ConditionalOnProperty(
+    name = "app.database.initialize",
+    havingValue = "true",
+    matchIfMissing = false
+)
 @RequiredArgsConstructor
 public class DatabaseInitializer {
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java
around lines 44 to 47, the DatabaseInitializer class should be restricted to run
only in non-production environments. To fix this, annotate the class with
@Profile specifying the profiles where it should be active, such as
@Profile("!prod") to exclude production. Alternatively, use a configuration
property with @ConditionalOnProperty to enable or disable the initializer based
on environment-specific settings.

@mosu-dev mosu-dev deleted a comment from gemini-code-assist bot Jul 12, 2025
@mosu-dev mosu-dev deleted a comment from coderabbitai bot Jul 12, 2025
@wlgns12370 wlgns12370 merged commit 4cb476b into develop Jul 13, 2025
2 checks passed
@wlgns12370 wlgns12370 deleted the feature/mosu-86 branch July 13, 2025 06:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[🛠 리팩토링] 한국어 Enum 반환 시 한글 반환으로 변경 [🛠 기능 구현] swagger 적용

1 participant