Skip to content

fix: 공지 읽음 흐름 보완 및 시각·스키마 정합성 정리 - #145

Merged
chazy-d merged 4 commits into
developfrom
fix/notice-read-and-schema-names
Aug 7, 2026
Merged

fix: 공지 읽음 흐름 보완 및 시각·스키마 정합성 정리#145
chazy-d merged 4 commits into
developfrom
fix/notice-read-and-schema-names

Conversation

@chazy-d

@chazy-d chazy-d commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🔗 관련 이슈 (Related Issue)

📝 작업 내용

프론트 연동 과정에서 보고된 세 가지 문제를 정리했습니다.

Swagger 문서가 공지 작성자 응답에 실제로는 내려가지 않는 필드를 광고하고 있었고, 공지를 등록한 본인에게도 자기 공지가 안 읽음으로 표시됐으며, 최근활동 응답의 읽음 필드명이 홈 알림과 달라 혼동을 유발했습니다.
추가로 위 작업 중 일부 네이티브 쿼리가 DB 시계로 시각을 기록해 애플리케이션 시계와 어긋나는 문제를 함께 발견해 정리했습니다.


1. Swagger WriterSummary 스키마명 충돌

springdoc은 OpenAPI 스키마 이름을 단순 클래스명으로 만듭니다. 아래 세 개의 중첩 클래스가 모두 WriterSummary라서 하나의 스키마로 합쳐졌습니다.

클래스 실제 필드
ProjectNoticeResponse.WriterSummary id, nickname
RecruitmentSummary.WriterSummary id, nickname, profileImageUrl
RecruitmentDetailResponse.WriterSummary id, nickname, profileImageUrl, primaryRole, locations

병합 결과 공지 API 문서에도 profileImageUrl, primaryRole, locations가 노출됐지만 실제 응답에는 없는 필드였습니다. 각 클래스에 @Schema(name = ...)으로 고유 이름을 부여해 세 스키마를 분리했습니다.

문서에서 사라진 필드일 뿐 응답 형태 변경은 없습니다.


2. 공지 상세 조회 추가

GET /api/v1/projects/{projectId}/notices/{noticeId}

응답 형태는 목록 조회의 items[] 한 건과 동일합니다.

공지사항 목록 화면 자체는 목록 조회 응답만으로 구성할 수 있습니다. 이 엔드포인트는 최근활동과 알림이 내려주는 targetType, targetId로 특정 공지에 딥링크할 때 필요합니다. 목록이 커서 페이지네이션(기본 20, 최대 50)이라 오래된 공지는 목록 조회만으로 도달할 수 없기 때문입니다.

읽음 처리(PATCH .../read)와 합치지 않고 분리해 두었습니다. 합치면 상세를 열거나 새로고침할 때마다 쓰기가 발생하고 캐시도 걸 수 없습니다.


3. 공지 작성자 자동 읽음 처리

공지 등록 시 작성자 본인의 읽음 레코드를 함께 생성하고, 등록 응답의 isReadtrue로 내려줍니다. 자기가 쓴 공지에 안 읽음 표시가 남던 문제가 해소됩니다.

방금 생성한 공지라 중복 행이 발생할 수 없어 upsert가 아닌 일반 저장을 사용합니다.


4. 네이티브 쿼리 시각을 애플리케이션 클럭으로 통일

프로젝트 대부분의 시각은 JPA Auditing, 즉 애플리케이션 시계로 기록됩니다. 그런데 일부 네이티브 upsert 쿼리만 NOW(6) / CURRENT_TIMESTAMP를 써서 MySQL 세션 타임존을 따랐고, 두 값이 9시간 어긋난 상태로 저장됐습니다.

특히 project_notice_read.read_at은 이번 PR에서 추가한 작성자 자동 읽음(엔티티 저장, 애플리케이션 시계)과 기존 읽음 처리 API(네이티브 upsert, DB 시계)가 같은 컬럼에 서로 다른 시계로 쓰게 되는 구조였습니다.

해당 쿼리 6개에서 DB 시계 사용을 제거하고 호출부가 LocalDateTime을 넘기도록 변경했습니다. 대상 테이블은 project_notice_read, project_pin, project_activity_read, notification_setting, recruitment_bookmark, video_bookmark입니다.

ProjectActivityReadCommandRepository는 H2와 MySQL용 SQL 상수를 따로 유지하는 구조라, 양쪽 모두 위치 파라미터 번호를 다시 매겼습니다.

DB 파라미터 그룹의 time_zone은 변경하지 않았습니다. 기존에 저장된 값의 해석이 전부 바뀌기 때문입니다.

이번 변경 이후 저장되는 값부터 정합성이 맞습니다. 이미 저장된 행의 보정은 이 PR 범위에 포함하지 않았습니다.


5. 최근활동 읽음 필드명 통일

