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
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ This reference holds DevLog-specific working rules that should live with the pro

- If the user says they will commit or asks only for a commit message, provide commit-message guidance instead of committing.
- Before proposing a commit message, inspect the actual diff and recent `git log`.
- When recent history contains GitHub merge commits, do not infer commit-message style from merge subjects such as `[#123] ... (#456)`. Open the merge commit with `git show --no-patch --format=full <merge-commit>` and use the individual commit messages in the body, or inspect nearby non-merge commits.
- Match the repository's current Korean style and prefix pattern.
- If the user explicitly specifies a prefix or noun-phrase ending, follow it exactly.
- For broad architecture refactors, split commits by layer when the user asks for staged commits.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
throw FirestoreError.dataNotFound("notification")
}

guard let currentValue = document.data()["isRead"] as? Bool else {
logger.error("isRead not found for notification: \(document.documentID)")
throw FirestoreError.dataNotFound("isRead")
}

try await document.reference.updateData(["isRead": !currentValue])
try await toggleReadValue(for: document.reference)
logger.info("Successfully toggled notification read")
} catch {
logger.error("Failed to toggle notification read", error: error)
Expand All @@ -302,6 +297,24 @@ private extension PushNotificationServiceImpl {
Self.record(error, code: code)
}

func toggleReadValue(for notificationRef: DocumentReference) async throws {
_ = try await store.runTransaction { transaction, errorPointer in
do {
let snapshot = try transaction.getDocument(notificationRef)
guard let currentValue = snapshot.data()?["isRead"] as? Bool else {
throw FirestoreError.dataNotFound("isRead")
}

transaction.updateData(["isRead": !currentValue], forDocument: notificationRef)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}
Comment thread
opficdev marked this conversation as resolved.

return nil
}
}

func makeQuery(
uid: String,
query: PushNotificationQuery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,8 @@ final class AppleAuthenticationServiceImpl: AuthenticationService {

let tokensRef = store.document(FirestorePath.userData(uid, document: .tokens))

logger.info("Starting Apple token document fetch for unlink. uid: \(uid)")
let doc = try await tokensRef.getDocument()

if doc.exists {
logger.info("Starting Apple refresh token deletion from Firestore for unlink. uid: \(uid)")
try await tokensRef.updateData([
"appleRefreshToken": FieldValue.delete()
])
}
logger.info("Starting Apple refresh token deletion from Firestore for unlink. uid: \(uid)")
try await deleteAppleRefreshToken(from: tokensRef)

logger.info("Starting Firebase Apple provider unlink. uid: \(uid)")
_ = try await user?.unlink(fromProvider: providerID.rawValue)
Expand Down Expand Up @@ -337,6 +330,28 @@ final class AppleAuthenticationServiceImpl: AuthenticationService {
}

private extension AppleAuthenticationServiceImpl {
func deleteAppleRefreshToken(from tokensRef: DocumentReference) async throws {
_ = try await store.runTransaction { transaction, errorPointer in
let snapshot: DocumentSnapshot

do {
snapshot = try transaction.getDocument(tokensRef)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}

if snapshot.exists {
transaction.updateData(
["appleRefreshToken": FieldValue.delete()],
forDocument: tokensRef
)
}

return nil
}
}

private static func record(_ error: Error, code: CrashlyticsError.Code) {
FirebaseCrashlyticsHelper.record(
error,
Expand Down
87 changes: 39 additions & 48 deletions Application/DevLogInfra/Sources/Service/TodoServiceImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -348,66 +348,57 @@ private extension TodoServiceImpl {
for todoRef: DocumentReference,
counterRef: DocumentReference
) async throws {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
store.runTransaction({ transaction, errorPointer in
let todoSnapshot: DocumentSnapshot
_ = try await store.runTransaction { transaction, errorPointer in
let todoSnapshot: DocumentSnapshot

do {
todoSnapshot = try transaction.getDocument(todoRef)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}

var todoData = data

if !todoSnapshot.exists {
let counterSnapshot: DocumentSnapshot

do {
todoSnapshot = try transaction.getDocument(todoRef)
counterSnapshot = try transaction.getDocument(counterRef)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}

var todoData = data

if !todoSnapshot.exists {
let counterSnapshot: DocumentSnapshot

do {
counterSnapshot = try transaction.getDocument(counterRef)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}

let nextNumberValue = counterSnapshot.data()?[CounterFieldKey.nextNumber.rawValue]
let nextNumber: Int

if let storedNextNumber = nextNumberValue as? Int {
nextNumber = storedNextNumber
} else if counterSnapshot.exists {
errorPointer?.pointee = NSError(
domain: "TodoServiceImpl",
code: 1,
userInfo: [NSLocalizedDescriptionKey: "Todo counter is invalid."]
)
return nil
} else {
nextNumber = 1
}
let nextNumberValue = counterSnapshot.data()?[CounterFieldKey.nextNumber.rawValue]
let nextNumber: Int

todoData[TodoFieldKey.number.rawValue] = nextNumber
transaction.setData(
[
CounterFieldKey.nextNumber.rawValue: nextNumber + 1,
CounterFieldKey.updatedAt.rawValue: FieldValue.serverTimestamp()
],
forDocument: counterRef,
merge: true
if let storedNextNumber = nextNumberValue as? Int {
nextNumber = storedNextNumber
} else if counterSnapshot.exists {
errorPointer?.pointee = NSError(
domain: "TodoServiceImpl",
code: 1,
userInfo: [NSLocalizedDescriptionKey: "Todo counter is invalid."]
)
return nil
} else {
nextNumber = 1
}

transaction.setData(todoData, forDocument: todoRef, merge: true)
return nil
}) { _, error in
if let error {
continuation.resume(throwing: error)
return
}

continuation.resume(returning: ())
todoData[TodoFieldKey.number.rawValue] = nextNumber
transaction.setData(
[
CounterFieldKey.nextNumber.rawValue: nextNumber + 1,
CounterFieldKey.updatedAt.rawValue: FieldValue.serverTimestamp()
],
forDocument: counterRef,
merge: true
)
}

transaction.setData(todoData, forDocument: todoRef, merge: true)
return nil
}
}

Expand Down
128 changes: 73 additions & 55 deletions Application/DevLogInfra/Sources/Service/UserServiceImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ final class UserServiceImpl: UserService {
}

do {
let userRef = store.document(FirestorePath.user(user.uid))
let infoRef = store.document(FirestorePath.userData(user.uid, document: .info))
let tokensRef = store.document(FirestorePath.userData(user.uid, document: .tokens))
let settingsRef = store.document(FirestorePath.userData(user.uid, document: .settings))
let todoCounterRef = store.document(FirestorePath.counter(user.uid, document: .todo))

// 사용자 기본 정보
var userField: [String: Any] = [
"currentProvider": response.providerID
Expand All @@ -62,64 +56,22 @@ final class UserServiceImpl: UserService {
userField["appleName"] = user.displayName
}

let userDocument = try await userRef.getDocument()
if !userDocument.exists {
userField["statusMsg"] = ""
userField["createdAt"] = FieldValue.serverTimestamp()
}

var settingField: [String: Any] = [:]
var tokenField: [String: Any] = [:]

if let fcmToken = response.fcmToken {
settingField["fcmToken"] = fcmToken
tokenField["fcmToken"] = fcmToken
}

// 깃헙 로그인 시 추가 정보 저장
if response.providerID == "github.com", let accessToken = response.accessToken {
settingField["githubAccessToken"] = accessToken
tokenField["githubAccessToken"] = accessToken
}

// Reference to capture ~ in concurrently-executing code; Swift 6 lang mode의 경고 해결
let userFieldSnapshot = userField
let settingFieldSnapshot = settingField
// -----------------------------------------------------

async let userUpdate: Void = userRef.setData(
["updatedAt": FieldValue.serverTimestamp()],
merge: true
try await upsertUserDocuments(
uid: user.uid,
userField: userField,
tokenField: tokenField
)
async let infoUpdate: Void = infoRef.setData(userFieldSnapshot, merge: true)
async let tokensUpdate: Void = {
guard !settingFieldSnapshot.isEmpty else { return }
try await tokensRef.setData(settingFieldSnapshot, merge: true)
}()

let settingsDocument = try await settingsRef.getDocument()
var settingsField: [String: Any] = [
"timeZone": TimeZone.autoupdatingCurrent.identifier
]
if !settingsDocument.exists {
settingsField["allowPushNotification"] = true
settingsField["pushNotificationHour"] = 9
settingsField["pushNotificationMinute"] = 0
}

let settingsFieldSnapshot = settingsField
async let settingsUpdate: Void = settingsRef.setData(settingsFieldSnapshot, merge: true)
async let todoCounterUpdate: Void? = { // 옵셔널이 포함된 이유: 신규 사용자일 때만 할 작업
guard !userDocument.exists else { return nil }

try await todoCounterRef.setData(
[
"nextNumber": 1,
"updatedAt": FieldValue.serverTimestamp()
],
merge: true
)
return nil
}()

_ = try await (userUpdate, infoUpdate, tokensUpdate, settingsUpdate, todoCounterUpdate)

logger.info("Successfully upserted user: \(user.uid)")
} catch {
Expand Down Expand Up @@ -241,4 +193,70 @@ private extension UserServiceImpl {
private func record(_ error: Error, code: CrashlyticsError.Code) {
Self.record(error, code: code)
}

func upsertUserDocuments(
uid: String,
userField: [String: Any],
tokenField: [String: Any]
) async throws {
let userRef = store.document(FirestorePath.user(uid))
let infoRef = store.document(FirestorePath.userData(uid, document: .info))
let tokensRef = store.document(FirestorePath.userData(uid, document: .tokens))
let settingsRef = store.document(FirestorePath.userData(uid, document: .settings))
let todoCounterRef = store.document(FirestorePath.counter(uid, document: .todo))

_ = try await store.runTransaction { transaction, errorPointer in
let userDocument: DocumentSnapshot
let settingsDocument: DocumentSnapshot

do {
userDocument = try transaction.getDocument(userRef)
settingsDocument = try transaction.getDocument(settingsRef)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}

var infoField = userField
if !userDocument.exists {
infoField["statusMsg"] = ""
infoField["createdAt"] = FieldValue.serverTimestamp()
}

var settingsField: [String: Any] = [
"timeZone": TimeZone.autoupdatingCurrent.identifier
]
if !settingsDocument.exists {
settingsField["allowPushNotification"] = true
settingsField["pushNotificationHour"] = 9
settingsField["pushNotificationMinute"] = 0
}

transaction.setData(
["updatedAt": FieldValue.serverTimestamp()],
forDocument: userRef,
merge: true
)
transaction.setData(infoField, forDocument: infoRef, merge: true)

if !tokenField.isEmpty {
transaction.setData(tokenField, forDocument: tokensRef, merge: true)
}

transaction.setData(settingsField, forDocument: settingsRef, merge: true)

if !userDocument.exists {
transaction.setData(
[
"nextNumber": 1,
"updatedAt": FieldValue.serverTimestamp()
],
forDocument: todoCounterRef,
merge: true
)
}

return nil
}
}
}
Loading