Skip to content

[Refactor/signup token version] - Signup Token Version사용#108

Merged
ekgns33 merged 8 commits intomainfrom
refactor/signup-token-version
Jul 3, 2025
Merged

[Refactor/signup token version] - Signup Token Version사용#108
ekgns33 merged 8 commits intomainfrom
refactor/signup-token-version

Conversation

@ekgns33
Copy link
Contributor

@ekgns33 ekgns33 commented Jul 2, 2025

작업내역

  • flyway 의존성 추가
  • db migration 스크립트 추가
  • SignupToken에 @Version 컬럼 추가, 상태 컬럼 추가
  • 토큰 사용시 애플리케이션 로직에서 수정
  • 낙관적 락 사용

Summary by CodeRabbit

  • New Features

    • Signup tokens now track usage status, preventing reuse and enhancing security.
    • Optimistic locking added to signup tokens for improved concurrency control.
    • Added a dedicated service to manage signup token validation and invalidation.
  • Database Migrations

    • Introduced Flyway for database migrations, including baseline scripts for production and test environments.
    • Added new columns to the signup token table to support versioning and usage tracking.
  • Bug Fixes

    • Signup now correctly prevents duplicate registration attempts with the same token.
  • Tests

    • Updated and added tests to verify correct responses for duplicate signup scenarios and token usage enforcement.
  • Chores

    • Updated build configuration to integrate Flyway migration support.
    • Adjusted test environment settings to enable Flyway migrations.

@coderabbitai
Copy link

coderabbitai bot commented Jul 2, 2025

Walkthrough

This update introduces Flyway database migration support, adds optimistic locking and a usage flag to the SignupToken entity, and modifies related repository queries and service logic to mark tokens as used instead of deleting them. Baseline and versioned migration scripts are added for both MySQL and H2, and acceptance tests are updated to reflect the new token usage logic and error scenarios.

Changes

Files/Paths Change Summary
build.gradle Added Flyway plugin and dependencies for database migration support.
src/main/java/org/runimo/runimo/auth/domain/SignupToken.java Added version and used fields; added markAsUsed() method for token state management.
src/main/java/org/runimo/runimo/auth/repository/SignupTokenRepository.java Modified query to filter for unused tokens only.
src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java Replaced token deletion with marking as used; removed helper method for token removal; integrated SignupTokenService.
src/main/java/org/runimo/runimo/auth/service/SignupTokenService.java Added new service for token extraction, validation, and invalidation with optimistic locking handling.
src/test/java/org/runimo/runimo/auth/domain/SignupTokenTest.java Added unit test verifying token cannot be marked as used twice.
src/test/java/org/runimo/runimo/auth/service/SignUpUsecaseTest.java Refactored tests to mock and use SignupTokenService instead of direct repository and JWT resolver.
src/main/resources/db/migration/V1__Create_baseline.sql Added baseline migration script for Flyway (MySQL).
src/main/resources/db/migration/V20250702__add_version_column_signup_token.sql Migration to add version and used columns to signup_token (MySQL).
src/test/java/org/runimo/runimo/auth/controller/AuthAcceptanceTest.java Renamed and added tests for signup with reused and duplicate tokens, covering new error scenarios.
src/test/resources/application.yml Enabled Flyway for tests, set migration paths, and adjusted initialization settings.
src/test/resources/db/migration/h2/V1__Create_baseline.sql Baseline migration script for H2, creates tables and inserts seed data.
src/test/resources/db/migration/h2/V20250702__add_version_column_signup_token.sql Migration to add version and used columns to signup_token (H2).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AuthController
    participant SignUpUsecaseImpl
    participant SignupTokenService
    participant SignupTokenRepository
    participant SignupToken

    User->>AuthController: POST /signup (with token)
    AuthController->>SignUpUsecaseImpl: registerUser(token, ...)
    SignUpUsecaseImpl->>SignupTokenService: extractPayload(token)
    SignUpUsecaseImpl->>SignupTokenService: findUnExpiredToken(token)
    SignupTokenService->>SignupTokenRepository: findByIdAndCreatedAtAfter(token, cutoff)
    SignupTokenRepository-->>SignupTokenService: SignupToken (unused)
    SignupTokenService-->>SignUpUsecaseImpl: SignupToken
    SignUpUsecaseImpl->>...: (proceed with user registration)
    SignUpUsecaseImpl->>SignupTokenService: invalidateSignupToken(SignupToken)
    SignupTokenService->>SignupToken: markAsUsed()
    SignupTokenService->>SignupTokenRepository: save(SignupToken)
    SignupTokenService-->>SignUpUsecaseImpl: (confirmation)
    AuthController-->>User: 200 OK or error response
