Skip to content

feat - Spotify API 연동 및 Add탭 작업#18

Merged
mark77234 merged 3 commits intodevfrom
feat/15
Feb 17, 2026
Merged

feat - Spotify API 연동 및 Add탭 작업#18
mark77234 merged 3 commits intodevfrom
feat/15

Conversation

@mark77234
Copy link
Collaborator

작업내용

  • Spotify API 연동 및 구조 정의
  • Add탭 UI 업데이트 및 리팩토링

Copilot AI review requested due to automatic review settings February 17, 2026 05:01
@mark77234 mark77234 linked an issue Feb 17, 2026 that may be closed by this pull request
2 tasks
@mark77234 mark77234 merged commit a39b1f3 into dev Feb 17, 2026
2 checks passed
@mark77234 mark77234 deleted the feat/15 branch February 17, 2026 05:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Spotify 검색 기능을 Add 탭에 연결하기 위해 Spotify API 서비스/모델을 추가하고, Add 탭 UI를 검색 중심 구조로 리팩토링한 PR입니다.

Changes:

  • Spotify 토큰 발급 및 트랙 검색 API 연동(서비스/모델) 추가
  • Add 탭 UI를 검색 필드 + 로딩/에러/빈결과/리스트 상태로 재구성
  • 프로젝트 버전/빌드 넘버 업데이트 및 Info.plist에 Spotify 인증 설정 키 추가

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
KillingPart/Views/Screens/Main/Add/Components/AddTrackListView.swift Spotify 트랙 리스트/행/아트워크 표시 UI 추가
KillingPart/Views/Screens/Main/Add/Components/AddSearchFieldView.swift 검색 입력 필드 및 clear 버튼 컴포넌트 추가
KillingPart/Views/Screens/Main/Add/Components/AddSearchContentView.swift 로딩/에러/빈 결과/리스트 상태별 콘텐츠 뷰 추가
KillingPart/Views/Screens/Main/Add/AddTabView.swift Add 탭을 검색 기반 화면으로 리팩토링하고 VM 연결
KillingPart/ViewModels/Add/AddTabViewModel.swift 검색 상태/태스크 관리 및 Spotify 검색 호출 로직 추가
KillingPart/Services/SpotifyService.swift Spotify 토큰 발급 및 트랙 검색 네트워크 레이어 추가
KillingPart/Models/SpotifyModel.swift Spotify 응답 디코딩 모델 및 화면용 간단 트랙 모델 추가
KillingPart/Info.plist SPOTIFY_BASIC_AUTH 키 추가(빌드 설정 치환)
KillingPart.xcodeproj/project.pbxproj 마케팅/빌드 버전 증가(1.0.6/6)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +127 to +176
private func fetchAccessToken(forceRefresh: Bool) async throws -> String {
if !forceRefresh, let cachedToken = await Self.tokenCache.validToken() {
return cachedToken
}

let basicAuth = try spotifyBasicAuth()
guard let url = URL(string: "https://accounts.spotify.com/api/token") else {
throw SpotifyServiceError.invalidResponse
}

var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue(basicAuth, forHTTPHeaderField: "Authorization")
request.httpBody = "grant_type=client_credentials".data(using: .utf8)

let (data, response) = try await performDataTask(with: request)
switch response.statusCode {
case 200..<300:
let decoded: SpotifyTokenResponse
do {
decoded = try decoder.decode(SpotifyTokenResponse.self, from: data)
} catch {
throw SpotifyServiceError.decodingFailed
}
await Self.tokenCache.save(
token: decoded.accessToken,
expiresIn: decoded.expiresIn
)
return decoded.accessToken
case 401:
await Self.tokenCache.clear()
throw SpotifyServiceError.unauthorized
default:
throw SpotifyServiceError.serverError(
statusCode: response.statusCode,
message: responseMessage(from: data)
)
}
}

private func spotifyBasicAuth() throws -> String {
let rawValue = (Bundle.main.object(forInfoDictionaryKey: "SPOTIFY_BASIC_AUTH") as? String ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
guard !rawValue.isEmpty, rawValue != "$(SPOTIFY_BASIC_AUTH)" else {
throw SpotifyServiceError.missingBasicAuth
}
return rawValue
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

현재 구현은 Spotify Client Credentials 플로우를 위해 SPOTIFY_BASIC_AUTH(클라이언트 ID/Secret 기반 Basic 인증값)를 앱 번들(Info.plist)에서 읽어 토큰을 발급받습니다. 클라이언트 앱에 client secret(또는 그로부터 생성된 Basic auth)을 포함하면 추출이 가능해 보안상 취약하며, Spotify 정책/일반적인 OAuth 보안 관점에서도 권장되지 않습니다. 토큰 발급은 백엔드에서 프록시로 처리하거나, 사용자 인증이 필요하다면 Authorization Code + PKCE 등 secret이 필요 없는 플로우로 전환하는 방향으로 수정해 주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +6
<key>SPOTIFY_BASIC_AUTH</key>
<string>$(SPOTIFY_BASIC_AUTH)</string>
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

SPOTIFY_BASIC_AUTH 값을 Info.plist에 포함시키면(빌드 설정 치환이더라도) 최종 앱 번들에 노출되어 역공학으로 추출될 수 있습니다. Spotify client secret/Basic auth를 클라이언트에 포함하지 않도록 구성(백엔드 프록시/PKCE 전환 등)으로 변경하는 게 안전합니다.

Suggested change
<key>SPOTIFY_BASIC_AUTH</key>
<string>$(SPOTIFY_BASIC_AUTH)</string>

Copilot uses AI. Check for mistakes.
Comment on lines +110 to +112
albumImageUrl: item.album.images.sorted { (lhs, rhs) in
(lhs.width ?? 0) > (rhs.width ?? 0)
}.first?.url,
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

앨범 이미지 선택을 위해 images.sorted { ... }.first로 매번 전체 정렬을 수행하고 있는데, 여기서는 가장 큰 width 1개만 필요하므로 max(by:) 등으로 최대값만 선택하면 불필요한 정렬 비용을 줄일 수 있습니다 (limit을 늘릴 경우에도 유리).

Suggested change
albumImageUrl: item.album.images.sorted { (lhs, rhs) in
(lhs.width ?? 0) > (rhs.width ?? 0)
}.first?.url,
albumImageUrl: item.album.images.max { (lhs, rhs) in
(lhs.width ?? 0) < (rhs.width ?? 0)
}?.url,

Copilot uses AI. Check for mistakes.
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 연동

1 participant