From 8ece87ff57b0ae66f38120d8a26b33661625fa61 Mon Sep 17 00:00:00 2001 From: patrick Date: Thu, 28 Dec 2023 21:08:06 +0000 Subject: [PATCH] Start generating parse methods (#4) Co-authored-by: Smaug123 Reviewed-on: https://gitea.patrickstevens.co.uk/patrick/puregym-unofficial-dotnet/pulls/4 --- PureGym.App/AuthArg.fs | 4 +- PureGym.App/Authenticate.fs | 2 +- PureGym.App/PureGym.App.fsproj | 2 +- PureGym.App/Sessions.fs | 10 +- PureGym.Test/PureGym.Test.fsproj | 7 +- PureGym.Test/TestJson.fs | 312 ++++++++++++++++++++++++++ PureGym/Api.fs | 356 +----------------------------- PureGym/Client.fs | 39 ++++ PureGym/Dto.fs | 367 +++++++++++++++++++++++++++++++ PureGym/GeneratedClient.fs | 169 ++++++++++++++ PureGym/GeneratedDto.fs | 350 +++++++++++++++++++++++++++++ PureGym/PureGym.fsproj | 35 ++- PureGym/SurfaceBaseline.txt | 66 ++++-- PureGym/myriad.toml | 0 PureGym/version.json | 2 +- flake.lock | 12 +- flake.nix | 6 +- nix/deps.nix | 256 +++++++++++++-------- 18 files changed, 1504 insertions(+), 491 deletions(-) create mode 100644 PureGym.Test/TestJson.fs create mode 100644 PureGym/Client.fs create mode 100644 PureGym/Dto.fs create mode 100644 PureGym/GeneratedClient.fs create mode 100644 PureGym/GeneratedDto.fs create mode 100644 PureGym/myriad.toml diff --git a/PureGym.App/AuthArg.fs b/PureGym.App/AuthArg.fs index 0ca15ec..3866cdc 100644 --- a/PureGym.App/AuthArg.fs +++ b/PureGym.App/AuthArg.fs @@ -5,8 +5,8 @@ open PureGym type AuthArg = | [] Bearer_Token of string - | [] User_Email of string - | [] Pin of string + | [] User_Email of string + | [] Pin of string | [] Others of string interface IArgParserTemplate with diff --git a/PureGym.App/Authenticate.fs b/PureGym.App/Authenticate.fs index 2f61c36..7bb4e32 100644 --- a/PureGym.App/Authenticate.fs +++ b/PureGym.App/Authenticate.fs @@ -14,7 +14,7 @@ type GetTokenArg = match s with | GetTokenArg.Pin _ -> "Eight-digit PureGym user's PIN" | GetTokenArg.User_Email _ -> "PureGym user's email address" - | GetTokenArg.StdIn _ -> "Read anything not specified on the command line from stdin" + | GetTokenArg.StdIn -> "Read anything not specified on the command line from stdin" static member Parse (args : ParseResults) : Result = let canUseStdin = args.TryGetResult(GetTokenArg.StdIn).IsSome diff --git a/PureGym.App/PureGym.App.fsproj b/PureGym.App/PureGym.App.fsproj index 0a7b218..2422fb1 100644 --- a/PureGym.App/PureGym.App.fsproj +++ b/PureGym.App/PureGym.App.fsproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 diff --git a/PureGym.App/Sessions.fs b/PureGym.App/Sessions.fs index 4e641f6..243cd14 100644 --- a/PureGym.App/Sessions.fs +++ b/PureGym.App/Sessions.fs @@ -17,8 +17,8 @@ type SessionsArgsFragment = type SessionsArgs = { Creds : Auth - FromDate : DateTime - ToDate : DateTime + FromDate : DateOnly + ToDate : DateOnly } static member Parse @@ -31,8 +31,8 @@ type SessionsArgs = { Creds = auth - FromDate = DateTime.Parse fromDate - ToDate = DateTime.Parse toDate + FromDate = DateOnly.Parse fromDate + ToDate = DateOnly.Parse toDate } |> Ok @@ -42,7 +42,7 @@ module Sessions = let run (args : SessionsArgs) = task { let! client = Api.make args.Creds - let! activity = client.GetSessions args.FromDate args.ToDate + let! activity = client.GetSessions (args.FromDate, args.ToDate) System.Console.WriteLine (string activity) return 0 diff --git a/PureGym.Test/PureGym.Test.fsproj b/PureGym.Test/PureGym.Test.fsproj index 05ad172..1624b9b 100644 --- a/PureGym.Test/PureGym.Test.fsproj +++ b/PureGym.Test/PureGym.Test.fsproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false true @@ -9,12 +9,15 @@ + + + - + diff --git a/PureGym.Test/TestJson.fs b/PureGym.Test/TestJson.fs new file mode 100644 index 0000000..0b8df68 --- /dev/null +++ b/PureGym.Test/TestJson.fs @@ -0,0 +1,312 @@ +namespace PureGym.Test + +open System +open System.Text.Json.Nodes +open NUnit.Framework +open FsUnitTyped +open PureGym + +[] +module TestJson = + + let gymOpeningHoursCases = + [ + """{"openingHours": [], "isAlwaysOpen": false}""", + { + GymOpeningHours.OpeningHours = [] + IsAlwaysOpen = false + } + """{"openingHours": ["something"], "isAlwaysOpen": false}""", + { + GymOpeningHours.OpeningHours = [ "something" ] + IsAlwaysOpen = false + } + ] + |> List.map TestCaseData + + [] + let ``GymOpeningHours JSON parse`` (json : string, expected : GymOpeningHours) = + JsonNode.Parse json |> GymOpeningHours.jsonParse |> shouldEqual expected + + let gymAccessOptionsCases = + List.allPairs [ true ; false ] [ true ; false ] + |> List.map (fun (a, b) -> + let s = sprintf """{"pinAccess": %b, "qrCodeAccess": %b}""" a b + + s, + { + GymAccessOptions.PinAccess = a + QrCodeAccess = b + } + ) + |> List.map TestCaseData + + [] + let ``GymAccessOptions JSON parse`` (json : string, expected : GymAccessOptions) = + JsonNode.Parse json |> GymAccessOptions.jsonParse |> shouldEqual expected + + let gymLocationCases = + [ + """{"latitude": 1.0, "longitude": 3.0}""", + { + GymLocation.Latitude = 1.0 + Longitude = 3.0 + } + ] + |> List.map TestCaseData + + [] + let ``GymLocation JSON parse`` (json : string, expected : GymLocation) = + JsonNode.Parse json |> GymLocation.jsonParse |> shouldEqual expected + + let gymAddressCases = + [ + """{"addressLine1": "", "postCode": "hi", "town": ""}""", + { + GymAddress.AddressLine1 = "" + AddressLine2 = None + AddressLine3 = None + County = None + Postcode = "hi" + Town = "" + } + """{"addressLine1": "", "addressLine2": null, "postCode": "hi", "town": ""}""", + { + GymAddress.AddressLine1 = "" + AddressLine2 = None + AddressLine3 = None + County = None + Postcode = "hi" + Town = "" + } + ] + |> List.map TestCaseData + + [] + let ``GymAddress JSON parse`` (json : string, expected : GymAddress) = + JsonNode.Parse (json, Nullable (JsonNodeOptions (PropertyNameCaseInsensitive = true))) + |> GymAddress.jsonParse + |> shouldEqual expected + + let gymCases = + let ovalJson = + """{"name":"London Oval","id":19,"status":2,"address":{"addressLine1":"Canterbury Court","addressLine2":"Units 4, 4A, 5 And 5A","addressLine3":"Kennington Park","town":"LONDON","county":null,"postcode":"SW9 6DE"},"phoneNumber":"+44 3444770005","emailAddress":"info.londonoval@puregym.com","staffMembers":null,"gymOpeningHours":{"isAlwaysOpen":true,"openingHours":[]},"reasonsToJoin":null,"accessOptions":{"pinAccess":true,"qrCodeAccess":true},"virtualTourUrl":null,"personalTrainersUrl":null,"webViewUrl":null,"floorPlanUrl":null,"location":{"longitude":"-0.110252","latitude":"51.480401"},"timeZone":"Europe/London","reopenDate":"2021-04-12T00:00:00+01 Europe/London"}""" + + let oval = + { + Gym.Name = "London Oval" + Id = 19 + Status = 2 + Address = + { + AddressLine1 = "Canterbury Court" + AddressLine2 = Some "Units 4, 4A, 5 And 5A" + AddressLine3 = Some "Kennington Park" + Town = "LONDON" + County = None + Postcode = "SW9 6DE" + } + PhoneNumber = "+44 3444770005" + EmailAddress = "info.londonoval@puregym.com" + GymOpeningHours = + { + IsAlwaysOpen = true + OpeningHours = [] + } + AccessOptions = + { + PinAccess = true + QrCodeAccess = true + } + Location = + { + Longitude = -0.110252 + Latitude = 51.480401 + } + TimeZone = "Europe/London" + ReopenDate = "2021-04-12T00:00:00+01 Europe/London" + } + + [ ovalJson, oval ] |> List.map TestCaseData + + [] + let ``Gym JSON parse`` (json : string, expected : Gym) = + JsonNode.Parse json |> Gym.jsonParse |> shouldEqual expected + + let memberCases = + let me = + { + Id = 1234567 + CompoundMemberId = "12A123456" + FirstName = "Patrick" + LastName = "Stevens" + HomeGymId = 19 + HomeGymName = "London Oval" + EmailAddress = "someone@somewhere" + GymAccessPin = "00000000" + DateOfBirth = DateOnly (1994, 01, 02) + MobileNumber = "+44 1234567" + Postcode = "W1A 1AA" + MembershipName = "Corporate" + MembershipLevel = 12 + SuspendedReason = 0 + MemberStatus = 2 + } + + let meJson = + """{ + "id": 1234567, + "compoundMemberId": "12A123456", + "firstName": "Patrick", + "lastName": "Stevens", + "homeGymId": 19, + "homeGymName": "London Oval", + "emailAddress": "someone@somewhere", + "gymAccessPin": "00000000", + "dateofBirth": "1994-01-02", + "mobileNumber": "+44 1234567", + "postCode": "W1A 1AA", + "membershipName": "Corporate", + "membershipLevel": 12, + "suspendedReason": 0, + "memberStatus": 2 +}""" + + [ meJson, me ] |> List.map TestCaseData + + [] + let ``Member JSON parse`` (json : string, expected : Member) = + json |> JsonNode.Parse |> Member.jsonParse |> shouldEqual expected + + let gymAttendanceCases = + let json = + """{ + "description": "65", + "totalPeopleInGym": 65, + "totalPeopleInClasses": 2, + "totalPeopleSuffix": null, + "isApproximate": false, + "attendanceTime": "2023-12-27T18:54:09.5101697", + "lastRefreshed": "2023-12-27T18:54:09.5101697Z", + "lastRefreshedPeopleInClasses": "2023-12-27T18:50:26.0782286Z", + "maximumCapacity": 0 +}""" + + let expected = + { + Description = "65" + TotalPeopleInGym = 65 + TotalPeopleInClasses = 2 + TotalPeopleSuffix = None + IsApproximate = false + AttendanceTime = + DateTime (2023, 12, 27, 18, 54, 09, 510, 169, DateTimeKind.Utc) + + TimeSpan.FromTicks 7L + LastRefreshed = + DateTime (2023, 12, 27, 18, 54, 09, 510, 169, DateTimeKind.Utc) + + TimeSpan.FromTicks 7L + LastRefreshedPeopleInClasses = + DateTime (2023, 12, 27, 18, 50, 26, 078, 228, DateTimeKind.Utc) + + TimeSpan.FromTicks 6L + MaximumCapacity = 0 + } + + [ json, expected ] |> List.map TestCaseData + + [] + let ``GymAttendance JSON parse`` (json : string, expected : GymAttendance) = + json |> JsonNode.Parse |> GymAttendance.jsonParse |> shouldEqual expected + + let memberActivityDtoCases = + let json = + """{"totalDuration":2217,"averageDuration":48,"totalVisits":46,"totalClasses":0,"isEstimated":false,"lastRefreshed":"2023-12-27T19:00:56.0309892Z"}""" + + let value = + { + TotalDuration = 2217 + AverageDuration = 48 + TotalVisits = 46 + TotalClasses = 0 + IsEstimated = false + LastRefreshed = + DateTime (2023, 12, 27, 19, 00, 56, 030, 989, DateTimeKind.Utc) + + TimeSpan.FromTicks 2L + } + + [ json, value ] |> List.map TestCaseData + + [] + let ``MemberActivityDto JSON parse`` (json : string, expected : MemberActivityDto) = + json |> JsonNode.Parse |> MemberActivityDto.jsonParse |> shouldEqual expected + + let sessionsCases = + let json = + """{ + "Summary":{"Total":{"Activities":0,"Visits":10,"Duration":445},"ThisWeek":{"Activities":0,"Visits":0,"Duration":0}}, + "Visits":[ + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-21T10:12:00","Duration":50,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-20T12:05:00","Duration":80,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-17T19:37:00","Duration":46,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-16T12:19:00","Duration":37,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-15T11:14:00","Duration":47,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-13T10:30:00","Duration":36,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-10T16:18:00","Duration":32,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-05T22:36:00","Duration":40,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-03T17:59:00","Duration":48,"Name":null}, + {"IsDurationEstimated":false,"Gym":{"Id":19,"Name":"London Oval","Status":"Blocked","Location":null,"GymAccess":null,"ContactInfo":null,"TimeZone":null},"StartTime":"2023-12-01T21:41:00","Duration":29,"Name":null}], + "Activities":[]} +""" + + let singleVisit startTime duration = + { + IsDurationEstimated = false + Gym = + { + Id = 19 + Name = "London Oval" + Status = "Blocked" + } + StartTime = startTime + Duration = duration + } + + let expected = + { + Summary = + { + Total = + { + Activities = 0 + Visits = 10 + Duration = 445 + } + ThisWeek = + { + Activities = 0 + Visits = 0 + Duration = 0 + } + } + Visits = + [ + singleVisit (DateTime (2023, 12, 21, 10, 12, 00)) 50 + singleVisit (DateTime (2023, 12, 20, 12, 05, 00)) 80 + singleVisit (DateTime (2023, 12, 17, 19, 37, 00)) 46 + singleVisit (DateTime (2023, 12, 16, 12, 19, 00)) 37 + singleVisit (DateTime (2023, 12, 15, 11, 14, 00)) 47 + singleVisit (DateTime (2023, 12, 13, 10, 30, 00)) 36 + singleVisit (DateTime (2023, 12, 10, 16, 18, 00)) 32 + singleVisit (DateTime (2023, 12, 05, 22, 36, 00)) 40 + singleVisit (DateTime (2023, 12, 03, 17, 59, 00)) 48 + singleVisit (DateTime (2023, 12, 01, 21, 41, 00)) 29 + ] + } + + [ json, expected ] |> List.map TestCaseData + + [] + let ``Sessions JSON parse`` (json : string, expected : Sessions) = + json + |> fun o -> JsonNode.Parse (o, Nullable (JsonNodeOptions (PropertyNameCaseInsensitive = true))) + |> Sessions.jsonParse + |> shouldEqual expected diff --git a/PureGym/Api.fs b/PureGym/Api.fs index 6ff4793..8a52448 100644 --- a/PureGym/Api.fs +++ b/PureGym/Api.fs @@ -2,361 +2,7 @@ namespace PureGym open System open System.Net.Http -open System.Text.Json.Serialization open System.Threading.Tasks -open RestEase - -/// Describes the opening hours of a given gym. -type GymOpeningHours = - { - /// If this is true, there should be no OpeningHours (but nothing enforces that). - IsAlwaysOpen : bool - /// This is a pretty unstructured list, which is in general not really parseable: it's human-readable only. - OpeningHours : string list - } - - /// Human-readable representation - override this.ToString () = - if this.IsAlwaysOpen then - "always open" - else - this.OpeningHours |> String.concat ", " - -/// How a human can authenticate with a gym when they physically try to enter it -type GymAccessOptions = - { - /// This gym has PIN entry pads - PinAccess : bool - /// This gym has a QR code scanner. QR codes can be generated with the PureGym app. - QrCodeAccess : bool - } - - /// Human-readable representation - override this.ToString () = - $"Pin access: %c{Char.emoji this.PinAccess}; QR code access: %c{Char.emoji this.QrCodeAccess}" - -/// Where a gym is on the Earth -type GymLocation = - { - /// Measured in degrees - Longitude : float - /// Measured in degrees - Latitude : float - } - -/// The postal address of a gym -type GymAddress = - { - /// E.g. "Canterbury Court" - [] - AddressLine1 : string - /// E.g. "Units 4, 4A, 5 And 5A" - AddressLine2 : string - /// E.g. "Kennington Park" - AddressLine3 : string - /// E.g. "LONDON" - [] - Town : string - County : string - /// E.g. "SW9 6DE" - [] - Postcode : string - } - - /// Human-readable statement of the address - override this.ToString () = - [ - yield Some this.AddressLine1 - yield this.AddressLine2 |> Option.ofObj - yield this.AddressLine3 |> Option.ofObj - match this.County with - | null -> yield Some $"%s{this.Town} %s{this.Postcode}" - | county -> - yield Some this.Town - yield Some $"%s{county} %s{this.Postcode}" - ] - |> Seq.choose id - |> String.concat "\n" - -/// Metadata about a physical gym -type Gym = - { - // The following fields are returned but are always null - // ReasonsToJoin : string - // VirtualTourUrl : Uri - // PersonalTrainersUrl : Uri - // WebViewUrl : Uri - // FloorPlanUrl : Uri - // StaffMembers : string - - /// The name of this gym, e.g. "London Oval" - [] - Name : string - /// This gym's ID in the PureGym system, e.g. 19 - [] - Id : int - /// I don't know what this status is. Please tell me if you know! - [] - Status : int - /// Postal address of this gym - [] - Address : GymAddress - /// Phone number of this gym, e.g. "+44 1234 567890" - [] - PhoneNumber : string - /// Contact email address for this gym's staff - [] - EmailAddress : string - /// When this gym is open - [] - GymOpeningHours : GymOpeningHours - /// How a human can physically authenticate when they physically enter this gym - [] - AccessOptions : GymAccessOptions - /// Where this gym is physically located - [] - Location : GymLocation - /// The IANA time zone this gym observes, e.g. "Europe/London" - [] - TimeZone : string - /// This is a date-time in the format yyyy-MM-ddTHH:mm:ss+01 Europe/London - ReopenDate : string - } - - /// Human-readable representation of the most important information about this gym - override this.ToString () = - $"""%s{this.Name} (%i{this.Id}) -{this.Address} -%s{this.EmailAddress} %s{this.PhoneNumber} -Opening hours: %s{string this.GymOpeningHours} -%s{string this.AccessOptions} -""" - -/// A human member of PureGym -type Member = - { - /// This member's ID. This is a fairly large number. - Id : int - /// No idea what this is - please tell me if you know! - CompoundMemberId : string - /// First name, e.g. "Patrick" - FirstName : string - /// Last name, e.g. "Stevens" - LastName : string - /// ID of the gym designated as this user's home gym. This is also the "Id" field of the appropriate Gym object. - HomeGymId : int - /// The name of the gym designated as this user's home gym. This is also the "Name" field of the appropriate - /// Gym object. - HomeGymName : string - /// This user's email address - EmailAddress : string - /// This user's gym access pin, probably 8 digits - GymAccessPin : string - /// This user's recorded date of birth - DateOfBirth : DateOnly - /// This user's phone number, human-readable - MobileNumber : string - /// This user's registered home postcode - Postcode : string - /// E.g. "Corporate" - MembershipName : string - MembershipLevel : int - SuspendedReason : int - MemberStatus : int - } - -/// Statistics for how many people are currently at a gym -type GymAttendance = - { - /// This appears always to be just equal to TotalPeopleInGym, but a string. - [] - Description : string - /// How many people are in the gym as of this statistics snapshot - [] - TotalPeopleInGym : int - /// How many people are in classes at the gym as of this statistics snapshot - [] - TotalPeopleInClasses : int - /// E.g. " or fewer" - TotalPeopleSuffix : string - [] - IsApproximate : bool - /// When the query was received (I think) - AttendanceTime : DateTime - /// When the "total people in gym" snapshot was taken that is reported here - LastRefreshed : DateTime - /// When the "number of people in classes" snapshot was taken that is reported here - LastRefreshedPeopleInClasses : DateTime - /// Maximum capacity of the gym, or 0 if no listed capacity - MaximumCapacity : int - } - - /// Human-readable representation - override this.ToString () = - let totalPeopleSuffix = - match this.TotalPeopleSuffix with - | null -> "" - | suffix -> suffix - - let capacity = - if this.MaximumCapacity = 0 then - "" - else - $" out of %i{this.MaximumCapacity} maximum" - - let classes = - if this.TotalPeopleInClasses = 0 then - "" - else - $"\n%i{this.TotalPeopleInClasses} in classes" - - $"""%i{this.TotalPeopleInGym}%s{totalPeopleSuffix} in gym%s{capacity} (is exact: %c{Char.emoji (not this.IsApproximate)})%s{classes} -Query made at %s{this.AttendanceTime.ToString "s"}%s{this.AttendanceTime.ToString "zzz"} -Snapshot correct as of %s{this.LastRefreshed.ToString "s"}%s{this.LastRefreshed.ToString "zzz"} -Classes info correct as of %s{this.LastRefreshedPeopleInClasses.ToString "s"}%s{this.LastRefreshedPeopleInClasses.ToString "zzz"}""" - -/// The visit statistics for a particular human to a particular gym. -/// The semantics of this class are basically unknown. -type MemberActivityThisMonth = - { - /// How many minutes, including classes, have been logged so far this month - TotalDurationMinutes : int - /// How long, in minutes, each visit has been on average this month - AverageDurationMinutes : int - /// How many visits have been made this month, excluding classes - TotalVisits : int - /// How many classes have been attended this month - TotalClasses : int - /// Whether this block of statistics is estimated rather than exact - IsEstimated : bool - /// When this data was constructed - LastRefreshed : DateTime - } - -/// Don't use this type. It's public because System.Text.Json can't do private types. -type MemberActivityDto = - { - [] - TotalDuration : int - [] - AverageDuration : int - [] - TotalVisits : int - [] - TotalClasses : int - [] - IsEstimated : bool - [] - LastRefreshed : DateTime - } - - member this.ToMemberActivity () = - { - TotalDurationMinutes = this.TotalDuration - AverageDurationMinutes = this.AverageDuration - TotalVisits = this.TotalVisits - TotalClasses = this.TotalClasses - IsEstimated = this.IsEstimated - LastRefreshed = this.LastRefreshed - } - -type SessionsAggregate = - { - /// Number of gym "activities" within some query-defined time period; presumably this is like classes? - /// It's always 0 for me. - Activities : int - /// Number of visits to the gym within some query-defined time period. - Visits : int - /// In minutes: total time spent in gym during the query-defined time period. - Duration : int - } - -/// The DTO for gym info returned from the Sessions endpoint. -type VisitGym = - { - // Omitting Location, GymAccess, ContactInfo, TimeZone because these were all null for me - /// The PureGym ID of this gym, e.g. 19 - Id : int - /// E.g. "London Oval", the canonical name of this gym - Name : string - /// For some reason this always seems to be "Blocked" - Status : string - } - -/// Summary of a single visit to a gym. -type Visit = - { - // Omitted Name because it always was null for me - /// Whether the Duration field is estimated. - IsDurationEstimated : bool - /// When the visit began. - StartTime : DateTime - /// In minutes. - Duration : int - /// Which gym was visited - Gym : VisitGym - } - - /// Human-readable non-round-trip representation. - override this.ToString () = - let startTime = this.StartTime.ToString "yyyy-MM-dd HH:mm" - $"%s{this.Gym.Name}: %s{startTime} (%i{this.Duration} minutes)" - -/// Aggregate statistics for gym visits across a time period. -type SessionsSummary = - { - /// Aggregate stats for gym visits within the query-dependent time period. - Total : SessionsAggregate - /// Aggregate stats for gym visits "this week", whatever that means to PureGym. - ThisWeek : SessionsAggregate - } - - /// Human-readable non-round-trip representation. - override this.ToString () = - $"%i{this.Total.Visits} visits, totalling %i{this.Total.Duration} minutes" - -type Sessions = - { - Summary : SessionsSummary - Visits : Visit list - } - - /// Human-readable non-round-trip representation. - override this.ToString () = - let summary = string this.Summary - let visits = this.Visits |> Seq.map string |> String.concat "\n" - - $"%s{summary}\n%s{visits}" - -/// The PureGym REST API. You probably want to instantiate one of these with `Api.make`. -[] -type IPureGymApi = - /// Get the complete list of all gyms known to PureGym. - [] - abstract GetGyms : unit -> Task - - /// Get information about the PureGym human whose credentials this client is authenticated with. - [] - abstract GetMember : unit -> Task - - /// Get information about how full the given gym currently is. The gym ID can be found from `GetGyms`. - [] - abstract GetGymAttendance : [] gymId : int -> Task - - /// Get information about a specific gym. - [] - abstract GetGym : [] gymId : int -> Task - - /// Get information about the activities logged against the currently authenticated PureGym human. - [] - abstract GetMemberActivity : unit -> Task - - /// Get information about the individual visits to all PureGyms the currently-authenticated PureGym human has made. - [] - abstract GetSessions : [] fromDate : DateTime -> [] toDate : DateTime -> Task - -// [] -// abstract GetMemberActivityAll : unit -> Task /// Methods for interacting with the PureGym REST API. [] @@ -377,5 +23,5 @@ module Api = client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0") - return RestClient.For client + return PureGymApi.make client } diff --git a/PureGym/Client.fs b/PureGym/Client.fs new file mode 100644 index 0000000..19fb745 --- /dev/null +++ b/PureGym/Client.fs @@ -0,0 +1,39 @@ +namespace PureGym + +open System +open System.Net.Http +open System.Threading +open System.Threading.Tasks +open RestEase + +/// The PureGym REST API. You probably want to instantiate one of these with `Api.make`. +[] +[] +type IPureGymApi = + /// Get the complete list of all gyms known to PureGym. + [] + abstract GetGyms : ?ct : CancellationToken -> Task + + /// Get information about the PureGym human whose credentials this client is authenticated with. + [] + abstract GetMember : ?ct : CancellationToken -> Task + + /// Get information about how full the given gym currently is. The gym ID can be found from `GetGyms`. + [] + abstract GetGymAttendance : [] gymId : int * ?ct : CancellationToken -> Task + + /// Get information about a specific gym. + [] + abstract GetGym : [] gymId : int * ?ct : CancellationToken -> Task + + /// Get information about the activities logged against the currently authenticated PureGym human. + [] + abstract GetMemberActivity : ?ct : CancellationToken -> Task + + /// Get information about the individual visits to all PureGyms the currently-authenticated PureGym human has made. + [] + abstract GetSessions : + [] fromDate : DateOnly * [] toDate : DateOnly * ?ct : CancellationToken -> Task + +// [] +// abstract GetMemberActivityAll : ?ct: CancellationToken -> Task diff --git a/PureGym/Dto.fs b/PureGym/Dto.fs new file mode 100644 index 0000000..175cb97 --- /dev/null +++ b/PureGym/Dto.fs @@ -0,0 +1,367 @@ +namespace PureGym + +open System +open System.Text.Json.Serialization + +/// Describes the opening hours of a given gym. +[] +type GymOpeningHours = + { + /// If this is true, there should be no OpeningHours (but nothing enforces that). + IsAlwaysOpen : bool + /// This is a pretty unstructured list, which is in general not really parseable: it's human-readable only. + OpeningHours : string list + } + + /// Human-readable representation + override this.ToString () = + if this.IsAlwaysOpen then + "always open" + else + this.OpeningHours |> String.concat ", " + +/// How a human can authenticate with a gym when they physically try to enter it +[] +type GymAccessOptions = + { + /// This gym has PIN entry pads + PinAccess : bool + /// This gym has a QR code scanner. QR codes can be generated with the PureGym app. + QrCodeAccess : bool + } + + /// Human-readable representation + override this.ToString () = + $"Pin access: %c{Char.emoji this.PinAccess}; QR code access: %c{Char.emoji this.QrCodeAccess}" + +/// Where a gym is on the Earth +[] +type GymLocation = + { + /// Measured in degrees + [] + Longitude : float + /// Measured in degrees + [] + Latitude : float + } + +/// The postal address of a gym +[] +type GymAddress = + { + /// E.g. "Canterbury Court" + [] + AddressLine1 : string + /// E.g. "Units 4, 4A, 5 And 5A" + AddressLine2 : string option + /// E.g. "Kennington Park" + AddressLine3 : string option + /// E.g. "LONDON" + [] + Town : string + /// Never seen this in the wild, sorry + County : string option + /// E.g. "SW9 6DE" + [] + Postcode : string + } + + /// Human-readable statement of the address + override this.ToString () = + [ + yield Some this.AddressLine1 + yield this.AddressLine2 + yield this.AddressLine3 + match this.County with + | None -> yield Some $"%s{this.Town} %s{this.Postcode}" + | Some county -> + yield Some this.Town + yield Some $"%s{county} %s{this.Postcode}" + ] + |> Seq.choose id + |> String.concat "\n" + +/// Metadata about a physical gym +[] +type Gym = + { + // The following fields are returned but are always null + // ReasonsToJoin : string + // VirtualTourUrl : Uri + // PersonalTrainersUrl : Uri + // WebViewUrl : Uri + // FloorPlanUrl : Uri + // StaffMembers : string + + /// The name of this gym, e.g. "London Oval" + [] + Name : string + /// This gym's ID in the PureGym system, e.g. 19 + [] + Id : int + /// I don't know what this status is. Please tell me if you know! + [] + Status : int + /// Postal address of this gym + [] + Address : GymAddress + /// Phone number of this gym, e.g. "+44 1234 567890" + [] + PhoneNumber : string + /// Contact email address for this gym's staff + [] + EmailAddress : string + /// When this gym is open + [] + GymOpeningHours : GymOpeningHours + /// How a human can physically authenticate when they physically enter this gym + [] + AccessOptions : GymAccessOptions + /// Where this gym is physically located + [] + Location : GymLocation + /// The IANA time zone this gym observes, e.g. "Europe/London" + [] + TimeZone : string + /// This is a date-time in the format yyyy-MM-ddTHH:mm:ss+01 Europe/London + ReopenDate : string + } + + /// Human-readable representation of the most important information about this gym + override this.ToString () = + $"""%s{this.Name} (%i{this.Id}) +{this.Address} +%s{this.EmailAddress} %s{this.PhoneNumber} +Opening hours: %s{string this.GymOpeningHours} +%s{string this.AccessOptions} +""" + +/// A human member of PureGym +[] +type Member = + { + /// This member's ID. This is a fairly large number. + Id : int + /// No idea what this is - please tell me if you know! + CompoundMemberId : string + /// First name, e.g. "Patrick" + FirstName : string + /// Last name, e.g. "Stevens" + LastName : string + /// ID of the gym designated as this user's home gym. This is also the "Id" field of the appropriate Gym object. + HomeGymId : int + /// The name of the gym designated as this user's home gym. This is also the "Name" field of the appropriate + /// Gym object. + HomeGymName : string + /// This user's email address + EmailAddress : string + /// This user's gym access pin, probably 8 digits + GymAccessPin : string + /// This user's recorded date of birth + [] + DateOfBirth : DateOnly + /// This user's phone number, human-readable + MobileNumber : string + /// This user's registered home postcode + [] + Postcode : string + /// E.g. "Corporate" + MembershipName : string + /// No idea what this is + MembershipLevel : int + /// No idea what this is + SuspendedReason : int + /// No idea what this is + MemberStatus : int + } + +/// Statistics for how many people are currently at a gym +[] +type GymAttendance = + { + /// This appears always to be just equal to TotalPeopleInGym, but a string. + [] + Description : string + /// How many people are in the gym as of this statistics snapshot + [] + TotalPeopleInGym : int + /// How many people are in classes at the gym as of this statistics snapshot + [] + TotalPeopleInClasses : int + /// E.g. " or fewer" + TotalPeopleSuffix : string option + /// Whether the number of people in the gym is approximate. This appears to become true when the number + /// of people in the gym is small enough (e.g. in Oval the threshold is 10). + [] + IsApproximate : bool + /// When the query was received (I think) + AttendanceTime : DateTime + /// When the "total people in gym" snapshot was taken that is reported here + LastRefreshed : DateTime + /// When the "number of people in classes" snapshot was taken that is reported here + LastRefreshedPeopleInClasses : DateTime + /// Maximum capacity of the gym, or 0 if no listed capacity + MaximumCapacity : int + } + + /// Human-readable representation + override this.ToString () = + let totalPeopleSuffix = + match this.TotalPeopleSuffix with + | None -> "" + | Some suffix -> suffix + + let capacity = + if this.MaximumCapacity = 0 then + "" + else + $" out of %i{this.MaximumCapacity} maximum" + + let classes = + if this.TotalPeopleInClasses = 0 then + "" + else + $"\n%i{this.TotalPeopleInClasses} in classes" + + $"""%i{this.TotalPeopleInGym}%s{totalPeopleSuffix} in gym%s{capacity} (is exact: %c{Char.emoji (not this.IsApproximate)})%s{classes} +Query made at %s{this.AttendanceTime.ToString "s"}%s{this.AttendanceTime.ToString "zzz"} +Snapshot correct as of %s{this.LastRefreshed.ToString "s"}%s{this.LastRefreshed.ToString "zzz"} +Classes info correct as of %s{this.LastRefreshedPeopleInClasses.ToString "s"}%s{this.LastRefreshedPeopleInClasses.ToString "zzz"}""" + +/// The visit statistics for a particular human to a particular gym. +/// The semantics of this class are basically unknown. +type MemberActivityThisMonth = + { + /// How many minutes, including classes, have been logged so far this month + TotalDurationMinutes : int + /// How long, in minutes, each visit has been on average this month + AverageDurationMinutes : int + /// How many visits have been made this month, excluding classes + TotalVisits : int + /// How many classes have been attended this month + TotalClasses : int + /// Whether this block of statistics is estimated rather than exact + IsEstimated : bool + /// When this data was constructed + LastRefreshed : DateTime + } + +/// Don't use this type. It's public because System.Text.Json can't do private types. +[] +type MemberActivityDto = + { + [] + TotalDuration : int + [] + AverageDuration : int + [] + TotalVisits : int + [] + TotalClasses : int + [] + IsEstimated : bool + [] + LastRefreshed : DateTime + } + + member this.ToMemberActivity () = + { + TotalDurationMinutes = this.TotalDuration + AverageDurationMinutes = this.AverageDuration + TotalVisits = this.TotalVisits + TotalClasses = this.TotalClasses + IsEstimated = this.IsEstimated + LastRefreshed = this.LastRefreshed + } + +/// Aggregation of visits made to some particular gym in some defined time period. +[] +type SessionsAggregate = + { + /// Number of gym "activities" within some query-defined time period; presumably this is like classes? + /// It's always 0 for me. + [] + Activities : int + /// Number of visits to the gym within some query-defined time period. + [] + Visits : int + /// In minutes: total time spent in gym during the query-defined time period. + [] + Duration : int + } + +/// The DTO for gym info returned from the Sessions endpoint. +[] +type VisitGym = + { + // Omitting Location, GymAccess, ContactInfo, TimeZone because these were all null for me + /// The PureGym ID of this gym, e.g. 19 + [] + Id : int + /// E.g. "London Oval", the canonical name of this gym + [] + Name : string + /// For some reason this always seems to be "Blocked" + [] + Status : string + } + +/// Summary of a single visit to a gym. +[] +type Visit = + { + // Omitted Name because it always was null for me + /// Whether the Duration field is estimated. + [] + IsDurationEstimated : bool + /// When the visit began. + [] + StartTime : DateTime + /// In minutes. + [] + Duration : int + /// Which gym was visited + [] + Gym : VisitGym + } + + /// Human-readable non-round-trip representation. + override this.ToString () = + let startTime = this.StartTime.ToString "yyyy-MM-dd HH:mm" + $"%s{this.Gym.Name}: %s{startTime} (%i{this.Duration} minutes)" + +/// Aggregate statistics for gym visits across a time period. +[] +type SessionsSummary = + { + /// Aggregate stats for gym visits within the query-dependent time period. + [] + Total : SessionsAggregate + /// Aggregate stats for gym visits "this week", whatever that means to PureGym. + [] + ThisWeek : SessionsAggregate + } + + /// Human-readable non-round-trippable representation + override this.ToString () = + $"%i{this.Total.Visits} visits, totalling %i{this.Total.Duration} minutes" + +/// Information about a particular user's visits to a particular gym. +[] +type Sessions = + { + /// Aggregated summary over some time period. + [] + Summary : SessionsSummary + /// List of all individual visits made within some time period. + [] + Visits : Visit list + } + + /// Human-readable non-round-trip representation. + override this.ToString () = + let summary = string this.Summary + let visits = this.Visits |> Seq.map string |> String.concat "\n" + + $"%s{summary}\n%s{visits}" diff --git a/PureGym/GeneratedClient.fs b/PureGym/GeneratedClient.fs new file mode 100644 index 0000000..4eb0939 --- /dev/null +++ b/PureGym/GeneratedClient.fs @@ -0,0 +1,169 @@ +//------------------------------------------------------------------------------ +// This code was generated by myriad. +// Changes to this file will be lost when the code is regenerated. +//------------------------------------------------------------------------------ + + +namespace PureGym + +open System +open System.Net.Http +open System.Threading +open System.Threading.Tasks +open RestEase + +/// Module for constructing a REST client. +[] +[] +module PureGymApi = + /// Create a REST client. + let make (client : System.Net.Http.HttpClient) : IPureGymApi = + { new IPureGymApi with + member _.GetGyms (ct : CancellationToken option) = + async { + let! ct = Async.CancellationToken + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Get, + RequestUri = System.Uri (client.BaseAddress.ToString () + "/v1/gyms/") + ) + + let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask + let response = response.EnsureSuccessStatusCode () + let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask + + let! node = + System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) + |> Async.AwaitTask + + return node.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + + member _.GetMember (ct : CancellationToken option) = + async { + let! ct = Async.CancellationToken + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Get, + RequestUri = System.Uri (client.BaseAddress.ToString () + "/v1/member") + ) + + let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask + let response = response.EnsureSuccessStatusCode () + let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask + + let! node = + System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) + |> Async.AwaitTask + + return Member.jsonParse node + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + + member _.GetGymAttendance (gymId : int, ct : CancellationToken option) = + async { + let! ct = Async.CancellationToken + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Get, + RequestUri = + System.Uri ( + client.BaseAddress.ToString () + + "/v1/gyms/{gym_id}/attendance".Replace ("{gym_id}", gymId.ToString ()) + ) + ) + + let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask + let response = response.EnsureSuccessStatusCode () + let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask + + let! node = + System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) + |> Async.AwaitTask + + return GymAttendance.jsonParse node + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + + member _.GetGym (gymId : int, ct : CancellationToken option) = + async { + let! ct = Async.CancellationToken + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Get, + RequestUri = + System.Uri ( + client.BaseAddress.ToString () + + "/v1/gyms/{gym_id}".Replace ("{gym_id}", gymId.ToString ()) + ) + ) + + let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask + let response = response.EnsureSuccessStatusCode () + let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask + + let! node = + System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) + |> Async.AwaitTask + + return Gym.jsonParse node + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + + member _.GetMemberActivity (ct : CancellationToken option) = + async { + let! ct = Async.CancellationToken + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Get, + RequestUri = System.Uri (client.BaseAddress.ToString () + "/v1/member/activity") + ) + + let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask + let response = response.EnsureSuccessStatusCode () + let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask + + let! node = + System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) + |> Async.AwaitTask + + return MemberActivityDto.jsonParse node + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + + member _.GetSessions (fromDate : DateOnly, toDate : DateOnly, ct : CancellationToken option) = + async { + let! ct = Async.CancellationToken + + let httpMessage = + new System.Net.Http.HttpRequestMessage ( + Method = System.Net.Http.HttpMethod.Get, + RequestUri = + System.Uri ( + client.BaseAddress.ToString () + + ("/v2/gymSessions/member" + + "?fromDate=" + + ((fromDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode) + + "&toDate=" + + ((toDate.ToString "yyyy-MM-dd") |> System.Web.HttpUtility.UrlEncode)) + ) + ) + + let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask + let response = response.EnsureSuccessStatusCode () + let! stream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask + + let! node = + System.Text.Json.Nodes.JsonNode.ParseAsync (stream, cancellationToken = ct) + |> Async.AwaitTask + + return Sessions.jsonParse node + } + |> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct)) + } diff --git a/PureGym/GeneratedDto.fs b/PureGym/GeneratedDto.fs new file mode 100644 index 0000000..411a9c5 --- /dev/null +++ b/PureGym/GeneratedDto.fs @@ -0,0 +1,350 @@ +//------------------------------------------------------------------------------ +// This code was generated by myriad. +// Changes to this file will be lost when the code is regenerated. +//------------------------------------------------------------------------------ + +namespace PureGym + +/// Module containing JSON parsing methods for the GymOpeningHours type +[] +[] +module GymOpeningHours = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymOpeningHours = + let OpeningHours = + node.["openingHours"].AsArray () + |> Seq.map (fun elt -> elt.AsValue().GetValue ()) + |> List.ofSeq + + let IsAlwaysOpen = node.["isAlwaysOpen"].AsValue().GetValue () + + { + IsAlwaysOpen = IsAlwaysOpen + OpeningHours = OpeningHours + } +namespace PureGym + +/// Module containing JSON parsing methods for the GymAccessOptions type +[] +[] +module GymAccessOptions = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymAccessOptions = + let QrCodeAccess = node.["qrCodeAccess"].AsValue().GetValue () + let PinAccess = node.["pinAccess"].AsValue().GetValue () + + { + PinAccess = PinAccess + QrCodeAccess = QrCodeAccess + } +namespace PureGym + +/// Module containing JSON parsing methods for the GymLocation type +[] +[] +module GymLocation = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymLocation = + let Latitude = + try + node.["latitude"].AsValue().GetValue () + with :? System.InvalidOperationException as exc -> + if exc.Message.Contains "cannot be converted to" then + if + System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString + then + node.["latitude"].AsValue().GetValue () |> System.Double.Parse + else + reraise () + else + reraise () + + let Longitude = + try + node.["longitude"].AsValue().GetValue () + with :? System.InvalidOperationException as exc -> + if exc.Message.Contains "cannot be converted to" then + if + System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString + then + node.["longitude"].AsValue().GetValue () |> System.Double.Parse + else + reraise () + else + reraise () + + { + Longitude = Longitude + Latitude = Latitude + } +namespace PureGym + +/// Module containing JSON parsing methods for the GymAddress type +[] +[] +module GymAddress = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymAddress = + let Postcode = node.["postcode"].AsValue().GetValue () + + let County = + match node.["county"] with + | null -> None + | v -> v.AsValue().GetValue () |> Some + + let Town = node.["town"].AsValue().GetValue () + + let AddressLine3 = + match node.["addressLine3"] with + | null -> None + | v -> v.AsValue().GetValue () |> Some + + let AddressLine2 = + match node.["addressLine2"] with + | null -> None + | v -> v.AsValue().GetValue () |> Some + + let AddressLine1 = node.["addressLine1"].AsValue().GetValue () + + { + AddressLine1 = AddressLine1 + AddressLine2 = AddressLine2 + AddressLine3 = AddressLine3 + Town = Town + County = County + Postcode = Postcode + } +namespace PureGym + +/// Module containing JSON parsing methods for the Gym type +[] +[] +module Gym = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Gym = + let ReopenDate = node.["reopenDate"].AsValue().GetValue () + let TimeZone = node.["timeZone"].AsValue().GetValue () + let Location = GymLocation.jsonParse node.["location"] + let AccessOptions = GymAccessOptions.jsonParse node.["accessOptions"] + let GymOpeningHours = GymOpeningHours.jsonParse node.["gymOpeningHours"] + let EmailAddress = node.["emailAddress"].AsValue().GetValue () + let PhoneNumber = node.["phoneNumber"].AsValue().GetValue () + let Address = GymAddress.jsonParse node.["address"] + let Status = node.["status"].AsValue().GetValue () + let Id = node.["id"].AsValue().GetValue () + let Name = node.["name"].AsValue().GetValue () + + { + Name = Name + Id = Id + Status = Status + Address = Address + PhoneNumber = PhoneNumber + EmailAddress = EmailAddress + GymOpeningHours = GymOpeningHours + AccessOptions = AccessOptions + Location = Location + TimeZone = TimeZone + ReopenDate = ReopenDate + } +namespace PureGym + +/// Module containing JSON parsing methods for the Member type +[] +[] +module Member = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Member = + let MemberStatus = node.["memberStatus"].AsValue().GetValue () + let SuspendedReason = node.["suspendedReason"].AsValue().GetValue () + let MembershipLevel = node.["membershipLevel"].AsValue().GetValue () + let MembershipName = node.["membershipName"].AsValue().GetValue () + let Postcode = node.["postCode"].AsValue().GetValue () + let MobileNumber = node.["mobileNumber"].AsValue().GetValue () + + let DateOfBirth = + node.["dateofBirth"].AsValue().GetValue () |> System.DateOnly.Parse + + let GymAccessPin = node.["gymAccessPin"].AsValue().GetValue () + let EmailAddress = node.["emailAddress"].AsValue().GetValue () + let HomeGymName = node.["homeGymName"].AsValue().GetValue () + let HomeGymId = node.["homeGymId"].AsValue().GetValue () + let LastName = node.["lastName"].AsValue().GetValue () + let FirstName = node.["firstName"].AsValue().GetValue () + let CompoundMemberId = node.["compoundMemberId"].AsValue().GetValue () + let Id = node.["id"].AsValue().GetValue () + + { + Id = Id + CompoundMemberId = CompoundMemberId + FirstName = FirstName + LastName = LastName + HomeGymId = HomeGymId + HomeGymName = HomeGymName + EmailAddress = EmailAddress + GymAccessPin = GymAccessPin + DateOfBirth = DateOfBirth + MobileNumber = MobileNumber + Postcode = Postcode + MembershipName = MembershipName + MembershipLevel = MembershipLevel + SuspendedReason = SuspendedReason + MemberStatus = MemberStatus + } +namespace PureGym + +/// Module containing JSON parsing methods for the GymAttendance type +[] +[] +module GymAttendance = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : GymAttendance = + let MaximumCapacity = node.["maximumCapacity"].AsValue().GetValue () + + let LastRefreshedPeopleInClasses = + node.["lastRefreshedPeopleInClasses"].AsValue().GetValue () + |> System.DateTime.Parse + + let LastRefreshed = + node.["lastRefreshed"].AsValue().GetValue () |> System.DateTime.Parse + + let AttendanceTime = + node.["attendanceTime"].AsValue().GetValue () |> System.DateTime.Parse + + let IsApproximate = node.["isApproximate"].AsValue().GetValue () + + let TotalPeopleSuffix = + match node.["totalPeopleSuffix"] with + | null -> None + | v -> v.AsValue().GetValue () |> Some + + let TotalPeopleInClasses = node.["totalPeopleInClasses"].AsValue().GetValue () + let TotalPeopleInGym = node.["totalPeopleInGym"].AsValue().GetValue () + let Description = node.["description"].AsValue().GetValue () + + { + Description = Description + TotalPeopleInGym = TotalPeopleInGym + TotalPeopleInClasses = TotalPeopleInClasses + TotalPeopleSuffix = TotalPeopleSuffix + IsApproximate = IsApproximate + AttendanceTime = AttendanceTime + LastRefreshed = LastRefreshed + LastRefreshedPeopleInClasses = LastRefreshedPeopleInClasses + MaximumCapacity = MaximumCapacity + } +namespace PureGym + +/// Module containing JSON parsing methods for the MemberActivityDto type +[] +[] +module MemberActivityDto = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : MemberActivityDto = + let LastRefreshed = + node.["lastRefreshed"].AsValue().GetValue () |> System.DateTime.Parse + + let IsEstimated = node.["isEstimated"].AsValue().GetValue () + let TotalClasses = node.["totalClasses"].AsValue().GetValue () + let TotalVisits = node.["totalVisits"].AsValue().GetValue () + let AverageDuration = node.["averageDuration"].AsValue().GetValue () + let TotalDuration = node.["totalDuration"].AsValue().GetValue () + + { + TotalDuration = TotalDuration + AverageDuration = AverageDuration + TotalVisits = TotalVisits + TotalClasses = TotalClasses + IsEstimated = IsEstimated + LastRefreshed = LastRefreshed + } +namespace PureGym + +/// Module containing JSON parsing methods for the SessionsAggregate type +[] +[] +module SessionsAggregate = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsAggregate = + let Duration = node.["Duration"].AsValue().GetValue () + let Visits = node.["Visits"].AsValue().GetValue () + let Activities = node.["Activities"].AsValue().GetValue () + + { + Activities = Activities + Visits = Visits + Duration = Duration + } +namespace PureGym + +/// Module containing JSON parsing methods for the VisitGym type +[] +[] +module VisitGym = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : VisitGym = + let Status = node.["Status"].AsValue().GetValue () + let Name = node.["Name"].AsValue().GetValue () + let Id = node.["Id"].AsValue().GetValue () + + { + Id = Id + Name = Name + Status = Status + } +namespace PureGym + +/// Module containing JSON parsing methods for the Visit type +[] +[] +module Visit = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Visit = + let Gym = VisitGym.jsonParse node.["Gym"] + let Duration = node.["Duration"].AsValue().GetValue () + + let StartTime = + node.["StartTime"].AsValue().GetValue () |> System.DateTime.Parse + + let IsDurationEstimated = node.["IsDurationEstimated"].AsValue().GetValue () + + { + IsDurationEstimated = IsDurationEstimated + StartTime = StartTime + Duration = Duration + Gym = Gym + } +namespace PureGym + +/// Module containing JSON parsing methods for the SessionsSummary type +[] +[] +module SessionsSummary = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : SessionsSummary = + let ThisWeek = SessionsAggregate.jsonParse node.["ThisWeek"] + let Total = SessionsAggregate.jsonParse node.["Total"] + + { + Total = Total + ThisWeek = ThisWeek + } +namespace PureGym + +/// Module containing JSON parsing methods for the Sessions type +[] +[] +module Sessions = + /// Parse from a JSON node. + let jsonParse (node : System.Text.Json.Nodes.JsonNode) : Sessions = + let Visits = + node.["Visits"].AsArray () + |> Seq.map (fun elt -> Visit.jsonParse elt) + |> List.ofSeq + + let Summary = SessionsSummary.jsonParse node.["Summary"] + + { + Summary = Summary + Visits = Visits + } diff --git a/PureGym/PureGym.fsproj b/PureGym/PureGym.fsproj index 7523f84..dad0e62 100644 --- a/PureGym/PureGym.fsproj +++ b/PureGym/PureGym.fsproj @@ -3,22 +3,41 @@ net6.0 true + true + FS3559 + + 1.1.1 - - - - - - + + + + + Dto.fs + + + + Client.fs + + + + + + + + + - - + + + + + diff --git a/PureGym/SurfaceBaseline.txt b/PureGym/SurfaceBaseline.txt index 5102d5c..b77519a 100644 --- a/PureGym/SurfaceBaseline.txt +++ b/PureGym/SurfaceBaseline.txt @@ -1,5 +1,3 @@ -PureGym.Api inherit obj -PureGym.Api.make [static method]: PureGym.Auth -> PureGym.IPureGymApi System.Threading.Tasks.Task PureGym.Auth inherit obj, implements PureGym.Auth System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Auth System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases PureGym.Auth+Tags inherit obj PureGym.Auth+Tags.Token [static field]: int = 1 @@ -37,6 +35,8 @@ PureGym.AuthToken.get_ExpiryTime [method]: unit -> System.DateTime option PureGym.AuthTokenModule inherit obj PureGym.AuthTokenModule.get [static method]: PureGym.UsernamePin -> PureGym.AuthToken System.Threading.Tasks.Task PureGym.AuthTokenModule.ofBearerToken [static method]: string -> PureGym.AuthToken +PureGym.Api inherit obj +PureGym.Api.make [static method]: PureGym.Auth -> PureGym.IPureGymApi System.Threading.Tasks.Task PureGym.Gym inherit obj, implements PureGym.Gym System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Gym System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.Gym..ctor [constructor]: (string, int, int, PureGym.GymAddress, string, string, PureGym.GymOpeningHours, PureGym.GymAccessOptions, PureGym.GymLocation, string, string) PureGym.Gym.AccessOptions [property]: [read-only] PureGym.GymAccessOptions @@ -67,22 +67,26 @@ PureGym.GymAccessOptions.get_PinAccess [method]: unit -> bool PureGym.GymAccessOptions.get_QrCodeAccess [method]: unit -> bool PureGym.GymAccessOptions.PinAccess [property]: [read-only] bool PureGym.GymAccessOptions.QrCodeAccess [property]: [read-only] bool +PureGym.GymAccessOptionsModule inherit obj +PureGym.GymAccessOptionsModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.GymAccessOptions PureGym.GymAddress inherit obj, implements PureGym.GymAddress System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymAddress System.IComparable, System.IComparable, System.Collections.IStructuralComparable -PureGym.GymAddress..ctor [constructor]: (string, string, string, string, string, string) +PureGym.GymAddress..ctor [constructor]: (string, string option, string option, string, string option, string) PureGym.GymAddress.AddressLine1 [property]: [read-only] string -PureGym.GymAddress.AddressLine2 [property]: [read-only] string -PureGym.GymAddress.AddressLine3 [property]: [read-only] string -PureGym.GymAddress.County [property]: [read-only] string +PureGym.GymAddress.AddressLine2 [property]: [read-only] string option +PureGym.GymAddress.AddressLine3 [property]: [read-only] string option +PureGym.GymAddress.County [property]: [read-only] string option PureGym.GymAddress.get_AddressLine1 [method]: unit -> string -PureGym.GymAddress.get_AddressLine2 [method]: unit -> string -PureGym.GymAddress.get_AddressLine3 [method]: unit -> string -PureGym.GymAddress.get_County [method]: unit -> string +PureGym.GymAddress.get_AddressLine2 [method]: unit -> string option +PureGym.GymAddress.get_AddressLine3 [method]: unit -> string option +PureGym.GymAddress.get_County [method]: unit -> string option PureGym.GymAddress.get_Postcode [method]: unit -> string PureGym.GymAddress.get_Town [method]: unit -> string PureGym.GymAddress.Postcode [property]: [read-only] string PureGym.GymAddress.Town [property]: [read-only] string +PureGym.GymAddressModule inherit obj +PureGym.GymAddressModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.GymAddress PureGym.GymAttendance inherit obj, implements PureGym.GymAttendance System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymAttendance System.IComparable, System.IComparable, System.Collections.IStructuralComparable -PureGym.GymAttendance..ctor [constructor]: (string, int, int, string, bool, System.DateTime, System.DateTime, System.DateTime, int) +PureGym.GymAttendance..ctor [constructor]: (string, int, int, string option, bool, System.DateTime, System.DateTime, System.DateTime, int) PureGym.GymAttendance.AttendanceTime [property]: [read-only] System.DateTime PureGym.GymAttendance.Description [property]: [read-only] string PureGym.GymAttendance.get_AttendanceTime [method]: unit -> System.DateTime @@ -93,26 +97,34 @@ PureGym.GymAttendance.get_LastRefreshedPeopleInClasses [method]: unit -> System. PureGym.GymAttendance.get_MaximumCapacity [method]: unit -> int PureGym.GymAttendance.get_TotalPeopleInClasses [method]: unit -> int PureGym.GymAttendance.get_TotalPeopleInGym [method]: unit -> int -PureGym.GymAttendance.get_TotalPeopleSuffix [method]: unit -> string +PureGym.GymAttendance.get_TotalPeopleSuffix [method]: unit -> string option PureGym.GymAttendance.IsApproximate [property]: [read-only] bool PureGym.GymAttendance.LastRefreshed [property]: [read-only] System.DateTime PureGym.GymAttendance.LastRefreshedPeopleInClasses [property]: [read-only] System.DateTime PureGym.GymAttendance.MaximumCapacity [property]: [read-only] int PureGym.GymAttendance.TotalPeopleInClasses [property]: [read-only] int PureGym.GymAttendance.TotalPeopleInGym [property]: [read-only] int -PureGym.GymAttendance.TotalPeopleSuffix [property]: [read-only] string +PureGym.GymAttendance.TotalPeopleSuffix [property]: [read-only] string option +PureGym.GymAttendanceModule inherit obj +PureGym.GymAttendanceModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.GymAttendance PureGym.GymLocation inherit obj, implements PureGym.GymLocation System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymLocation System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.GymLocation..ctor [constructor]: (float, float) PureGym.GymLocation.get_Latitude [method]: unit -> float PureGym.GymLocation.get_Longitude [method]: unit -> float PureGym.GymLocation.Latitude [property]: [read-only] float PureGym.GymLocation.Longitude [property]: [read-only] float +PureGym.GymLocationModule inherit obj +PureGym.GymLocationModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.GymLocation +PureGym.GymModule inherit obj +PureGym.GymModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.Gym PureGym.GymOpeningHours inherit obj, implements PureGym.GymOpeningHours System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymOpeningHours System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.GymOpeningHours..ctor [constructor]: (bool, string list) PureGym.GymOpeningHours.get_IsAlwaysOpen [method]: unit -> bool PureGym.GymOpeningHours.get_OpeningHours [method]: unit -> string list PureGym.GymOpeningHours.IsAlwaysOpen [property]: [read-only] bool PureGym.GymOpeningHours.OpeningHours [property]: [read-only] string list +PureGym.GymOpeningHoursModule inherit obj +PureGym.GymOpeningHoursModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.GymOpeningHours PureGym.GymSelector inherit obj, implements PureGym.GymSelector System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymSelector System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 3 cases PureGym.GymSelector+Id inherit PureGym.GymSelector PureGym.GymSelector+Id.get_Item [method]: unit -> int @@ -139,12 +151,12 @@ PureGym.GymSelector.Tag [property]: [read-only] int PureGym.GymSelectorModule inherit obj PureGym.GymSelectorModule.canonicalId [static method]: PureGym.IPureGymApi -> PureGym.GymSelector -> int System.Threading.Tasks.Task PureGym.IPureGymApi - interface with 6 member(s) -PureGym.IPureGymApi.GetGym [method]: int -> PureGym.Gym System.Threading.Tasks.Task -PureGym.IPureGymApi.GetGymAttendance [method]: int -> PureGym.GymAttendance System.Threading.Tasks.Task -PureGym.IPureGymApi.GetGyms [method]: unit -> PureGym.Gym list System.Threading.Tasks.Task -PureGym.IPureGymApi.GetMember [method]: unit -> PureGym.Member System.Threading.Tasks.Task -PureGym.IPureGymApi.GetMemberActivity [method]: unit -> PureGym.MemberActivityDto System.Threading.Tasks.Task -PureGym.IPureGymApi.GetSessions [method]: System.DateTime -> System.DateTime -> PureGym.Sessions System.Threading.Tasks.Task +PureGym.IPureGymApi.GetGym [method]: (int, System.Threading.CancellationToken option) -> PureGym.Gym System.Threading.Tasks.Task +PureGym.IPureGymApi.GetGymAttendance [method]: (int, System.Threading.CancellationToken option) -> PureGym.GymAttendance System.Threading.Tasks.Task +PureGym.IPureGymApi.GetGyms [method]: System.Threading.CancellationToken option -> PureGym.Gym list System.Threading.Tasks.Task +PureGym.IPureGymApi.GetMember [method]: System.Threading.CancellationToken option -> PureGym.Member System.Threading.Tasks.Task +PureGym.IPureGymApi.GetMemberActivity [method]: System.Threading.CancellationToken option -> PureGym.MemberActivityDto System.Threading.Tasks.Task +PureGym.IPureGymApi.GetSessions [method]: (System.DateOnly, System.DateOnly, System.Threading.CancellationToken option) -> PureGym.Sessions System.Threading.Tasks.Task PureGym.Member inherit obj, implements PureGym.Member System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Member System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.Member..ctor [constructor]: (int, string, string, string, int, string, string, string, System.DateOnly, string, string, string, int, int, int) PureGym.Member.CompoundMemberId [property]: [read-only] string @@ -192,6 +204,8 @@ PureGym.MemberActivityDto.ToMemberActivity [method]: unit -> PureGym.MemberActiv PureGym.MemberActivityDto.TotalClasses [property]: [read-only] int PureGym.MemberActivityDto.TotalDuration [property]: [read-only] int PureGym.MemberActivityDto.TotalVisits [property]: [read-only] int +PureGym.MemberActivityDtoModule inherit obj +PureGym.MemberActivityDtoModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.MemberActivityDto PureGym.MemberActivityThisMonth inherit obj, implements PureGym.MemberActivityThisMonth System.IEquatable, System.Collections.IStructuralEquatable, PureGym.MemberActivityThisMonth System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.MemberActivityThisMonth..ctor [constructor]: (int, int, int, int, bool, System.DateTime) PureGym.MemberActivityThisMonth.AverageDurationMinutes [property]: [read-only] int @@ -206,6 +220,10 @@ PureGym.MemberActivityThisMonth.LastRefreshed [property]: [read-only] System.Dat PureGym.MemberActivityThisMonth.TotalClasses [property]: [read-only] int PureGym.MemberActivityThisMonth.TotalDurationMinutes [property]: [read-only] int PureGym.MemberActivityThisMonth.TotalVisits [property]: [read-only] int +PureGym.MemberModule inherit obj +PureGym.MemberModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.Member +PureGym.PureGymApiModule inherit obj +PureGym.PureGymApiModule.make [static method]: System.Net.Http.HttpClient -> PureGym.IPureGymApi PureGym.Sessions inherit obj, implements PureGym.Sessions System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Sessions System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.Sessions..ctor [constructor]: (PureGym.SessionsSummary, PureGym.Visit list) PureGym.Sessions.get_Summary [method]: unit -> PureGym.SessionsSummary @@ -220,12 +238,18 @@ PureGym.SessionsAggregate.get_Activities [method]: unit -> int PureGym.SessionsAggregate.get_Duration [method]: unit -> int PureGym.SessionsAggregate.get_Visits [method]: unit -> int PureGym.SessionsAggregate.Visits [property]: [read-only] int +PureGym.SessionsAggregateModule inherit obj +PureGym.SessionsAggregateModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.SessionsAggregate +PureGym.SessionsModule inherit obj +PureGym.SessionsModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.Sessions PureGym.SessionsSummary inherit obj, implements PureGym.SessionsSummary System.IEquatable, System.Collections.IStructuralEquatable, PureGym.SessionsSummary System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.SessionsSummary..ctor [constructor]: (PureGym.SessionsAggregate, PureGym.SessionsAggregate) PureGym.SessionsSummary.get_ThisWeek [method]: unit -> PureGym.SessionsAggregate PureGym.SessionsSummary.get_Total [method]: unit -> PureGym.SessionsAggregate PureGym.SessionsSummary.ThisWeek [property]: [read-only] PureGym.SessionsAggregate PureGym.SessionsSummary.Total [property]: [read-only] PureGym.SessionsAggregate +PureGym.SessionsSummaryModule inherit obj +PureGym.SessionsSummaryModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.SessionsSummary PureGym.UsernamePin inherit obj, implements PureGym.UsernamePin System.IEquatable, System.Collections.IStructuralEquatable, PureGym.UsernamePin System.IComparable, System.IComparable, System.Collections.IStructuralComparable PureGym.UsernamePin..ctor [constructor]: (string, string) PureGym.UsernamePin.get_Pin [method]: unit -> string @@ -249,4 +273,8 @@ PureGym.VisitGym.get_Name [method]: unit -> string PureGym.VisitGym.get_Status [method]: unit -> string PureGym.VisitGym.Id [property]: [read-only] int PureGym.VisitGym.Name [property]: [read-only] string -PureGym.VisitGym.Status [property]: [read-only] string \ No newline at end of file +PureGym.VisitGym.Status [property]: [read-only] string +PureGym.VisitGymModule inherit obj +PureGym.VisitGymModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.VisitGym +PureGym.VisitModule inherit obj +PureGym.VisitModule.jsonParse [static method]: System.Text.Json.Nodes.JsonNode -> PureGym.Visit diff --git a/PureGym/myriad.toml b/PureGym/myriad.toml new file mode 100644 index 0000000..e69de29 diff --git a/PureGym/version.json b/PureGym/version.json index 958fb13..f3c770d 100644 --- a/PureGym/version.json +++ b/PureGym/version.json @@ -1,5 +1,5 @@ { - "version": "2.0", + "version": "3.0", "publicReleaseRefSpec": [ "^refs/heads/main$" ], diff --git a/flake.lock b/flake.lock index 2e18027..e0fb3ff 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1696981262, - "narHash": "sha256-YaCOjdqhbjBeyMjxlgFWt4XD/b9pGKWURgS3uEwNLtc=", + "lastModified": 1703792911, + "narHash": "sha256-BzCq3IiOlTghYtgPngIUnJDeGlRdz4RJGyS9faONrOE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a2b87a4f66f309d2f4b789fd0457f5fc5db0a9a6", + "rev": "1d17e304ac93dde75178d7ad47abbecc0357c937", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6abaf4f..61e31c6 100644 --- a/flake.nix +++ b/flake.nix @@ -17,8 +17,8 @@ projectFile = "./PureGym.App/PureGym.App.fsproj"; testProjectFile = "./PureGym.Test/PureGym.Test.fsproj"; pname = "puregym"; - dotnet-sdk = pkgs.dotnet-sdk_7; - dotnet-runtime = pkgs.dotnetCorePackages.runtime_7_0; + dotnet-sdk = pkgs.dotnet-sdk_8; + dotnet-runtime = pkgs.dotnetCorePackages.runtime_8_0; version = "0.1"; dotnetTool = toolName: toolVersion: sha256: pkgs.stdenvNoCC.mkDerivation rec { @@ -81,7 +81,7 @@ }; devShells.default = pkgs.mkShell { buildInputs = - [pkgs.alejandra pkgs.dotnet-sdk_7 pkgs.python3 pkgs.nodePackages.markdown-link-check] + [pkgs.alejandra pkgs.dotnet-sdk_8 pkgs.python3 pkgs.nodePackages.markdown-link-check] ++ ( if pkgs.stdenv.isDarwin then [pkgs.darwin.apple_sdk.frameworks.CoreServices] diff --git a/nix/deps.nix b/nix/deps.nix index 176513c..72339e6 100644 --- a/nix/deps.nix +++ b/nix/deps.nix @@ -1,21 +1,6 @@ # This file was automatically generated by passthru.fetch-deps. # Please don't edit it manually, your changes might get overwritten! {fetchNuGet}: [ - (fetchNuGet { - pname = "Fastenshtein"; - version = "1.0.0.8"; - sha256 = "1rvw27rz7qb2n68i0jvvcr224fcpy5yzzxaj1bp89jw41cpdabp2"; - }) - (fetchNuGet { - pname = "Argu"; - version = "6.1.1"; - sha256 = "1v996g0760qhiys2ahdpnvkldaxr2jn5f1falf789glnk4a6f3xl"; - }) - (fetchNuGet { - pname = "System.Configuration.ConfigurationManager"; - version = "4.4.0"; - sha256 = "1hjgmz47v5229cbzd2pwz2h0dkq78lb2wp9grx8qr72pb5i0dk7v"; - }) (fetchNuGet { pname = "fantomas"; version = "6.2.0"; @@ -26,75 +11,105 @@ version = "4.0.12"; sha256 = "0v56sv4cz8bgrfqjjg0q96619qs9dvvi0a6lp7hzz2mi82i1inmq"; }) + (fetchNuGet { + pname = "Argu"; + version = "6.1.1"; + sha256 = "1v996g0760qhiys2ahdpnvkldaxr2jn5f1falf789glnk4a6f3xl"; + }) (fetchNuGet { pname = "coverlet.collector"; version = "3.2.0"; sha256 = "1qxpv8v10p5wn162lzdm193gdl6c5f81zadj8h889dprlnj3g8yr"; }) (fetchNuGet { - pname = "FSharp.Core"; - version = "6.0.0"; - sha256 = "1hjhvr39c1vpgrdmf8xln5q86424fqkvy9nirkr29vl2461d2039"; + pname = "Fantomas.Core"; + version = "6.1.1"; + sha256 = "1h2wsiy4fzwsg9vrlpk6w7zsvx6bc4wg4x25zqc48akg04fwpi0m"; + }) + (fetchNuGet { + pname = "Fantomas.FCS"; + version = "6.1.1"; + sha256 = "0733dm5zjdp8w5wwalqlv1q52pghfr04863i9wy807f4qfd7rrin"; + }) + (fetchNuGet { + pname = "Fastenshtein"; + version = "1.0.0.8"; + sha256 = "1rvw27rz7qb2n68i0jvvcr224fcpy5yzzxaj1bp89jw41cpdabp2"; + }) + (fetchNuGet { + pname = "FsCheck"; + version = "2.16.6"; + sha256 = "176rwky6b5rk8dzldiz4068p7m9c5y9ygzbhadrs14jkl94pc56n"; }) (fetchNuGet { pname = "FSharp.Core"; - version = "7.0.400"; - sha256 = "1pl6iqqcpm9djfn7f6ms5j1xbcyz00nb808qd6pmsjrnylflalgp"; + version = "6.0.1"; + sha256 = "0qks2aadkhsffg9a6xq954ll9xacnph852avd7ljh9n2g6vj06qj"; + }) + (fetchNuGet { + pname = "FSharp.Core"; + version = "8.0.100"; + sha256 = "06z3vg8yj7i83x6gmnzl2lka1bp4hzc07h6mrydpilxswnmy2a0l"; + }) + (fetchNuGet { + pname = "FsUnit"; + version = "5.6.1"; + sha256 = "1zffn9dm2c44v8qjzwfg6y3psydiv2bn3n305rf7mc57cmm4ygv3"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Ref"; - version = "6.0.22"; - sha256 = "0fqpl1fr213b4fb3c6xw3fy6669yxqcp1bzcnayw80yrskw8lpxs"; + version = "6.0.25"; + sha256 = "1vrmqn5j6ibwkqasbf7x7n4w5jdclnz3giymiwvym2wa0y5zc59q"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64"; - version = "6.0.22"; - sha256 = "1xvqqc7bzj764g3scp0saqxlfiv866crgi8chz57vhjp9sgd61jw"; + version = "6.0.25"; + sha256 = "0mgcs4si7mwd0f555s1vg17pf4nqfaijd1pci359l1pgrmv70rrg"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64"; - version = "7.0.11"; - sha256 = "0hmsqy4yc3023mcp5rg0h59yv3f8cnjhxw1g4i8md67vm5y04lfv"; + version = "8.0.0"; + sha256 = "05y1xb5fw8lzvb4si77a5qwfwfz1855crqbphrwky6x9llivbhkx"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.linux-x64"; - version = "6.0.22"; - sha256 = "1gcv99y295fnhy12fyx8wqvbhbj6mz8p5bm66ppwdxb3zykjg2l8"; + version = "6.0.25"; + sha256 = "0wvzhqhlmlbnpa18qp8m3wcrlcgj3ckvp3iv2n7g8vb60c3238aq"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.linux-x64"; - version = "7.0.11"; - sha256 = "18sk9wka8z5354ca77q43hi0615yjssdjbyi0hqq92w6zmg43vgc"; + version = "8.0.0"; + sha256 = "18zdbcb2bn7wy1dp14z5jyqiiwr9rkad1lcb158r5ikjfq1rg5iw"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64"; - version = "6.0.22"; - sha256 = "1ib0x1w33wqy7lgzjf14dvgx981xpjffjqd800d7wgxisgmakrmr"; + version = "6.0.25"; + sha256 = "1pywgvb8ck1d5aadmijd5s3z6yclchd9pa6dsahijmm55ibplx36"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64"; - version = "7.0.11"; - sha256 = "1j0zbd4rmmd3ylgixsvyj145g2r6px6b9d9k4yxxg6d61x90c165"; + version = "8.0.0"; + sha256 = "1nbxzmj6cnccylxis67c54c0ik38ma4rwdvgg6sxd6r04219maqm"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.osx-x64"; - version = "6.0.22"; - sha256 = "026r38a7by7wdfd3virjdaah3y2sjjmnabgf5l25vdnwpwc7c31d"; + version = "6.0.25"; + sha256 = "1zlf0w7i6r02719dv3nw4jy14sa0rs53i89an5alz5qmywdy3f1d"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.osx-x64"; - version = "7.0.11"; - sha256 = "0wxw7vgygg6hqzq479n0pfjizr69wq7ja03a0qh8bma8b9q2mn6f"; + version = "8.0.0"; + sha256 = "1wqkbjd1ywv9w397l7rsb89mijc5n0hv7jq9h09xfz6wn9qsp152"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.win-x64"; - version = "6.0.22"; - sha256 = "0ygdqsd312kqpykwb0k2942n45q1w3yn1nia6m1ahf7b74926qb5"; + version = "6.0.25"; + sha256 = "1fbsnm4056cpd4avgpi5sq05m1yd9k4x229ckxpr4q7yc94sncwy"; }) (fetchNuGet { pname = "Microsoft.AspNetCore.App.Runtime.win-x64"; - version = "7.0.11"; - sha256 = "05ywwfn5lzx6y999f7gwmablkxi2zvska4sg20ihmjzp3xakcmk0"; + version = "8.0.0"; + sha256 = "08vlmswmiyp2nxlr9d77716hk7kz7h9x5bl8wh76xzbj5id1xlb2"; }) (fetchNuGet { pname = "Microsoft.CodeCoverage"; @@ -108,114 +123,124 @@ }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.linux-arm64"; - version = "6.0.22"; - sha256 = "0gri1gqznm5c8fsb6spqb3j88a3b0br0iy50y66fh4hz9wc4fwzm"; + version = "6.0.25"; + sha256 = "052388yjivzkfllkss0nljbzmjx787jqdjsbb6ls855sp6wh9xfd"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.linux-arm64"; - version = "7.0.11"; - sha256 = "03nkxjn4wq30rw0163rqi8sngfxmcvwgm0wg7sgyb1cdh0q1ai68"; + version = "8.0.0"; + sha256 = "0bpg3v9dnalz7yh7lsgriw9rnm9jx37mqhhvf7snznb3sfk7rgwb"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.linux-x64"; - version = "6.0.22"; - sha256 = "0k1i74wn6j7nq0bd8m6jrpl65wda6qc9pglppvz4ybk0n2ab1rbi"; + version = "6.0.25"; + sha256 = "103xy6kncjwbbchfnpqvsjpjy92x3dralcg9pw939jp0dwggwarz"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.linux-x64"; - version = "7.0.11"; - sha256 = "12hh69sr4wf8sjcw3q71vky51sn854ffahbq6rgz3njzvbvc0dbj"; + version = "8.0.0"; + sha256 = "1c7l68bm05d94x5wk1y33mnd4v8m196vyprgrzqnh94yrqy6fkf7"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.osx-arm64"; - version = "6.0.22"; - sha256 = "0166gwarhhnary19lf80ff33bkx00mkm24f17bc8j6v7g3a7zvq6"; + version = "6.0.25"; + sha256 = "13m14pdx5xfxky07xgxf6hjd7g9l4k6k40wvp9znhvn27pa0wdxv"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.osx-x64"; - version = "6.0.22"; - sha256 = "038bjwk201p2kzs3jflrkhlnszf7cwalafq0nvs2v8bp7jlnx5ib"; + version = "6.0.25"; + sha256 = "132pgjhv42mqzx4007sd59bkds0fwsv5xaz07y2yffbn3lzr228k"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.osx-x64"; - version = "7.0.11"; - sha256 = "1j1k735gkwba93n5yck87wppfpsbny979hppcygwrk81myf3fv03"; + version = "8.0.0"; + sha256 = "0jmzf58vv45j0hqlxq8yalpjwi328vp2mjr3h0pdg0qr143iivnr"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.win-x64"; - version = "6.0.22"; - sha256 = "1bjy3zmrmaq97xp0f3nzs3ax330ji632avrfpg8xz4vc5p8s1xpc"; + version = "6.0.25"; + sha256 = "039433rm4w37h9qri11v3lrpddpz7zcly9kq8vmk6w1ixzlqwf01"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Host.win-x64"; - version = "7.0.11"; - sha256 = "0ifshdx19bgnbgynbk6iy6gybnxmp63nylrn7068x66hvcavh7kh"; + version = "8.0.0"; + sha256 = "1n8yr13df2f6jhxpfazs6rxahfqm18fhjvfm16g5d60c3za1hwnk"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Ref"; - version = "6.0.22"; - sha256 = "0km8184kma8kgz7iyl3j6apj1n7vskzdhzmq3myy3y36ysqrb4wf"; + version = "6.0.25"; + sha256 = "0jfhmfxpx1h4f3axgf60gc8d4cnlvbb853400kag6nk0875hr0x1"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.linux-arm64"; - version = "6.0.22"; - sha256 = "01gbl9dgky4h7ijxryz3527l39v23lkcvk4fs4w91ra4pris2n8p"; + version = "6.0.25"; + sha256 = "0jpcmva1l8z36r4phz055l7fz9s6z8pv8pqc4ia69mhhgvr0ks7y"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.linux-arm64"; - version = "7.0.11"; - sha256 = "1gzwc96fs222ddia0k1924cn7gxm2a4anqgcxhmavx56x76wsy6f"; + version = "8.0.0"; + sha256 = "0gwqmkmr7jy3sjh9gha82amlry41gp8nwswy2iqfw54f28db63n7"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.linux-x64"; - version = "6.0.22"; - sha256 = "09gfqdxbh36bjx20fw9k94b9qa9bwffhrq0ldwn834mx31bgrfs8"; + version = "6.0.25"; + sha256 = "012jml0bqxbspahf1j4bvvd91pz85hsbcyhq00gxczcazhxpkhz4"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.linux-x64"; - version = "7.0.11"; - sha256 = "0vxza49wwiia0d3m887yiaprp3xnax2bgzhj5bf080b4ayapzkf9"; + version = "8.0.0"; + sha256 = "042cjvnwrrjs3mw5q8q5kinh0cwkks33i3n1vyifaid2jbr3wlc0"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.osx-arm64"; - version = "6.0.22"; - sha256 = "1x7wclv93q8wp7rip5nwnsxbqcami92yilvzbp0yn42ddkw177ds"; + version = "6.0.25"; + sha256 = "0wgwxpyy1n550sw7npjg69zpxknwn0ay30m2qybvqb5mj857qzxi"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.osx-arm64"; - version = "7.0.11"; - sha256 = "15b62hxrpfy19xvyxlyligixxpa9sysfgi47xi4imx5055fhwphh"; + version = "8.0.0"; + sha256 = "06ndp4wh1cap01dql3nixka4g56bf6ipmqys7xaxvg4xisf79x8d"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.osx-x64"; - version = "6.0.22"; - sha256 = "1sq1ygsrpv2sl85wrs8382wgkjic0zylaj1y8kcvhczcmkpk3wr5"; + version = "6.0.25"; + sha256 = "08vr7c5bg5x3w35l54z1azif7ysfc2yiyz50ip1dl0mpqywvlswr"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.osx-x64"; - version = "7.0.11"; - sha256 = "018qf23b0jixfh3fm74zqaakk01qx6yq21gk2mdn68b0xhnvlzma"; + version = "8.0.0"; + sha256 = "1kh5bnaf6h9mr4swcalrp304625frjiw6mlz1052rxwzsdq98a96"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.win-x64"; - version = "6.0.22"; - sha256 = "1nn254xv1hi5c4rg38fbfkln3031vv545lv9f4df31i8c1yfzz24"; + version = "6.0.25"; + sha256 = "03snpmx204xvc9668riisvvdjjgdqhwj7yjp85w5lh8j8ygrqkif"; }) (fetchNuGet { pname = "Microsoft.NETCore.App.Runtime.win-x64"; - version = "7.0.11"; - sha256 = "12xmw2kcpf5rh8sv4y0mqzp917f7q8g4mfh5navqw4jmnxyb26qq"; + version = "8.0.0"; + sha256 = "054icf5jjnwnswrnv1r05x3pfjvacbz6g3dj8caar1zp53k49rkk"; }) (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "1.1.0"; sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm"; }) + (fetchNuGet { + pname = "Microsoft.NETCore.Platforms"; + version = "1.1.1"; + sha256 = "164wycgng4mi9zqi2pnsf1pq6gccbqvw6ib916mqizgjmd8f44pj"; + }) (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "2.0.0"; sha256 = "1fk2fk2639i7nzy58m9dvpdnzql4vb8yl8vr19r2fp8lmj9w2jr0"; }) + (fetchNuGet { + pname = "Microsoft.NETCore.Targets"; + version = "1.1.3"; + sha256 = "05smkcyxir59rgrmp7d6327vvrlacdgldfxhmyr1azclvga1zfsq"; + }) (fetchNuGet { pname = "Microsoft.TestPlatform.ObjectModel"; version = "17.6.0"; @@ -226,6 +251,16 @@ version = "17.6.0"; sha256 = "16vpicp4q2kbpgr3qwpsxg7srabxqszx23x6smjvvrvz7qmr5v8i"; }) + (fetchNuGet { + pname = "Myriad.Core"; + version = "0.8.3"; + sha256 = "0s5pdckjw4x0qrbd4i3cz9iili5cppg5qnjbr7zjbbhhmxzb24xw"; + }) + (fetchNuGet { + pname = "Myriad.Sdk"; + version = "0.8.3"; + sha256 = "0qv78c5s5m04xb8h17nnn2ig26zcyya91k2dpj745cm1cbnzvvgc"; + }) (fetchNuGet { pname = "NETStandard.Library"; version = "2.0.0"; @@ -273,8 +308,8 @@ }) (fetchNuGet { pname = "NUnit"; - version = "3.13.3"; - sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6"; + version = "3.14.0"; + sha256 = "19p8911lrfds1k9rv47jk1bbn665s0pvghkd06gzbg78j6mzzqqa"; }) (fetchNuGet { pname = "NUnit.Analyzers"; @@ -291,6 +326,31 @@ version = "1.6.4"; sha256 = "1mvi3nbrr450g3fgd1y4wg3bwl9k1agyjfd9wdkqk12714bsln8l"; }) + (fetchNuGet { + pname = "runtime.any.System.Runtime"; + version = "4.3.0"; + sha256 = "1cqh1sv3h5j7ixyb7axxbdkqx6cxy00p4np4j91kpm492rf4s25b"; + }) + (fetchNuGet { + pname = "runtime.native.System"; + version = "4.3.0"; + sha256 = "15hgf6zaq9b8br2wi1i3x0zvmk410nlmsmva9p0bbg73v6hml5k4"; + }) + (fetchNuGet { + pname = "runtime.unix.System.Private.Uri"; + version = "4.3.0"; + sha256 = "1jx02q6kiwlvfksq1q9qr17fj78y5v6mwsszav4qcz9z25d5g6vk"; + }) + (fetchNuGet { + pname = "System.Configuration.ConfigurationManager"; + version = "4.4.0"; + sha256 = "1hjgmz47v5229cbzd2pwz2h0dkq78lb2wp9grx8qr72pb5i0dk7v"; + }) + (fetchNuGet { + pname = "System.Diagnostics.DiagnosticSource"; + version = "7.0.0"; + sha256 = "1jxhvsh5mzdf0sgb4dfmbys1b12ylyr5pcfyj1map354fiq3qsgm"; + }) (fetchNuGet { pname = "System.Formats.Asn1"; version = "5.0.0"; @@ -306,11 +366,26 @@ version = "4.5.0"; sha256 = "1gq4s8w7ds1sp8f9wqzf8nrzal40q5cd2w4pkf4fscrl2ih3hkkj"; }) + (fetchNuGet { + pname = "System.Memory"; + version = "4.5.5"; + sha256 = "08jsfwimcarfzrhlyvjjid61j02irx6xsklf32rv57x2aaikvx0h"; + }) + (fetchNuGet { + pname = "System.Private.Uri"; + version = "4.3.0"; + sha256 = "04r1lkdnsznin0fj4ya1zikxiqr0h6r6a1ww2dsm60gqhdrf0mvx"; + }) (fetchNuGet { pname = "System.Reflection.Metadata"; version = "1.6.0"; sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4"; }) + (fetchNuGet { + pname = "System.Runtime"; + version = "4.3.1"; + sha256 = "03ch4d2acf6q037a4njxpll2kkx3dwzlg07yxr4z5m6j1kqgmm27"; + }) (fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "6.0.0"; @@ -348,8 +423,8 @@ }) (fetchNuGet { pname = "System.Text.Encodings.Web"; - version = "7.0.0"; - sha256 = "1151hbyrcf8kyg1jz8k9awpbic98lwz9x129rg7zk1wrs6vjlpxl"; + version = "8.0.0"; + sha256 = "1wbypkx0m8dgpsaqgyywz4z760xblnwalb241d5qv9kx8m128i11"; }) (fetchNuGet { pname = "System.Text.Json"; @@ -358,7 +433,12 @@ }) (fetchNuGet { pname = "System.Text.Json"; - version = "7.0.3"; - sha256 = "0zjrnc9lshagm6kdb9bdh45dmlnkpwcpyssa896sda93ngbmj8k9"; + version = "8.0.0"; + sha256 = "134savxw0sq7s448jnzw17bxcijsi1v38mirpbb6zfxmqlf04msw"; + }) + (fetchNuGet { + pname = "WoofWare.Myriad.Plugins"; + version = "1.1.1"; + sha256 = "1maj1p93cg8c22l9ldq310n21cbhg5rpjkkrm6cjh7dm4rpvr9mg"; }) ]