Skip to content

마이페이지 CRUD, 독서기록 CRUD, 모든 API 엑세스 토큰 적용, ENUM 정상화#78

Merged
icarus0616 merged 7 commits into
devfrom
feat/MyPage/1
Jan 28, 2026
Merged

마이페이지 CRUD, 독서기록 CRUD, 모든 API 엑세스 토큰 적용, ENUM 정상화#78
icarus0616 merged 7 commits into
devfrom
feat/MyPage/1

Conversation

@icarus0616
Copy link
Copy Markdown
Collaborator

@icarus0616 icarus0616 commented Jan 28, 2026

#74
#72
#71
#70
#69
#68

Summary by CodeRabbit

Release Notes

  • New Features

    • Added user profile management with nickname and privacy settings configuration
    • Introduced profile avatar upload functionality with image validation
    • Implemented reading calendar view displaying daily reading activity with book thumbnails
  • Improvements

    • Enhanced API security using Bearer token authentication across all endpoints
    • Improved database enum persistence for consistent data storage

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

This pull request refactors authentication across multiple controller classes to use Spring Security's @AuthenticationPrincipal pattern with CustomUserDetails instead of header-based userId parameters. It also introduces new user management features for profile and avatar handling, adds reading calendar functionality, and applies explicit JPA enum persistence annotations to entities.

Changes

Cohort / File(s) Summary
Authentication Refactoring - Controllers
BookshelvesController.java, ReadingLogsController.java, UserBooksController.java
Replaced direct userId header parameters with @AuthenticationPrincipal CustomUserDetails across all CRUD endpoints. Methods now extract userId via userDetails.getUserId(). Updated Swagger/OpenAPI annotations and response codes (added 401 authentication failure responses).
JPA Enum Mapping
Bookshelves.java, UserBooks.java
Added explicit @Enumerated(EnumType.STRING) annotations to enum fields (sortOrder, status, format) to ensure string-based database persistence instead of ordinal values.
User Profile Management - Controllers
MeProfileController.java, MeAvatarController.java
New controllers for profile retrieval/updates and avatar uploads. Both use @AuthenticationPrincipal CustomUserDetails authentication. MeAvatarController accepts multipart file uploads; MeProfileController provides GET/PATCH endpoints for profile management.
User Profile Management - Services
MeProfileService.java, MeAvatarService.java
New services for profile operations (get/update with visibility flags, partial updates) and avatar management (multipart file validation, S3 upload, profile image URL persistence).
Reading Calendar Feature
MeReadingCalendarController.java, ReadingCalendarService.java
New controller and service for retrieving calendar views by month. Service parses month strings, calculates date ranges, and queries repository for daily thumbnail data.
DTOs - User Domain
MeProfileResponse.java, MeProfileUpdateRequest.java, MeAvatarUpdateResponse.java, ReadingCalendarResponse.java, ReadingCalendarViewAllResponse.java
New and updated data transfer objects supporting profile/avatar responses and reading calendar data. MeProfileUpdateRequest enhanced from empty record to include nickname, isShelfPublic, isBooklogPublic fields.
Repository Enhancements
ReadingLogsRepository.java, UserBooksRepository.java
ReadingLogsRepository adds new findCalendarDayThumbnails method with CalendarDayThumbnailRow projection interface. UserBooksRepository changes listOrderByAuthorAsc parameter type from String to ReadingStatus enum.
Formatting & Minor Updates
AmazonS3Manager.java
Added blank lines with no behavioral changes.

Poem

🐰 With authentication woven through,
And profiles gleaming fresh and new,
Calendars that track the reading days,
In security's most trusted ways!
Our bookshelves bloom, the avatars bright,
Spring brings the features into light! ✨📚

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title comprehensively describes all major changes: MyPage CRUD operations, reading logs CRUD, token-based API access implementation, and enum normalization, aligning with the actual changeset.

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

✨ 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

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

Copy link
Copy Markdown

@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: 4