Loading

Suggested labels

rocket: feature :rocket:, size/M

Suggested reviewers

  • jeeheaG

Poem

🐇 Hop, hop, the tokens dance anew,
Marked as used, no duplicates through.
Flyway guides the database flight,
Migrations steady, shining bright.
With locks and flags, our code takes flight,
A rabbit’s cheer for progress right! 🎉🐰✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3880ea0 and 74d55f1.

📒 Files selected for processing (4)
  • src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java (2 hunks)
  • src/main/java/org/runimo/runimo/auth/service/SignupTokenService.java (1 hunks)
  • src/test/java/org/runimo/runimo/auth/domain/SignupTokenTest.java (1 hunks)
  • src/test/java/org/runimo/runimo/auth/service/SignUpUsecaseTest.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/test/java/org/runimo/runimo/auth/domain/SignupTokenTest.java (1)
src/main/java/org/runimo/runimo/auth/exceptions/SignUpException.java (1)
  • SignUpException (6-15)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (8)
src/test/java/org/runimo/runimo/auth/domain/SignupTokenTest.java (1)

9-25: Well-structured unit test for token usage validation.

The test correctly validates that a signup token can only be marked as used once, which aligns with the PR's goal of implementing proper token state management and optimistic locking. The test structure follows best practices with clear given-when-then sections.

src/test/java/org/runimo/runimo/auth/service/SignUpUsecaseTest.java (3)

45-45: Good refactoring to use service layer abstraction.

Replacing direct repository and JWT resolver dependencies with SignupTokenService improves the separation of concerns and makes the code more maintainable.


52-59: Constructor properly updated for service injection.

The constructor correctly reflects the new dependency structure with SignupTokenService replacing the previous direct dependencies.


69-72: Method calls correctly updated to use service abstraction.

The service method calls properly encapsulate the token extraction and lookup logic, maintaining the same functionality while improving code organization.

src/main/java/org/runimo/runimo/auth/service/SignupTokenService.java (4)

15-22: Well-structured service class with proper dependency injection.

The service is correctly annotated and structured with clear dependencies and a reasonable expiration cutoff constant.


23-25: Clean delegation to JWT resolver.

The extractPayload method properly delegates to the JWT resolver, maintaining single responsibility.


27-32: Proper token lookup with expiration validation.

The method correctly implements the 10-minute expiration window and throws appropriate business exceptions for invalid tokens.


34-41: Excellent optimistic locking exception handling.

The method properly handles the optimistic locking scenario by catching OptimisticLockingFailureException and converting it to a meaningful business exception. This correctly treats concurrent token usage as a sign-up failure, which aligns with the business requirements.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 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.

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

🧹 Nitpick comments (7)
build.gradle (2)

7-8: Check whether the Flyway Gradle plugin already brings in flyway-core.

The plugin org.flywaydb.flyway usually declares flyway-core on its class-path.
Keeping an explicit implementation 'org.flywaydb:flyway-core' (line 49) can lead to a duplicate dependency with potentially conflicting versions if the plugin is bumped independently from the library coordinates.

If the build still works after removing the explicit dependency, prefer relying on the one that the plugin supplies and keep only the database-specific artefact:

-    implementation 'org.flywaydb:flyway-core'
     implementation 'org.flywaydb:flyway-mysql'

49-51: Use runtimeOnly for JDBC-specific Flyway extensions.

flyway-mysql is needed only at runtime when migrations run against MySQL, not at compile time.
Mark it runtimeOnly to slim the compile class-path:

-    implementation 'org.flywaydb:flyway-mysql'
+    runtimeOnly 'org.flywaydb:flyway-mysql'
src/main/resources/db/migration/V20250702__add_version_column_signup_token.sql (1)

7-10: UPDATE is redundant after adding default values.

Since both new columns are NOT NULL with defaults (0 / FALSE), existing rows are auto-populated during ALTER TABLE.
The subsequent UPDATE does a full-table write for no gain and may lock the table unnecessarily.

Safe to drop:

--- 기존 토큰들은 사용되지 않은 것으로 처리
-UPDATE `signup_token`
-SET `used`    = FALSE,
-    `version` = 0;
src/test/resources/application.yml (2)

29-34: clean-disabled: false on tests might drop objects needed by future parallel tests.

Allowing flywayClean in the test profile is convenient but risky when multiple test classes use the same in-memory DB concurrently (e.g., with spring-test’s @DirtiesContext).
If not strictly required, set it to true and invoke Flyway.clean() explicitly inside tests that need a fresh schema.


40-48: defer-datasource-initialization: false combined with sql.init.mode: never is redundant.

With sql.init.mode: never Spring will not run schema.sql/data.sql anyway; the defer-datasource-initialization flag only affects those initializers.
You can remove the property to keep the config minimal.

src/main/resources/db/migration/V1__Create_baseline.sql (1)

26-28: Single-row SELECT statements in baseline scripts are harmless but noisy.

Flyway records migrations based on checksum; returning result sets is unnecessary and can surprise new contributors.
Consider deleting the final SELECT—the baseline marker comment alone suffices.

src/test/java/org/runimo/runimo/auth/controller/AuthAcceptanceTest.java (1)

172-180: Consider cleaning up redundant token creation.

The token creation logic here is redundant since a valid token already exists in the setUp method. The test could reuse the existing token or clarify why a new token instance is needed.

-        SignupToken signupToken = new SignupToken(
-            "valid-token",
-            "provider-id", 
-            "refresh-token",
-            SocialProvider.KAKAO
-        );
-        token = jwtTokenFactory.generateSignupTemporalToken("provider-id", SocialProvider.KAKAO,
-            "valid-token");
         request = new AuthSignupRequest(token, "username", Gender.UNKNOWN);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9078ffc and 8aa5977.

