Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ private NotificationListResponse.NotificationSummary toSummary(Notification noti
}

private String createScheduleAssignedContent(String scheduleTitle, String assigneeName) {
return assigneeName + "님이 [" + scheduleTitle + "] 담당자로 지정되었어요";
return assigneeName + "님이 [" + scheduleTitle + "] 담당자로 지명되었어요.";
}

private String createScheduleAssignedTitle(String projectTitle) {
Expand All @@ -681,15 +681,15 @@ private String createProjectJoinedTitle(String projectTitle) {
}

private String createProjectJoinedContent(String joinerName) {
return joinerName + "님이 프로젝트에 합류했어요";
return joinerName + "님이 프로젝트에 합류했어요.";
}

private String createScheduleCreatedTitle(String projectTitle) {
return joinTitle(projectTitle, "새 일정");
}

private String createScheduleCreatedContent(String scheduleTitle, String creatorName) {
return creatorName + "님이 [" + scheduleTitle + "] 일정을 등록했어요";
return creatorName + "님이 [" + scheduleTitle + "] 일정을 등록했어요.";
}

private String createVideoFeedbackCommentedTitle(String projectTitle) {
Expand All @@ -702,10 +702,10 @@ private String createVideoFeedbackCommentedContent(
int groupCount
) {
if (groupCount <= 1) {
return commenterName + "님이 [" + videoTitle + "]에 새로운 피드백을 남겼어요";
return commenterName + "님이 [" + videoTitle + "]에 새로운 피드백을 남겼어요.";
}

return "[" + videoTitle + "]에 새로운 피드백 " + groupCount + "건이 등록되었어요";
return "[" + videoTitle + "]에 새로운 피드백 " + groupCount + "건이 등록되었어요.";
}

private String createRecruitmentAppliedTitle(String projectTitle) {
Expand All @@ -718,26 +718,26 @@ private String createRecruitmentAppliedContent(
int groupCount
) {
if (groupCount <= 1) {
return applicantName + "님이 [" + recruitmentTitle + "]에 지원했어요";
return applicantName + "님이 [" + recruitmentTitle + "]에 지원했어요.";
}

return "[" + recruitmentTitle + "]에 새로운 지원자 " + groupCount + "명이 지원했어요";
return "[" + recruitmentTitle + "]에 새로운 지원자 " + groupCount + "명이 지원했어요.";
}

private String createNoticeCreatedTitle(String projectTitle) {
return joinTitle(projectTitle, "새 공지");
}

private String createNoticeCreatedContent(String noticeTitle, String creatorName) {
return creatorName + "님이 새 공지를 등록했어요: " + noticeTitle;
return creatorName + "님이 새 공지를 등록했어요: " + noticeTitle + ".";
}

private String createFileUploadedTitle(String projectTitle) {
return joinTitle(projectTitle, "새 파일");
}

private String createFileUploadedContent(String fileName, String uploaderName) {
return uploaderName + "님이 [" + fileName + "] 파일을 등록했어요";
return uploaderName + "님이 [" + fileName + "] 파일을 등록했어요.";
}

private String createFallbackTitle(NotificationType type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.slatto.domain.project.service;

import com.slatto.domain.notification.service.ActivityLogService;
import com.slatto.domain.notification.service.NotificationService;
import com.slatto.domain.project.dto.ProjectFileDownloadResponse;
import com.slatto.domain.project.dto.ProjectFileResponse;
import com.slatto.domain.project.dto.ProjectFileListResponse;
Expand All @@ -12,6 +13,7 @@
import com.slatto.domain.project.entity.ProjectMember;
import com.slatto.domain.project.exception.ProjectErrorCode;
import com.slatto.domain.project.repository.ProjectFileRepository;
import com.slatto.domain.project.repository.ProjectMemberRepository;
import com.slatto.domain.user.entity.Users;
import com.slatto.global.exception.BaseException;
import com.slatto.global.response.code.CommonErrorCode;
Expand Down Expand Up @@ -53,8 +55,10 @@ public class ProjectFileService {

private final ProjectFileRepository projectFileRepository;
private final ProjectAccessValidator projectAccessValidator;
private final ProjectMemberRepository projectMemberRepository;
private final StorageService storageService;
private final ActivityLogService activityLogService;
private final NotificationService notificationService;

public ProjectFileListResponse getProjectFiles(
Long projectId,
Expand Down Expand Up @@ -123,6 +127,14 @@ public ProjectFileResponse uploadProjectFile(

ProjectFile savedFile = projectFileRepository.save(projectFile);
activityLogService.createFileUploadedLog(projectId, currentUserId, savedFile.getId(), savedFile.getFileName());
notificationService.createFileUploadedNotifications(
projectId,
project.getTitle(),
savedFile.getFileName(),
currentMember.getUser().getNickname(),
getActiveProjectMemberUserIds(projectId),
currentUserId
);
Comment on lines +130 to +138

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 기대 결과: PROJECT_FILE 알림의 targetId가 파일 ID이며 모든 소비자가 같은 계약을 사용한다.
rg -n -C 6 \
  'createFileUploadedNotifications|NotificationTargetType\.PROJECT_FILE|targetType|targetId' \
  src/main/java src/test/java

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Candidate files =="
fd -p 'NotificationService\.java|NotificationTargetType\.java|ProjectFileService\.java|ProjectFile\.java|NotificationListProcessor|Notification.*Consumer|ActivityLogService\.java' src/main/java || true

echo
echo "== NotificationTargetType =="
rg -n -C 4 'enum NotificationTargetType|PROJECT_FILE|PROJECT_FILE' src/main/java src/test/java || true

echo
echo "== NotificationService file sections =="
file="$(fd 'NotificationService\.java' src/main/java | head -n1)"
if [ -n "${file}" ]; then
  wc -l "$file"
  rg -n -C 8 'createFileUploadedNotifications|createScheduleAssigned|createProjectJoined|createScheduleCreated|createVideoFeedbackCommented|targetType|targetId' "$file"
fi

echo
echo "== Processor/UserConsumer references =="
rg -n -C 5 'PROJECT_FILE|fileUploaded|SCHEDULE_CREATED|PROJECT|targetType|targetId|Notification.*EventListener|StreamListener|KafkaListener|Consumer' src/main/java/src/test java 2>/dev/null | head -n 200 || true
rg -n -C 5 'PROJECT_FILE|fileUploaded|SCHEDULE_CREATED|PROJECT|targetType|targetId|Notification.*EventListener|StreamListener|KafkaListener|Consumer' src/main/java src/test/java | head -n 240 || true

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 50058


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ProjectFileService upload section =="
file="$(fd 'ProjectFileService\.java' src/main/java | head -n1)"
if [ -n "${file}" ]; then
  sed -n '1,150p' "$file" | cat -n
fi

echo
echo "== ActivityLogService file upload section =="
file="$(fd 'ActivityLogService\.java' src/main/java | head -n1)"
if [ -n "${file}" ]; then
  rg -n -C 10 'createFileUploadedLog|createFileUploaded' "$file" || true
fi

echo
echo "== Read-only scan: PROJECT_FILE usages =="
rg -n -C 3 'NotificationTargetType\.PROJECT_FILE|PROJECT_FILE|PROJECT_FILE|PROJECT_FILE|targetType.*PROJECT_FILE|targetType.*"PROJECT_FILE"|type.*FILE_UPLOADED|FILE_UPLOADED' src/main/java src/test/java || true

echo
echo "== Read-only scan: Notification click/route helpers =="
rg -n -C 4 'targetType|targetId|get.*Route|navigate|router|FileUploaded|fileUploaded|FILE_UPLOADED|PROJECT_FILE' src/main/java src/test/java || true

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 50376


PROJECT_FILE 알림의 대상 ID를 파일 ID로 맞추세요.

createFileUploadedNotifications()targetTypePROJECT_FILE로 설정한 뒤 targetIdprojectId를 저장합니다. NotificationTargetType.PROJECT_FILE을 사용할 목적이라면 savedFile.getId()를 전달하고 NotificationServicetargetId도 해당 파일 id로 고정하세요. 프로젝트 ID가 실제 대상이면 PROJECT_FILE 대신 targetTypePROJECT로 변경하세요.

수정 예시
 notificationService.createFileUploadedNotifications(
     projectId,
+    savedFile.getId(),
     project.getTitle(),
 public void createFileUploadedNotifications(
     Long projectId,
+    Long fileId,
     String projectTitle,
...
-    .targetId(projectId)
+    .targetId(fileId)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/project/service/ProjectFileService.java`
around lines 130 - 137, Update the file-upload notification flow around
createFileUploadedNotifications to use savedFile.getId() as the PROJECT_FILE
targetId, and ensure NotificationService preserves that file ID when setting the
target. Do not use projectId as the target for PROJECT_FILE; if the intended
target remains the project, change the target type to PROJECT instead.


return toResponse(savedFile);
}
Expand Down Expand Up @@ -321,6 +333,14 @@ private int normalizePageSize(int size) {
return Math.min(size, MAX_PAGE_SIZE);
}

private List<Long> getActiveProjectMemberUserIds(Long projectId) {
return projectMemberRepository.findAllActiveMembersByProjectId(projectId)
.stream()
.map(ProjectMember::getUser)
.map(Users::getId)
.toList();
}

private ProjectFileResponse toResponse(ProjectFile projectFile) {
Users uploader = projectFile.getUploader();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.slatto.domain.project.repository.ProjectMemberRepository;
import com.slatto.domain.project.repository.ProjectUserRoleRepository;
import com.slatto.domain.notification.service.ActivityLogService;
import com.slatto.domain.notification.service.NotificationService;
import com.slatto.domain.user.entity.Users;
import com.slatto.domain.user.enums.RoleName;
import com.slatto.domain.user.repository.UserRepository;
Expand Down Expand Up @@ -50,6 +51,7 @@ public class ProjectInvitationService {
private final ProjectAccessValidator projectAccessValidator;
private final ProjectInvitationProperties projectInvitationProperties;
private final ActivityLogService activityLogService;
private final NotificationService notificationService;
private final SecureRandom secureRandom = new SecureRandom();

@Transactional
Expand Down Expand Up @@ -117,6 +119,12 @@ public ProjectInvitationAcceptResponse acceptInvitation(
saveProjectRoles(projectMember, roleNames);
projectInvitation.accept(accepter);
activityLogService.createProjectMemberJoinedLog(project.getId(), accepter.getId());
notificationService.createProjectJoinedNotifications(

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.

excludeUserId를 추가하지 않아 다른 서비스들과 다르게 알림 대상에 행위자를 포함한 것은 의도하신 것일까요!?

project.getId(),
project.getTitle(),
accepter.getNickname(),
getActiveProjectMemberUserIds(project.getId())
);

return ProjectInvitationAcceptResponse.builder()
.projectId(project.getId())
Expand All @@ -134,6 +142,14 @@ private void saveProjectRoles(ProjectMember projectMember, List<RoleName> roleNa
projectUserRoleRepository.saveAll(projectUserRoles);
}

private List<Long> getActiveProjectMemberUserIds(Long projectId) {
return projectMemberRepository.findAllActiveMembersByProjectId(projectId)
.stream()
.map(ProjectMember::getUser)
.map(Users::getId)
.toList();
}

private void validateAcceptableInvitation(ProjectInvitation projectInvitation) {
if (projectInvitation.isAccepted()) {
throw new BaseException(ProjectErrorCode.PROJECT_INVITATION_ALREADY_ACCEPTED);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.slatto.domain.project.service;

import com.slatto.domain.notification.service.ActivityLogService;
import com.slatto.domain.notification.service.NotificationService;
import com.slatto.domain.project.dto.ProjectNoticeCreateRequest;
import com.slatto.domain.project.dto.ProjectNoticeListResponse;
import com.slatto.domain.project.dto.ProjectNoticeReadResponse;
Expand All @@ -11,6 +12,7 @@
import com.slatto.domain.project.entity.ProjectNotice;
import com.slatto.domain.project.entity.ProjectNoticeRead;
import com.slatto.domain.project.exception.ProjectErrorCode;
import com.slatto.domain.project.repository.ProjectMemberRepository;
import com.slatto.domain.project.repository.ProjectNoticeReadRepository;
import com.slatto.domain.project.repository.ProjectNoticeRepository;
import com.slatto.domain.user.entity.Users;
Expand All @@ -35,8 +37,10 @@ public class ProjectNoticeService {

private final ProjectNoticeRepository projectNoticeRepository;
private final ProjectNoticeReadRepository projectNoticeReadRepository;
private final ProjectMemberRepository projectMemberRepository;
private final ProjectAccessValidator projectAccessValidator;
private final ActivityLogService activityLogService;
private final NotificationService notificationService;

public ProjectNoticeListResponse getProjectNotices(
Long projectId,
Expand Down Expand Up @@ -95,6 +99,15 @@ public ProjectNoticeResponse createProjectNotice(
);
ProjectNotice savedNotice = projectNoticeRepository.save(projectNotice);
activityLogService.createNoticeCreatedLog(projectId, currentUserId, savedNotice.getId());
notificationService.createNoticeCreatedNotifications(
projectId,
savedNotice.getId(),
project.getTitle(),
savedNotice.getTitle(),
currentMember.getUser().getNickname(),
getActiveProjectMemberUserIds(projectId),
currentUserId
);

return toResponse(savedNotice, false);
}
Expand Down Expand Up @@ -202,6 +215,14 @@ private Map<Long, Boolean> getReadByNoticeId(Long userId, List<ProjectNotice> pr
));
}

private List<Long> getActiveProjectMemberUserIds(Long projectId) {
return projectMemberRepository.findAllActiveMembersByProjectId(projectId)
.stream()
.map(ProjectMember::getUser)
.map(Users::getId)
.toList();
}

private int normalizePageSize(int size) {
if (size <= 0) {
return DEFAULT_PAGE_SIZE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ public ScheduleResponse createSchedule(Long currentUserId, ScheduleCreateRequest
writer.getId()
);
if (savedSchedule.isProjectSchedule()) {
notificationService.createScheduleCreatedNotifications(
project.getId(),
savedSchedule.getId(),
project.getTitle(),
savedSchedule.getTitle(),
writer.getNickname(),
getActiveProjectMemberUserIds(project.getId()),
writer.getId()
);
activityLogService.createScheduleCreatedLog(
project.getId(),
currentUserId,
Expand Down Expand Up @@ -377,6 +386,14 @@ private List<Users> getProjectParticipantUsers(Long projectId, List<Long> partic
.toList();
}

private List<Long> getActiveProjectMemberUserIds(Long projectId) {
return projectMemberRepository.findAllActiveMembersByProjectId(projectId)
.stream()
.map(ProjectMember::getUser)
.map(Users::getId)
.toList();
}

private Users getActiveUser(Long userId) {
return userRepository.findByIdAndDeletedAtIsNull(userId)
.orElseThrow(() -> new BaseException(CommonErrorCode.NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;

import java.time.LocalDate;
Expand Down Expand Up @@ -56,6 +57,9 @@ class ProjectNoticeActivityFlowIntegrationTest {
@Autowired
private ProjectActivityReadRepository projectActivityReadRepository;

@MockBean
private NotificationService notificationService;

@Autowired
private UserRepository userRepository;

Expand Down
Loading