Skip to content

Commit a227e3b

Browse files
authored
Merge branch 'master' into peng-u-0807/lecture-reminder
2 parents eb8cc5a + 82da079 commit a227e3b

File tree

111 files changed

+2547
-283
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+2547
-283
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ SNUTT-2022/SNUTT/GoogleService*.plist
3232
SNUTT-2022/archive
3333
SNUTT-2022/fastlane/report.xml
3434
**/*.p8
35-
.vscode
35+
.vscode
36+
37+
friends-react-native

SNUTT-2022/SNUTT/Assets/STFont.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ struct STFont {
2626
static let semibold13: UIFont = .systemFont(ofSize: 13, weight: .semibold)
2727
static let medium13: UIFont = .systemFont(ofSize: 13, weight: .medium)
2828
static let regular13: UIFont = .systemFont(ofSize: 13)
29-
29+
30+
static let bold12: UIFont = .systemFont(ofSize: 12, weight: .bold)
3031
static let regular12: UIFont = .systemFont(ofSize: 12)
3132

3233
static let bold11: UIFont = .systemFont(ofSize: 11, weight: .bold)

SNUTT-2022/SNUTT/ViewModels/ThemeSettingViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class ThemeSettingViewModel: BaseViewModel, ObservableObject {
131131
services.timetableService.selectTimetableTheme(theme: theme)
132132
services.themeService.closeBottomSheet()
133133

134-
guard let timetableId = targetTimetable?.id else { return }
134+
guard let timetableId = appState.timetable.current?.id else { return }
135135
do {
136136
try await services.timetableService.updateTimetableTheme(timetableId: timetableId)
137137
} catch {

SNUTT-2022/SNUTT/Views/Scenes/Settings/ThemeSettingScene.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ struct ThemeSettingScene: View {
9999
.resizable()
100100
.frame(width: 14, height: 14)
101101
Text("테마는 어떻게 적용하나요?")
102-
.font(.system(size: 12, weight: .bold))
102+
.font(STFont.bold12.font)
103103
.foregroundColor(STColor.gray2)
104104
}
105105
Text("시간표 적용은 시간표 목록 > 더보기 버튼 > 테마 설정에서 개별적으로 적용할 수 있어요.")
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//
2+
// DebugNetworkLoggingMiddleware.swift
3+
// SNUTT
4+
//
5+
// Copyright © 2025 wafflestudio.com. All rights reserved.
6+
//
7+
8+
#if DEBUG
9+
import Foundation
10+
import HTTPTypes
11+
import OpenAPIRuntime
12+
13+
public struct DebugNetworkLoggingMiddleware: ClientMiddleware {
14+
public init() {}
15+
16+
public func intercept(
17+
_ request: HTTPRequest,
18+
body: HTTPBody?,
19+
baseURL: URL,
20+
operationID _: String,
21+
next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
22+
) async throws -> (HTTPResponse, HTTPBody?) {
23+
let logID = UUID()
24+
let startTime = Date()
25+
26+
// Capture request details
27+
let requestURL = buildFullURL(baseURL: baseURL, request: request)
28+
let requestHeaders = extractHeaders(from: request.headerFields)
29+
let requestBody = await extractBody(from: body)
30+
31+
let requestLog = NetworkLogEntry.Request(
32+
method: request.method.rawValue,
33+
url: requestURL,
34+
headers: requestHeaders,
35+
body: requestBody
36+
)
37+
38+
// Create initial log entry
39+
let initialEntry = NetworkLogEntry(
40+
id: logID,
41+
timestamp: startTime,
42+
request: requestLog
43+
)
44+
45+
// Add to store
46+
await NetworkLogStore.shared.addLog(initialEntry)
47+
48+
// Execute request
49+
do {
50+
let (response, responseBody) = try await next(request, body, baseURL)
51+
let duration = Date().timeIntervalSince(startTime)
52+
53+
// Capture response details
54+
let responseData = try await Data(collecting: responseBody!, upTo: .max)
55+
let responseHeaders = extractHeaders(from: response.headerFields)
56+
let responseBodyString = extractBodyString(from: responseData)
57+
58+
let responseLog = NetworkLogEntry.Response(
59+
statusCode: response.status.code,
60+
headers: responseHeaders,
61+
body: responseBodyString
62+
)
63+
64+
// Update log entry with response
65+
await NetworkLogStore.shared.updateLog(id: logID, response: responseLog, duration: duration)
66+
67+
return (response, HTTPBody(responseData))
68+
} catch {
69+
// Update log entry with error
70+
await NetworkLogStore.shared.updateLog(id: logID, error: error.localizedDescription)
71+
72+
throw error
73+
}
74+
}
75+
76+
// MARK: - Helper Methods
77+
78+
private func buildFullURL(baseURL: URL, request: HTTPRequest) -> String {
79+
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
80+
components?.path = request.path ?? ""
81+
return components?.url?.absoluteString ?? baseURL.absoluteString
82+
}
83+
84+
private func extractHeaders(from headerFields: HTTPFields) -> [String: String] {
85+
var headers: [String: String] = [:]
86+
for field in headerFields {
87+
headers[field.name.rawName] = field.value
88+
}
89+
return headers
90+
}
91+
92+
private func extractBody(from body: HTTPBody?) async -> String? {
93+
guard let body = body else { return nil }
94+
95+
do {
96+
let data = try await Data(collecting: body, upTo: .max)
97+
return extractBodyString(from: data)
98+
} catch {
99+
return "Failed to extract body: \(error.localizedDescription)"
100+
}
101+
}
102+
103+
private func extractBodyString(from data: Data) -> String? {
104+
guard !data.isEmpty else { return nil }
105+
106+
// Try to parse as JSON for pretty printing
107+
if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []),
108+
let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
109+
let prettyString = String(data: prettyData, encoding: .utf8)
110+
{
111+
return prettyString
112+
}
113+
114+
// Fallback to plain string
115+
return String(data: data, encoding: .utf8) ?? "Binary data (\(data.count) bytes)"
116+
}
117+
}
118+
#endif
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// NetworkLogEntry.swift
3+
// SNUTT
4+
//
5+
// Copyright © 2025 wafflestudio.com. All rights reserved.
6+
//
7+
8+
#if DEBUG
9+
import Foundation
10+
import HTTPTypes
11+
12+
public struct NetworkLogEntry: Identifiable, Sendable {
13+
public let id: UUID
14+
public let timestamp: Date
15+
public let request: Request
16+
public let response: Response?
17+
public let duration: TimeInterval?
18+
public let error: String?
19+
20+
public init(
21+
id: UUID = UUID(),
22+
timestamp: Date = Date(),
23+
request: Request,
24+
response: Response? = nil,
25+
duration: TimeInterval? = nil,
26+
error: String? = nil
27+
) {
28+
self.id = id
29+
self.timestamp = timestamp
30+
self.request = request
31+
self.response = response
32+
self.duration = duration
33+
self.error = error
34+
}
35+
36+
public struct Request: Sendable {
37+
public let method: String
38+
public let url: String
39+
public let headers: [String: String]
40+
public let body: String?
41+
42+
public init(method: String, url: String, headers: [String: String], body: String?) {
43+
self.method = method
44+
self.url = url
45+
self.headers = headers
46+
self.body = body
47+
}
48+
}
49+
50+
public struct Response: Sendable {
51+
public let statusCode: Int
52+
public let headers: [String: String]
53+
public let body: String?
54+
55+
public init(statusCode: Int, headers: [String: String], body: String?) {
56+
self.statusCode = statusCode
57+
self.headers = headers
58+
self.body = body
59+
}
60+
}
61+
62+
// MARK: - Computed Properties
63+
64+
public var statusColor: String {
65+
guard let response = response else { return "gray" }
66+
switch response.statusCode {
67+
case 200..<300: return "green"
68+
case 300..<400: return "blue"
69+
case 400..<500: return "orange"
70+
case 500..<600: return "red"
71+
default: return "gray"
72+
}
73+
}
74+
75+
public var summary: String {
76+
let method = request.method
77+
let url = request.url
78+
if let response = response {
79+
return "\(method) \(url) - \(response.statusCode)"
80+
} else if let error = error {
81+
return "\(method) \(url) - Error: \(error)"
82+
} else {
83+
return "\(method) \(url) - Pending"
84+
}
85+
}
86+
87+
public var formattedDuration: String {
88+
guard let duration = duration else { return "N/A" }
89+
return String(format: "%.2f ms", duration * 1000)
90+
}
91+
92+
public func matches(searchText: String) -> Bool {
93+
guard !searchText.isEmpty else { return true }
94+
let lowercased = searchText.lowercased()
95+
96+
return request.url.lowercased().contains(lowercased) || request.method.lowercased().contains(lowercased)
97+
|| (response?.statusCode.description.contains(lowercased) ?? false)
98+
|| (request.body?.lowercased().contains(lowercased) ?? false)
99+
|| (response?.body?.lowercased().contains(lowercased) ?? false)
100+
}
101+
}
102+
#endif
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// NetworkLogStore.swift
3+
// SNUTT
4+
//
5+
// Copyright © 2025 wafflestudio.com. All rights reserved.
6+
//
7+
8+
#if DEBUG
9+
import Foundation
10+
import Observation
11+
12+
@MainActor
13+
@Observable
14+
public final class NetworkLogStore {
15+
public static let shared = NetworkLogStore()
16+
17+
public private(set) var logs: [NetworkLogEntry] = []
18+
private let maxLogs = 1000
19+
20+
private init() {}
21+
22+
public func addLog(_ log: NetworkLogEntry) {
23+
logs.insert(log, at: 0)
24+
if logs.count > maxLogs {
25+
logs = Array(logs.prefix(maxLogs))
26+
}
27+
}
28+
29+
public func updateLog(id: UUID, response: NetworkLogEntry.Response, duration: TimeInterval) {
30+
if let index = logs.firstIndex(where: { $0.id == id }) {
31+
let updatedLog = logs[index]
32+
logs[index] = NetworkLogEntry(
33+
id: updatedLog.id,
34+
timestamp: updatedLog.timestamp,
35+
request: updatedLog.request,
36+
response: response,
37+
duration: duration,
38+
error: nil
39+
)
40+
}
41+
}
42+
43+
public func updateLog(id: UUID, error: String) {
44+
if let index = logs.firstIndex(where: { $0.id == id }) {
45+
let updatedLog = logs[index]
46+
logs[index] = NetworkLogEntry(
47+
id: updatedLog.id,
48+
timestamp: updatedLog.timestamp,
49+
request: updatedLog.request,
50+
response: nil,
51+
duration: nil,
52+
error: error
53+
)
54+
}
55+
}
56+
57+
public func clearLogs() {
58+
logs.removeAll()
59+
}
60+
}
61+
#endif

SNUTT/Modules/Feature/APIClientInterface/Sources/Middlewares/AuthenticationMiddleware.swift renamed to SNUTT/Modules/Feature/APIClient/Sources/Infra/Middlewares/AuthenticationMiddleware.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Copyright © 2024 wafflestudio.com. All rights reserved.
66
//
77

8+
import APIClientInterface
89
import Foundation
910
import HTTPTypes
1011
import OpenAPIRuntime

SNUTT/Modules/Feature/APIClientInterface/Sources/Middlewares/ErrorDecodingMiddleware.swift renamed to SNUTT/Modules/Feature/APIClient/Sources/Infra/Middlewares/ErrorDecodingMiddleware.swift

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Copyright © 2025 wafflestudio.com. All rights reserved.
66
//
77

8+
import APIClientInterface
89
import Foundation
910
import HTTPTypes
1011
import OpenAPIRuntime
@@ -38,18 +39,14 @@ public struct ErrorDecodingMiddleware: ClientMiddleware {
3839
}
3940
}
4041

41-
/// Client-side representation of server errors that aren't predefined in the client.
42-
private struct ClientUnknownServerError: LocalizedError {
43-
var errorDescription: String?
44-
var failureReason: String?
45-
var recoverySuggestion: String?
46-
47-
init?(jsonData: [String: Any]) {
48-
guard jsonData["errcode"] != nil else { return nil }
49-
errorDescription = jsonData["title"] as? String
50-
failureReason = jsonData["displayMessage"] as? String
51-
52-
// Field not implemented by server, reserved for future use when added
53-
recoverySuggestion = jsonData["recoveryMessage"] as? String
42+
extension ClientUnknownServerError {
43+
public init?(jsonData: [String: Any]) {
44+
guard let errorCode = jsonData["errcode"] as? Int else { return nil }
45+
self.init(
46+
errorCode: errorCode,
47+
errorDescription: jsonData["title"] as? String,
48+
failureReason: jsonData["displayMessage"] as? String,
49+
recoverySuggestion: jsonData["recoveryMessage"] as? String
50+
)
5451
}
5552
}

0 commit comments

Comments
 (0)