Skip to content

feat: #30 - 회원가입, 로그인 api 연결#31

Merged
KimNahun merged 1 commit into
developfrom
feat/#30-Register_API
Feb 18, 2026
Merged

feat: #30 - 회원가입, 로그인 api 연결#31
KimNahun merged 1 commit into
developfrom
feat/#30-Register_API

Conversation

@KimNahun
Copy link
Copy Markdown
Contributor

@KimNahun KimNahun commented Feb 17, 2026

연결된 이슈

작업 내용

  • 앱 실행 시 자동 인증 플로우 구현 (Signup + Login API 연동)
  • 기존 하드코딩된 accessToken 제거, 키체인에서 실제 토큰을 읽도록 변경
  • 기존 Result 기반 AuthService를 async throws 패턴으로 리팩토링
  • Login DTO/Model 분리 (SignupDTO와 LoginDTO 별도 파일)

인증 플로우

  • 키체인에 uuid가 없는 경우 (신규 유저): Signup API → Login API → accessToken/uuid 키체인 저장 → 홈 진입
  • 키체인에 uuid가 있는 경우 (기존 유저): Login API만 호출 → accessToken 갱신 → 홈 진입
  • 인증이 완료된 후에만 홈 화면에 진입하도록 보장 (RootRouter.didLoad()의 즉시 attachMain() 제거)

참고

  • 현재 GoogleService-Info.plist 연동 전이라 FCM 디바이스 토큰을 받아올 수 없어, 임시로 UUID().uuidString을
    fcmToken으로 전달하고 있습니다. Firebase 연동 후 실제 FCM 토큰으로 교체 필요합니다.

💻 주요 코드 설명

TokenProviderAdapter

  • 기존 하드코딩된 accessToken을 제거하고, 키체인에서 실제 토큰을 읽도록 복원
// Before
public func accessToken() -> String? {
//    tokenRepository.get(.accessToken)
    "eyJhbGciOiJIUzUxMiJ9..."
}

// After
public func accessToken() -> String? {
    tokenRepository.get(.accessToken)
}

RootInteractor

  • didBecomeActive()에서 인증 플로우 실행, 성공 시에만 attachMain() 호출
private func performAuthFlow() {
    Task { [weak self] in
        guard let self else { return }
        do {
            if let uuid = self.tokenRepository.get(.uuid) {
                // 기존 유저: 로그인만
                let loginResult = try await self.authRepository.login(uuid: uuid)
                self.tokenRepository.save(loginResult.accessToken, for: .accessToken)
            } else {
                // 신규 유저: 회원가입 → 로그인
                let fcmToken = self.tokenRepository.get(.fcmToken) ?? UUID().uuidString
                let signupResult = try await self.authRepository.signup(
                    info: SignupInfo(fcmToken: fcmToken)
                )
                self.tokenRepository.save(signupResult.uuid, for: .uuid)
                self.tokenRepository.save(signupResult.accessToken, for: .accessToken)

                let loginResult = try await self.authRepository.login(uuid: signupResult.uuid)
                self.tokenRepository.save(loginResult.accessToken, for: .accessToken)
            }
            await MainActor.run {
                self.router?.attachMain()
            }
        } catch {
            print("[RootInteractor] Auth flow failed: \(error)")
        }
    }
}

기타 더 이야기해볼 점

Firebase(GoogleService-Info.plist) 연동 후 실제 FCM 토큰 저장 로직 추가 필요
인증 실패 시 재시도 또는 에러 화면 처리 추후 논의 필요

Summary by CodeRabbit

릴리즈 노트

  • 새로운 기능

    • 로그인 기능이 추가되었습니다.
    • 토큰 관리 시스템이 개선되었습니다.
    • 앱 시작 시 자동 인증 프로세스가 구현되었습니다.
  • 개선사항

    • 인증 흐름이 최적화되었습니다.

@KimNahun KimNahun requested a review from ChoiAnYong February 17, 2026 19:59
@KimNahun KimNahun self-assigned this Feb 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 17, 2026

Walkthrough

인증 시스템을 리팩토링하여 Result 기반 에러 처리에서 async throws 패턴으로 전환했습니다. AuthRepository와 TokenRepository를 의존성으로 주입받고, 앱 실행 시 회원가입 또는 로그인 플로우를 자동으로 수행하도록 변경했습니다.

Changes

Cohort / File(s) Summary
핵심 인증 레이어
Projects/Domain/Sources/Interface/Auth/AuthServiceProtocol.swift, Projects/Data/Sources/DI/AuthServiceFactory.swift, Projects/Data/Sources/Repository/Auth/AuthRepository.swift
AuthRepositoryInterface 프로토콜 도입으로 이름 변경, Result 패턴에서 async throws로 전환, login 메서드 추가. AuthServiceFactory에서 AuthRepository를 생성하는 팩토리 메서드 추가.
데이터 변환 및 DTO
Projects/Data/Sources/Transform/AuthTransform.swift, Projects/Modules/Networks/Sources/DTO/Auth/LoginDTO.swift, Projects/Domain/Sources/Model/Auth/LoginResult.swift
SignupResponse와 LoginResponse를 도메인 모델로 변환하는 toDomain() 확장 메서드 추가. LoginRequest, LoginResponse DTO 정의, LoginResult 도메인 모델 정의.
네트워크 서비스 계층
Projects/Modules/Networks/Sources/Service/AuthService.swift, Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift
AuthServiceProtocol 프로토콜 추가, signup/login 메서드를 async throws 패턴으로 변경. AuthAPI에 login 케이스 추가 및 라우팅 설정.
의존성 주입 및 라우터
Projects/Application/AppComponent.swift, Projects/Features/RootFeature/Sources/RootBuilder.swift, Projects/Features/RootFeature/Sources/RootRouter.swift
AppComponent에서 authRepository, tokenRepository 노출. RootDependency에 의존성 추가, RootRouter에서 didLoad() 시 attachMain() 자동 호출 제거.
인증 플로우 구현
Projects/Features/RootFeature/Sources/RootInteractor.swift
authRepository, tokenRepository 의존성 주입. 활성화 시 performAuthFlow()를 통해 UUID 존재 여부에 따라 로그인 또는 회원가입 수행하는 플로우 추가.
에러 처리 변경
Projects/Domain/Sources/Model/Auth/SignupError.swift, Projects/Modules/Networks/Sources/ErrorMapping/SignupError+Mapping.swift
SignupField, SignupError 열거형 및 매핑 로직 제거. 기존 Result 기반 에러 처리 대신 async throws 패턴으로 통일.
토큰 관리
Projects/Data/Sources/Adapter/TokenProviderAdapter.swift
accessToken() 메서드 구현으로 tokenRepository.get(.accessToken) 호출로 변경.

Sequence Diagram(s)

sequenceDiagram
    participant RootInteractor
    participant AuthRepository
    participant TokenRepository
    participant AuthService
    participant Network as Network API

    RootInteractor->>RootInteractor: activate()
    RootInteractor->>RootInteractor: performAuthFlow()
    
    alt UUID가 존재하는 경우
        RootInteractor->>AuthRepository: login(uuid: String)
        AuthRepository->>AuthService: login(request: LoginRequest)
        AuthService->>Network: POST /api/v1/auth/login
        Network-->>AuthService: LoginResponse
        AuthService-->>AuthRepository: LoginResponse
        AuthRepository-->>RootInteractor: LoginResult
        RootInteractor->>TokenRepository: save(.accessToken)
    else UUID가 없는 경우
        RootInteractor->>AuthRepository: signup(info: SignupInfo)
        AuthRepository->>AuthService: signup(request: SignupRequest)
        AuthService->>Network: POST /api/v1/auth/signup
        Network-->>AuthService: SignupResponse
        AuthService-->>AuthRepository: SignupResponse
        AuthRepository-->>RootInteractor: SignupResult
        RootInteractor->>TokenRepository: save(.uuid)
        RootInteractor->>TokenRepository: save(.accessToken)
        RootInteractor->>AuthRepository: login(uuid: String)
        AuthRepository->>AuthService: login(request: LoginRequest)
        AuthService->>Network: POST /api/v1/auth/login
        Network-->>AuthService: LoginResponse
        AuthService-->>AuthRepository: LoginResponse
        AuthRepository-->>RootInteractor: LoginResult
        RootInteractor->>TokenRepository: save(.accessToken)
    end
    
    RootInteractor->>RootInteractor: attachMain()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Feat/#3 네트워크 모듈화 #5: 동일한 인증/네트워크 계층에서 AuthService, AuthRepository, DTO, 에러 매핑 및 AuthAPI를 다루고 있어 코드 수준에서 직접 관련됨.

