-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUserService.swift
More file actions
199 lines (166 loc) · 7.51 KB
/
UserService.swift
File metadata and controls
199 lines (166 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//
// UserService.swift
// DevLog
//
// Created by opfic on 6/4/25.
//
import FirebaseAuth
import FirebaseFirestore
final class UserService {
private let store = Firestore.firestore()
private let logger = Logger(category: "UserService")
// 유저를 Firestore에 저장 및 업데이트
func upsertUser(_ response: AuthDataResponse) async throws {
logger.info("Upserting user with provider: \(response.providerID)")
guard let user = Auth.auth().currentUser else {
logger.error("User not authenticated")
throw AuthError.notAuthenticated
}
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
]
// 공급자 이슈로 인한 nil 방지
if let email = user.email {
userField["email"] = email
}
if let displayName = user.displayName, displayName != "" {
userField["name"] = displayName
}
// Apple은 최초 새 이름 설정 시에만 이름을 제공
if response.providerID == "apple.com" &&
user.displayName != nil && user.displayName != "" {
userField["appleName"] = user.displayName
}
let userDocument = try await userRef.getDocument()
if !userDocument.exists {
userField["statusMsg"] = ""
}
var settingField = ["fcmToken": response.fcmToken]
// 깃헙 로그인 시 추가 정보 저장
if response.providerID == "github.com", let accessToken = response.accessToken {
settingField["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
)
async let infoUpdate: Void = infoRef.setData(userFieldSnapshot, merge: true)
async let tokensUpdate: Void = 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 {
logger.error("Failed to upsert user", error: error)
throw error
}
}
func fetchUserProfile() async throws -> UserProfileResponse {
logger.info("Fetching user profile")
guard let uid = Auth.auth().currentUser?.uid else {
logger.error("User not authenticated")
throw AuthError.notAuthenticated
}
do {
let infoRef = store.document(FirestorePath.userData(uid, document: .info))
let data = try await infoRef.getDocument().data()
guard let provider = data?["currentProvider"] as? String,
let name = data?[provider == "apple.com" ? "appleName" : "name"] as? String,
let email = data?["email"] as? String,
let statusMessage = data?["statusMsg"] as? String
else {
logger.error("User profile data not found")
throw FirestoreError.dataNotFound("User Profile")
}
logger.info("Successfully fetched user profile for: \(email)")
return UserProfileResponse(
name: name,
email: email,
statusMessage: statusMessage,
avatarURL: Auth.auth().currentUser?.photoURL
)
} catch {
logger.error("Failed to fetch user profile", error: error)
throw error
}
}
func upsertStatusMessage(_ message: String) async throws {
logger.info("Upserting status message")
guard let uid = Auth.auth().currentUser?.uid else {
logger.error("User not authenticated")
throw AuthError.notAuthenticated
}
do {
let infoRef = store.document(FirestorePath.userData(uid, document: .info))
try await infoRef.setData(["statusMsg": message], merge: true)
logger.info("Successfully upserted status message")
} catch {
logger.error("Failed to upsert status message", error: error)
throw error
}
}
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 {
let tokensRef = store.document(FirestorePath.userData(userId, document: .tokens))
try await tokensRef.setData(["fcmToken": fcmToken], merge: true)
logger.info("Successfully updated FCM token")
} catch {
logger.error("Failed to update FCM token", error: error)
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(FirestorePath.userData(userId, document: .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
}
}
}