Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 0 additions & 29 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,7 @@ permissions:
checks: write

jobs:
detect_qa_tag:
runs-on: macos-latest
outputs:
has_qa_tag: ${{ steps.detect.outputs.has_qa_tag }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
fetch-tags: true

- name: Detect QA Tag on PR Head
id: detect
shell: bash
run: |
set -euo pipefail
PR_HEAD_SHA="${{ github.event.pull_request.head.sha }}"
MATCHED_TAG="$(git tag --points-at "$PR_HEAD_SHA" | grep -E '^qa(-local)?-' | head -n 1 || true)"

if [ -n "$MATCHED_TAG" ]; then
echo "Found QA tag on PR head: $MATCHED_TAG"
echo "has_qa_tag=true" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "No QA tag found on PR head"
echo "has_qa_tag=false" >> "$GITHUB_OUTPUT"

build:
needs: detect_qa_tag
if: needs.detect_qa_tag.outputs.has_qa_tag != 'true'
runs-on: macos-latest
timeout-minutes: 30
steps:
Expand Down
21 changes: 21 additions & 0 deletions DevLog/App/Assembler/AppLayerAssembler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// AppLayerAssembler.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

final class AppLayerAssembler: Assembler {
func assemble(_ container: any DIContainer) {
container.register(FCMTokenSyncHandler.self) {
FCMTokenSyncHandler(
repository: container.resolve(UserDataRepository.self)
)
}
container.register(UserTimeZoneSyncHandler.self) {
UserTimeZoneSyncHandler(
repository: container.resolve(UserDataRepository.self)
)
}
}
}
3 changes: 2 additions & 1 deletion DevLog/App/Assembler/Assembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ final class AppAssembler: Assembler {
PersistenceAssembler(),
InfraAssembler(),
DataAssembler(),
DomainAssembler()
DomainAssembler(),
AppLayerAssembler()
]

func assemble(_ container: any DIContainer) {
Expand Down
28 changes: 10 additions & 18 deletions DevLog/App/Delegate/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import UIKit
import Firebase
import FirebaseAuth
import GoogleSignIn

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

func application(
_ app: UIApplication,
Expand All @@ -26,7 +26,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
FirebaseApp.configure()

_ = container.resolve(FCMTokenSyncHandler.self)
_ = container.resolve(UserTimeZoneSyncHandler.self)

// 알림 권한 요청
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
Expand All @@ -41,7 +43,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
}

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

// Firebase Messaging 설정
Messaging.messaging().delegate = self
Expand Down Expand Up @@ -78,21 +80,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
) {
if let fcmToken = fcmToken {
logger.info("FCM token: \(fcmToken)")
}
}
}

private extension AppDelegate {
func updateUserTimeZone() {
Task {
do {
guard let uid = Auth.auth().currentUser?.uid else { return }
let settingsRef = Firestore.firestore().document("users/\(uid)/userData/settings")

try await settingsRef.setData(["timeZone": TimeZone.autoupdatingCurrent.identifier], merge: true)
} catch {
logger.error("Failed to update timeZone", error: error)
}
NotificationCenter.default.post(
name: .didRefreshFCMToken,
object: nil,
userInfo: ["fcmToken": fcmToken]
)
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions DevLog/App/Handler/FCMTokenSyncHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// FCMTokenSyncHandler.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

import Combine
import Foundation

final class FCMTokenSyncHandler {
private let repository: UserDataRepository
private let logger = Logger(category: "FCMTokenSyncHandler")
private var cancellables = Set<AnyCancellable>()

init(
repository: UserDataRepository,
notificationCenter: NotificationCenter = .default
) {
self.repository = repository

notificationCenter.publisher(for: .didRefreshFCMToken)
.compactMap { $0.userInfo?["fcmToken"] as? String }
.sink { [weak self] fcmToken in
Task {
do {
try await self?.repository.updateFCMToken(fcmToken)
} catch {
self?.logger.error("Failed to sync refreshed FCM token", error: error)
}
}
}
.store(in: &cancellables)
}
}
35 changes: 35 additions & 0 deletions DevLog/App/Handler/UserTimeZoneSyncHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// UserTimeZoneSyncHandler.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

import Combine
import UIKit

final class UserTimeZoneSyncHandler {
private let repository: UserDataRepository
private let logger = Logger(category: "UserTimeZoneSyncHandler")
private var cancellables = Set<AnyCancellable>()

init(
repository: UserDataRepository,
notificationCenter: NotificationCenter = .default
) {
self.repository = repository

notificationCenter.publisher(for: .didRequestUserTimeZoneSync)
.merge(with: notificationCenter.publisher(for: UIApplication.willEnterForegroundNotification))
.sink { [weak self] _ in
Task {
do {
try await self?.repository.updateUserTimeZone()
} catch {
self?.logger.error("Failed to sync user timeZone", error: error)
}
}
}
.store(in: &cancellables)
}
}
13 changes: 13 additions & 0 deletions DevLog/App/Notification/NotificationName+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// NotificationName+.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

import Foundation

extension Notification.Name {
static let didRefreshFCMToken = Notification.Name("didRefreshFCMToken")
static let didRequestUserTimeZoneSync = Notification.Name("didRequestUserTimeZoneSync")
}
10 changes: 9 additions & 1 deletion DevLog/Data/Repository/UserDataRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ final class UserDataRepositoryImpl: UserDataRepository {
}

func upsertStatusMessage(_ message: String) async throws {
try await self.userService.upsertStatusMessage(message)
try await userService.upsertStatusMessage(message)
}

func updateFCMToken(_ fcmToken: String) async throws {
try await userService.updateFCMToken(fcmToken)
}

func updateUserTimeZone() async throws {
try await userService.updateUserTimeZone()
}
}
2 changes: 2 additions & 0 deletions DevLog/Domain/Protocol/UserDataRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
protocol UserDataRepository {
func fetch() async throws -> UserProfile
func upsertStatusMessage(_ message: String) async throws
func updateFCMToken(_ fcmToken: String) async throws
func updateUserTimeZone() async throws
}
28 changes: 27 additions & 1 deletion DevLog/Infra/Service/UserService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,12 @@ final class UserService {
}
}

func updateFCMToken(_ userId: String, fcmToken: String) async throws {
func updateFCMToken(_ fcmToken: String) async throws {
guard let userId = Auth.auth().currentUser?.uid else {
logger.info("Skipping FCM token update because no authenticated user exists")
return
}

logger.info("Updating FCM token for user: \(userId)")

do {
Expand All @@ -157,4 +162,25 @@ final class UserService {
throw error
}
}

func updateUserTimeZone() async throws {
guard let userId = Auth.auth().currentUser?.uid else {
logger.info("Skipping timeZone update because no authenticated user exists")
return
}

logger.info("Updating timeZone for user: \(userId)")

do {
let settingsRef = store.document("users/\(userId)/userData/settings")
try await settingsRef.setData(
["timeZone": TimeZone.autoupdatingCurrent.identifier],
merge: true
)
logger.info("Successfully updated timeZone")
} catch {
logger.error("Failed to update timeZone", error: error)
throw error
}
}
}