Skip to content

Commit faaf412

Browse files
authored
Merge pull request #19 from ApptiveDev/dev
[Release] 1.0.6 업데이트
2 parents 0cd3340 + a39b1f3 commit faaf412

34 files changed

Lines changed: 1749 additions & 288 deletions

KillingPart.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@
434434
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
435435
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
436436
CODE_SIGN_STYLE = Automatic;
437-
CURRENT_PROJECT_VERSION = 4;
437+
CURRENT_PROJECT_VERSION = 6;
438438
DEAD_CODE_STRIPPING = YES;
439439
DEVELOPMENT_TEAM = GQ89YG5G9R;
440440
ENABLE_APP_SANDBOX = YES;
@@ -459,7 +459,7 @@
459459
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
460460
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
461461
MACOSX_DEPLOYMENT_TARGET = 14.0;
462-
MARKETING_VERSION = 1.0.4;
462+
MARKETING_VERSION = 1.0.6;
463463
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
464464
PRODUCT_NAME = "$(TARGET_NAME)";
465465
REGISTER_APP_GROUPS = YES;
@@ -479,7 +479,7 @@
479479
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
480480
CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements;
481481
CODE_SIGN_STYLE = Automatic;
482-
CURRENT_PROJECT_VERSION = 4;
482+
CURRENT_PROJECT_VERSION = 6;
483483
DEAD_CODE_STRIPPING = YES;
484484
DEVELOPMENT_TEAM = GQ89YG5G9R;
485485
ENABLE_APP_SANDBOX = YES;
@@ -504,7 +504,7 @@
504504
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
505505
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
506506
MACOSX_DEPLOYMENT_TARGET = 14.0;
507-
MARKETING_VERSION = 1.0.4;
507+
MARKETING_VERSION = 1.0.6;
508508
PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart;
509509
PRODUCT_NAME = "$(TARGET_NAME)";
510510
REGISTER_APP_GROUPS = YES;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0x7B",
9+
"green" : "0x7B",
10+
"red" : "0x7B"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x7B",
27+
"green" : "0x7B",
28+
"red" : "0x7B"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0x26",
9+
"green" : "0x26",
10+
"red" : "0x26"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x26",
27+
"green" : "0x26",
28+
"red" : "0x26"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}