📒 Files selected for processing (10)
  • build.gradle (2 hunks)
  • src/main/java/org/runimo/runimo/auth/domain/SignupToken.java (3 hunks)
  • src/main/java/org/runimo/runimo/auth/repository/SignupTokenRepository.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java (1 hunks)
  • src/main/resources/db/migration/V1__Create_baseline.sql (1 hunks)
  • src/main/resources/db/migration/V20250702__add_version_column_signup_token.sql (1 hunks)
  • src/test/java/org/runimo/runimo/auth/controller/AuthAcceptanceTest.java (2 hunks)
  • src/test/resources/application.yml (1 hunks)
  • src/test/resources/db/migration/h2/V1__Create_baseline.sql (1 hunks)
  • src/test/resources/db/migration/h2/V20250702__add_version_column_signup_token.sql (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (9)
src/main/resources/db/migration/V20250702__add_version_column_signup_token.sql (1)

1-6: Column name version can collide with JPA’s implicit column mapping.

@Version fields default to the column name version.
Because it is also a common SQL keyword across vendors (and is reserved in some), always quoting it may become necessary when Flyway executes on different RDBMSes.

Consider renaming or adding an explicit @Column(name = "token_version") in the entity and reflecting that here to avoid any cross-dialect surprises.

src/test/resources/db/migration/h2/V20250702__add_version_column_signup_token.sql (1)

1-6: Keep H2 and MySQL scripts identical only when behaviour truly matches.

H2 in MODE=MYSQL accepts back-ticks, but subtle type differences (e.g., BOOLEAN vs BIT) can surface later. Validate that the generated schema for tests matches production (SHOW COLUMNS signup_token) to avoid green-test / red-prod scenarios.

src/main/java/org/runimo/runimo/auth/repository/SignupTokenRepository.java (1)

12-14: LGTM! Query correctly filters unused tokens.

The addition of AND st.used = false properly aligns with the new token usage semantics where tokens are marked as used instead of deleted.

src/main/java/org/runimo/runimo/auth/service/SignUpUsecaseImpl.java (1)

60-60: LGTM! Proper token lifecycle management.

Replacing token deletion with markAsUsed() correctly implements the new token usage semantics and enables optimistic locking to prevent concurrent token usage.

src/main/java/org/runimo/runimo/auth/domain/SignupToken.java (1)

38-43: LGTM! Proper optimistic locking and usage tracking implementation.

The @Version annotation for optimistic locking and the used boolean field with default false are correctly implemented for tracking token usage state.

src/test/java/org/runimo/runimo/auth/controller/AuthAcceptanceTest.java (2)

156-191: LGTM! Test correctly validates token reuse scenario.

The test properly verifies that attempting to reuse the same signup token results in a 401 Unauthorized response, which aligns with the new token usage semantics.


193-230: LGTM! Test correctly validates duplicate user with different token.

The test properly verifies that attempting to create a duplicate user with a different token results in a 409 Conflict response, maintaining the existing duplicate user validation logic.

src/test/resources/db/migration/h2/V1__Create_baseline.sql (2)

88-95: LGTM! Baseline migration correctly represents original schema.

The signup_token table definition in this baseline migration correctly shows the original structure without the version and used columns, which will be added in the subsequent migration. This properly establishes the schema baseline.


265-266: LGTM! Proper baseline migration documentation.

The message clearly indicates this is a baseline migration marking the existing schema as V1, which allows subsequent versioned migrations to start from V2.

Comment on lines +54 to +59
public void markAsUsed() {
if (this.used) {
throw new SignUpException(UserHttpResponseCode.SIGNIN_FAIL_ALREADY_EXIST);
}
this.used = true;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix the error code for token reuse scenario.

The markAsUsed() method throws SIGNIN_FAIL_ALREADY_EXIST which seems inappropriate for a token reuse scenario. Consider using a more specific error code like TOKEN_ALREADY_USED or TOKEN_INVALID.

 public void markAsUsed() {
     if (this.used) {
-        throw new SignUpException(UserHttpResponseCode.SIGNIN_FAIL_ALREADY_EXIST);
+        throw new SignUpException(UserHttpResponseCode.TOKEN_INVALID);
     }
     this.used = true;
 }
📝 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 markAsUsed() {
if (this.used) {
throw new SignUpException(UserHttpResponseCode.SIGNIN_FAIL_ALREADY_EXIST);
}
this.used = true;
}
public void markAsUsed() {
if (this.used) {
throw new SignUpException(UserHttpResponseCode.TOKEN_INVALID);
}
this.used = true;
}
🤖 Prompt for AI Agents
In src/main/java/org/runimo/runimo/auth/domain/SignupToken.java lines 54 to 59,
the markAsUsed() method throws an incorrect error code SIGNIN_FAIL_ALREADY_EXIST
when the token is already used. Replace this error code with a more appropriate
one such as TOKEN_ALREADY_USED or TOKEN_INVALID to accurately reflect the token
reuse scenario.

@pull-request-size pull-request-size bot added size/XL and removed size/L labels Jul 2, 2025
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jul 2, 2025

@ekgns33 ekgns33 merged commit 531e924 into main Jul 3, 2025
4 checks passed
@ekgns33 ekgns33 deleted the refactor/signup-token-version branch July 3, 2025 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant