Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions KillingPart.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
CURRENT_PROJECT_VERSION = 6;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = GQ89YG5G9R;
ENABLE_APP_SANDBOX = YES;
Expand All @@ -459,7 +459,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0.4;
MARKETING_VERSION = 1.0.6;
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand All @@ -479,7 +479,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
CURRENT_PROJECT_VERSION = 6;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = GQ89YG5G9R;
ENABLE_APP_SANDBOX = YES;
Expand All @@ -504,7 +504,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0.4;
MARKETING_VERSION = 1.0.6;
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down
38 changes: 38 additions & 0 deletions KillingPart/Assets.xcassets/kpGray400.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x7B",
"green" : "0x7B",
"red" : "0x7B"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x7B",
"green" : "0x7B",
"red" : "0x7B"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
38 changes: 38 additions & 0 deletions KillingPart/Assets.xcassets/kpGray700.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x26",
"green" : "0x26",
"red" : "0x26"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x26",
"green" : "0x26",
"red" : "0x26"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
2 changes: 2 additions & 0 deletions KillingPart/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SPOTIFY_BASIC_AUTH</key>
<string>$(SPOTIFY_BASIC_AUTH)</string>
<key>BASE_URL</key>
<string>$(BASE_URL)</string>
<key>KAKAO_NATIVE_APP_KEY</key>
Expand Down
48 changes: 48 additions & 0 deletions KillingPart/Models/DiaryModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

enum DiaryScope: String, Decodable {
case `public` = "PUBLIC"
case `private` = "PRIVATE"
case killingPart = "KILLING_PART"
}

struct DiaryFeedModel: Decodable, Identifiable {
let diaryId: Int
let artist: String
let musicTitle: String
let albumImageUrl: String
let content: String
let videoUrl: String
let scope: DiaryScope
let duration: String
let totalDuration: String
let start: String
let end: String
let createDate: String
let updateDate: String
let isLiked: Bool
let isStored: Bool
let likeCount: Int
let userId: Int
let username: String?
let tag: String?
let profileImageUrl: String?

var id: Int { diaryId }

var albumImageURL: URL? {
URL(string: albumImageUrl)
}
}

struct DiaryFeedPageModel: Decodable {
let size: Int
let number: Int
let totalElements: Int
let totalPages: Int
}

struct MyDiaryFeedsResponse: Decodable {
let content: [DiaryFeedModel]
let page: DiaryFeedPageModel
}
56 changes: 56 additions & 0 deletions KillingPart/Models/SpotifyModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation

struct SpotifyTokenResponse: Decodable {
let accessToken: String
let tokenType: String
let expiresIn: Int

enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case tokenType = "token_type"
case expiresIn = "expires_in"
}
}

struct SpotifySearchResponse: Decodable {
let tracks: SpotifyTracks
}

struct SpotifyTracks: Decodable {
let items: [SpotifyTrackItem]
}

struct SpotifyTrackItem: Decodable {
let id: String
let name: String
let artists: [SpotifyArtist]
let album: SpotifyAlbum
}

struct SpotifyArtist: Decodable {
let name: String
}

struct SpotifyAlbum: Decodable {
let id: String
let images: [SpotifyAlbumImage]
}

struct SpotifyAlbumImage: Decodable {
let url: String
let width: Int?
let height: Int?
}

struct SpotifySimpleTrack: Identifiable {
let id: String
let title: String
let artist: String
let albumImageUrl: String?
let albumId: String

var albumImageURL: URL? {
guard let albumImageUrl else { return nil }
return URL(string: albumImageUrl)
}
}
21 changes: 21 additions & 0 deletions KillingPart/Models/UserModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

struct UserModel: Decodable {
let userId: Int
let username: String
let tag: String
let identifier: String
let profileImageUrl: String
let userRoleType: String
let socialType: String

var profileImageURL: URL? {
URL(string: profileImageUrl)
}
}

