Repository navigation
feat: 코스 일정 푸시 알림 기능 구현 #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,9 @@ dependencies { | |
| // AWS S3 | ||
| implementation platform('software.amazon.awssdk:bom:2.41.24') | ||
| implementation 'software.amazon.awssdk:s3' | ||
|
|
||
| // Firebase Cloud Messaging | ||
| implementation 'com.google.firebase:firebase-admin:9.10.0' | ||
|
Comment on lines
+60
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
file="src/main/java/com/daytodo/domain/user/push/FirebasePushNotificationGateway.java"
if rg -n -C 4 'setToken\(|addToken\(|addAllTokens\(' "$file"; then
echo "Deprecated token-builder usage found. Verify the gateway implementation."
else
echo "No deprecated token-builder usage found."
fiRepository: DayTodo/DayTodo_BE Length of output: 644 🌐 Web query:
💡 Result: The Firebase Admin Java SDK version 9.10.0, released on July 2, 2026, introduces a deprecation of the token-based messaging approach in favor of Firebase Installation IDs (FIDs) [1][2][3]. Key changes in version 9.10.0 include: Deprecation of Token Fields and Methods: - The token field in the Message class is deprecated [1]. - The tokens field in the MulticastMessage class is deprecated [1]. - The setToken method in Message.Builder is deprecated [1]. - The addToken and addAllTokens methods in MulticastMessage.Builder are deprecated [1]. Introduction of FID Support: - The SDK now supports sending messages using Firebase Installation IDs (FIDs) [1]. - New fields have been added: the fid field in the Message class and the fids field in the MulticastMessage class [1]. - New methods have been introduced: setFid in Message.Builder, and addFid and addAllFids in MulticastMessage.Builder [1]. While the MulticastMessage class continues to support both tokens and FIDs, developers are advised to migrate to the new FID-based fields, as the token-based fields are now officially deprecated [1]. [1][2] Citations:
Replace the
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| tasks.named('test') { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package com.daytodo.domain.user.entity; | ||
|
|
||
| import com.daytodo.domain.common.BaseEntity; | ||
| import com.daytodo.domain.user.enums.DevicePlatform; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.EnumType; | ||
| import jakarta.persistence.Enumerated; | ||
| import jakarta.persistence.FetchType; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.JoinColumn; | ||
| import jakarta.persistence.ManyToOne; | ||
| import jakarta.persistence.Table; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @Entity | ||
| @Table(name = "fcm_token") | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class FcmToken extends BaseEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "fcm_token_id") | ||
| private Long id; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | ||
| @JoinColumn(name = "user_id", nullable = false) | ||
| private User user; | ||
|
|
||
| @Column(name = "token", nullable = false, unique = true, length = 512) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Make token registration atomic across users. Line 35 enforces global token uniqueness. Use a database upsert, or handle the constraint conflict by reloading and reassigning the token in a new transaction. 🤖 Prompt for AI Agents |
||
| private String token; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(name = "platform", nullable = false, length = 20) | ||
| private DevicePlatform platform; | ||
|
|
||
| public FcmToken(User user, String token, DevicePlatform platform) { | ||
| this.user = user; | ||
| this.token = token; | ||
| this.platform = platform; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| package com.daytodo.domain.user.entity; | ||
| import com.daytodo.domain.user.enums.NotificationType; | ||
| import com.daytodo.domain.user.enums.NotificationDeliveryStatus; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.EntityListeners; | ||
|
|
@@ -12,6 +13,7 @@ | |
| import jakarta.persistence.JoinColumn; | ||
| import jakarta.persistence.ManyToOne; | ||
| import jakarta.persistence.Table; | ||
| import jakarta.persistence.UniqueConstraint; | ||
| import java.time.LocalDateTime; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
|
|
@@ -21,7 +23,13 @@ | |
|
|
||
| @Getter | ||
| @Entity | ||
| @Table(name = "notification") | ||
| @Table( | ||
| name = "notification", | ||
| uniqueConstraints = @UniqueConstraint( | ||
| name = "uk_notification_user_course_type", | ||
| columnNames = {"user_id", "course_id", "notification_type"} | ||
| ) | ||
| ) | ||
|
Comment on lines
+26
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate schema-management configuration and migrations.
fd -t f -i '(flyway|liquibase|migration)|^V[0-9].*__.*\.(sql|xml|yaml|yml)$' .
rg -n -i -C 3 \
'notification|delivery_status|attempt_count|uk_notification_user_course_type|flyway|liquibase|ddl-auto' \
srcRepository: DayTodo/DayTodo_BE Length of output: 50374 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'Relevant files:\n'
fd -t f 'Notification.java|application.yml|pom.xml|build.gradle|settings.gradle|flyway|liquibase|migration.sql|M[0-9]|db|schema' .
printf '\nNotification entity:\n'
cat -n src/main/java/com/daytodo/domain/user/entity/Notification.java
printf '\nApplication schema config:\n'
cat -n src/main/resources/application.yml | sed -n '1,80p'
printf '\nBuild schema-management deps:\n'
if [ -f pom.xml ]; then
rg -n -C 2 '(flyway|liquibase|maven-fmp|spring-boot-maven-plugin)' pom.xml
fi
printf '\nFind migration scripts only:\n'
fd -t f 'migration|\.sql$|flyway|liquibase' . | sed -n '1,120p'
printf '\nSearch db-related tables/corresponding scripts:\n'
rg -n -i -C 2 'create table.*notification|alter table.*notification|notification_table|delivery_status|attempt_count|uk_notification_user_course_type|unique constraint|constraint' . \
-g '*.sql' -g '*db*' -g 'src/main/resources/**' | sed -n '1,220p'Repository: DayTodo/DayTodo_BE Length of output: 7173 Add a migration for the The live profile uses 🤖 Prompt for AI Agents |
||
| @EntityListeners(AuditingEntityListener.class) | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class Notification { | ||
|
|
@@ -50,6 +58,22 @@ public class Notification { | |
| @Column(name = "is_read", nullable = false) | ||
| private boolean read; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(name = "delivery_status", length = 20, nullable = false) | ||
| private NotificationDeliveryStatus deliveryStatus; | ||
|
|
||
| @Column(name = "attempt_count", nullable = false) | ||
| private int attemptCount; | ||
|
|
||
| @Column(name = "sent_at") | ||
| private LocalDateTime sentAt; | ||
|
|
||
| @Column(name = "next_retry_at") | ||
| private LocalDateTime nextRetryAt; | ||
|
|
||
| @Column(name = "last_error", length = 1000) | ||
| private String lastError; | ||
|
|
||
| @CreatedDate | ||
| @Column(name = "created_at", nullable = false, updatable = false) | ||
| private LocalDateTime createdAt; | ||
|
|
@@ -67,9 +91,26 @@ public Notification( | |
| this.title = title; | ||
| this.body = body; | ||
| this.read = false; | ||
| this.deliveryStatus = NotificationDeliveryStatus.PENDING; | ||
| this.attemptCount = 0; | ||
| } | ||
|
|
||
| public void markAsRead() { | ||
| this.read = true; | ||
| } | ||
|
|
||
| public void markSent(LocalDateTime sentAt) { | ||
| this.deliveryStatus = NotificationDeliveryStatus.SENT; | ||
| this.attemptCount++; | ||
| this.sentAt = sentAt; | ||
| this.nextRetryAt = null; | ||
| this.lastError = null; | ||
| } | ||
|
|
||
| public void markFailed(String error, LocalDateTime nextRetryAt) { | ||
| this.deliveryStatus = NotificationDeliveryStatus.FAILED; | ||
| this.attemptCount++; | ||
| this.nextRetryAt = nextRetryAt; | ||
| this.lastError = error == null ? null : error.substring(0, Math.min(error.length(), 1000)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.daytodo.domain.user.enums; | ||
|
|
||
| public enum DevicePlatform { | ||
| ANDROID, | ||
| IOS | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.daytodo.domain.user.enums; | ||
|
|
||
| public enum NotificationDeliveryStatus { | ||
| PENDING, | ||
| SENT, | ||
| FAILED | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Quote the cron expression.
Line 13 contains spaces. If deployment or local tooling loads this file as a shell-compatible dotenv file, the unquoted assignment is parsed as multiple words. Quote the value to preserve the six-field cron expression.
Proposed fix
The supplied dotenv-linter warning also identifies this value.
📝 Committable suggestion
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 13-13: [UnorderedKey] The COURSE_REMINDER_CRON key should go before the DB_PW key
(UnorderedKey)
[warning] 13-13: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
🤖 Prompt for AI Agents
Source: Linters/SAST tools