Skip to content

Feat group info#30

Open
jee-woo wants to merge 3 commits intodevelopfrom
feat-group-info
Open

Feat group info#30
jee-woo wants to merge 3 commits intodevelopfrom
feat-group-info

Conversation

@jee-woo
Copy link
Copy Markdown
Owner

@jee-woo jee-woo commented Mar 10, 2026

No description provided.

@jee-woo jee-woo requested a review from yujamint March 10, 2026 14:20
@jee-woo jee-woo self-assigned this Mar 10, 2026

if (!diary.getAuthor().getId().equals(user.getId())) {
throw new org.springframework.security.access.AccessDeniedException("본인이 작성한 일기만 삭제할 수 있습니다.");
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

앞에 패키지명을 제거하고 import하면 좋을 거 같아요!

log.info("Group created with id: {}", group.getId());
return new GroupResponseDto(group.getId(), group.getName(), code, MemberStatus.ACCEPTED);
return new GroupResponseDto(group.getId(), group.getName(), code, MemberStatus.ACCEPTED, 1, null);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

GroupResponseDto 내에 신규 그룹에 의한 DTO를 반환하는 정적 팩토리 메서드를 만들어도 좋을 것 같아요!
ex. GroupResponseDto.fromNewGroup()

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 introduces significant enhancements to diary and group management. It provides users with the ability to delete their own diary entries, enriches the information displayed for individual diaries and groups, and refines the sorting of group lists for a more intuitive user experience. The changes also include underlying repository and service layer updates to support these new features and improve data handling.

Highlights

  • Diary Deletion: Implemented diary deletion functionality, including a new API endpoint and service logic to remove diary entries, associated keys, and S3 images.
  • Enhanced Diary Details: Enhanced the DiaryDetailResponseDto to include an isMine flag, indicating if the current user is the author of the diary.
  • Richer Group Information: Extended GroupResponseDto with memberCount and lastDiaryAt fields to provide richer group information.
  • Improved Group Listing and Sorting: Improved group listing by adding sorting logic based on member status and the last diary entry date.
  • S3 Object Deletion: Added S3 object deletion capability to the S3Uploader utility.
  • Ordered Notifications: Updated notification retrieval to order results by creation date in descending order.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/main/java/com/diary/shared_diary/controller/DiaryController.java
    • Added a new DELETE endpoint for deleting diaries.
  • src/main/java/com/diary/shared_diary/dto/diary/DiaryDetailResponseDto.java
    • Added isMine field and updated the constructor to accept and set this new field.
  • src/main/java/com/diary/shared_diary/dto/group/GroupResponseDto.java
    • Added memberCount and lastDiaryAt fields to the record.
  • src/main/java/com/diary/shared_diary/repository/DiaryKeyRepository.java
    • Added a deleteByDiary method to remove diary keys associated with a specific diary.
  • src/main/java/com/diary/shared_diary/repository/DiaryRepository.java
    • Added findTop1ByGroupOrderByCreatedAtDesc to retrieve the most recent diary for a group.
  • src/main/java/com/diary/shared_diary/repository/NotificationRepository.java
    • Modified findByReceiverId to findByReceiverIdOrderByCreatedAtDesc for ordered notification retrieval.
  • src/main/java/com/diary/shared_diary/service/DiaryService.java
    • Implemented the deleteDiary method and updated getDiaryDetail to calculate and pass the isMine flag.
  • src/main/java/com/diary/shared_diary/service/GroupService.java
    • Updated getGroupsByUserEmail to populate memberCount and lastDiaryAt and apply new sorting logic.
    • createGroup was also updated to use the new GroupResponseDto constructor.
  • src/main/java/com/diary/shared_diary/util/S3Uploader.java
    • Added a delete method to remove objects from S3.
Activity
  • No specific activity (comments, reviews, progress) has been recorded for this pull request yet.
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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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 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. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

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
Copy Markdown
Contributor

@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 introduces functionality to delete diaries and enriches group information with member count and last diary timestamp. A security audit identified two medium-severity broken access control issues in the diary deletion logic, specifically the lack of a group membership check and the failure to validate the group context provided in the URL. Additionally, the review noted an unused path variable, a significant N+1 performance issue when fetching group information, and a suggestion to simplify complex sorting logic. The changes otherwise look good, including the addition of S3 deletion and ordering of notifications.

Comment on lines +48 to 59
List<GroupResponseDto> dtos = groupMembers.stream()
.map(groupMember -> {
Group group = groupMember.getGroup();
return new GroupResponseDto(group.getId(), group.getName(), group.getCode(), groupMember.getStatus());
int memberCount = (int) group.getGroupMembers().stream()
.filter(gm -> gm.getStatus() == MemberStatus.ACCEPTED)
.count();
LocalDateTime lastDiaryAt = diaryRepository.findTop1ByGroupOrderByCreatedAtDesc(group)
.map(Diary::getCreatedAt)
.orElse(null);
return new GroupResponseDto(group.getId(), group.getName(), group.getCode(), groupMember.getStatus(), memberCount, lastDiaryAt);
})
.toList();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

This block of code introduces a significant performance issue known as the N+1 query problem. For each group the user is a member of, two additional database queries are executed inside the map operation:

  1. group.getGroupMembers().stream()...count(): This triggers a lazy-loading query to fetch all members of a group just to count them.
  2. diaryRepository.findTop1ByGroupOrderByCreatedAtDesc(group): This executes another query to find the latest diary for the group.

If a user is in N groups, this will result in 2*N extra queries, which can severely degrade performance.

To optimize this, you should fetch the required information in batches. Here's a suggested approach:

  1. Collect all groupIds from the initial groupMembers list.
  2. Execute a single query to get the count of accepted members for all those groups at once. You can use a GROUP BY clause.
  3. Execute another single query to get the latest diary's creation date for all those groups, again using GROUP BY.
  4. Store these results in Maps (e.g., Map<Long, Integer> for member counts, Map<Long, LocalDateTime> for last diary dates).
  5. Use these maps within your map stream to construct the GroupResponseDto without hitting the database in a loop.

Comment on lines +38 to +43
@PathVariable Long groupId,
@PathVariable Long diaryId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
log.info("[DIARY-DELETE] Request user: {}, diary: {}", userDetails.getUsername(), diaryId);
diaryService.deleteDiary(diaryId, userDetails.getUsername());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The deleteDiary endpoint is mapped under a path that includes {groupId}, but the implementation ignores this parameter and does not validate that the diary being deleted actually belongs to the specified group. This creates a broken access control vulnerability, allowing a user to delete their diary by providing an arbitrary or incorrect group ID in the URL, bypassing intended hierarchical access control. For data integrity and security, it's crucial to pass the groupId to the service method and add the corresponding validation logic there.

        diaryService.deleteDiary(groupId, diaryId, userDetails.getUsername());

Comment on lines +96 to +98
if (!diary.getAuthor().getId().equals(user.getId())) {
throw new org.springframework.security.access.AccessDeniedException("본인이 작성한 일기만 삭제할 수 있습니다.");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The deleteDiary method only verifies that the requesting user is the author of the diary. It fails to check if the user is still an active member of the group to which the diary belongs. This is inconsistent with the getDiaryDetail method, which correctly validates group membership. As a result, a user who has been removed from a group can still delete their previous diaries within that group, even though they can no longer view them.

Suggested change
if (!diary.getAuthor().getId().equals(user.getId())) {
throw new org.springframework.security.access.AccessDeniedException("본인이 작성한 일기만 삭제할 수 있습니다.");
}
Diary diary = diaryRepository.getOrThrow(diaryId);
User user = userRepository.getByEmailOrThrow(email);
groupMemberService.validateAcceptedMember(user, diary.getGroup());
if (!diary.getAuthor().getId().equals(user.getId())) {
throw new org.springframework.security.access.AccessDeniedException("본인이 작성한 일기만 삭제할 수 있습니다.");
}

Comment on lines +64 to +70
.thenComparing(dto -> {
if (dto.status() == MemberStatus.ACCEPTED) {
LocalDateTime lastDiaryAt = dto.lastDiaryAt();
return lastDiaryAt != null ? lastDiaryAt : LocalDateTime.MIN;
}
return joinedAtByGroupId.getOrDefault(dto.id(), LocalDateTime.MIN);
}, Comparator.reverseOrder()))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The lambda function used for the secondary sorting criterion is quite complex, with conditional logic that makes it hard to read. To improve readability and maintainability, this can be simplified.
Instead of manually handling nulls by returning LocalDateTime.MIN, you can use Comparator.nullsLast(Comparator.reverseOrder()) as the key comparator.
The key extractor can then be simplified to: dto -> dto.status() == MemberStatus.ACCEPTED ? dto.lastDiaryAt() : joinedAtByGroupId.get(dto.id()).
This makes the intent of sorting null lastDiaryAt values last more explicit and the code more concise.

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.

2 participants