🤖 Fix all issues with AI agents
In
`@booklog/src/main/java/com/example/booklog/domain/library/shelves/controller/ReadingLogsController.java`:
- Line 14: The file ReadingLogsController.java contains an invalid/unused import
"import io.swagger.v3.oas.annotations.Parameter.*;" — replace it with the
correct annotation import "io.swagger.v3.oas.annotations.Parameter" if you
intend to use the `@Parameter` annotation, or simply remove the import entirely if
`@Parameter` is not used in ReadingLogsController; update the import statement
accordingly so it is syntactically correct and only present when referenced by
the controller code.

In
`@booklog/src/main/java/com/example/booklog/domain/users/controller/MeProfileController.java`:
- Around line 58-64: Add a specific exception handler method
handleIllegalArgumentException in the GlobalExceptionHandler to catch
IllegalArgumentException and return HTTP 400 with an ApiResponse using
GeneralErrorCode.VALIDATION_ERROR; implement it to log the error (e.g.,
log.error("IllegalArgumentException: {}", e.getMessage())) and return
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.onFailure(GeneralErrorCode.VALIDATION_ERROR,
e.getMessage())); this ensures validation errors thrown by services like
MeProfileService.updateProfile (e.g., NICKNAME_EMPTY, NICKNAME_TOO_LONG) map to
400 instead of hitting the generic Exception handler.

In
`@booklog/src/main/java/com/example/booklog/domain/users/service/MeAvatarService.java`:
- Around line 44-51: AmazonS3Manager.uploadFile currently swallows IOExceptions
and can return a URL for a non-existent object; change uploadFile to propagate
failures by throwing an unchecked IllegalStateException (include the caught
IOException as the cause and a descriptive message) instead of returning a
fake/silent URL, and then update MeAvatarService (the code around
amazonS3Manager.uploadFile(...) and user.updateProfile(...)) to either let that
exception bubble or catch it and translate it to an application-level error
before persisting the user's profileImageUrl so you never save a broken URL to
the DB.

