Skip to content

Commit 2172f51

Browse files
authored
Merge pull request #74 from YAPP-Github/TNT-158-trainerSignup
[TNT-158] FCM 구현
2 parents b2e30d7 + d4da1c4 commit 2172f51

File tree

13 files changed

+256
-50
lines changed

13 files changed

+256
-50
lines changed

TnT/Projects/Data/Sources/LocalStorage/KeyChainManager.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,14 @@ public extension KeyChainManager {
111111
enum Key {
112112
case sessionId
113113
case userId
114+
case apns
114115

115116
/// 키 고유 문자열
116117
var keyString: String {
117118
switch self {
118119
case .sessionId: return "com.TnT.sessionId"
119120
case .userId: return "com.TnT.userId"
121+
case .apns: return "come.TnT.apns"
120122
}
121123
}
122124

@@ -125,6 +127,7 @@ public extension KeyChainManager {
125127
switch self {
126128
case .sessionId: return KeyConverter(type: String.self, convert: { $0 })
127129
case .userId: return KeyConverter(type: Int.self, convert: { Int($0) })
130+
case .apns: return KeyConverter(type: String.self, convert: { $0 })
128131
}
129132
}
130133
}

TnT/Projects/Data/Sources/Network/Service/SocialLogin/SocialLogInRepositoryImpl.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,10 @@ public struct SocialLogInRepositoryImpl: SocialLoginRepository {
2828
}
2929

3030
public func appleLogin() async -> AppleLoginInfo? {
31-
let result = await loginManager.appleLogin()
32-
33-
return result
31+
return await loginManager.appleLogin()
3432
}
3533

3634
public func kakaoLogin() async -> KakaoLoginInfo? {
37-
let result = await loginManager.kakaoLogin()
38-
return result
35+
return await loginManager.kakaoLogin()
3936
}
4037
}

TnT/Projects/Domain/Sources/DTO/User/UserResponseDTO.swift

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ public struct PostSocialLoginResDTO: Decodable {
2727
public let isSignUp: Bool
2828
/// 회원 타입 (TRAINER, TRAINEE, UNREGISTERED)
2929
public let memberType: MemberTypeResDTO
30+
31+
/// Coding Keys를 활용해 Decodable 처리
32+
enum CodingKeys: String, CodingKey {
33+
case sessionId
34+
case socialId
35+
case socialEmail
36+
case socialType
37+
case isSignUp
38+
case memberType
39+
}
3040
}
3141

3242
/// Trainer, Trainee, Unregistered로 구분되는 MemberTypeDTO
@@ -46,19 +56,61 @@ public enum MemberTypeResDTO: String, Decodable {
4656
/// 회원 정보 응답 DTO
4757
public struct PostSignUpResDTO: Decodable {
4858
/// 회원 타입 (trainer, trainee)
49-
let memberType: String
59+
public let memberType: String
5060
/// 세션 ID
51-
let sessionId: String
61+
public let sessionId: String
5262
/// 회원 이름
53-
let name: String
63+
public let name: String
5464
/// 프로필 이미지 URL
55-
let profileImageUrl: String?
65+
public let profileImageUrl: String?
66+
67+
public init(
68+
memberType: String,
69+
sessionId: String,
70+
name: String,
71+
profileImageUrl: String?
72+
) {
73+
self.memberType = memberType
74+
self.sessionId = sessionId
75+
self.name = name
76+
self.profileImageUrl = profileImageUrl
77+
}
5678
}
5779

80+
public enum MemberType: String, Decodable {
81+
case trainer = "TRAINER"
82+
case trainee = "TRAINEE"
83+
case unregistered = "UNREGISTERED"
84+
85+
public init?(rawValue: String) {
86+
switch rawValue.lowercased() {
87+
case "TRAINER":
88+
self = .trainer
89+
case "TRAINEE":
90+
self = .trainee
91+
case "UNREGISTERED":
92+
self = .unregistered
93+
default:
94+
return nil
95+
}
96+
}
97+
}
98+
5899
/// 로그아웃 응답 DTO
59100
public struct PostLogoutResDTO: Decodable {
60101
let sessionId: String
61102
}
62103

63104
/// 회원탈퇴 응답 DTO
64105
public typealias PostWithdrawalResDTO = EmptyResponse
106+
107+
public extension PostSignUpResDTO {
108+
func toEntity() -> PostSignUpResEntity {
109+
return .init(
110+
memberType: self.memberType,
111+
sessionId: self.sessionId,
112+
name: self.name,
113+
profileImageUrl: self.profileImageUrl
114+
)
115+
}
116+
}

TnT/Projects/Domain/Sources/Entity/SocailLoginEntity.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,6 @@ public struct PostSocialLoginResEntity: Equatable {
4949
public let socialType: String?
5050
/// 가입 여부 (`true`: 이미 가입됨, `false`: 미가입)
5151
public let isSignUp: Bool
52+
/// 멤버타입
53+
public let membertype: MemberTypeResDTO
5254
}

TnT/Projects/Domain/Sources/Mapper/PostSocialMapper.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public extension PostSocialLoginResDTO {
2626
socialId: self.socialId,
2727
socialEmail: self.socialEmail,
2828
socialType: self.socialType,
29-
isSignUp: self.isSignUp
29+
isSignUp: self.isSignUp,
30+
membertype: self.memberType
3031
)
3132
}
3233
}
@@ -52,15 +53,16 @@ public extension PostSignUpEntity {
5253
goalContents: self.goalContents ?? []
5354
)
5455
}
55-
}
56-
57-
public extension PostSignUpResDTO {
58-
func toEntity() -> PostSignUpResEntity {
59-
return .init(
60-
memberType: self.memberType,
61-
sessionId: self.sessionId,
62-
name: self.name,
63-
profileImageUrl: self.profileImageUrl
56+
57+
/// `PostSocialLoginResDTO` → `PostSocialLoginResEntity` 변환
58+
static func toResEntity(from dto: PostSocialLoginResDTO) -> PostSocialLoginResEntity {
59+
return PostSocialLoginResEntity(
60+
sessionId: dto.sessionId,
61+
socialId: dto.socialId,
62+
socialEmail: dto.socialEmail,
63+
socialType: dto.socialType,
64+
isSignUp: dto.isSignUp,
65+
membertype: dto.memberType
6466
)
6567
}
6668
}

TnT/Projects/Presentation/Sources/Onboarding/Common/LoginFeature.swift

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@ import SwiftUI
1111

1212
import Domain
1313
import DIContainer
14+
import Data
1415

1516
@Reducer
1617
public struct LoginFeature {
1718
@ObservableState
1819
public struct State: Equatable {
19-
@Shared var signUpEntity: PostSignUpEntity
20+
public var userType: UserType?
21+
public var nickname: String?
22+
public var socialType: LoginType?
23+
public var socialEmail: String?
2024
public var postUserEntity: PostSocialEntity?
25+
public var termState: Bool = false
26+
public var fcmToken: String?
27+
@Shared var signUpEntity: PostSignUpEntity
2128
@Presents var termFeature: TermFeature.State?
2229

2330
public init(
@@ -32,7 +39,7 @@ public struct LoginFeature {
3239
@Dependency(\.userUseCase) private var userUseCase: UserUseCase
3340
@Dependency(\.userUseRepoCase) private var userUseCaseRepo: UserRepository
3441
@Dependency(\.socialLogInUseCase) private var socialLoginUseCase: SocialLoginUseCase
35-
@Dependency(\.keyChainManager) var keyChainManager
42+
@Dependency(\.keyChainManager) var keyChainManager: KeyChainManager
3643

3744
public enum Action: ViewAction {
3845
/// 뷰에서 일어나는 액션을 처리합니다.(카카오,애플로그인 실행)
@@ -72,12 +79,14 @@ public struct LoginFeature {
7279
switch view {
7380
case .tappedAppleLogin:
7481
return .run { @Sendable send in
75-
let result = await socialLoginUseCase.appleLogin()
76-
guard let result else { return }
82+
guard let result = await socialLoginUseCase.appleLogin() else { return }
83+
let fcmToken: String? = try keyChainManager.read(for: .apns)
7784

78-
let entity = PostSocialEntity(
85+
/// 서버 <-> 소셜 로그인을 위한 객체 생성
86+
let entity: PostSocialEntity = PostSocialEntity(
7987
socialType: .apple,
80-
fcmToken: "asdfg", // TODO: FCM 로직 나오면 추후 수정
88+
fcmToken: fcmToken ?? "",
89+
socialAccessToken: "",
8190
idToken: result.identityToken
8291
)
8392

@@ -86,13 +95,15 @@ public struct LoginFeature {
8695

8796
case .tappedKakaoLogin:
8897
return .run { @Sendable send in
89-
let result = await socialLoginUseCase.kakaoLogin()
90-
guard let result else { return }
98+
guard let result = await socialLoginUseCase.kakaoLogin() else { return }
99+
let fcmToken: String? = try keyChainManager.read(for: .apns)
91100

92-
let entity = PostSocialEntity(
101+
/// 서버 <-> 소셜 로그인을 위한 객체 생성
102+
let entity: PostSocialEntity = PostSocialEntity(
93103
socialType: .kakao,
94-
fcmToken: "asdfg", // TODO: FCM 로직 나오면 추후 수정
95-
socialAccessToken: result.accessToken
104+
fcmToken: fcmToken ?? "",
105+
socialAccessToken: result.accessToken,
106+
idToken: ""
96107
)
97108

98109
await send(.postSocialLogin(entity: entity))
@@ -114,7 +125,8 @@ public struct LoginFeature {
114125
return .none
115126

116127
case .postSocialLogin(let entity):
117-
let post = entity.toDTO()
128+
let post: PostSocialLoginReqDTO = entity.toDTO()
129+
118130
return .run { send in
119131
do {
120132
let result = try await userUseCaseRepo.postSocialLogin(post)

TnT/Projects/Presentation/Sources/Onboarding/Common/TermFeature.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public struct TermFeature {
4040

4141
public init() { }
4242

43+
@Dependency(\.dismiss) var dismiss
4344
public var body: some ReducerOf<Self> {
4445
Reduce { state, action in
4546
switch action {
@@ -54,7 +55,10 @@ public struct TermFeature {
5455
return .none
5556

5657
case .nextButtonTapped:
57-
return .send(.setNavigating)
58+
return .concatenate(
59+
// .run { _ in await self.dismiss() },
60+
.send(.setNavigating)
61+
)
5862
}
5963

6064
case .setNavigating:

TnT/Projects/TnTApp/Sources/Application/AppDelegate.swift

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,75 @@
77
//
88

99
import UIKit
10+
import Firebase
11+
import FirebaseMessaging
12+
import UserNotifications
13+
14+
import Data
1015

1116
class AppDelegate: UIResponder, UIApplicationDelegate {
1217

13-
func application(_ application: UIApplication,
14-
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
18+
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
19+
/// Firebase 설정
20+
FirebaseApp.configure()
21+
UNUserNotificationCenter.current().delegate = self
22+
/// FCM 메시징 델리게이트 설정
23+
Messaging.messaging().delegate = self
24+
25+
/// 알림 권한 요청
26+
NotificationManager.shared.checkNotificationPermission()
27+
28+
/// APNs 등록 요청
29+
UIApplication.shared.registerForRemoteNotifications()
30+
1531
return true
1632
}
1733

18-
// MARK: UISceneSession Lifecycle
19-
func application(
20-
_ application: UIApplication,
21-
configurationForConnecting connectingSceneSession: UISceneSession,
22-
options: UIScene.ConnectionOptions
23-
) -> UISceneConfiguration {
24-
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
34+
// MARK: - APNs 등록 성공
35+
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
36+
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
37+
do {
38+
try keyChainManager.save(deviceToken, for: .apns)
39+
} catch {
40+
print("KeyChain 저장 중 오류 발생 \(error.localizedDescription)")
41+
}
42+
43+
print("✅ APNs Device Token: \(tokenString)")
44+
Messaging.messaging().apnsToken = deviceToken
45+
}
46+
47+
// MARK: - APNs 등록 실패
48+
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
49+
print("❌ APNs 등록 실패: \(error.localizedDescription)")
2550
}
51+
}
2652

27-
func application(
28-
_ application: UIApplication,
29-
didDiscardSceneSessions sceneSessions: Set<UISceneSession>
30-
) {}
53+
// MARK: - UNUserNotificationCenterDelegate
54+
extension AppDelegate: UNUserNotificationCenterDelegate {
55+
/// 포그라운드에서 알림 처리
56+
func userNotificationCenter(
57+
_ center: UNUserNotificationCenter,
58+
willPresent notification: UNNotification,
59+
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
60+
) {
61+
completionHandler([.sound, .badge, .list, .banner])
62+
}
63+
}
64+
65+
// MARK: - MessagingDelegate (FCM 토큰 가져오기)
66+
extension AppDelegate: MessagingDelegate {
67+
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
68+
guard let fcmToken = fcmToken else {
69+
print("❌ FCM 토큰을 가져오지 못했습니다.")
70+
return
71+
}
72+
73+
do {
74+
try keyChainManager.save(fcmToken, for: .apns)
75+
} catch {
76+
print("❌ FCM 토큰 저장 중 오류 발생: \(error.localizedDescription)")
77+
}
78+
79+
print("✅ FCM 등록 토큰: \(fcmToken)")
80+
}
3181
}

0 commit comments

Comments
 (0)