struct UserStaticsModel: Decodable {
let fanCount: Int
let pickCount: Int
let killingPartCount: Int
}
7 changes: 6 additions & 1 deletion KillingPart/Services/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ enum HTTPMethod: String {
struct APIRequest {
let path: String
let method: HTTPMethod
var queryItems: [URLQueryItem]
var requiresAuthorization: Bool
var headers: [String: String]
var body: Data?

init(
path: String,
method: HTTPMethod,
queryItems: [URLQueryItem] = [],
requiresAuthorization: Bool = false,
headers: [String: String] = [:],
body: Data? = nil
) {
self.path = path
self.method = method
self.queryItems = queryItems
self.requiresAuthorization = requiresAuthorization
self.headers = headers
self.body = body
Expand Down Expand Up @@ -119,7 +122,9 @@ final class APIClient: APIClienting {
}

private func buildRequest(from request: APIRequest) throws -> URLRequest {
var urlRequest = URLRequest(url: APIConfiguration.endpoint(path: request.path))
var urlRequest = URLRequest(
url: APIConfiguration.endpoint(path: request.path, queryItems: request.queryItems)
)
urlRequest.httpMethod = request.method.rawValue
urlRequest.httpBody = request.body

Expand Down
15 changes: 13 additions & 2 deletions KillingPart/Services/APIConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,21 @@ enum APIConfiguration {
return baseURL
}()

static func endpoint(path: String) -> URL {
static func endpoint(path: String, queryItems: [URLQueryItem] = []) -> URL {
let components = path.split(separator: "/").map(String.init)
return components.reduce(baseURL) { partialURL, component in
let urlWithPath = components.reduce(baseURL) { partialURL, component in
partialURL.appendingPathComponent(component)
}

guard !queryItems.isEmpty else {
return urlWithPath
}

guard var urlComponents = URLComponents(url: urlWithPath, resolvingAgainstBaseURL: false) else {
return urlWithPath
}

urlComponents.queryItems = queryItems
return urlComponents.url ?? urlWithPath
}
}
75 changes: 75 additions & 0 deletions KillingPart/Services/DiaryService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Foundation

protocol DiaryServicing {
func fetchMyFeeds(page: Int, size: Int) async throws -> MyDiaryFeedsResponse
}

enum DiaryServiceError: LocalizedError {
case invalidResponse
case serverError(statusCode: Int, message: String?)
case decodingFailed
case sessionExpired
case networkFailure(message: String)

var errorDescription: String? {
switch self {
case .invalidResponse:
return "서버 응답을 확인할 수 없어요."
case .serverError(_, let message):
return message ?? "요청 처리에 실패했어요."
case .decodingFailed:
return "응답 파싱에 실패했어요."
case .sessionExpired:
return "세션이 만료되었어요. 다시 로그인해 주세요."
case .networkFailure(let message):
return message
}
}
}

struct DiaryService: DiaryServicing {
private let apiClient: APIClienting

init(apiClient: APIClienting = APIClient.shared) {
self.apiClient = apiClient
}

func fetchMyFeeds(page: Int = 0, size: Int = 5) async throws -> MyDiaryFeedsResponse {
do {
let request = APIRequest(
path: "/diaries/my",
method: .get,
queryItems: [
URLQueryItem(name: "page", value: String(page)),
URLQueryItem(name: "size", value: String(size))
],
requiresAuthorization: true
)

return try await apiClient.request(request, responseType: MyDiaryFeedsResponse.self)
} catch {
throw mapError(error)
}
}

private func mapError(_ error: Error) -> DiaryServiceError {
if let diaryServiceError = error as? DiaryServiceError {
return diaryServiceError
}

if let apiError = error as? APIClientError {
switch apiError {
case .invalidResponse:
return .invalidResponse
case .missingAccessToken, .missingRefreshToken, .unauthorized:
return .sessionExpired
case .serverError(let statusCode, let message):
return .serverError(statusCode: statusCode, message: message)
case .decodingFailed:
return .decodingFailed
}
}

return .networkFailure(message: "네트워크 요청 중 오류가 발생했어요.")
}
}
Loading