import Foundation /// Builder for the MCMS list-endpoint query parameters. /// /// Supported by most list endpoints: `query`, `projection`, `sort`, `limit` /// (server default 1000), `skip`, `next` (cursor = a document `_id`), `distinct`. /// Stats/log endpoints additionally require `start-time` and accept `end-time` /// (UTC `yyyy-MM-dd HH:mm:ss`). struct APIQuery { /// Custom Mongo query as `key=value` pairs (joined with commas by the API). var query: [String: String] = [:] /// Field inclusion/exclusion: `1` include, `0` exclude. var projection: [String: Int] = [:] /// Sort field → direction (`1` asc, `-1` desc). var sort: [String: Int] = [:] var limit: Int? var skip: Int? /// Pagination cursor: excludes all items at/before this `_id`. var next: String? /// Attribute to fetch unique values for. var distinct: String? var startTime: Date? var endTime: Date? static let empty = APIQuery() private static let timeFormatter: DateFormatter = { let f = DateFormatter() f.locale = Locale(identifier: "en_US_POSIX") f.timeZone = TimeZone(identifier: "UTC") f.dateFormat = "yyyy-MM-dd HH:mm:ss" return f }() func queryItems() -> [URLQueryItem] { var items: [URLQueryItem] = [] if !query.isEmpty { let joined = query.map { "\($0)=\($1)" }.joined(separator: ",") items.append(.init(name: "query", value: joined)) } if !projection.isEmpty { let joined = projection.map { "\($0)=\($1)" }.joined(separator: ",") items.append(.init(name: "projection", value: joined)) } if !sort.isEmpty { let joined = sort.map { "\($0)=\($1)" }.joined(separator: ",") items.append(.init(name: "sort", value: joined)) } if let limit { items.append(.init(name: "limit", value: "\(limit)")) } if let skip { items.append(.init(name: "skip", value: "\(skip)")) } if let next { items.append(.init(name: "next", value: next)) } if let distinct { items.append(.init(name: "distinct", value: distinct)) } if let startTime { items.append(.init(name: "start-time", value: Self.timeFormatter.string(from: startTime))) } if let endTime { items.append(.init(name: "end-time", value: Self.timeFormatter.string(from: endTime))) } return items } /// Convenience for a recent time window on stats/log endpoints. static func window(lastHours hours: Int, limit: Int? = nil) -> APIQuery { var q = APIQuery() q.startTime = Calendar.current.date(byAdding: .hour, value: -hours, to: Date()) q.limit = limit return q } }