Merge pull request 'fix: decode actual API response format' (#29) from schwifty/fix-api-response-decoding into main
This commit is contained in:
commit
1624e0e59d
3 changed files with 228 additions and 123 deletions
|
|
@ -6,11 +6,42 @@ struct Business: Identifiable, Codable, Hashable {
|
||||||
let imageExtension: String?
|
let imageExtension: String?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case id = "ID"
|
case businessId = "BusinessID"
|
||||||
case name = "BusinessName"
|
case name = "Name"
|
||||||
|
// Fallbacks for alternate API shapes
|
||||||
|
case altId = "ID"
|
||||||
|
case altName = "BusinessName"
|
||||||
case imageExtension = "ImageExtension"
|
case imageExtension = "ImageExtension"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
// BusinessID (from list endpoint) or ID (from other endpoints), always as String
|
||||||
|
if let bid = try? container.decode(Int.self, forKey: .businessId) {
|
||||||
|
id = String(bid)
|
||||||
|
} else if let bid = try? container.decode(String.self, forKey: .businessId) {
|
||||||
|
id = bid
|
||||||
|
} else if let aid = try? container.decode(Int.self, forKey: .altId) {
|
||||||
|
id = String(aid)
|
||||||
|
} else if let aid = try? container.decode(String.self, forKey: .altId) {
|
||||||
|
id = aid
|
||||||
|
} else {
|
||||||
|
id = ""
|
||||||
|
}
|
||||||
|
// Name (from list endpoint) or BusinessName (from other endpoints)
|
||||||
|
name = (try? container.decode(String.self, forKey: .name))
|
||||||
|
?? (try? container.decode(String.self, forKey: .altName))
|
||||||
|
?? ""
|
||||||
|
imageExtension = try? container.decode(String.self, forKey: .imageExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(id, forKey: .businessId)
|
||||||
|
try container.encode(name, forKey: .name)
|
||||||
|
try container.encodeIfPresent(imageExtension, forKey: .imageExtension)
|
||||||
|
}
|
||||||
|
|
||||||
var headerImageURL: URL? {
|
var headerImageURL: URL? {
|
||||||
guard let ext = imageExtension, !ext.isEmpty else { return nil }
|
guard let ext = imageExtension, !ext.isEmpty else { return nil }
|
||||||
return URL(string: "\(APIConfig.imageBaseURL)/businesses/\(id)/header.\(ext)")
|
return URL(string: "\(APIConfig.imageBaseURL)/businesses/\(id)/header.\(ext)")
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,38 @@ struct ServicePoint: Identifiable, Codable, Hashable {
|
||||||
let businessId: String
|
let businessId: String
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case id = "ID"
|
case servicePointId = "ServicePointID"
|
||||||
|
case altId = "ID"
|
||||||
case name = "Name"
|
case name = "Name"
|
||||||
case businessId = "BusinessID"
|
case businessId = "BusinessID"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
// ServicePointID (from list/save endpoints) or ID (fallback)
|
||||||
|
if let sid = try? container.decode(Int.self, forKey: .servicePointId) {
|
||||||
|
id = String(sid)
|
||||||
|
} else if let sid = try? container.decode(String.self, forKey: .servicePointId) {
|
||||||
|
id = sid
|
||||||
|
} else if let aid = try? container.decode(Int.self, forKey: .altId) {
|
||||||
|
id = String(aid)
|
||||||
|
} else if let aid = try? container.decode(String.self, forKey: .altId) {
|
||||||
|
id = aid
|
||||||
|
} else {
|
||||||
|
id = ""
|
||||||
|
}
|
||||||
|
name = (try? container.decode(String.self, forKey: .name)) ?? ""
|
||||||
|
if let bid = try? container.decode(Int.self, forKey: .businessId) {
|
||||||
|
businessId = String(bid)
|
||||||
|
} else {
|
||||||
|
businessId = (try? container.decode(String.self, forKey: .businessId)) ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(id, forKey: .servicePointId)
|
||||||
|
try container.encode(name, forKey: .name)
|
||||||
|
try container.encode(businessId, forKey: .businessId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,72 +110,105 @@ actor APIClient {
|
||||||
|
|
||||||
// MARK: - Businesses
|
// MARK: - Businesses
|
||||||
|
|
||||||
|
/// API returns: { "OK": true, "BUSINESSES": [...], "Businesses": [...] }
|
||||||
|
private struct BusinessListResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
|
let ERROR: String?
|
||||||
|
let BUSINESSES: [Business]?
|
||||||
|
let Businesses: [Business]?
|
||||||
|
|
||||||
|
var businesses: [Business] { BUSINESSES ?? Businesses ?? [] }
|
||||||
|
}
|
||||||
|
|
||||||
func listBusinesses(token: String) async throws -> [Business] {
|
func listBusinesses(token: String) async throws -> [Business] {
|
||||||
let data = try await post(path: "/businesses/list.php", body: [:], token: token)
|
let data = try await post(path: "/businesses/list.php", body: [:], token: token)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<[Business]>.self, from: data)
|
let resp = try JSONDecoder().decode(BusinessListResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to load businesses")
|
throw APIError.serverError(resp.ERROR ?? "Failed to load businesses")
|
||||||
}
|
}
|
||||||
return resp.data ?? []
|
return resp.businesses
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Service Points
|
// MARK: - Service Points
|
||||||
|
|
||||||
|
/// API returns: { "OK": true, "SERVICEPOINTS": [...], "GRANTED_SERVICEPOINTS": [...] }
|
||||||
|
private struct ServicePointListResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
|
let ERROR: String?
|
||||||
|
let SERVICEPOINTS: [ServicePoint]?
|
||||||
|
}
|
||||||
|
|
||||||
func listServicePoints(businessId: String, token: String) async throws -> [ServicePoint] {
|
func listServicePoints(businessId: String, token: String) async throws -> [ServicePoint] {
|
||||||
let body: [String: Any] = ["BusinessID": businessId]
|
let body: [String: Any] = ["BusinessID": businessId]
|
||||||
let data = try await post(path: "/servicepoints/list.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/servicepoints/list.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<[ServicePoint]>.self, from: data)
|
let resp = try JSONDecoder().decode(ServicePointListResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to load service points")
|
throw APIError.serverError(resp.ERROR ?? "Failed to load service points")
|
||||||
}
|
}
|
||||||
return resp.data ?? []
|
return resp.SERVICEPOINTS ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// API returns: { "OK": true, "SERVICEPOINT": { ... } }
|
||||||
|
private struct ServicePointSaveResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
|
let ERROR: String?
|
||||||
|
let SERVICEPOINT: ServicePoint?
|
||||||
}
|
}
|
||||||
|
|
||||||
func createServicePoint(name: String, businessId: String, token: String) async throws -> ServicePoint {
|
func createServicePoint(name: String, businessId: String, token: String) async throws -> ServicePoint {
|
||||||
let body: [String: Any] = ["Name": name, "BusinessID": businessId]
|
let body: [String: Any] = ["Name": name, "BusinessID": businessId]
|
||||||
let data = try await post(path: "/servicepoints/save.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/servicepoints/save.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<ServicePoint>.self, from: data)
|
let resp = try JSONDecoder().decode(ServicePointSaveResponse.self, from: data)
|
||||||
guard resp.success, let sp = resp.data else {
|
guard resp.OK, let sp = resp.SERVICEPOINT else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to create service point")
|
throw APIError.serverError(resp.ERROR ?? "Failed to create service point")
|
||||||
}
|
}
|
||||||
return sp
|
return sp
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Beacon Sharding
|
// MARK: - Beacon Sharding
|
||||||
|
|
||||||
struct NamespaceResponse: Codable {
|
/// API returns: { "OK": true, "BeaconShardUUID": "...", "BeaconMajor": 5 }
|
||||||
let uuid: String?
|
private struct AllocateNamespaceResponse: Codable {
|
||||||
let UUID: String?
|
let OK: Bool
|
||||||
let major: Int?
|
let ERROR: String?
|
||||||
let Major: Int?
|
let MESSAGE: String?
|
||||||
var shardUUID: String { uuid ?? UUID ?? "" }
|
let BeaconShardUUID: String?
|
||||||
var shardMajor: Int { major ?? Major ?? 0 }
|
let BeaconMajor: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
func allocateBusinessNamespace(businessId: String, token: String) async throws -> (uuid: String, major: Int) {
|
func allocateBusinessNamespace(businessId: String, token: String) async throws -> (uuid: String, major: Int) {
|
||||||
let body: [String: Any] = ["BusinessID": businessId]
|
let body: [String: Any] = ["BusinessID": businessId]
|
||||||
let data = try await post(path: "/beacon-sharding/allocate_business_namespace.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/beacon-sharding/allocate_business_namespace.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<NamespaceResponse>.self, from: data)
|
let resp = try JSONDecoder().decode(AllocateNamespaceResponse.self, from: data)
|
||||||
guard resp.success, let ns = resp.data else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to allocate namespace")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to allocate namespace")
|
||||||
}
|
}
|
||||||
return (ns.shardUUID, ns.shardMajor)
|
return (resp.BeaconShardUUID ?? "", resp.BeaconMajor ?? 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MinorResponse: Codable {
|
/// API returns: { "OK": true, "BeaconMinor": 3 }
|
||||||
let minor: Int?
|
private struct AllocateMinorResponse: Codable {
|
||||||
let Minor: Int?
|
let OK: Bool
|
||||||
var allocated: Int { minor ?? Minor ?? 0 }
|
let ERROR: String?
|
||||||
|
let MESSAGE: String?
|
||||||
|
let BeaconMinor: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
func allocateMinor(businessId: String, servicePointId: String, token: String) async throws -> Int {
|
func allocateMinor(businessId: String, servicePointId: String, token: String) async throws -> Int {
|
||||||
let body: [String: Any] = ["BusinessID": businessId, "ServicePointID": servicePointId]
|
let body: [String: Any] = ["BusinessID": businessId, "ServicePointID": servicePointId]
|
||||||
let data = try await post(path: "/beacon-sharding/allocate_servicepoint_minor.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/beacon-sharding/allocate_servicepoint_minor.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<MinorResponse>.self, from: data)
|
let resp = try JSONDecoder().decode(AllocateMinorResponse.self, from: data)
|
||||||
guard resp.success, let m = resp.data else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to allocate minor")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to allocate minor")
|
||||||
}
|
}
|
||||||
return m.allocated
|
return resp.BeaconMinor ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// API returns: { "OK": true, "BeaconHardwareID": 42, ... }
|
||||||
|
private struct OKResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
|
let ERROR: String?
|
||||||
|
let MESSAGE: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerBeaconHardware(
|
func registerBeaconHardware(
|
||||||
|
|
@ -198,9 +231,9 @@ actor APIClient {
|
||||||
]
|
]
|
||||||
if let mac = macAddress { body["MacAddress"] = mac }
|
if let mac = macAddress { body["MacAddress"] = mac }
|
||||||
let data = try await post(path: "/beacon-sharding/register_beacon_hardware.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/beacon-sharding/register_beacon_hardware.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<EmptyData>.self, from: data)
|
let resp = try JSONDecoder().decode(OKResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to register beacon")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to register beacon")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,23 +245,24 @@ actor APIClient {
|
||||||
) async throws {
|
) async throws {
|
||||||
let body: [String: Any] = ["UUID": uuid, "Major": major, "Minor": minor]
|
let body: [String: Any] = ["UUID": uuid, "Major": major, "Minor": minor]
|
||||||
let data = try await post(path: "/beacon-sharding/verify_beacon_broadcast.php", body: body, token: token)
|
let data = try await post(path: "/beacon-sharding/verify_beacon_broadcast.php", body: body, token: token)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<EmptyData>.self, from: data)
|
let resp = try JSONDecoder().decode(OKResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to verify broadcast")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to verify broadcast")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResolveResponse: Codable {
|
/// API returns: { "OK": true, "BusinessName": "...", "BusinessID": 5 }
|
||||||
let businessName: String?
|
private struct ResolveBusinessResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
|
let ERROR: String?
|
||||||
let BusinessName: String?
|
let BusinessName: String?
|
||||||
var name: String { businessName ?? BusinessName ?? "Unknown" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveBusiness(uuid: String, major: Int, token: String) async throws -> String {
|
func resolveBusiness(uuid: String, major: Int, token: String) async throws -> String {
|
||||||
let body: [String: Any] = ["UUID": uuid, "Major": major]
|
let body: [String: Any] = ["UUID": uuid, "Major": major]
|
||||||
let data = try await post(path: "/beacon-sharding/resolve_business.php", body: body, token: token)
|
let data = try await post(path: "/beacon-sharding/resolve_business.php", body: body, token: token)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<ResolveResponse>.self, from: data)
|
let resp = try JSONDecoder().decode(ResolveBusinessResponse.self, from: data)
|
||||||
return resp.data?.name ?? "Unknown"
|
return resp.BusinessName ?? "Unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Service Point Management
|
// MARK: - Service Point Management
|
||||||
|
|
@ -236,24 +270,24 @@ actor APIClient {
|
||||||
func deleteServicePoint(servicePointId: String, businessId: String, token: String) async throws {
|
func deleteServicePoint(servicePointId: String, businessId: String, token: String) async throws {
|
||||||
let body: [String: Any] = ["ID": servicePointId, "BusinessID": businessId]
|
let body: [String: Any] = ["ID": servicePointId, "BusinessID": businessId]
|
||||||
let data = try await post(path: "/servicepoints/delete.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/servicepoints/delete.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<EmptyData>.self, from: data)
|
let resp = try JSONDecoder().decode(OKResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to delete service point")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to delete service point")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateServicePoint(servicePointId: String, name: String, businessId: String, token: String) async throws {
|
func updateServicePoint(servicePointId: String, name: String, businessId: String, token: String) async throws {
|
||||||
let body: [String: Any] = ["ID": servicePointId, "Name": name, "BusinessID": businessId]
|
let body: [String: Any] = ["ID": servicePointId, "Name": name, "BusinessID": businessId]
|
||||||
let data = try await post(path: "/servicepoints/save.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/servicepoints/save.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<EmptyData>.self, from: data)
|
let resp = try JSONDecoder().decode(OKResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to update service point")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to update service point")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Beacon Management
|
// MARK: - Beacon Management
|
||||||
|
|
||||||
struct BeaconListResponse: Codable {
|
struct BeaconListItem: Codable {
|
||||||
let id: String?
|
let id: String?
|
||||||
let ID: String?
|
let ID: String?
|
||||||
let uuid: String?
|
let uuid: String?
|
||||||
|
|
@ -272,125 +306,135 @@ actor APIClient {
|
||||||
let IsVerified: Bool?
|
let IsVerified: Bool?
|
||||||
}
|
}
|
||||||
|
|
||||||
func listBeacons(businessId: String, token: String) async throws -> [BeaconListResponse] {
|
/// API returns: { "OK": true, "BEACONS": [...] }
|
||||||
|
private struct BeaconListAPIResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
|
let ERROR: String?
|
||||||
|
let BEACONS: [BeaconListItem]?
|
||||||
|
}
|
||||||
|
|
||||||
|
func listBeacons(businessId: String, token: String) async throws -> [BeaconListItem] {
|
||||||
let body: [String: Any] = ["BusinessID": businessId]
|
let body: [String: Any] = ["BusinessID": businessId]
|
||||||
let data = try await post(path: "/beacon-sharding/list_beacons.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/beacons/list.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<[BeaconListResponse]>.self, from: data)
|
let resp = try JSONDecoder().decode(BeaconListAPIResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to list beacons")
|
throw APIError.serverError(resp.ERROR ?? "Failed to list beacons")
|
||||||
}
|
}
|
||||||
return resp.data ?? []
|
return resp.BEACONS ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
func decommissionBeacon(beaconId: String, businessId: String, token: String) async throws {
|
func decommissionBeacon(beaconId: String, businessId: String, token: String) async throws {
|
||||||
let body: [String: Any] = ["ID": beaconId, "BusinessID": businessId]
|
let body: [String: Any] = ["ID": beaconId, "BusinessID": businessId]
|
||||||
let data = try await post(path: "/beacon-sharding/decommission_beacon.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/beacons/wipe.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<EmptyData>.self, from: data)
|
let resp = try JSONDecoder().decode(OKResponse.self, from: data)
|
||||||
guard resp.success else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to decommission beacon")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to decommission beacon")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupByMac(macAddress: String, token: String) async throws -> BeaconListResponse? {
|
func lookupByMac(macAddress: String, token: String) async throws -> BeaconListItem? {
|
||||||
let body: [String: Any] = ["MacAddress": macAddress]
|
let body: [String: Any] = ["MacAddress": macAddress]
|
||||||
let data = try await post(path: "/beacon-sharding/lookup_by_mac.php", body: body, token: token)
|
let data = try await post(path: "/beacons/lookupByMac.php", body: body, token: token)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<BeaconListResponse>.self, from: data)
|
// This may return a single beacon object or OK: false
|
||||||
return resp.data
|
struct LookupResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
|
let ID: String?
|
||||||
|
let UUID: String?
|
||||||
|
let Major: Int?
|
||||||
|
let Minor: Int?
|
||||||
|
let MacAddress: String?
|
||||||
|
let BeaconType: String?
|
||||||
|
let ServicePointID: String?
|
||||||
|
let IsVerified: Bool?
|
||||||
|
}
|
||||||
|
let resp = try JSONDecoder().decode(LookupResponse.self, from: data)
|
||||||
|
guard resp.OK, resp.ID != nil else { return nil }
|
||||||
|
return BeaconListItem(
|
||||||
|
id: resp.ID, ID: resp.ID,
|
||||||
|
uuid: resp.UUID, UUID: resp.UUID,
|
||||||
|
major: resp.Major, Major: resp.Major,
|
||||||
|
minor: resp.Minor, Minor: resp.Minor,
|
||||||
|
macAddress: resp.MacAddress, MacAddress: resp.MacAddress,
|
||||||
|
beaconType: resp.BeaconType, BeaconType: resp.BeaconType,
|
||||||
|
servicePointId: resp.ServicePointID, ServicePointID: resp.ServicePointID,
|
||||||
|
isVerified: resp.IsVerified, IsVerified: resp.IsVerified
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBeaconStatus(uuid: String, major: Int, minor: Int, token: String) async throws -> BeaconListResponse? {
|
func getBeaconStatus(uuid: String, major: Int, minor: Int, token: String) async throws -> BeaconListItem? {
|
||||||
let body: [String: Any] = ["UUID": uuid, "Major": major, "Minor": minor]
|
let body: [String: Any] = ["UUID": uuid, "Major": major, "Minor": minor]
|
||||||
let data = try await post(path: "/beacon-sharding/beacon_status.php", body: body, token: token)
|
let data = try await post(path: "/beacon-sharding/verify_beacon_broadcast.php", body: body, token: token)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<BeaconListResponse>.self, from: data)
|
let resp = try JSONDecoder().decode(OKResponse.self, from: data)
|
||||||
return resp.data
|
guard resp.OK else { return nil }
|
||||||
|
// The verify endpoint confirms status but doesn't return full beacon details
|
||||||
|
return BeaconListItem(
|
||||||
|
id: nil, ID: nil,
|
||||||
|
uuid: uuid, UUID: uuid,
|
||||||
|
major: major, Major: major,
|
||||||
|
minor: minor, Minor: minor,
|
||||||
|
macAddress: nil, MacAddress: nil,
|
||||||
|
beaconType: nil, BeaconType: nil,
|
||||||
|
servicePointId: nil, ServicePointID: nil,
|
||||||
|
isVerified: true, IsVerified: true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Beacon Config (server-configured values)
|
// MARK: - Beacon Config (server-configured values)
|
||||||
|
|
||||||
|
/// API returns: { "OK": true, "UUID": "...", "Major": 5, "Minor": 3, ... }
|
||||||
struct BeaconConfigResponse: Codable {
|
struct BeaconConfigResponse: Codable {
|
||||||
let uuid: String?
|
let OK: Bool
|
||||||
|
let ERROR: String?
|
||||||
|
let MESSAGE: String?
|
||||||
let UUID: String?
|
let UUID: String?
|
||||||
let major: Int?
|
|
||||||
let Major: Int?
|
let Major: Int?
|
||||||
let minor: Int?
|
|
||||||
let Minor: Int?
|
let Minor: Int?
|
||||||
let txPower: Int?
|
|
||||||
let TxPower: Int?
|
let TxPower: Int?
|
||||||
let measuredPower: Int?
|
|
||||||
let MeasuredPower: Int?
|
let MeasuredPower: Int?
|
||||||
let advInterval: Int?
|
|
||||||
let AdvInterval: Int?
|
let AdvInterval: Int?
|
||||||
|
|
||||||
var configUUID: String { uuid ?? UUID ?? "" }
|
var configUUID: String { UUID ?? "" }
|
||||||
var configMajor: Int { major ?? Major ?? 0 }
|
var configMajor: Int { Major ?? 0 }
|
||||||
var configMinor: Int { minor ?? Minor ?? 0 }
|
var configMinor: Int { Minor ?? 0 }
|
||||||
var configTxPower: Int { txPower ?? TxPower ?? 1 }
|
var configTxPower: Int { TxPower ?? 1 }
|
||||||
var configMeasuredPower: Int { measuredPower ?? MeasuredPower ?? -100 }
|
var configMeasuredPower: Int { MeasuredPower ?? -100 }
|
||||||
var configAdvInterval: Int { advInterval ?? AdvInterval ?? 2 }
|
var configAdvInterval: Int { AdvInterval ?? 2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBeaconConfig(businessId: String, servicePointId: String, token: String) async throws -> BeaconConfigResponse {
|
func getBeaconConfig(businessId: String, servicePointId: String, token: String) async throws -> BeaconConfigResponse {
|
||||||
let body: [String: Any] = ["BusinessID": businessId, "ServicePointID": servicePointId]
|
let body: [String: Any] = ["BusinessID": businessId, "ServicePointID": servicePointId]
|
||||||
let data = try await post(path: "/beacon-sharding/get_beacon_config.php", body: body, token: token, businessId: businessId)
|
let data = try await post(path: "/beacon-sharding/get_beacon_config.php", body: body, token: token, businessId: businessId)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<BeaconConfigResponse>.self, from: data)
|
let resp = try JSONDecoder().decode(BeaconConfigResponse.self, from: data)
|
||||||
guard resp.success, let config = resp.data else {
|
guard resp.OK else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to get beacon config")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to get beacon config")
|
||||||
}
|
}
|
||||||
return config
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - User Profile
|
// MARK: - User Profile
|
||||||
|
|
||||||
|
/// Note: /users/profile.php endpoint may not exist on server.
|
||||||
|
/// Using a flat response decoder matching the standard API format.
|
||||||
struct UserProfile: Codable {
|
struct UserProfile: Codable {
|
||||||
let id: String?
|
let OK: Bool?
|
||||||
let ID: String?
|
let ID: IntOrString?
|
||||||
let firstName: String?
|
|
||||||
let FirstName: String?
|
let FirstName: String?
|
||||||
let lastName: String?
|
|
||||||
let LastName: String?
|
let LastName: String?
|
||||||
let contactNumber: String?
|
|
||||||
let ContactNumber: String?
|
let ContactNumber: String?
|
||||||
|
|
||||||
|
var userId: String { ID?.stringValue ?? "" }
|
||||||
|
var firstName: String { FirstName ?? "" }
|
||||||
|
var lastName: String { LastName ?? "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProfile(token: String) async throws -> UserProfile {
|
func getProfile(token: String) async throws -> UserProfile {
|
||||||
let data = try await post(path: "/users/profile.php", body: [:], token: token)
|
let data = try await post(path: "/users/profile.php", body: [:], token: token)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<UserProfile>.self, from: data)
|
let resp = try JSONDecoder().decode(UserProfile.self, from: data)
|
||||||
guard resp.success, let profile = resp.data else {
|
return resp
|
||||||
throw APIError.serverError(resp.message ?? "Failed to load profile")
|
|
||||||
}
|
|
||||||
return profile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
||||||
private struct EmptyData: Codable {}
|
|
||||||
|
|
||||||
private struct APIResponse<T: Codable>: Codable {
|
|
||||||
let success: Bool
|
|
||||||
let message: String?
|
|
||||||
let data: T?
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case success = "Success"
|
|
||||||
case message = "Message"
|
|
||||||
case data = "Data"
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
// Handle both bool and int/string for Success
|
|
||||||
if let b = try? container.decode(Bool.self, forKey: .success) {
|
|
||||||
success = b
|
|
||||||
} else if let i = try? container.decode(Int.self, forKey: .success) {
|
|
||||||
success = i != 0
|
|
||||||
} else {
|
|
||||||
success = false
|
|
||||||
}
|
|
||||||
message = try? container.decode(String.self, forKey: .message)
|
|
||||||
data = try? container.decode(T.self, forKey: .data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func post(
|
private func post(
|
||||||
path: String,
|
path: String,
|
||||||
body: [String: Any],
|
body: [String: Any],
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue