Packages/MoonriseKit/Sources/Feeds/Launches.swift
56 lines · 2216 bytes
1import Foundation
2import SkyEngine
3
4/// An upcoming launch as served by the Moonrise backend (from Launch Library 2).
5public struct Launch: Codable, Sendable, Identifiable, Hashable {
6 public var id: String
7 public var name: String
8 public var net: Date
9 public var windowStart: Date?
10 public var windowEnd: Date?
11 /// Launch Library 2 status abbreviation: Go, TBC, TBD, Hold, Success, Failure, …
12 public var status: String
13 public var statusName: String
14 public var provider: String
15 public var rocket: String
16 public var mission: String?
17 public var missionType: String?
18 public var missionDescription: String?
19 public var orbit: String?
20 public var pad: String
21 public var location: String
22 public var padLatitude: Double?
23 public var padLongitude: Double?
24 /// Element sets for this launch's objects (a Starlink train, rocket bodies, payloads), once known.
25 public var elements: [OrbitalElements]?
26 /// "supgp" (SpaceX data via CelesTrak, possibly predicted before launch) or "gp" (CelesTrak catalogue).
27 public var elementsSource: String?
28}
29
30public struct LaunchList: Codable, Sendable {
31 public var fetchedAt: Date?
32 public var launches: [Launch]
33 /// Launched in the last 14 days, latest first.
34 public var recent: [Launch]?
35}
36
37public enum LaunchFeed {
38 static let decoder: JSONDecoder = {
39 let d = JSONDecoder()
40 d.dateDecodingStrategy = .custom { decoder in
41 let s = try decoder.singleValueContainer().decode(String.self)
42 if let date = try? Date(s, strategy: .iso8601) { return date }
43 if let date = try? Date(s, strategy: Date.ISO8601FormatStyle(includingFractionalSeconds: true)) { return date }
44 throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "bad date \(s)"))
45 }
46 return d
47 }()
48
49 public static func decode(_ data: Data) throws -> LaunchList {
50 try decoder.decode(LaunchList.self, from: data)
51 }
52
53 public static func fetch(base: URL, session: URLSession = .shared) async throws -> LaunchList {
54 try decode(await session.data(from: base.appending(path: "v1/launches")).0)
55 }
56}