In
`@booklog/src/main/java/com/example/booklog/domain/users/service/ReadingCalendarService.java`:
- Around line 42-47: The parseYearMonth(String month) method throws
IllegalArgumentException for invalid formats which is currently mapped to HTTP
500 by the generic handler; fix by either adding a specific handler in
GlobalExceptionHandler: add an `@ExceptionHandler`(IllegalArgumentException.class)
method that returns ResponseEntity with HttpStatus.BAD_REQUEST and an
appropriate message, or change parseYearMonth to throw new
ResponseStatusException(HttpStatus.BAD_REQUEST, "month 형식이 올바르지 않습니다. 예)
2026-01") instead of IllegalArgumentException; reference the parseYearMonth
method and GlobalExceptionHandler class to locate where to implement the change.
🧹 Nitpick comments (3)
booklog/src/main/java/com/example/booklog/domain/library/shelves/controller/BookshelvesController.java (1)

90-96: Consider adding @Valid annotation for request body validation.

The update method is missing the @Valid annotation on BookshelfUpdateRequest, while the create method (line 46) includes it. This inconsistency could allow invalid data to bypass validation constraints defined in BookshelfUpdateRequest.

🔧 Suggested fix
     public void update(
             `@AuthenticationPrincipal` CustomUserDetails userDetails,
             `@PathVariable`(name = "shelfId") Long shelfId,
-            `@RequestBody` BookshelfUpdateRequest req
+            `@RequestBody` `@Valid` BookshelfUpdateRequest req
     ) {
booklog/src/main/java/com/example/booklog/domain/users/service/MeProfileService.java (1)

25-26: Simplify Optional handling.

.orElseGet(() -> null) can be simplified to .orElse(null) since null is a constant and doesn't require lazy evaluation.

♻️ Suggested simplification
-        UserSettings settings = userSettingsRepository.findById(userId)
-                .orElseGet(() -> null);
+        UserSettings settings = userSettingsRepository.findById(userId)
+                .orElse(null);
booklog/src/main/java/com/example/booklog/domain/library/shelves/repository/ReadingLogsRepository.java (1)

27-61: Consider indexing to keep the calendar query fast at scale.
The correlated NOT EXISTS per day can get expensive as logs grow; a composite index on (user_book_id, read_date, created_at, id) (and/or a supporting index on user_book.user_id) will help the optimizer.

import io.swagger.v3.oas.annotations.media.*;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter.*;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Invalid or unused import statement.

io.swagger.v3.oas.annotations.Parameter is an annotation type, not a class with nested members. The wildcard import Parameter.* is syntactically incorrect for annotations and this import doesn't appear to be used anywhere in the file.

🔧 Suggested fix
-import io.swagger.v3.oas.annotations.Parameter.*;

If you intended to use @Parameter annotation for path variables, the correct import would be:

import io.swagger.v3.oas.annotations.Parameter;
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/library/shelves/controller/ReadingLogsController.java`
at line 14, The file ReadingLogsController.java contains an invalid/unused
import "import io.swagger.v3.oas.annotations.Parameter.*;" — replace it with the
correct annotation import "io.swagger.v3.oas.annotations.Parameter" if you
intend to use the `@Parameter` annotation, or simply remove the import entirely if
`@Parameter` is not used in ReadingLogsController; update the import statement
accordingly so it is syntactically correct and only present when referenced by
the controller code.

Comment on lines +58 to +64
@PatchMapping("/profile")
public MeProfileResponse updateProfile(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody MeProfileUpdateRequest req
) {
return meProfileService.updateProfile(userDetails.getUserId(), req);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for ControllerAdvice or ExceptionHandler handling IllegalArgumentException
rg -n -A5 'ControllerAdvice|ExceptionHandler.*IllegalArgumentException' --type=java

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 1345


🏁 Script executed:

cat -n booklog/src/main/java/com/example/booklog/global/common/GlobalExceptionHandler.java

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 3661


🏁 Script executed:

find . -name "MeProfileService.java" -type f | head -20

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 161


🏁 Script executed:

cat -n ./booklog/src/main/java/com/example/booklog/domain/users/service/MeProfileService.java

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 3961


Add specific exception handler for IllegalArgumentException to return HTTP 400 for validation errors.

The service throws IllegalArgumentException for validation errors (NICKNAME_EMPTY, NICKNAME_TOO_LONG) and data errors (USER_NOT_FOUND), but the global GlobalExceptionHandler lacks a specific handler for this exception. Currently, IllegalArgumentException is caught by the generic Exception handler and returns HTTP 500, when validation errors should return HTTP 400. Add an @ExceptionHandler(IllegalArgumentException.class) to map these errors appropriately:

`@ExceptionHandler`(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<String>> handleIllegalArgumentException(IllegalArgumentException e) {
    log.error("IllegalArgumentException: {}", e.getMessage());
    ApiResponse<String> response = ApiResponse.onFailure(
            GeneralErrorCode.VALIDATION_ERROR,
            e.getMessage()
    );
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/users/controller/MeProfileController.java`
around lines 58 - 64, Add a specific exception handler method
handleIllegalArgumentException in the GlobalExceptionHandler to catch
IllegalArgumentException and return HTTP 400 with an ApiResponse using
GeneralErrorCode.VALIDATION_ERROR; implement it to log the error (e.g.,
log.error("IllegalArgumentException: {}", e.getMessage())) and return
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.onFailure(GeneralErrorCode.VALIDATION_ERROR,
e.getMessage())); this ensures validation errors thrown by services like
MeProfileService.updateProfile (e.g., NICKNAME_EMPTY, NICKNAME_TOO_LONG) map to
400 instead of hitting the generic Exception handler.

Comment on lines +44 to +51
// 2) S3 key 생성 + 업로드
String key = amazonS3Manager.generateProfileKeyName(uuid);
String url = amazonS3Manager.uploadFile(key, file);

// 3) Users 갱신 (닉네임 유지, 이미지만 변경)
user.updateProfile(user.getNickname(), url);

return new MeAvatarUpdateResponse(user.getId(), user.getProfileImageUrl());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "MeAvatarService.java" -o -name "AmazonS3Manager.java"

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 232


🏁 Script executed:

git ls-files | grep -E "(MeAvatarService|AmazonS3Manager)" | head -20

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 228


🏁 Script executed:

cat -n booklog/src/main/java/com/example/booklog/domain/users/service/MeAvatarService.java

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 2876


🏁 Script executed:

cat -n booklog/src/main/java/com/example/booklog/aws/s3/AmazonS3Manager.java

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 2127


Fail fast on S3 upload errors before updating the user.

AmazonS3Manager.uploadFile() (line 30-40) catches IOException silently but still returns a URL to a non-existent object. This allows the service (line 46) to persist a broken profileImageUrl to the database without any error signal.

Required fix: Modify AmazonS3Manager.uploadFile() to throw IllegalStateException on IOException instead of swallowing it:

Required change in AmazonS3Manager
     } catch (IOException e){
-        log.error("error at AmazonS3Manager uploadFile : {}", (Object) e.getStackTrace());
+        log.error("error at AmazonS3Manager uploadFile", e);
+        throw new IllegalStateException("S3_UPLOAD_FAILED", e);
     }

