bugger/Sources/Services/Feishu/FeishuModels.swift

164 lines
5.0 KiB
Swift

import Foundation
struct FeishuAPIResponse<T: Decodable>: Decodable {
let code: Int
let msg: String
let data: T?
}
struct RecordListData: Decodable {
let items: [RecordItem]
let hasMore: Bool
let pageToken: String?
let total: Int?
}
struct RecordItem: Decodable {
let recordId: String
let fields: [String: JSONValue]
enum CodingKeys: String, CodingKey {
case recordId = "record_id"
case fields
}
}
struct OAuthTokenData: Decodable {
let accessToken: String
let tokenType: String?
let expiresIn: Int
let refreshToken: String?
let refreshExpiresIn: Int?
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case tokenType = "token_type"
case expiresIn = "expires_in"
case refreshToken = "refresh_token"
case refreshExpiresIn = "refresh_expires_in"
}
}
struct TenantAccessTokenData: Decodable {
let tenantAccessToken: String
let expire: Int
enum CodingKeys: String, CodingKey {
case tenantAccessToken = "tenant_access_token"
case expire
}
}
struct UserInfoData: Decodable {
let name: String?
let enName: String?
enum CodingKeys: String, CodingKey {
case name
case enName = "en_name"
}
}
enum JSONValue: Decodable {
case string(String)
case number(Double)
case bool(Bool)
case array([JSONValue])
case object([String: JSONValue])
case null
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(Double.self) {
self = .number(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode([JSONValue].self) {
self = .array(value)
} else if let value = try? container.decode([String: JSONValue].self) {
self = .object(value)
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unsupported JSON value"
)
}
}
var stringValue: String? {
if case let .string(value) = self { return value }
if case let .number(value) = self { return String(value) }
return nil
}
var firstUserName: String? {
guard case let .array(items) = self else { return nil }
for item in items {
if case let .object(fields) = item,
let name = fields["name"]?.stringValue {
return name
}
}
return nil
}
}
enum BugMapper {
static func map(
_ record: RecordItem,
config: AppConfig
) -> Bug {
let fields = record.fields
let mappings = config.fieldMappings
return Bug(
id: record.recordId,
title: stringValue(in: fields, key: mappings.titleField) ?? "Untitled",
priority: BugPriority(rawValue: stringValue(in: fields, key: mappings.priorityField) ?? "") ?? .unknown,
status: BugStatus(rawValue: stringValue(in: fields, key: mappings.statusField) ?? "") ?? .unknown,
assignee: userName(in: fields, key: mappings.assigneeField) ?? "Unknown",
reporter: userName(in: fields, key: mappings.reporterField),
createdAt: FeishuDateParser.parse(rawValue(in: fields, key: mappings.createdAtField)) ?? .distantPast,
updatedAt: FeishuDateParser.parse(rawValue(in: fields, key: mappings.updatedAtField)) ?? .distantPast,
feishuURL: FeishuURLBuilder.recordURL(
baseDomain: config.feishuBaseDomain,
appToken: config.appToken,
tableId: config.tableId,
recordId: record.recordId
)
)
}
private static func rawValue(in fields: [String: JSONValue], key: String) -> Any? {
guard let value = fields[key] else { return nil }
switch value {
case let .string(string): return string
case let .number(number): return number
case let .bool(bool): return bool
case let .array(array):
return array.compactMap { item -> String? in
if case let .object(fields) = item {
return fields["name"]?.stringValue
}
return item.stringValue
}
case let .object(object):
return object["name"]?.stringValue ?? object["text"]?.stringValue
case .null:
return nil
}
}
private static func stringValue(in fields: [String: JSONValue], key: String) -> String? {
fields[key]?.stringValue
}
private static func userName(in fields: [String: JSONValue], key: String) -> String? {
fields[key]?.firstUserName ?? fields[key]?.stringValue
}
}