Skip to content

Commit

Permalink
Merge pull request #74 from YAPP-Github/TNT-158-trainerSignup
Browse files Browse the repository at this point in the history
[TNT-158] FCM ๊ตฌํ˜„
  • Loading branch information
syss220211 authored Feb 12, 2025
2 parents b2e30d7 + d4da1c4 commit 2172f51
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 50 deletions.
3 changes: 3 additions & 0 deletions TnT/Projects/Data/Sources/LocalStorage/KeyChainManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,14 @@ public extension KeyChainManager {
enum Key {
case sessionId
case userId
case apns

/// ํ‚ค ๊ณ ์œ  ๋ฌธ์ž์—ด
var keyString: String {
switch self {
case .sessionId: return "com.TnT.sessionId"
case .userId: return "com.TnT.userId"
case .apns: return "come.TnT.apns"
}
}

Expand All @@ -125,6 +127,7 @@ public extension KeyChainManager {
switch self {
case .sessionId: return KeyConverter(type: String.self, convert: { $0 })
case .userId: return KeyConverter(type: Int.self, convert: { Int($0) })
case .apns: return KeyConverter(type: String.self, convert: { $0 })
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,10 @@ public struct SocialLogInRepositoryImpl: SocialLoginRepository {
}

public func appleLogin() async -> AppleLoginInfo? {
let result = await loginManager.appleLogin()

return result
return await loginManager.appleLogin()
}

public func kakaoLogin() async -> KakaoLoginInfo? {
let result = await loginManager.kakaoLogin()
return result
return await loginManager.kakaoLogin()
}
}
60 changes: 56 additions & 4 deletions TnT/Projects/Domain/Sources/DTO/User/UserResponseDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ public struct PostSocialLoginResDTO: Decodable {
public let isSignUp: Bool
/// ํšŒ์› ํƒ€์ž… (TRAINER, TRAINEE, UNREGISTERED)
public let memberType: MemberTypeResDTO

/// Coding Keys๋ฅผ ํ™œ์šฉํ•ด Decodable ์ฒ˜๋ฆฌ
enum CodingKeys: String, CodingKey {
case sessionId
case socialId
case socialEmail
case socialType
case isSignUp
case memberType
}
}

/// Trainer, Trainee, Unregistered๋กœ ๊ตฌ๋ถ„๋˜๋Š” MemberTypeDTO
Expand All @@ -46,19 +56,61 @@ public enum MemberTypeResDTO: String, Decodable {
/// ํšŒ์› ์ •๋ณด ์‘๋‹ต DTO
public struct PostSignUpResDTO: Decodable {
/// ํšŒ์› ํƒ€์ž… (trainer, trainee)
let memberType: String
public let memberType: String
/// ์„ธ์…˜ ID
let sessionId: String
public let sessionId: String
/// ํšŒ์› ์ด๋ฆ„
let name: String
public let name: String
/// ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ URL
let profileImageUrl: String?
public let profileImageUrl: String?

public init(
memberType: String,
sessionId: String,
name: String,
profileImageUrl: String?
) {
self.memberType = memberType
self.sessionId = sessionId
self.name = name
self.profileImageUrl = profileImageUrl
}
}

public enum MemberType: String, Decodable {
case trainer = "TRAINER"
case trainee = "TRAINEE"
case unregistered = "UNREGISTERED"

public init?(rawValue: String) {
switch rawValue.lowercased() {
case "TRAINER":
self = .trainer
case "TRAINEE":
self = .trainee
case "UNREGISTERED":
self = .unregistered
default:
return nil
}
}
}

/// ๋กœ๊ทธ์•„์›ƒ ์‘๋‹ต DTO
public struct PostLogoutResDTO: Decodable {
let sessionId: String
}

/// ํšŒ์›ํƒˆํ‡ด ์‘๋‹ต DTO
public typealias PostWithdrawalResDTO = EmptyResponse

public extension PostSignUpResDTO {
func toEntity() -> PostSignUpResEntity {
return .init(
memberType: self.memberType,
sessionId: self.sessionId,
name: self.name,
profileImageUrl: self.profileImageUrl
)
}
}
2 changes: 2 additions & 0 deletions TnT/Projects/Domain/Sources/Entity/SocailLoginEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ public struct PostSocialLoginResEntity: Equatable {
public let socialType: String?
/// ๊ฐ€์ž… ์—ฌ๋ถ€ (`true`: ์ด๋ฏธ ๊ฐ€์ž…๋จ, `false`: ๋ฏธ๊ฐ€์ž…)
public let isSignUp: Bool
/// ๋ฉค๋ฒ„ํƒ€์ž…
public let membertype: MemberTypeResDTO
}
22 changes: 12 additions & 10 deletions TnT/Projects/Domain/Sources/Mapper/PostSocialMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public extension PostSocialLoginResDTO {
socialId: self.socialId,
socialEmail: self.socialEmail,
socialType: self.socialType,
isSignUp: self.isSignUp
isSignUp: self.isSignUp,
membertype: self.memberType
)
}
}
Expand All @@ -52,15 +53,16 @@ public extension PostSignUpEntity {
goalContents: self.goalContents ?? []
)
}
}

