Skip to content

Commit

Permalink
google oauth 구현 및 정보 입력
Browse files Browse the repository at this point in the history
  • Loading branch information
kimkyumbi committed Dec 29, 2024
1 parent 615274d commit ecdcfc5
Show file tree
Hide file tree
Showing 23 changed files with 333 additions and 503 deletions.
4 changes: 2 additions & 2 deletions Projects/App/Sources/Application/GPleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}


@main
struct GoogleSignInProjectApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@StateObject var viewModel: LoginViewModel = LoginViewModel()
@StateObject var userInfoViewModel: UserInfoViewModel = UserInfoViewModel()

var body: some Scene {
WindowGroup {
UserInfoView(viewModel: UserInfoViewModel())
LoginView(viewModel: LoginViewModel(), userInfoViewModel: userInfoViewModel)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ struct LoginButton: View {
var body: some View {
Button {
loginViewModel.signIn()

} label: {
VStack {
HStack(spacing: 12) {
Expand All @@ -26,8 +27,6 @@ struct LoginButton: View {
.background(GPleAsset.Color.white.swiftUIColor)
.cornerRadius(12)
.padding(.horizontal, 20)

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import SwiftUI

struct LoginView: View {
@StateObject var viewModel: LoginViewModel
@StateObject var userInfoViewModel: UserInfoViewModel

var body: some View {
ZStack {
Expand Down Expand Up @@ -32,6 +33,10 @@ struct LoginView: View {
LoginButton(loginViewModel: viewModel)
.padding(.bottom, 40)
}

if viewModel.isSignedIn == true {
UserInfoView(viewModel: UserInfoViewModel())
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Domain
final class LoginViewModel: ObservableObject {
@Published var isSignedIn: Bool = false
@Published var errorMessage: String?

private let provider = MoyaProvider<AuthAPI>()

private func getGoogleClientID() -> String? {
Expand All @@ -19,11 +19,13 @@ final class LoginViewModel: ObservableObject {
return clientID
}

@MainActor
func signIn() {
guard let clientID = getGoogleClientID() else {
print("Google Client ID is missing.")
return
}


let configuration = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.configuration = configuration
Expand All @@ -46,31 +48,64 @@ final class LoginViewModel: ObservableObject {
return
}

print("CILENT_ID: \(clientID)")
print("ID Token: \(idToken)")
self?.sendIdTokenToServer(idToken)
}
}

@MainActor
private func sendIdTokenToServer(_ idToken: String) {
provider.request(.login(idToken: idToken)) { [weak self] result in
switch result {
case .success(let response):
case let .success(res):
do {
if let responseData = try JSONSerialization.jsonObject(with: response.data, options: []) as? [String: Any] {
print("Response: \(responseData)")
DispatchQueue.main.async {
self?.isSignedIn = true
}

let loginResponse = try JSONDecoder().decode(Token.self, from: res.data)

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

if let accessTokenExpirationDate = dateFormatter.date(from: loginResponse.accessExpiredAt),
let refreshTokenExpirationDate = dateFormatter.date(from: loginResponse.refreshExpiredAt) {

let accessTokenExpirationInterval = accessTokenExpirationDate.timeIntervalSinceNow
let refreshTokenExpirationInterval = refreshTokenExpirationDate.timeIntervalSinceNow

KeyChain.shared.saveTokenWithExpiration(
key: Const.KeyChainKey.accessToken,
token: loginResponse.accessToken,
expiresIn: accessTokenExpirationInterval
)

KeyChain.shared.saveTokenWithExpiration(
key: Const.KeyChainKey.refreshToken,
token: loginResponse.refreshToken,
expiresIn: refreshTokenExpirationInterval
)

print("Access Token 만료 날짜: \(accessTokenExpirationDate)")
print("Refresh Token 만료 날짜: \(refreshTokenExpirationDate)")
print("토큰 저장 완료")
} else {
DispatchQueue.main.async {
self?.errorMessage = "Unexpected response format."
}
print("만료 시간 변환 오류: 유효한 날짜 형식이 아님.")
}
print("------------------------------------------------------------")

DispatchQueue.main.async {
print("Access Token: \(loginResponse.accessToken)")
print("Refresh Token: \(loginResponse.refreshToken)")
print("Access Token Expiry: \(loginResponse.accessExpiredAt)")
print("Refresh Token Expiry: \(loginResponse.refreshExpiredAt)")
self?.isSignedIn = true
}
} catch {
DispatchQueue.main.async {
self?.errorMessage = "Failed to parse response: \(error.localizedDescription)"
}
}

case .failure(let error):
DispatchQueue.main.async {
self?.errorMessage = "Request failed: \(error.localizedDescription)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ struct UserInfoView: View {
backColor: GPleAsset.Color.gray1000.swiftUIColor,
buttonState: viewModel.name.isEmpty == false && viewModel.number.isEmpty == false,
buttonOkColor: GPleAsset.Color.main.swiftUIColor
)
) {
viewModel.submitUserInfo()
}
.padding(.bottom, 12)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,59 @@
import Foundation
import Combine
import Moya
import Domain

final class UserInfoViewModel: ObservableObject {
// 입력된 사용자 정보
@Published var name: String = ""
@Published var number: String = ""

// 네트워크 요청 상태를 나타내는 퍼블리셔
@Published var isLoading: Bool = false
@Published var errorMessage: String? = nil

// Moya 프로바이더 초기화
private let provider = MoyaProvider<UserAPI>()

private func fetchAccessToken() -> String? {
return KeyChain.shared.read(key: Const.KeyChainKey.accessToken)
}

// 사용자 정보 제출 함수
func submitUserInfo() {
// 로딩 상태 시작
isLoading = true
errorMessage = nil
print("name: \(name), number: \(number)")

// 인증 토큰 (실제 앱에서는 안전한 저장소에서 가져와야 함)
guard let authorizationToken = fetchAccessToken() else { return }

// API 호출
provider.request(.userInfoInput(authorization: authorizationToken, name: name, number: number, file: nil)) { [weak self] result in
// 로딩 상태 종료
DispatchQueue.main.async {
self?.isLoading = false
}

switch result {
case .success(let response):
// 서버 응답 처리
if (200...299).contains(response.statusCode) {
// 성공 처리
print("사용자 정보가 성공적으로 전송되었습니다.")
} else {
// 서버 오류 메시지 처리
DispatchQueue.main.async {
self?.errorMessage = "서버 오류: \(response.statusCode)"
}
}
case .failure(let error):
// 네트워크 오류 처리
DispatchQueue.main.async {
self?.errorMessage = "네트워크 오류: \(error.localizedDescription)"
}
}
}
}
}
44 changes: 22 additions & 22 deletions Projects/Domain/Sources/API/Auth/AuthAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,23 @@ import Moya

public enum AuthAPI {
case login(idToken: String)
case logout
case refresh
case logout(refreshToken: String)
case refresh(idToken: String)
}

extension AuthAPI: GPleAPI {
public var domain: GPleDomain {
.auth
extension AuthAPI: TargetType {
public var baseURL: URL {
return URL(string: "https://port-0-gple-backend-eg4e2alkoplc4q.sel4.cloudtype.app")!
}

public var urlPath: String {
public var path: String {
switch self {
case .login:
return "/google/login"
return "/auth/google/token"
case .logout:
return "/logout"
return "/auth/logout"
case .refresh:
return "/"
}
}

public var jwtTokenType: JwtTokenType {
switch self {
case .login, .refresh:
return .refreshToken

case .logout:
return .accessToken
return "/auth/"
}
}

Expand All @@ -48,13 +38,23 @@ extension AuthAPI: GPleAPI {

public var task: Task {
switch self {
case let .login(idToken):
case let .login(idToken), let .refresh(idToken):
return .requestParameters(parameters: [
"idTonek" : idToken
"idToken" : idToken
],
encoding: JSONEncoding.default)
case .logout, .refresh:
case .logout:
return .requestPlain
}
}

public var headers: [String : String]? {
switch self {
case
.logout(let refreshToken):
return ["Content-Type": "application/json", "refreshToken": refreshToken]
default:
return ["Content-Type": "application/json"]
}
}
}
Loading

0 comments on commit ecdcfc5

Please sign in to comment.