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
15 changes: 15 additions & 0 deletions AppleStoreProductKiosk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,22 @@
C8F725F52E790FA100C2A1DE /* AppleStoreProductKioskUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppleStoreProductKioskUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
7F41C5542E798756007C4017 /* Exceptions for "AppleStoreProductKiosk" folder in "AppleStoreProductKiosk" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Resources/Info.plist,
);
target = C8F725DD2E790F9F00C2A1DE /* AppleStoreProductKiosk */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
C8F725E02E790F9F00C2A1DE /* AppleStoreProductKiosk */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
7F41C5542E798756007C4017 /* Exceptions for "AppleStoreProductKiosk" folder in "AppleStoreProductKiosk" target */,
);
path = AppleStoreProductKiosk;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -415,6 +428,7 @@
DEVELOPMENT_TEAM = N94CS4N6VR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AppleStoreProductKiosk/Resources/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down Expand Up @@ -448,6 +462,7 @@
DEVELOPMENT_TEAM = N94CS4N6VR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = AppleStoreProductKiosk/Resources/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down
37 changes: 37 additions & 0 deletions AppleStoreProductKiosk/Data/Model/Error/DataError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// DataError.swift
// AppleStoreProductKiosk
//
// Created by Wonji Suh on 9/16/25.
//

import Foundation

/// λ„€νŠΈμ›Œν¬ 및 데이터 처리 κ³Όμ •μ—μ„œ λ°œμƒν•  수 μžˆλŠ” 였λ₯˜λ₯Ό μ •μ˜ν•œ enumμž…λ‹ˆλ‹€.
enum DataError: Error, LocalizedError, Sendable, Equatable {
/// 데이터가 μ—†μŒ
case noData

/// μ»€μŠ€ν…€ μ—λŸ¬ λ©”μ‹œμ§€
case customError(String)

/// μ²˜λ¦¬ν•˜μ§€ μ•Šμ€ HTTP μƒνƒœ μ½”λ“œ
case unhandledStatusCode(Int)

/// HTTP 응닡 였λ₯˜ (응닡 객체, μ—λŸ¬ λ©”μ‹œμ§€)
case httpResponseError(HTTPURLResponse, String)

/// μ‚¬μš©μžμ—κ²Œ ν‘œμ‹œν•  μ—λŸ¬ λ©”μ‹œμ§€
var errorDescription: String? {
switch self {
case .noData:
return "데이터λ₯Ό λ°›μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€"
case .customError(let message):
return message
case .unhandledStatusCode(let code):
return "μ²˜λ¦¬λ˜μ§€ μ•Šμ€ μƒνƒœ μ½”λ“œ: \(code)"
case .httpResponseError(let response, let message):
return "HTTP \(response.statusCode): \(message)"
}
}
}
19 changes: 19 additions & 0 deletions AppleStoreProductKiosk/Network/API/BaseAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// BaseAPI.swift
// AppleStoreProductKiosk
//
// Created by Wonji Suh on 9/16/25.
//

import Foundation

enum BaseAPI {
case baseURL

var description: String {
switch self {
case .baseURL:
return "https://applestoreproductkiosk.free.beeceptor.com"
}
}
}
19 changes: 19 additions & 0 deletions AppleStoreProductKiosk/Network/API/KioskProductAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// KioskProductAPI.swift
// AppleStoreProductKiosk
//
// Created by Wonji Suh on 9/16/25.
//

import Foundation

enum KioskProductAPI {
case productLists

var description: String {
switch self {
case .productLists:
return "/products"
}
}
}
22 changes: 22 additions & 0 deletions AppleStoreProductKiosk/Network/API/KioskProductDomain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// KioskProductDomain.swift
// AppleStoreProductKiosk
//
// Created by Wonji Suh on 9/16/25.
//

import Foundation

enum KioskProductDomain {
case producut
}


extension KioskProductDomain {
var url: String {
switch self {
case .producut:
return "/api/apple-store-kiosk"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Extension+Data.swift
// AppleStoreProductKiosk
//
// Created by Wonji Suh on 9/16/25.
//

import Foundation

/// Data νƒ€μž…μ— λŒ€ν•œ JSON λ””μ½”λ”© ν™•μž₯μž…λ‹ˆλ‹€.
extension Data {
/// Dataλ₯Ό μ§€μ •ν•œ Decodable νƒ€μž…μœΌλ‘œ λ””μ½”λ”©ν•©λ‹ˆλ‹€.
///
/// - Parameter type: λ””μ½”λ”©ν•  νƒ€μž…
/// - Returns: λ””μ½”λ”©λœ 객체
/// - Throws: λ””μ½”λ”© μ‹€νŒ¨ μ‹œ μ—λŸ¬ λ°œμƒ
func decoded<T: Decodable>(as type: T.Type) throws -> T {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: self)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// URLSessionLogger.swift
// AppleStoreProductKiosk
//
// Created by Wonji Suh on 9/16/25.
//

import Foundation
import LogMacro

/// URLSession λ„€νŠΈμ›Œν¬ μš”μ²­/응닡을 μ½˜μ†”μ— 둜그둜 좜λ ₯ν•˜λŠ” μ‹±κΈ€ν„΄ μœ ν‹Έλ¦¬ν‹°μž…λ‹ˆλ‹€.
///
/// - DEBUG λͺ¨λ“œμ—μ„œλ§Œ λ™μž‘ν•˜λ©°, μš”μ²­/μ‘λ‹΅μ˜ λ©”μ„œλ“œ, URL, 헀더, λ°”λ””, μ—λŸ¬ 등을 보기 μ’‹κ²Œ 좜λ ₯ν•©λ‹ˆλ‹€.
final class URLSessionLogger {
/// μ‹±κΈ€ν„΄ μΈμŠ€ν„΄μŠ€
static let shared = URLSessionLogger()

/// μ™ΈλΆ€μ—μ„œ 직접 μƒμ„±ν•˜μ§€ λͺ»ν•˜λ„둝 private init
private init() {}

// MARK: - μš”μ²­ 둜그

/// λ„€νŠΈμ›Œν¬ μš”μ²­ 정보λ₯Ό μ½˜μ†”μ— 좜λ ₯ν•©λ‹ˆλ‹€.
/// - Parameter request: λ‘œκΉ…ν•  URLRequest
@MainActor
func logRequest(_ request: URLRequest) {
#if DEBUG
Log.debug("⎑--------------------- REQUEST ---------------------⎀")

// HTTP λ©”μ„œλ“œ
if let method = request.httpMethod {
Log.network("[Method]", method)
}

// μš”μ²­ URL
if let url = request.url?.absoluteString {
Log.network("[URL]", url)
}

// 헀더
if let headers = request.allHTTPHeaderFields {
Log.network("[Headers]")
for (key, value) in headers {
Log.network(" \(key): \(value)")
}
}

// λ°”λ””
if let body = request.httpBody,
let bodyString = String(data: body, encoding: .utf8) {
Log.network("[Body]")
Log.network(" \(bodyString)")
}

Log.network("⎣------------------ END REQUEST --------------------⎦")
#endif
}

// MARK: - 응닡 둜그

/// λ„€νŠΈμ›Œν¬ 응닡 정보λ₯Ό μ½˜μ†”μ— 좜λ ₯ν•©λ‹ˆλ‹€.
/// - Parameters:
/// - data: 응닡 데이터
/// - response: URLResponse 객체
/// - error: λ„€νŠΈμ›Œν¬ μ—λŸ¬
@MainActor
func logResponse(data: Data?, response: URLResponse?, error: Error?) {
#if DEBUG
Log.network("⎑--------------------- RESPONSE --------------------⎀")

// 응닡 URL
if let url = response?.url?.absoluteString {
Log.network("[URL]", url)
}

// HTTP μƒνƒœ μ½”λ“œ 및 헀더
if let httpResponse = response as? HTTPURLResponse {
Log.network("[Status Code] \(httpResponse.statusCode)")
Log.network("[Response Headers]")
for (key, value) in httpResponse.allHeaderFields {
Log.network(" \(key): \(value)")
}
}

// λ°”λ”” (JSON pretty print μ‹œλ„, μ‹€νŒ¨ μ‹œ raw 좜λ ₯)
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
let prettyData = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])
if let prettyString = String(data: prettyData, encoding: .utf8) {
Log.network("[Body (Map)]")
Log.network(prettyString)
}
} catch {
// JSON λ””μ½”λ”© μ‹€νŒ¨ μ‹œ 원본 λ¬Έμžμ—΄ 좜λ ₯
if let rawString = String(data: data, encoding: .utf8) {
Log.network("[Body (Raw)]")
Log.network(rawString)
} else {
Log.network("[Body] Cannot decode data")
}
}
}

// μ—λŸ¬
if let error = error {
Log.network("[Error] \(error.localizedDescription)")
}

Log.network("⎣------------------ END RESPONSE -------------------⎦")
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// HTTPMethod.swift
// AppleStoreProductKiosk
//
// Created by Wonji Suh on 9/16/25.
//

import Foundation

/// HTTP μš”μ²­μ—μ„œ μ‚¬μš©λ˜λŠ” λ©”μ„œλ“œ νƒ€μž…μ„ μ •μ˜ν•œ enumμž…λ‹ˆλ‹€.
enum HTTPMethod: String {
/// GET: 데이터 쑰회
case get = "GET"
/// POST: 데이터 생성
case post = "POST"
/// PUT: 데이터 전체 μˆ˜μ •
case put = "PUT"
/// DELETE: 데이터 μ‚­μ œ
case delete = "DELETE"
}

Loading
Loading