public extension PostSignUpResDTO {
func toEntity() -> PostSignUpResEntity {
return .init(
memberType: self.memberType,
sessionId: self.sessionId,
name: self.name,
profileImageUrl: self.profileImageUrl

/// `PostSocialLoginResDTO` โ†’ `PostSocialLoginResEntity` ๋ณ€ํ™˜
static func toResEntity(from dto: PostSocialLoginResDTO) -> PostSocialLoginResEntity {
return PostSocialLoginResEntity(
sessionId: dto.sessionId,
socialId: dto.socialId,
socialEmail: dto.socialEmail,
socialType: dto.socialType,
isSignUp: dto.isSignUp,
membertype: dto.memberType
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ import SwiftUI

import Domain
import DIContainer
import Data

@Reducer
public struct LoginFeature {
@ObservableState
public struct State: Equatable {
@Shared var signUpEntity: PostSignUpEntity
public var userType: UserType?
public var nickname: String?
public var socialType: LoginType?
public var socialEmail: String?
public var postUserEntity: PostSocialEntity?
public var termState: Bool = false
public var fcmToken: String?
@Shared var signUpEntity: PostSignUpEntity
@Presents var termFeature: TermFeature.State?

public init(
Expand All @@ -32,7 +39,7 @@ public struct LoginFeature {
@Dependency(\.userUseCase) private var userUseCase: UserUseCase
@Dependency(\.userUseRepoCase) private var userUseCaseRepo: UserRepository
@Dependency(\.socialLogInUseCase) private var socialLoginUseCase: SocialLoginUseCase
@Dependency(\.keyChainManager) var keyChainManager
@Dependency(\.keyChainManager) var keyChainManager: KeyChainManager

public enum Action: ViewAction {
/// ๋ทฐ์—์„œ ์ผ์–ด๋‚˜๋Š” ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.(์นด์นด์˜ค,์• ํ”Œ๋กœ๊ทธ์ธ ์‹คํ–‰)
Expand Down Expand Up @@ -72,12 +79,14 @@ public struct LoginFeature {
switch view {
case .tappedAppleLogin:
return .run { @Sendable send in
let result = await socialLoginUseCase.appleLogin()
guard let result else { return }
guard let result = await socialLoginUseCase.appleLogin() else { return }
let fcmToken: String? = try keyChainManager.read(for: .apns)

let entity = PostSocialEntity(
/// ์„œ๋ฒ„ <-> ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ์œ„ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ
let entity: PostSocialEntity = PostSocialEntity(
socialType: .apple,
fcmToken: "asdfg", // TODO: FCM ๋กœ์ง ๋‚˜์˜ค๋ฉด ์ถ”ํ›„ ์ˆ˜์ •
fcmToken: fcmToken ?? "",
socialAccessToken: "",
idToken: result.identityToken
)

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

case .tappedKakaoLogin:
return .run { @Sendable send in
let result = await socialLoginUseCase.kakaoLogin()
guard let result else { return }
guard let result = await socialLoginUseCase.kakaoLogin() else { return }
let fcmToken: String? = try keyChainManager.read(for: .apns)

let entity = PostSocialEntity(
/// ์„œ๋ฒ„ <-> ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ์œ„ํ•œ ๊ฐ์ฒด ์ƒ์„ฑ
let entity: PostSocialEntity = PostSocialEntity(
socialType: .kakao,
fcmToken: "asdfg", // TODO: FCM ๋กœ์ง ๋‚˜์˜ค๋ฉด ์ถ”ํ›„ ์ˆ˜์ •
socialAccessToken: result.accessToken
fcmToken: fcmToken ?? "",
socialAccessToken: result.accessToken,
idToken: ""
)

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

case .postSocialLogin(let entity):
let post = entity.toDTO()
let post: PostSocialLoginReqDTO = entity.toDTO()

return .run { send in
do {
let result = try await userUseCaseRepo.postSocialLogin(post)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public struct TermFeature {

public init() { }

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

case .nextButtonTapped:
return .send(.setNavigating)
return .concatenate(
// .run { _ in await self.dismiss() },
.send(.setNavigating)
)
}

case .setNavigating:
Expand Down
76 changes: 63 additions & 13 deletions TnT/Projects/TnTApp/Sources/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,75 @@
//

import UIKit
import Firebase
import FirebaseMessaging
import UserNotifications

import Data

class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
/// Firebase ์„ค์ •
FirebaseApp.configure()
UNUserNotificationCenter.current().delegate = self
/// FCM ๋ฉ”์‹œ์ง• ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ์„ค์ •
Messaging.messaging().delegate = self

/// ์•Œ๋ฆผ ๊ถŒํ•œ ์š”์ฒญ
NotificationManager.shared.checkNotificationPermission()

/// APNs ๋“ฑ๋ก ์š”์ฒญ
UIApplication.shared.registerForRemoteNotifications()

return true
}

// MARK: UISceneSession Lifecycle
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
// MARK: - APNs ๋“ฑ๋ก ์„ฑ๊ณต
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
do {
try keyChainManager.save(deviceToken, for: .apns)
} catch {
print("KeyChain ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ \(error.localizedDescription)")
}

print("โœ… APNs Device Token: \(tokenString)")
Messaging.messaging().apnsToken = deviceToken
}

// MARK: - APNs ๋“ฑ๋ก ์‹คํŒจ
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("โŒ APNs ๋“ฑ๋ก ์‹คํŒจ: \(error.localizedDescription)")
}
}

func application(
_ application: UIApplication,
didDiscardSceneSessions sceneSessions: Set<UISceneSession>
) {}
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
/// ํฌ๊ทธ๋ผ์šด๋“œ์—์„œ ์•Œ๋ฆผ ์ฒ˜๋ฆฌ
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler([.sound, .badge, .list, .banner])
}
}

// MARK: - MessagingDelegate (FCM ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ)
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
guard let fcmToken = fcmToken else {
print("โŒ FCM ํ† ํฐ์„ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.")
return
}

do {
try keyChainManager.save(fcmToken, for: .apns)
} catch {
print("โŒ FCM ํ† ํฐ ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: \(error.localizedDescription)")
}

print("โœ… FCM ๋“ฑ๋ก ํ† ํฐ: \(fcmToken)")
}
}
Loading

0 comments on commit 2172f51

Please sign in to comment.