Transform to multi-account support
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// MeteClient.swift
|
||||
// meterios
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum MeteClientError: Error {
|
||||
case StatusCodeLooksBad(explanation: String)
|
||||
}
|
||||
|
||||
/// **MeteClient** is the client-side implementation of the Space Market API. Pure Swift, no platform-specific frameworks used.
|
||||
class MeteClient {
|
||||
private let address: String
|
||||
private let userID: Int
|
||||
|
||||
init(address: String, userID: Int) {
|
||||
self.address = address
|
||||
self.userID = userID
|
||||
}
|
||||
|
||||
func CanConnect() async throws -> Bool {
|
||||
// Send HTTP request to the drinks endpoint
|
||||
let url = URL(string: "https://\(address)/api/v1/users/stats.json")
|
||||
let (_, response) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
let statusCode = (response as? HTTPURLResponse)?.statusCode
|
||||
return statusCode == 200
|
||||
}
|
||||
|
||||
// GetDrinks returns the list of drinks available at the backend
|
||||
func GetDrinks() async throws -> Array<MeteClientDrink> {
|
||||
// Send HTTP request to the drinks endpoint
|
||||
let url = URL(string: "https://\(address)/api/v1/drinks")
|
||||
let (data, _) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Decode JSON to array of drinks
|
||||
let decoder = JSONDecoder()
|
||||
let drinks = try decoder.decode([MeteClientDrink].self, from: data)
|
||||
|
||||
return drinks
|
||||
}
|
||||
|
||||
// GetUser returns the user object for the given ID
|
||||
func GetUser() async throws -> MeteClientUser {
|
||||
// Send HTTP request to the user endpoint
|
||||
let url = URL(string: "https://\(address)/api/v1/users/\(userID).json")
|
||||
let (data, _) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Decode JSON to array of drinks
|
||||
let decoder = JSONDecoder()
|
||||
let user = try decoder.decode(MeteClientUser.self, from: data)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// GetUsers returns all users registered at the backend
|
||||
func GetUsers() async throws -> Array<MeteClientUser> {
|
||||
// Send HTTP request to the user endpoint
|
||||
let url = URL(string: "https://\(address)/api/v1/users.json")
|
||||
let (data, _) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Decode JSON to array of drinks
|
||||
let decoder = JSONDecoder()
|
||||
let users = try decoder.decode([MeteClientUser].self, from: data)
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
// BookDrink subtracts the recommended price off the balance from the user given by ID
|
||||
func BookDrink(_ drinkID: Int) async throws {
|
||||
// Send HTTP request to the user endpoint
|
||||
let url = URL(string: "https://\(address)/api/v1/users/\(userID)/buy.json?drink=\(drinkID)")
|
||||
let (_, response) = try await URLSession.shared.data(from: url!)
|
||||
|
||||
// Check status code
|
||||
let statusCode = (response as? HTTPURLResponse)?.statusCode
|
||||
if statusCode != 204 {
|
||||
throw MeteClientError.StatusCodeLooksBad(explanation: "Failed to book drink \(drinkID) for user \(userID)@\(address): HTTP status code \(statusCode ?? 0) received for \(url!)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// MeteClientDrink.swift
|
||||
// meterios
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
/// **MeteClientDrink** resembles a drink retrieved from Space Market API, Version 1. Pure Swift, no platform-specific frameworks used.
|
||||
struct MeteClientDrink: Identifiable, Codable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let logoURL: String
|
||||
let price: Float?
|
||||
let bottle_size: Float?
|
||||
let caffeine: Int?
|
||||
let active: Bool
|
||||
|
||||
// KeyMap is an enum covering the field names as used by SpaceMarket API
|
||||
private enum CodingKeys : String, CodingKey { case id, name, bottle_size, caffeine, price, active, logoURL = "logo_url" }
|
||||
|
||||
// Init function to be used with JSONDecoder using SpaceMarket API's strange types
|
||||
init(from decoder : Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.id = try container.decode(Int.self, forKey: .id)
|
||||
self.name = try container.decode(String.self, forKey: .name)
|
||||
self.bottle_size = Float((try? container.decode(String.self, forKey: .bottle_size)) ?? "")
|
||||
self.caffeine = (try? container.decode(Int.self, forKey: .caffeine))
|
||||
self.price = Float((try? container.decode(String.self, forKey: .price)) ?? "")
|
||||
self.active = (try? container.decode(Bool.self, forKey: .active)) ?? false
|
||||
self.logoURL = (try? container.decode(String.self, forKey: .logoURL)) ?? ""
|
||||
}
|
||||
|
||||
// Init function, as straight-forward as it gets
|
||||
init(id: Int, name: String, logoURL: String = "", price: Float = 0, bottle_size: Float = 0, caffeine: Int = 0, active: Bool = true) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.logoURL = logoURL
|
||||
self.price = price
|
||||
self.bottle_size = bottle_size
|
||||
self.caffeine = caffeine
|
||||
self.active = active
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// MeteClientUser.swift
|
||||
// meterios
|
||||
//
|
||||
// (c) 2025 Martin "maride" Dessauer
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
/// **MeteClientUser** resembles a user retrieved from Space Market API, Version 1. Pure Swift, no platform-specific frameworks used.
|
||||
struct MeteClientUser: Decodable, Identifiable, Hashable {
|
||||
let id: UUID = UUID() // Careful: meterios-internal ID, not Mete/Backend user accoint ID!
|
||||
let meteID: Int
|
||||
let displayName: String
|
||||
let displayImageURL: URL?
|
||||
let balance: Float?
|
||||
|
||||
private enum KeyMap : String, CodingKey { case id, name, email, balance }
|
||||
|
||||
// Init function to be used with JSONDecoder, converting from SpaceMarket API's strange types to proper types
|
||||
init(from decoder : Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: KeyMap.self)
|
||||
self.meteID = try container.decode(Int.self, forKey: .id)
|
||||
self.displayName = try container.decode(String.self, forKey: .name)
|
||||
self.displayImageURL = URL(string: String(
|
||||
format: "https://secure.gravatar.com/avatar/%@?s=300",
|
||||
Insecure.MD5.hash(
|
||||
// e-mails may be empty as they are not required during the registration process in Mete
|
||||
// however, Gravatar requires them for the profile picture functionality.
|
||||
// Default to the profile picture of md5("0"), which is simply the Gravatar logo on blue background
|
||||
data: (((try? container.decode(String.self, forKey: .email)) ?? "0").data(using: .utf8)!)
|
||||
).compactMap {
|
||||
String(format: "%02x", $0)
|
||||
}.joined()
|
||||
))
|
||||
self.balance = Float(try container.decode(String.self, forKey: .balance))
|
||||
}
|
||||
|
||||
// Init function, as straight-forward as it gets
|
||||
init(meteID: Int, displayName: String, displayImageURL: URL?, balance: Float) {
|
||||
self.meteID = meteID
|
||||
self.displayName = displayName
|
||||
self.displayImageURL = displayImageURL
|
||||
self.balance = balance
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user