홈 알림은 이미 isRead를 쓰고 있는데 최근활동만 isNew를 써서 프론트에서 혼동이 있었습니다. isRead로 통일했습니다.

isNew: true  →  isRead: false
isNew: false →  isRead: true

이름만 바뀐 것이 아니라 값의 의미가 반대입니다. 프론트 배포와 순서를 맞춰야 합니다.

Swagger summary 문구도 홈 알림과 동일하게 맞췄습니다. 엔드포인트 경로 변경은 없습니다.

이전 변경
프로젝트 최근활동 개별 확인 프로젝트 최근활동 단건 읽음 처리
프로젝트 최근활동 전체 확인 프로젝트 최근활동 전체 읽음 처리

✅ PR 체크리스트

  • PR 제목은 커밋 컨벤션을 따랐습니다.
  • 기존 저장 데이터 보정은 이번 PR 범위에서 제외했습니다.
  • 변경 사항에 대한 테스트를 진행했습니다.
  • ./gradlew compileJava, ./gradlew test를 통과했습니다.

Summary by CodeRabbit

  • 새 기능

    • 프로젝트 공지 상세 조회 API가 추가되었습니다.
    • 공지 작성자는 생성 즉시 읽음 상태로 표시됩니다.
    • 공지 상세 조회 시 현재 사용자의 읽음 여부가 표시됩니다.
  • 개선 사항

    • 최근 활동 상태가 isNew에서 isRead로 변경되어 의미가 명확해졌습니다.
    • 공지 및 활동 읽음 처리, 프로젝트·채용·영상 북마크의 시간이 일관되게 기록됩니다.
    • API 문서의 상태 설명과 응답 스키마가 보다 명확해졌습니다.
  • 테스트

    • 사용자별 읽음 상태와 활동 범위가 올바르게 분리되는지 검증이 강화되었습니다.

@chazy-d chazy-d self-assigned this Aug 7, 2026
@chazy-d chazy-d added feature 새로운 기능 추가 fix 버그 수정 labels Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

최근 활동 응답 필드를 isRead로 변경했습니다. 프로젝트 공지 상세 조회와 읽음 기록 처리를 추가했습니다. 여러 저장소가 데이터베이스 시각 대신 애플리케이션에서 전달한 LocalDateTime을 사용합니다. Swagger 중첩 스키마 이름도 명시했습니다.

Changes

알림 읽음 상태 및 공지 흐름