KillingPart/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>SPOTIFY_BASIC_AUTH</key>
6+
<string>$(SPOTIFY_BASIC_AUTH)</string>
57
<key>BASE_URL</key>
68
<string>$(BASE_URL)</string>
79
<key>KAKAO_NATIVE_APP_KEY</key>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Foundation
2+
3+
enum DiaryScope: String, Decodable {
4+
case `public` = "PUBLIC"
5+
case `private` = "PRIVATE"
6+
case killingPart = "KILLING_PART"
7+
}
8+
9+
struct DiaryFeedModel: Decodable, Identifiable {
10+
let diaryId: Int
11+
let artist: String
12+
let musicTitle: String
13+
let albumImageUrl: String
14+
let content: String
15+
let videoUrl: String
16+
let scope: DiaryScope
17+
let duration: String
18+
let totalDuration: String
19+
let start: String
20+
let end: String
21+
let createDate: String
22+
let updateDate: String
23+
let isLiked: Bool
24+
let isStored: Bool
25+
let likeCount: Int
26+
let userId: Int
27+
let username: String?
28+
let tag: String?
29+
let profileImageUrl: String?
30+
31+
var id: Int { diaryId }
32+
33+
var albumImageURL: URL? {
34+
URL(string: albumImageUrl)
35+
}
36+
}
37+
38+
struct DiaryFeedPageModel: Decodable {
39+
let size: Int
40+
let number: Int
41+
let totalElements: Int
42+
let totalPages: Int
43+
}
44+
45+
struct MyDiaryFeedsResponse: Decodable {
46+
let content: [DiaryFeedModel]
47+
let page: DiaryFeedPageModel
48+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Foundation
2+
3+
struct SpotifyTokenResponse: Decodable {
4+
let accessToken: String
5+
let tokenType: String
6+
let expiresIn: Int
7+
8+
enum CodingKeys: String, CodingKey {
9+
case accessToken = "access_token"
10+
case tokenType = "token_type"
11+
case expiresIn = "expires_in"
12+
}
13+
}
14+
15+
struct SpotifySearchResponse: Decodable {
16+
let tracks: SpotifyTracks
17+
}
18+
19+
struct SpotifyTracks: Decodable {
20+
let items: [SpotifyTrackItem]
21+
}
22+
23+
struct SpotifyTrackItem: Decodable {
24+
let id: String
25+
let name: String
26+
let artists: [SpotifyArtist]
27+
let album: SpotifyAlbum
28+
}
29+
30+
struct SpotifyArtist: Decodable {
31+
let name: String
32+
}
33+
34+
struct SpotifyAlbum: Decodable {
35+
let id: String
36+
let images: [SpotifyAlbumImage]
37+
}
38+
39+
struct SpotifyAlbumImage: Decodable {
40+
let url: String
41+
let width: Int?
42+
let height: Int?
43+
}
44+
45+
struct SpotifySimpleTrack: Identifiable {
46+
let id: String
47+
let title: String
48+
let artist: String
49+
let albumImageUrl: String?
50+
let albumId: String
51+
52+
var albumImageURL: URL? {
53+
guard let albumImageUrl else { return nil }
54+
return URL(string: albumImageUrl)
55+
}
56+
}

KillingPart/Models/UserModel.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Foundation
2+
3+
struct UserModel: Decodable {
4+
let userId: Int
5+
let username: String
6+
let tag: String
7+
let identifier: String
8+
let profileImageUrl: String
9+
let userRoleType: String
10+
let socialType: String
11+
12+
var profileImageURL: URL? {
13+
URL(string: profileImageUrl)
14+
}
15+
}
16+
17+
struct UserStaticsModel: Decodable {
18+
let fanCount: Int
19+
let pickCount: Int
20+
let killingPartCount: Int
21+
}

KillingPart/Services/APIClient.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,22 @@ enum HTTPMethod: String {
1111
struct APIRequest {
1212
let path: String
1313
let method: HTTPMethod
14+
var queryItems: [URLQueryItem]
1415
var requiresAuthorization: Bool
1516
var headers: [String: String]
1617
var body: Data?
1718

1819
init(
1920
path: String,
2021
method: HTTPMethod,
22+
queryItems: [URLQueryItem] = [],
2123
requiresAuthorization: Bool = false,
2224
headers: [String: String] = [:],
2325
body: Data? = nil
2426
) {
2527
self.path = path
2628
self.method = method
29+
self.queryItems = queryItems
2730
self.requiresAuthorization = requiresAuthorization
2831
self.headers = headers
2932
self.body = body
@@ -119,7 +122,9 @@ final class APIClient: APIClienting {
119122
}
120123

121124
private func buildRequest(from request: APIRequest) throws -> URLRequest {
122-
var urlRequest = URLRequest(url: APIConfiguration.endpoint(path: request.path))
125+
var urlRequest = URLRequest(
126+
url: APIConfiguration.endpoint(path: request.path, queryItems: request.queryItems)
127+
)
123128
urlRequest.httpMethod = request.method.rawValue
124129
urlRequest.httpBody = request.body
125130

KillingPart/Services/APIConfiguration.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,21 @@ enum APIConfiguration {
1616
return baseURL
1717
}()
1818

19-
static func endpoint(path: String) -> URL {
19+
static func endpoint(path: String, queryItems: [URLQueryItem] = []) -> URL {
2020
let components = path.split(separator: "/").map(String.init)
21-
return components.reduce(baseURL) { partialURL, component in
21+
let urlWithPath = components.reduce(baseURL) { partialURL, component in
2222
partialURL.appendingPathComponent(component)
2323
}
24+
25+
guard !queryItems.isEmpty else {
26+
return urlWithPath
27+
}
28+
29+
guard var urlComponents = URLComponents(url: urlWithPath, resolvingAgainstBaseURL: false) else {
30+
return urlWithPath
31+
}
32+
33+
urlComponents.queryItems = queryItems
34+
return urlComponents.url ?? urlWithPath
2435
}
2536
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Foundation
2+
3+
protocol DiaryServicing {
4+
func fetchMyFeeds(page: Int, size: Int) async throws -> MyDiaryFeedsResponse
5+
}
6+
7+
enum DiaryServiceError: LocalizedError {
8+
case invalidResponse
9+
case serverError(statusCode: Int, message: String?)
10+
case decodingFailed
11+
case sessionExpired
12+
case networkFailure(message: String)
13+
14+
var errorDescription: String? {
15+
switch self {
16+
case .invalidResponse:
17+
return "서버 응답을 확인할 수 없어요."
18+
case .serverError(_, let message):
19+
return message ?? "요청 처리에 실패했어요."
20+
case .decodingFailed:
21+
return "응답 파싱에 실패했어요."
22+
case .sessionExpired:
23+
return "세션이 만료되었어요. 다시 로그인해 주세요."
24+
case .networkFailure(let message):
25+
return message
26+
}
27+
}
28+
}
29+
30+
struct DiaryService: DiaryServicing {
31+
private let apiClient: APIClienting
32+
33+
init(apiClient: APIClienting = APIClient.shared) {
34+
self.apiClient = apiClient
35+
}
36+
37+
func fetchMyFeeds(page: Int = 0, size: Int = 5) async throws -> MyDiaryFeedsResponse {
38+
do {
39+
let request = APIRequest(
40+
path: "/diaries/my",
41+
method: .get,
42+
queryItems: [
43+
URLQueryItem(name: "page", value: String(page)),
44+
URLQueryItem(name: "size", value: String(size))
45+
],
46+
requiresAuthorization: true
47+
)
48+
49+
return try await apiClient.request(request, responseType: MyDiaryFeedsResponse.self)
50+
} catch {
51+
throw mapError(error)
52+
}
53+
}
54+
55+
private func mapError(_ error: Error) -> DiaryServiceError {
56+
if let diaryServiceError = error as? DiaryServiceError {
57+
return diaryServiceError
58+
}
59+
60+
if let apiError = error as? APIClientError {
61+
switch apiError {
62+
case .invalidResponse:
63+
return .invalidResponse
64+
case .missingAccessToken, .missingRefreshToken, .unauthorized:
65+
return .sessionExpired
66+
case .serverError(let statusCode, let message):
67+
return .serverError(statusCode: statusCode, message: message)
68+
case .decodingFailed:
69+
return .decodingFailed
70+
}
71+
}
72+
73+
return .networkFailure(message: "네트워크 요청 중 오류가 발생했어요.")
74+
}
75+
}

0 commit comments

Comments
 (0)