Suggested labels

🧑🏻‍💻 feat, 🙇🏻‍♂️ Nahun

Suggested reviewers

  • ChoiAnYong

Poem

🐰 회원가입 완성했다네,
로그인 플로우 깔끔하게,
async throws로 비동기 날아,
토큰도 쏙 저장하고,
API 연결 완벽해! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목 'feat: #30 - 회원가입, 로그인 api 연결'은 주요 변경사항인 회원가입 및 로그인 API 연결을 명확하게 설명하고 있습니다.
Description check ✅ Passed PR 설명은 연결된 이슈, 작업 내용, 인증 플로우, 주요 코드 설명, 참고사항을 포함하여 템플릿의 주요 섹션을 충실히 작성했습니다.
Linked Issues check ✅ Passed PR 변경사항이 이슈 #30의 목표인 '회원가입 API 연결'을 충족합니다: Signup/Login API 연동, 자동 인증 플로우, DTO 분리, AuthService 비동기화 등이 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 회원가입/로그인 API 연동 및 인증 플로우 구현이라는 명확한 범위 내에 있으며, 불필요한 부가 변경은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#30-Register_API

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
Projects/Domain/Sources/Interface/Auth/AuthServiceProtocol.swift (1)

11-14: 파일명과 프로토콜 이름 불일치.

파일 경로는 AuthServiceProtocol.swift이지만, 프로토콜 이름은 AuthRepositoryInterface로 변경되었습니다. 파일명도 AuthRepositoryInterface.swift로 변경하여 일관성을 유지하는 것이 좋습니다. (헤더 주석 Line 2는 이미 AuthRepositoryInterface.swift로 업데이트된 상태입니다.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Domain/Sources/Interface/Auth/AuthServiceProtocol.swift` around
lines 11 - 14, The protocol name and file name are inconsistent: the file is
AuthServiceProtocol.swift but the protocol is declared as
AuthRepositoryInterface; rename the protocol to match the file or rename the
file to match the protocol—pick one consistent name (e.g., rename the file to
AuthRepositoryInterface.swift or rename the protocol to AuthServiceProtocol) and
update all references/imports/usages accordingly (search for
AuthRepositoryInterface and AuthServiceProtocol across the codebase and update
call sites, conforming types, and the header comment that already mentions
AuthRepositoryInterface.swift). Ensure the symbols SignupInfo, SignupResult, and
LoginResult remain referenced correctly after the rename.
Projects/Data/Sources/Transform/AuthTransform.swift (1)

12-30: SignupResultLoginResult의 동일한 구조 참고.

현재 SignupResultLoginResult가 동일한 필드(uuid, accessToken, nickname)를 가지며, 변환 로직도 동일합니다. 향후 두 타입이 분기할 가능성이 있다면 현 구조가 적합하지만, 그렇지 않다면 하나의 AuthResult 타입으로 통합하는 것도 고려할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Data/Sources/Transform/AuthTransform.swift` around lines 12 - 30,
SignupResponse.toDomain() and LoginResponse.toDomain() produce identical objects
(uuid, accessToken, nickname); consolidate by introducing a single AuthResult
(or protocol AuthResultProtocol) and update both conversion methods to
return/match AuthResult instead of separate SignupResult/LoginResult, or remove
duplicate types by replacing SignupResult and LoginResult with AuthResult and
adjust all usages to the new type; change references in SignupResponse.toDomain,
LoginResponse.toDomain, and any code expecting SignupResult/LoginResult to use
AuthResult (or conform to the new protocol) to remove duplication.
Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift (1)

38-44: task 프로퍼티의 중복 케이스를 하나로 합칠 수 있습니다.

signuplogin 모두 동일한 .requestJSONEncodable(request) 패턴을 사용하고 있으므로, method처럼 하나의 case로 합칠 수 있습니다. 현재 구조도 괜찮지만, 향후 case가 늘어날 때 유지보수에 도움이 됩니다.

♻️ 선택적 리팩토링 제안
     public var task: Moya.Task {
         switch self {
-        case .signup(let request):
-            return .requestJSONEncodable(request)
-        case .login(let request):
+        case .signup(let request), .login(let request):
             return .requestJSONEncodable(request)
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift` around lines 38 -
44, task 프로퍼티에서 signup과 login 케이스가 동일한 처리를 하고 있으니 중복을 제거하세요: AuthAPI의 public var
task 구현에서 switch 문을 수정해 .signup(let request), .login(let request)처럼 여러 케이스를 한 줄로
묶고 하나의 반환(.requestJSONEncodable(request))만 사용하도록 변경하면 됩니다; 관련 심볼: task, .signup,
.login, .requestJSONEncodable.
Projects/Features/RootFeature/Sources/RootInteractor.swift (1)

61-63: SwiftLint unneeded_override 경고: willResignActive()super 호출만 하고 있습니다.

Task 취소 로직을 추가하지 않는다면 이 override는 제거해도 됩니다. Task 취소를 추가한다면 유지하면서 취소 로직을 넣으세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/RootFeature/Sources/RootInteractor.swift` around lines 61 -
63, The override of willResignActive() in RootInteractor only calls super and
triggers a SwiftLint unneeded_override; either remove the willResignActive()
override entirely from the RootInteractor class, or keep it and implement task
cancellation logic (cancel any active Task, child Task handles, Combine
subscriptions, or async work) and then call super.willResignActive(); refer to
the willResignActive() method in RootInteractor to apply the change.
Projects/Modules/Networks/Sources/Service/AuthService.swift (2)

17-17: @unchecked Sendable 사용에 대한 안전성 확인이 필요합니다.

AuthService@unchecked Sendable로 선언되어 있습니다. 내부의 MoyaProvider는 Alamofire의 Session을 사용하며, Alamofire Session은 자체적으로 thread-safe하게 설계되어 있어 현재로는 문제가 없을 가능성이 높습니다. 다만, @unchecked는 컴파일러의 동시성 검증을 우회하므로, 향후 프로퍼티가 추가될 때 주의가 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/Service/AuthService.swift` at line 17,
AuthService is marked `@unchecked` Sendable which bypasses Swift's concurrency
checks—inspect the class (AuthService) and its stored properties (notably the
MoyaProvider instance) and either remove `@unchecked` Sendable or make the safety
explicit: ensure all stored properties are immutable (use let) and are Sendable
types, or wrap any non-Sendable mutable state (e.g., MoyaProvider/Alamofire
Session) behind an actor or synchronize access; then add a succinct comment near
the AuthService declaration documenting why it is safe (reference
MoyaProvider/Alamofire Session thread-safety) if you keep `@unchecked` Sendable.

25-25: asyncThowsRequest 메서드명에 오타가 있습니다 (ThowsThrows).

이 메서드는 MoyaProvider+Async.swift의 111번 줄에 정의되어 있으며, 현재 5개의 서비스 파일에서 12번 사용 중입니다 (AuthService, TravelProgramService, UserTravelService, PlaceService, TravelTemplateService). 현재 PR과 무관한 기존 오타이지만, 별도 PR에서 수정하면 코드 가독성이 개선됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/Service/AuthService.swift` at line 25, 메서드명
오타 asyncThowsRequest을 올바른 asyncThrowsRequest로 수정하세요: MoyaProvider+Async.swift에
정의된 메서 asyncThowsRequest의 선언과 모든 호출부(AuthService의 signup 호출을 비롯해
TravelProgramService, UserTravelService, PlaceService, TravelTemplateService 등 총
12곳)를 동일하게 리네임해 컴파일 오류를 방지하고 코드 가독성을 개선합니다; 선언부 시그니처(비동기 throws 표기)와 익스텐션/프로토콜
구현 위치의 이름을 모두 일관되게 변경하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Projects/Features/RootFeature/Sources/RootInteractor.swift`:
- Around line 88-90: The catch in RootInteractor that currently calls
print("[RootInteractor] Auth flow failed: \(error)") should be replaced with
real error handling: log the error with context, call a new method (e.g.,
handleAuthError(_:) or presentAuthError(_:)) on RootInteractor to trigger UI
feedback or navigation to an error screen, and/or enqueue a retry via a
retryAuth() helper; also add a clear TODO comment referencing the new method so
a follow-up issue can be created to implement retry/backoff UX if you don't
implement it now.
- Around line 65-92: The Task created in performAuthFlow() is not retained or
cancelled, so authentication continues after the Interactor is deactivated; fix
by storing the returned Task in a property (e.g., add a private var authTask:
Task<Void, Never>?), assign authTask = Task { ... } inside performAuthFlow(),
and cancel it in willResignActive() by calling authTask?.cancel(); also sprinkle
try Task.checkCancellation() between major await points (before/after
authRepository.signup/login and before tokenRepository.save) to exit early when
cancelled and avoid unintended tokenRepository.save(...) calls; ensure
router?.attachMain() remains invoked on MainActor only if not cancelled.

In `@Projects/Modules/Networks/Sources/DTO/Auth/LoginDTO.swift`:
- Around line 19-23: The LoginResponse struct's accessToken will fail to decode
if the server returns snake_case ("access_token"); update the model or decoder:
either add a CodingKeys enum to LoginResponse mapping case accessToken =
"access_token" (refer to LoginResponse and accessToken) or configure
JSONDecoder.keyDecodingStrategy = .convertFromSnakeCase for all decoders created
(update decoder creation in MoyaProvider+Async.swift where JSONDecoder instances
are constructed) so snake_case keys map to camelCase properties.

---

Nitpick comments:
In `@Projects/Data/Sources/Transform/AuthTransform.swift`:
- Around line 12-30: SignupResponse.toDomain() and LoginResponse.toDomain()
produce identical objects (uuid, accessToken, nickname); consolidate by
introducing a single AuthResult (or protocol AuthResultProtocol) and update both
conversion methods to return/match AuthResult instead of separate
SignupResult/LoginResult, or remove duplicate types by replacing SignupResult
and LoginResult with AuthResult and adjust all usages to the new type; change
references in SignupResponse.toDomain, LoginResponse.toDomain, and any code
expecting SignupResult/LoginResult to use AuthResult (or conform to the new
protocol) to remove duplication.

In `@Projects/Domain/Sources/Interface/Auth/AuthServiceProtocol.swift`:
- Around line 11-14: The protocol name and file name are inconsistent: the file
is AuthServiceProtocol.swift but the protocol is declared as
AuthRepositoryInterface; rename the protocol to match the file or rename the
file to match the protocol—pick one consistent name (e.g., rename the file to
AuthRepositoryInterface.swift or rename the protocol to AuthServiceProtocol) and
update all references/imports/usages accordingly (search for
AuthRepositoryInterface and AuthServiceProtocol across the codebase and update
call sites, conforming types, and the header comment that already mentions
AuthRepositoryInterface.swift). Ensure the symbols SignupInfo, SignupResult, and
LoginResult remain referenced correctly after the rename.

In `@Projects/Features/RootFeature/Sources/RootInteractor.swift`:
- Around line 61-63: The override of willResignActive() in RootInteractor only
calls super and triggers a SwiftLint unneeded_override; either remove the
willResignActive() override entirely from the RootInteractor class, or keep it
and implement task cancellation logic (cancel any active Task, child Task
handles, Combine subscriptions, or async work) and then call
super.willResignActive(); refer to the willResignActive() method in
RootInteractor to apply the change.

In `@Projects/Modules/Networks/Sources/Service/AuthService.swift`:
- Line 17: AuthService is marked `@unchecked` Sendable which bypasses Swift's
concurrency checks—inspect the class (AuthService) and its stored properties
(notably the MoyaProvider instance) and either remove `@unchecked` Sendable or
make the safety explicit: ensure all stored properties are immutable (use let)
and are Sendable types, or wrap any non-Sendable mutable state (e.g.,
MoyaProvider/Alamofire Session) behind an actor or synchronize access; then add
a succinct comment near the AuthService declaration documenting why it is safe
(reference MoyaProvider/Alamofire Session thread-safety) if you keep `@unchecked`
Sendable.
- Line 25: 메서드명 오타 asyncThowsRequest을 올바른 asyncThrowsRequest로 수정하세요:
MoyaProvider+Async.swift에 정의된 메서 asyncThowsRequest의 선언과 모든 호출부(AuthService의
signup 호출을 비롯해 TravelProgramService, UserTravelService, PlaceService,
TravelTemplateService 등 총 12곳)를 동일하게 리네임해 컴파일 오류를 방지하고 코드 가독성을 개선합니다; 선언부
시그니처(비동기 throws 표기)와 익스텐션/프로토콜 구현 위치의 이름을 모두 일관되게 변경하세요.

In `@Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift`:
- Around line 38-44: task 프로퍼티에서 signup과 login 케이스가 동일한 처리를 하고 있으니 중복을 제거하세요:
AuthAPI의 public var task 구현에서 switch 문을 수정해 .signup(let request), .login(let
request)처럼 여러 케이스를 한 줄로 묶고 하나의 반환(.requestJSONEncodable(request))만 사용하도록 변경하면
됩니다; 관련 심볼: task, .signup, .login, .requestJSONEncodable.

Comment on lines +65 to +92
private func performAuthFlow() {
Task { [weak self] in
guard let self else { return }

do {
if let uuid = self.tokenRepository.get(.uuid) {
let loginResult = try await self.authRepository.login(uuid: uuid)
self.tokenRepository.save(loginResult.accessToken, for: .accessToken)
} else {
let fcmToken = self.tokenRepository.get(.fcmToken) ?? UUID().uuidString
let signupResult = try await self.authRepository.signup(
info: SignupInfo(fcmToken: fcmToken)
)
self.tokenRepository.save(signupResult.uuid, for: .uuid)
self.tokenRepository.save(signupResult.accessToken, for: .accessToken)

let loginResult = try await self.authRepository.login(uuid: signupResult.uuid)
self.tokenRepository.save(loginResult.accessToken, for: .accessToken)
}

await MainActor.run {
self.router?.attachMain()
}
} catch {
print("[RootInteractor] Auth flow failed: \(error)")
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Task가 저장·취소되지 않아 Interactor 비활성화 이후에도 인증 플로우가 계속 실행될 수 있습니다.

performAuthFlow()에서 생성된 Task가 프로퍼티에 저장되지 않아, willResignActive() 시점에 취소할 수 없습니다. Interactor가 deactivate된 후에도 네트워크 요청이 완료되면 tokenRepository.save()가 호출되어 의도치 않은 토큰 저장이 발생할 수 있습니다.

🛡️ Task 저장 및 취소 처리 제안
 final class RootInteractor: PresentableInteractor<RootPresentable>, RootInteractable {
 
     weak var router: RootRouting?
     weak var listener: RootListener?
 
     private let authRepository: AuthRepositoryInterface
     private let tokenRepository: TokenRepositoryProtocol
     private let disposeBag = DisposeBag()
+    private var authTask: Task<Void, Never>?
 
     // ...
 
-    override func willResignActive() {
-        super.willResignActive()
-    }
+    override func willResignActive() {
+        super.willResignActive()
+        authTask?.cancel()
+        authTask = nil
+    }
 
     private func performAuthFlow() {
-        Task { [weak self] in
+        authTask = Task { [weak self] in
             guard let self else { return }
             // ...
         }
     }
 }

추가로, Task 내부에서 try Task.checkCancellation()을 주요 await 지점 사이에 넣으면 취소 시 조기 종료가 가능합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/RootFeature/Sources/RootInteractor.swift` around lines 65 -
92, The Task created in performAuthFlow() is not retained or cancelled, so
authentication continues after the Interactor is deactivated; fix by storing the
returned Task in a property (e.g., add a private var authTask: Task<Void,
Never>?), assign authTask = Task { ... } inside performAuthFlow(), and cancel it
in willResignActive() by calling authTask?.cancel(); also sprinkle try
Task.checkCancellation() between major await points (before/after
authRepository.signup/login and before tokenRepository.save) to exit early when
cancelled and avoid unintended tokenRepository.save(...) calls; ensure
router?.attachMain() remains invoked on MainActor only if not cancelled.

Comment on lines +88 to +90
} catch {
print("[RootInteractor] Auth flow failed: \(error)")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

인증 실패 시 print만 하고 있어 사용자에게 어떤 피드백도 제공되지 않습니다.

PR 목표에서 에러 처리가 미구현이라고 언급되어 있지만, 현재 상태로는 인증 실패 시 앱이 빈 화면에 머물게 됩니다. 최소한 재시도 로직이나 에러 화면 전환을 위한 TODO를 명시해두면 추후 추적에 도움이 됩니다.

             } catch {
-                print("[RootInteractor] Auth flow failed: \(error)")
+                // TODO: 인증 실패 시 재시도 또는 에러 화면 처리 필요 (추후 구현)
+                print("[RootInteractor] Auth flow failed: \(error)")
             }

에러 처리 로직(재시도/에러 화면) 구현을 위한 이슈를 생성해 드릴까요?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/RootFeature/Sources/RootInteractor.swift` around lines 88 -
90, The catch in RootInteractor that currently calls print("[RootInteractor]
Auth flow failed: \(error)") should be replaced with real error handling: log
the error with context, call a new method (e.g., handleAuthError(_:) or
presentAuthError(_:)) on RootInteractor to trigger UI feedback or navigation to
an error screen, and/or enqueue a retry via a retryAuth() helper; also add a
clear TODO comment referencing the new method so a follow-up issue can be
created to implement retry/backoff UX if you don't implement it now.

Comment on lines +19 to +23
public struct LoginResponse: Decodable, Sendable {
public let uuid: String
public let accessToken: String
public let nickname: String
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "keyDecodingStrategy|convertFromSnakeCase|CodingKeys" --type swift -C 3

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 57


🏁 Script executed:

cat -n "Projects/Modules/Networks/Sources/DTO/Auth/LoginDTO.swift"

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 680


🏁 Script executed:

rg -n "JSONDecoder" --type swift -C 2

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 2511


accessToken 프로퍼티의 JSON 키 매핑 문제를 해결하세요.

현재 코드는 JSON 디코딩 시 keyDecodingStrategy를 설정하지 않았으며, CodingKeys도 정의되어 있지 않습니다. 서버가 access_token(snake_case)을 반환하는 경우 디코딩이 실패합니다.

다음 중 하나를 선택하세요:

  • CodingKeys enum을 추가하여 명시적으로 매핑: case accessToken = "access_token"
  • 또는 JSONDecoderkeyDecodingStrategy.convertFromSnakeCase로 설정 (MoyaProvider+Async.swift의 모든 JSONDecoder 인스턴스에 적용 필요)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/DTO/Auth/LoginDTO.swift` around lines 19 -
23, The LoginResponse struct's accessToken will fail to decode if the server
returns snake_case ("access_token"); update the model or decoder: either add a
CodingKeys enum to LoginResponse mapping case accessToken = "access_token"
(refer to LoginResponse and accessToken) or configure
JSONDecoder.keyDecodingStrategy = .convertFromSnakeCase for all decoders created
(update decoder creation in MoyaProvider+Async.swift where JSONDecoder instances
are constructed) so snake_case keys map to camelCase properties.

Copy link
Copy Markdown
Contributor

@ChoiAnYong ChoiAnYong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다

@KimNahun KimNahun merged commit d0ec5e2 into develop Feb 18, 2026
2 checks passed
@ChoiAnYong ChoiAnYong deleted the feat/#30-Register_API branch February 18, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 회원가입 API 연결

2 participants