Layer / File(s) Summary
최근 활동 읽음 상태 계약
src/main/java/com/slatto/domain/notification/controller/RecentActivityController.java, src/main/java/com/slatto/domain/notification/dto/ActivityLogListResponse.java, src/main/java/com/slatto/domain/notification/service/RecentActivityService.java, src/test/java/com/slatto/domain/notification/*
최근 활동 응답 필드를 isNew에서 isRead로 변경했습니다. 단건·전체 읽음 처리와 멤버별 상태 격리를 통합 테스트에서 검증합니다.
애플리케이션 시각 기반 저장
src/main/java/com/slatto/domain/notification/repository/*, src/main/java/com/slatto/domain/notification/service/NotificationSettingService.java, src/main/java/com/slatto/domain/project/repository/ProjectPinRepository.java, src/main/java/com/slatto/domain/project/service/ProjectService.java, src/main/java/com/slatto/domain/recruitment/*, src/main/java/com/slatto/domain/video/repository/VideoBookmarkRepository.java
알림 설정, 활동 읽음 기록, 프로젝트 핀, 채용 북마크, 영상 북마크의 생성·갱신 시각에 애플리케이션의 LocalDateTime 값을 사용합니다.
프로젝트 공지 상세 조회 및 읽음 처리
src/main/java/com/slatto/domain/project/controller/ProjectNoticeController.java, src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java, src/main/java/com/slatto/domain/project/repository/ProjectNoticeReadRepository.java, src/main/java/com/slatto/domain/project/dto/ProjectNoticeResponse.java, src/main/java/com/slatto/domain/recruitment/dto/*
GET /api/v1/projects/{projectId}/notices/{noticeId} 엔드포인트를 추가했습니다. 공지 생성자는 읽음 상태로 저장하고, 상세 조회 사용자의 읽음 여부를 응답에 반영합니다. 중첩 Swagger 스키마 이름을 지정했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ProjectNoticeController
  participant ProjectNoticeService
  participant ProjectNoticeReadRepository

  Client->>ProjectNoticeController: GET project notice detail
  ProjectNoticeController->>ProjectNoticeService: getProjectNotice(userId, projectId, noticeId)
  ProjectNoticeService->>ProjectNoticeReadRepository: find user read status
  ProjectNoticeReadRepository-->>ProjectNoticeService: read status
  ProjectNoticeService-->>ProjectNoticeController: ProjectNoticeResponse
  ProjectNoticeController-->>Client: ApiResponse<ProjectNoticeResponse>
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: sangwon02

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 공지 읽음 처리와 시각·스키마 정합성 개선이라는 주요 변경 사항을 간결하게 설명합니다.
Description check ✅ Passed 관련 이슈, 작업 내용, 변경 범위, 테스트 결과와 체크리스트를 포함해 설명이 대부분 완전합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/com/slatto/domain/notification/service/ProjectNoticeActivityFlowIntegrationTest.java (1)

100-104: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

공지 작성자의 자동 읽음 상태를 테스트에 반영하세요.

Line 100의 다른 멤버 isRead=false 검증은 올바릅니다. 그러나 공지 작성자의 ProjectActivityRead 레코드가 생성되므로 Line 104의 isEmpty()는 테스트를 실패시킵니다. 작성자와 활동 ID를 검증하고, 작성자의 응답이 isRead=true인지도 추가로 확인하세요.

수정 예시
-        assertThat(projectActivityReadRepository.findAll()).isEmpty();
+        assertThat(projectActivityReadRepository.findAll())
+            .singleElement()
+            .satisfies(read -> {
+                assertThat(read.getProjectMember().getUser().getId()).isEqualTo(chaTaehoon.getId());
+                assertThat(read.getActivityLog().getId()).isEqualTo(activityLog.getId());
+            });
+
+        ActivityLogListResponse authorResponse = recentActivityService.getRecentActivities(
+            project.getId(), chaTaehoon.getId(), null, 20
+        );
+        assertThat(authorResponse.items()).singleElement()
+            .extracting(ActivityLogListResponse.ActivityLogItem::isRead)
+            .isEqualTo(true);
🤖 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/test/java/com/slatto/domain/notification/service/ProjectNoticeActivityFlowIntegrationTest.java`
around lines 100 - 104, Update the assertions in
ProjectNoticeActivityFlowIntegrationTest so the notice author’s automatically
created ProjectActivityRead record is expected instead of asserting
projectActivityReadRepository.findAll() is empty. Verify the record belongs to
the author and the activity ID, and assert that the author’s read response is
true while preserving the existing unread-member assertions.
🤖 Prompt for all review comments with 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.

Outside diff comments:
In
`@src/test/java/com/slatto/domain/notification/service/ProjectNoticeActivityFlowIntegrationTest.java`:
- Around line 100-104: Update the assertions in
ProjectNoticeActivityFlowIntegrationTest so the notice author’s automatically
created ProjectActivityRead record is expected instead of asserting
projectActivityReadRepository.findAll() is empty. Verify the record belongs to
the author and the activity ID, and assert that the author’s read response is
true while preserving the existing unread-member assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a3416405-863c-4179-8dea-95506f0df6e1

📥 Commits

Reviewing files that changed from the base of the PR and between 466680e and 9f78320.

📒 Files selected for processing (20)
  • src/main/java/com/slatto/domain/notification/controller/RecentActivityController.java
  • src/main/java/com/slatto/domain/notification/dto/ActivityLogListResponse.java
  • src/main/java/com/slatto/domain/notification/repository/NotificationSettingRepository.java
  • src/main/java/com/slatto/domain/notification/repository/ProjectActivityReadCommandRepository.java
  • src/main/java/com/slatto/domain/notification/service/NotificationSettingService.java
  • src/main/java/com/slatto/domain/notification/service/RecentActivityService.java
  • src/main/java/com/slatto/domain/project/controller/ProjectNoticeController.java
  • src/main/java/com/slatto/domain/project/dto/ProjectNoticeResponse.java
  • src/main/java/com/slatto/domain/project/repository/ProjectNoticeReadRepository.java
  • src/main/java/com/slatto/domain/project/repository/ProjectPinRepository.java
  • src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java
  • src/main/java/com/slatto/domain/project/service/ProjectService.java
  • src/main/java/com/slatto/domain/recruitment/dto/RecruitmentDetailResponse.java
  • src/main/java/com/slatto/domain/recruitment/dto/RecruitmentSummary.java
  • src/main/java/com/slatto/domain/recruitment/repository/RecruitmentBookmarkRepository.java
  • src/main/java/com/slatto/domain/recruitment/service/RecruitmentBookmarkService.java
  • src/main/java/com/slatto/domain/video/repository/VideoBookmarkRepository.java
  • src/test/java/com/slatto/domain/notification/controller/RecentActivityControllerIntegrationTest.java
  • src/test/java/com/slatto/domain/notification/service/ProjectNoticeActivityFlowIntegrationTest.java
  • src/test/java/com/slatto/domain/notification/service/RecentActivityServiceIntegrationTest.java

@chazy-d
chazy-d merged commit 2b19696 into develop Aug 7, 2026
2 checks passed
@guingguing
guingguing deleted the fix/notice-read-and-schema-names branch August 11, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 새로운 기능 추가 fix 버그 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant