fixed some bugs

This commit is contained in:
2026-09-07 11:42:53 +08:00
parent aca36c78c1
commit 9230db3b69
12 changed files with 883 additions and 14 deletions

View File

@@ -0,0 +1,177 @@
import Foundation
struct UserResultSummary: Identifiable, Decodable {
let attemptId: String
let videoHash: String
let courseTitle: String
let sentenceIndex: Int
let sentenceText: String
let overallScore: Float
let passed: Bool
let createdAt: String
var id: String { attemptId }
private enum CodingKeys: String, CodingKey {
case overallScore = "overall_score"
case courseTitle = "course_title"
case sentenceIndex = "sentence_index"
case sentenceText = "sentence_text"
case createdAt = "created_at"
case attemptId = "attempt_id"
case videoHash = "video_hash"
case passed
}
}
struct UserDubShareSummary: Identifiable, Decodable {
let shareId: String
let videoHash: String
let courseTitle: String
let title: String
let segmentCount: Int
let averageScore: Float
let createdAt: String
var id: String { shareId }
private enum CodingKeys: String, CodingKey {
case averageScore = "average_score"
case courseTitle = "course_title"
case segmentCount = "segment_count"
case createdAt = "created_at"
case shareId = "share_id"
case videoHash = "video_hash"
case title
}
}
struct DubShareSegmentDetail: Decodable {
let index: Int
let overallScore: Float
let recognizedText: String
let scoreDetails: [String: Float]
let audioURL: String
private enum CodingKeys: String, CodingKey {
case overallScore = "overall_score"
case recognizedText = "recognized_text"
case scoreDetails = "score_details"
case audioURL = "audio_url"
case index
}
}
struct DubShareDetail: Decodable {
let shareId: String
let videoHash: String
let title: String
let segments: [DubShareSegmentDetail]
private enum CodingKeys: String, CodingKey {
case shareId = "share_id"
case videoHash = "video_hash"
case title
case segments
}
}
enum UserHistoryError: LocalizedError {
case badStatus(Int, String)
var errorDescription: String? {
switch self {
case .badStatus(let code, let body):
return "HTTP \(code): \(body.prefix(200))"
}
}
}
enum UserHistoryAPI {
static func results(token: String) async throws -> [UserResultSummary] {
try await getJSON([UserResultSummary].self, path: "api/v1/me/results", token: token)
}
static func dubShares(token: String) async throws -> [UserDubShareSummary] {
try await getJSON([UserDubShareSummary].self, path: "api/v1/me/dub-shares", token: token)
}
static func dubShareDetail(token: String, shareId: String) async throws -> DubShareDetail {
try await getJSON(
DubShareDetail.self,
path: "api/v1/dub-shares/\(shareId)",
token: token
)
}
static func deleteResult(token: String, attemptId: String) async throws {
try await delete(path: "api/v1/me/results/\(attemptId)", token: token)
}
static func deleteDubShare(token: String, shareId: String) async throws {
try await delete(path: "api/v1/me/dub-shares/\(shareId)", token: token)
}
static func downloadAudio(
token: String,
audioPath: String,
destination: URL
) async throws {
let result = try await HTTPClient.execute(
baseURL: AppConfig.serverBaseURL,
path: audioPath,
method: "GET",
timeout: AppConfig.readTimeout,
headers: authorizationHeaders(token: token)
)
guard (200...299).contains(result.statusCode) else {
throw UserHistoryError.badStatus(
result.statusCode,
String(data: result.body, encoding: .utf8) ?? ""
)
}
try? FileManager.default.removeItem(at: destination)
try result.body.write(to: destination, options: .atomic)
}
private static func getJSON<T: Decodable>(
_ type: T.Type,
path: String,
token: String
) async throws -> T {
let result = try await HTTPClient.execute(
baseURL: AppConfig.serverBaseURL,
path: path,
method: "GET",
timeout: AppConfig.readTimeout,
headers: authorizationHeaders(token: token)
)
guard (200...299).contains(result.statusCode) else {
throw UserHistoryError.badStatus(
result.statusCode,
String(data: result.body, encoding: .utf8) ?? ""
)
}
return try JSONDecoder().decode(T.self, from: result.body)
}
private static func delete(path: String, token: String) async throws {
let result = try await HTTPClient.execute(
baseURL: AppConfig.serverBaseURL,
path: path,
method: "DELETE",
timeout: AppConfig.readTimeout,
headers: authorizationHeaders(token: token)
)
guard (200...299).contains(result.statusCode) else {
throw UserHistoryError.badStatus(
result.statusCode,
String(data: result.body, encoding: .utf8) ?? ""
)
}
}
private static func authorizationHeaders(token: String) -> [String: String] {
token.isEmpty ? [:] : ["Authorization": "Bearer \(token)"]
}
}