Optionally, add defensive error handling in the service layer as well.

🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/users/service/MeAvatarService.java`
around lines 44 - 51, AmazonS3Manager.uploadFile currently swallows IOExceptions
and can return a URL for a non-existent object; change uploadFile to propagate
failures by throwing an unchecked IllegalStateException (include the caught
IOException as the cause and a descriptive message) instead of returning a
fake/silent URL, and then update MeAvatarService (the code around
amazonS3Manager.uploadFile(...) and user.updateProfile(...)) to either let that
exception bubble or catch it and translate it to an application-level error
before persisting the user's profileImageUrl so you never save a broken URL to
the DB.

Comment on lines +42 to +47
private YearMonth parseYearMonth(String month) {
try {
return YearMonth.parse(month); // "YYYY-MM"
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("month 형식이 올바르지 않습니다. 예) 2026-01");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for global exception handling / advice.
rg -n "@ControllerAdvice|@RestControllerAdvice|@ExceptionHandler|ResponseStatusException" -g "*.java"

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 587


🏁 Script executed:

#!/bin/bash
# Find where ReadingCalendarService is used
rg -n "ReadingCalendarService|parseYearMonth" -g "*.java" -A 2

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 2116


🏁 Script executed:

#!/bin/bash
# Look for exception configuration or mapping
rg -n "IllegalArgumentException|exception-mapping|error.handling" -g "*.java" -g "*.properties" -g "*.yml" -g "*.yaml"

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 10997


🏁 Script executed:

#!/bin/bash
# Read the GlobalExceptionHandler to see how it handles exceptions
cat -n booklog/src/main/java/com/example/booklog/global/common/GlobalExceptionHandler.java

Repository: Project-BookLog/BookLog-BackEnd

Length of output: 3661


Add specific handler for IllegalArgumentException to return HTTP 400.

The global exception handler catches all Exception types and returns HTTP 500 (line 62-71 of GlobalExceptionHandler.java). Since IllegalArgumentException is thrown for invalid month format, add a specific @ExceptionHandler(IllegalArgumentException.class) that returns HttpStatus.BAD_REQUEST, or use ResponseStatusException(HttpStatus.BAD_REQUEST, ...) directly in the parseYearMonth method.

🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/users/service/ReadingCalendarService.java`
around lines 42 - 47, The parseYearMonth(String month) method throws
IllegalArgumentException for invalid formats which is currently mapped to HTTP
500 by the generic handler; fix by either adding a specific handler in
GlobalExceptionHandler: add an `@ExceptionHandler`(IllegalArgumentException.class)
method that returns ResponseEntity with HttpStatus.BAD_REQUEST and an
appropriate message, or change parseYearMonth to throw new
ResponseStatusException(HttpStatus.BAD_REQUEST, "month 형식이 올바르지 않습니다. 예)
2026-01") instead of IllegalArgumentException; reference the parseYearMonth
method and GlobalExceptionHandler class to locate where to implement the change.

@icarus0616 icarus0616 merged commit 7836428 into dev Jan 28, 2026
1 check 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