fixed some bugs
This commit is contained in:
177
ios/OralTrainer/Services/UserHistory.swift
Normal file
177
ios/OralTrainer/Services/UserHistory.swift
Normal 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)"]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user