256 lines
8.8 KiB
Swift
256 lines
8.8 KiB
Swift
import Foundation
|
|
|
|
// MARK: - MemberProfile Response
|
|
struct MemberProfileResponse: Codable {
|
|
let code: Int
|
|
let data: MemberProfile
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case code, data
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(code, forKey: .code)
|
|
try container.encode(data, forKey: .data)
|
|
}
|
|
}
|
|
|
|
// MARK: - MemberProfile
|
|
struct MemberProfile: Codable {
|
|
let materialCounter: MaterialCounter
|
|
let userInfo: MemberUserInfo
|
|
let storiesCount: Int
|
|
let conversationsCount: Int
|
|
let remainPoints: Int
|
|
let totalPoints: Int
|
|
let usedBytes: Int
|
|
let totalBytes: Int
|
|
let titleRankings: [String]
|
|
let medalInfos: [MedalInfo]
|
|
let membershipLevel: String
|
|
let membershipEndAt: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case materialCounter = "material_counter"
|
|
case userInfo = "user_info"
|
|
case storiesCount = "stories_count"
|
|
case conversationsCount = "conversations_count"
|
|
case remainPoints = "remain_points"
|
|
case totalPoints = "total_points"
|
|
case usedBytes = "used_bytes"
|
|
case totalBytes = "total_bytes"
|
|
case titleRankings = "title_rankings"
|
|
case medalInfos = "medal_infos"
|
|
case membershipLevel = "membership_level"
|
|
case membershipEndAt = "membership_end_at"
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
materialCounter = try container.decode(MaterialCounter.self, forKey: .materialCounter)
|
|
userInfo = try container.decode(MemberUserInfo.self, forKey: .userInfo)
|
|
storiesCount = try container.decode(Int.self, forKey: .storiesCount)
|
|
conversationsCount = try container.decode(Int.self, forKey: .conversationsCount)
|
|
remainPoints = try container.decode(Int.self, forKey: .remainPoints)
|
|
totalPoints = try container.decode(Int.self, forKey: .totalPoints)
|
|
usedBytes = try container.decode(Int.self, forKey: .usedBytes)
|
|
totalBytes = try container.decode(Int.self, forKey: .totalBytes)
|
|
titleRankings = try container.decode([String].self, forKey: .titleRankings)
|
|
|
|
if let medalInfos = try? container.decode([MedalInfo].self, forKey: .medalInfos) {
|
|
self.medalInfos = medalInfos
|
|
} else {
|
|
self.medalInfos = []
|
|
}
|
|
|
|
membershipLevel = try container.decode(String.self, forKey: .membershipLevel)
|
|
membershipEndAt = try container.decode(String.self, forKey: .membershipEndAt)
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(materialCounter, forKey: .materialCounter)
|
|
try container.encode(userInfo, forKey: .userInfo)
|
|
try container.encode(storiesCount, forKey: .storiesCount)
|
|
try container.encode(conversationsCount, forKey: .conversationsCount)
|
|
try container.encode(remainPoints, forKey: .remainPoints)
|
|
try container.encode(totalPoints, forKey: .totalPoints)
|
|
try container.encode(usedBytes, forKey: .usedBytes)
|
|
try container.encode(totalBytes, forKey: .totalBytes)
|
|
try container.encode(titleRankings, forKey: .titleRankings)
|
|
try container.encode(medalInfos, forKey: .medalInfos)
|
|
try container.encode(membershipLevel, forKey: .membershipLevel)
|
|
try container.encode(membershipEndAt, forKey: .membershipEndAt)
|
|
}
|
|
}
|
|
|
|
// MARK: - MemberUserInfo
|
|
struct MemberUserInfo: Codable {
|
|
let userId: String
|
|
let accessToken: String
|
|
let avatarFileUrl: String?
|
|
let nickname: String
|
|
let account: String
|
|
let email: String
|
|
let refreshToken: String?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case userId = "user_id"
|
|
case accessToken = "access_token"
|
|
case avatarFileUrl = "avatar_file_url"
|
|
case nickname, account, email
|
|
case refreshToken = "refresh_token"
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(userId, forKey: .userId)
|
|
try container.encode(accessToken, forKey: .accessToken)
|
|
try container.encodeIfPresent(avatarFileUrl, forKey: .avatarFileUrl)
|
|
try container.encode(nickname, forKey: .nickname)
|
|
try container.encode(account, forKey: .account)
|
|
try container.encode(email, forKey: .email)
|
|
try container.encodeIfPresent(refreshToken, forKey: .refreshToken)
|
|
}
|
|
}
|
|
|
|
// MARK: - MaterialCounter
|
|
struct MaterialCounter: Codable {
|
|
let userId: Int64
|
|
let totalCount: TotalCount
|
|
let categoryCount: [String: CategoryCount]
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case userId = "user_id"
|
|
case totalCount = "total_count"
|
|
case categoryCount = "category_count"
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(userId, forKey: .userId)
|
|
try container.encode(totalCount, forKey: .totalCount)
|
|
try container.encode(categoryCount, forKey: .categoryCount)
|
|
}
|
|
}
|
|
|
|
// MARK: - TotalCount
|
|
struct TotalCount: Codable {
|
|
let videoCount: Int
|
|
let photoCount: Int
|
|
let liveCount: Int
|
|
let videoLength: Double
|
|
let coverUrl: String?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case videoCount = "video_count"
|
|
case photoCount = "photo_count"
|
|
case liveCount = "live_count"
|
|
case videoLength = "video_length"
|
|
case coverUrl = "cover_url"
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(videoCount, forKey: .videoCount)
|
|
try container.encode(photoCount, forKey: .photoCount)
|
|
try container.encode(liveCount, forKey: .liveCount)
|
|
try container.encode(videoLength, forKey: .videoLength)
|
|
try container.encodeIfPresent(coverUrl, forKey: .coverUrl)
|
|
}
|
|
}
|
|
|
|
// MARK: - CategoryCount
|
|
struct CategoryCount: Codable {
|
|
let videoCount: Int
|
|
let photoCount: Int
|
|
let liveCount: Int
|
|
let videoLength: Double
|
|
let coverUrl: String?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case videoCount = "video_count"
|
|
case photoCount = "photo_count"
|
|
case liveCount = "live_count"
|
|
case videoLength = "video_length"
|
|
case coverUrl = "cover_url"
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(videoCount, forKey: .videoCount)
|
|
try container.encode(photoCount, forKey: .photoCount)
|
|
try container.encode(liveCount, forKey: .liveCount)
|
|
try container.encode(videoLength, forKey: .videoLength)
|
|
try container.encodeIfPresent(coverUrl, forKey: .coverUrl)
|
|
}
|
|
}
|
|
|
|
// MARK: - MedalInfo
|
|
struct MedalInfo: Codable, Identifiable {
|
|
let id: Int
|
|
let url: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id, url
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(id, forKey: .id)
|
|
try container.encode(url, forKey: .url)
|
|
}
|
|
}
|
|
|
|
// MARK: - API Response Wrapper
|
|
struct MemberAPIResponse<T: Codable>: Codable {
|
|
let code: Int
|
|
let message: String
|
|
let data: T
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case code, message, data
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(code, forKey: .code)
|
|
try container.encode(message, forKey: .message)
|
|
try container.encode(data, forKey: .data)
|
|
}
|
|
}
|
|
|
|
// MARK: - Date Formatter
|
|
class DateFormatterManager {
|
|
static let shared = DateFormatterManager()
|
|
|
|
let iso8601Full: ISO8601DateFormatter = {
|
|
let formatter = ISO8601DateFormatter()
|
|
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
return formatter
|
|
}()
|
|
|
|
private init() {}
|
|
}
|
|
|
|
// MARK: - JSON Decoder Extension
|
|
extension JSONDecoder {
|
|
static let `default`: JSONDecoder = {
|
|
let decoder = JSONDecoder()
|
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
|
decoder.dateDecodingStrategy = .custom { decoder -> Date in
|
|
let container = try decoder.singleValueContainer()
|
|
let dateString = try container.decode(String.self)
|
|
|
|
if let date = DateFormatterManager.shared.iso8601Full.date(from: dateString) {
|
|
return date
|
|
}
|
|
|
|
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)")
|
|
}
|
|
return decoder
|
|
}()
|
|
}
|