Skip to content

Commit e7beeab

Browse files
authored
[#204] 업데이트 된 FCM 토큰이 Firestore에 남아있는 이슈를 해결한다 (#305)
* feat: NotificationCenter을 통해 FCM 토큰이 변경된 것을 전파하고, 전파된 것을 받는 것을 resolve 하여 업데이트 하드록 변경 * refactor: 시간대를 저장하는 기능 역시 동일한 형태로 재구성 * chore: qa-* 기반 태그 찾는 로직 제거 * fix: 앱이 켜진 이후 시간대가 변경되면 반영되지 않는 현상 해결
1 parent fae35af commit e7beeab

File tree

10 files changed

+154
-50
lines changed

10 files changed

+154
-50
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,7 @@ permissions:
1616
checks: write
1717

1818
jobs:
19-
detect_qa_tag:
20-
runs-on: macos-latest
21-
outputs:
22-
has_qa_tag: ${{ steps.detect.outputs.has_qa_tag }}
23-
steps:
24-
- uses: actions/checkout@v5
25-
with:
26-
fetch-depth: 0
27-
fetch-tags: true
28-
29-
- name: Detect QA Tag on PR Head
30-
id: detect
31-
shell: bash
32-
run: |
33-
set -euo pipefail
34-
PR_HEAD_SHA="${{ github.event.pull_request.head.sha }}"
35-
MATCHED_TAG="$(git tag --points-at "$PR_HEAD_SHA" | grep -E '^qa(-local)?-' | head -n 1 || true)"
36-
37-
if [ -n "$MATCHED_TAG" ]; then
38-
echo "Found QA tag on PR head: $MATCHED_TAG"
39-
echo "has_qa_tag=true" >> "$GITHUB_OUTPUT"
40-
exit 0
41-
fi
42-
43-
echo "No QA tag found on PR head"
44-
echo "has_qa_tag=false" >> "$GITHUB_OUTPUT"
45-
4619
build:
47-
needs: detect_qa_tag
48-
if: needs.detect_qa_tag.outputs.has_qa_tag != 'true'
4920
runs-on: macos-latest
5021
timeout-minutes: 30
5122
steps:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// AppLayerAssembler.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/19/26.
6+
//
7+
8+
final class AppLayerAssembler: Assembler {
9+
func assemble(_ container: any DIContainer) {
10+
container.register(FCMTokenSyncHandler.self) {
11+
FCMTokenSyncHandler(
12+
repository: container.resolve(UserDataRepository.self)
13+
)
14+
}
15+
container.register(UserTimeZoneSyncHandler.self) {
16+
UserTimeZoneSyncHandler(
17+
repository: container.resolve(UserDataRepository.self)
18+
)
19+
}
20+
}
21+
}

DevLog/App/Assembler/Assembler.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ final class AppAssembler: Assembler {
1414
PersistenceAssembler(),
1515
InfraAssembler(),
1616
DataAssembler(),
17-
DomainAssembler()
17+
DomainAssembler(),
18+
AppLayerAssembler()
1819
]
1920

2021
func assemble(_ container: any DIContainer) {

DevLog/App/Delegate/AppDelegate.swift

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
import UIKit
99
import Firebase
10-
import FirebaseAuth
1110
import GoogleSignIn
1211

1312
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
1413
private let logger = Logger(category: "AppDelegate")
14+
private let container = AppDIContainer.shared
1515

1616
func application(
1717
_ app: UIApplication,
@@ -26,7 +26,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
2626
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
2727
) -> Bool {
2828
FirebaseApp.configure()
29-
29+
_ = container.resolve(FCMTokenSyncHandler.self)
30+
_ = container.resolve(UserTimeZoneSyncHandler.self)
31+
3032
// 알림 권한 요청
3133
UNUserNotificationCenter.current().delegate = self
3234
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
@@ -41,7 +43,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
4143
}
4244

4345
// 앱이 온그라운드로 되었을 때, 로그인 세션이 존재한다면 현재 유저의 timeZone 저장
44-
updateUserTimeZone()
46+
NotificationCenter.default.post(name: .didRequestUserTimeZoneSync, object: nil)
4547

4648
// Firebase Messaging 설정
4749
Messaging.messaging().delegate = self
@@ -78,21 +80,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
7880
) {
7981
if let fcmToken = fcmToken {
8082
logger.info("FCM token: \(fcmToken)")
81-
}
82-
}
83-
}
84-
85-
private extension AppDelegate {
86-
func updateUserTimeZone() {
87-
Task {
88-
do {
89-
guard let uid = Auth.auth().currentUser?.uid else { return }
90-
let settingsRef = Firestore.firestore().document("users/\(uid)/userData/settings")
91-
92-
try await settingsRef.setData(["timeZone": TimeZone.autoupdatingCurrent.identifier], merge: true)
93-
} catch {
94-
logger.error("Failed to update timeZone", error: error)
95-
}
83+
NotificationCenter.default.post(
84+
name: .didRefreshFCMToken,
85+
object: nil,
86+
userInfo: ["fcmToken": fcmToken]
87+
)
9688
}
9789
}
9890
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// FCMTokenSyncHandler.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/19/26.
6+
//
7+
8+
import Combine
9+
import Foundation
10+
11+
final class FCMTokenSyncHandler {
12+
private let repository: UserDataRepository
13+
private let logger = Logger(category: "FCMTokenSyncHandler")
14+
private var cancellables = Set<AnyCancellable>()
15+
16+
init(
17+
repository: UserDataRepository,
18+
notificationCenter: NotificationCenter = .default
19+
) {
20+
self.repository = repository
21+
22+
notificationCenter.publisher(for: .didRefreshFCMToken)
23+
.compactMap { $0.userInfo?["fcmToken"] as? String }
24+
.sink { [weak self] fcmToken in
25+
Task {
26+
do {
27+
try await self?.repository.updateFCMToken(fcmToken)
28+
} catch {
29+
self?.logger.error("Failed to sync refreshed FCM token", error: error)
30+
}
31+
}
32+
}
33+
.store(in: &cancellables)
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// UserTimeZoneSyncHandler.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/19/26.
6+
//
7+
8+
import Combine
9+
import UIKit
10+
11+
final class UserTimeZoneSyncHandler {
12+
private let repository: UserDataRepository
13+
private let logger = Logger(category: "UserTimeZoneSyncHandler")
14+
private var cancellables = Set<AnyCancellable>()
15+
16+
init(
17+
repository: UserDataRepository,
18+
notificationCenter: NotificationCenter = .default
19+
) {
20+
self.repository = repository
21+
22+
notificationCenter.publisher(for: .didRequestUserTimeZoneSync)
23+
.merge(with: notificationCenter.publisher(for: UIApplication.willEnterForegroundNotification))
24+
.sink { [weak self] _ in
25+
Task {
26+
do {
27+
try await self?.repository.updateUserTimeZone()
28+
} catch {
29+
self?.logger.error("Failed to sync user timeZone", error: error)
30+
}
31+
}
32+
}
33+
.store(in: &cancellables)
34+
}
35+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// NotificationName+.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/19/26.
6+
//
7+
8+
import Foundation
9+
10+
extension Notification.Name {
11+
static let didRefreshFCMToken = Notification.Name("didRefreshFCMToken")
12+
static let didRequestUserTimeZoneSync = Notification.Name("didRequestUserTimeZoneSync")
13+
}

DevLog/Data/Repository/UserDataRepositoryImpl.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ final class UserDataRepositoryImpl: UserDataRepository {
1919
}
2020

2121
func upsertStatusMessage(_ message: String) async throws {
22-
try await self.userService.upsertStatusMessage(message)
22+
try await userService.upsertStatusMessage(message)
23+
}
24+
25+
func updateFCMToken(_ fcmToken: String) async throws {
26+
try await userService.updateFCMToken(fcmToken)
27+
}
28+
29+
func updateUserTimeZone() async throws {
30+
try await userService.updateUserTimeZone()
2331
}
2432
}

DevLog/Domain/Protocol/UserDataRepository.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
protocol UserDataRepository {
99
func fetch() async throws -> UserProfile
1010
func upsertStatusMessage(_ message: String) async throws
11+
func updateFCMToken(_ fcmToken: String) async throws
12+
func updateUserTimeZone() async throws
1113
}

DevLog/Infra/Service/UserService.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ final class UserService {
145145
}
146146
}
147147

148-
func updateFCMToken(_ userId: String, fcmToken: String) async throws {
148+
func updateFCMToken(_ fcmToken: String) async throws {
149+
guard let userId = Auth.auth().currentUser?.uid else {
150+
logger.info("Skipping FCM token update because no authenticated user exists")
151+
return
152+
}
153+
149154
logger.info("Updating FCM token for user: \(userId)")
150155

151156
do {
@@ -157,4 +162,25 @@ final class UserService {
157162
throw error
158163
}
159164
}
165+
166+
func updateUserTimeZone() async throws {
167+
guard let userId = Auth.auth().currentUser?.uid else {
168+
logger.info("Skipping timeZone update because no authenticated user exists")
169+
return
170+
}
171+
172+
logger.info("Updating timeZone for user: \(userId)")
173+
174+
do {
175+
let settingsRef = store.document("users/\(userId)/userData/settings")
176+
try await settingsRef.setData(
177+
["timeZone": TimeZone.autoupdatingCurrent.identifier],
178+
merge: true
179+
)
180+
logger.info("Successfully updated timeZone")
181+
} catch {
182+
logger.error("Failed to update timeZone", error: error)
183+
throw error
184+
}
185+
}
160186
}

0 commit comments

Comments
 (0)