Compare commits

...

18 Commits

Author SHA1 Message Date
2ca8c5e43b Bump ApiSurface (#20)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #20
2025-09-08 21:29:00 +00:00
6b2ebdffba Relax JSON schema and update deps (#19)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #19
2024-12-24 19:27:15 +00:00
541b69a853 Upgrade to net9 (#18)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #18
2024-11-28 22:19:54 +00:00
5a75d01466 Bump nixpkgs (#17)
All checks were successful
ci/woodpecker/manual/build Pipeline was successful
ci/woodpecker/manual/all-checks-complete Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #17
2024-10-26 08:37:04 +00:00
bf2a00a7f1 Bump nixpkgs (#16)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/manual/build Pipeline was successful
ci/woodpecker/manual/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #16
2024-09-07 15:03:27 +00:00
f679bc4328 Bump nixpkgs (#15)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #15
2024-07-24 18:08:17 +00:00
e699f4d9ad Add reproducibility check (#14)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #14
2024-07-12 10:12:27 +00:00
a29133a443 Simplify flake (#12)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #12
2024-05-31 23:49:34 +00:00
eb1cc43a08 Fix pipeline (#13)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
Reviewed-on: #13
2024-05-31 23:10:23 +00:00
419f27053f Add all-gyms (#11)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #11
2024-02-12 22:08:02 +00:00
f182c6ebad Make reopen date optional (#10)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
It turns out that the API doesn't necessarily give you this.

Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #10
2024-02-02 16:19:57 +00:00
e96ae78665 Use WoofWare.Myriad entirely to generate the REST API (#9)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #9
2024-01-30 00:17:45 +00:00
58fdb23719 Direnv support (#8)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #8
2024-01-28 23:35:55 +00:00
cdbc73b07f Bump WoofWare.Myriad (#7)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #7
2023-12-30 11:59:42 +00:00
0be5485603 Upgrade WoofWare.Myriad to 1.1.10 (#6)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #6
2023-12-29 23:36:03 +00:00
c8a29356b7 Bump source gen (#5)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #5
2023-12-29 12:24:52 +00:00
8ece87ff57 Start generating parse methods (#4)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #4
2023-12-28 21:08:06 +00:00
2741c5e36c Add visits info (#3)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #3
2023-11-01 16:49:56 +00:00
36 changed files with 2829 additions and 867 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"fantomas": {
"version": "6.2.0",
"version": "6.3.15",
"commands": [
"fantomas"
]

View File

@@ -2,7 +2,6 @@ root=true
[*]
charset=utf-8
end_of_line=crlf
trim_trailing_whitespace=true
insert_final_newline=true
indent_style=space

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

1
.fantomasignore Normal file
View File

@@ -0,0 +1 @@
.direnv/

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ riderModule.iml
.DS_Store
result
.profile*
.direnv/
node_modules/
package.json

View File

@@ -1,22 +0,0 @@
steps:
build:
image: nixos/nix
commands:
- echo 'experimental-features = flakes nix-command' >> /etc/nix/nix.conf
# Lint
- "nix flake check"
# Test
- nix build
- nix run . -- --help
- nix run . -- auth --help
- nix run . -- lookup-gym --help
- nix run . -- fullness --help
- nix run . -- activity --help
- nix develop --command markdown-link-check README.md
- nix develop --command dotnet test
- nix develop --command dotnet test --configuration Release
when:
- event: "push"
evaluate: 'CI_COMMIT_BRANCH == CI_REPO_DEFAULT_BRANCH'
- event: "pull_request"

29
.woodpecker/build.yaml Normal file
View File

@@ -0,0 +1,29 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/woodpecker-ci/woodpecker/main/pipeline/frontend/yaml/linter/schema/schema.json
{
"steps": {
"build": {
"image": "nixos/nix",
"commands": [
"echo 'experimental-features = flakes nix-command' >> /etc/nix/nix.conf",
# Lint
"nix flake check",
# Test
"nix build",
# Reproducibility
"nix build --rebuild",
"nix run . -- --help",
"nix run . -- auth --help",
"nix run . -- lookup-gym --help",
"nix run . -- fullness --help",
"nix run . -- activity --help",
"nix develop --command markdown-link-check README.md",
"nix develop --command dotnet test",
"nix develop --command dotnet test --configuration Release"
],
"when": [
{ "event": "push", "evaluate": "CI_COMMIT_BRANCH == CI_REPO_DEFAULT_BRANCH" },
{ "event": "pull_request" }
]
}
}
}

53
PureGym.App/AllGyms.fs Normal file
View File

@@ -0,0 +1,53 @@
namespace PureGym.App
open System.Threading
open Argu
open PureGym
type AllGymsArgsFragment =
| [<Unique ; EqualsAssignmentOrSpaced>] Terse of bool
interface IArgParserTemplate with
member s.Usage =
match s with
| Terse _ -> "print only 'id,gym-name'"
type AllGymsArgs =
{
Creds : Auth
Terse : bool
}
static member Parse
(auth : Auth)
(args : AllGymsArgsFragment ParseResults)
: Result<AllGymsArgs, ArguParseException>
=
let terse =
match args.TryGetResult AllGymsArgsFragment.Terse with
| None -> false
| Some x -> x
{
Creds = auth
Terse = terse
}
|> Ok
[<RequireQualifiedAccess>]
module AllGyms =
let run (args : AllGymsArgs) =
task {
let! client = Api.makeWithoutRefresh CancellationToken.None args.Creds
let! s = client.GetGyms ()
if args.Terse then
for gym in s do
System.Console.WriteLine $"%i{gym.Id},%s{gym.Name}"
else
for gym in s do
System.Console.WriteLine (string<Gym> gym)
return 0
}

View File

@@ -5,8 +5,8 @@ open PureGym
type AuthArg =
| [<Unique ; CustomAppSettings "PUREGYM_BEARER_TOKEN">] Bearer_Token of string
| [<Unique>] User_Email of string
| [<Unique>] Pin of string
| [<Unique ; EqualsAssignmentOrSpaced>] User_Email of string
| [<Unique ; EqualsAssignmentOrSpaced>] Pin of string
| [<GatherUnrecognized>] Others of string
interface IArgParserTemplate with

View File

@@ -1,5 +1,6 @@
namespace PureGym.App
open System.Threading
open Argu
open System
open PureGym
@@ -14,7 +15,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<GetTokenArg>) : Result<UsernamePin, ArguParseException> =
let canUseStdin = args.TryGetResult(GetTokenArg.StdIn).IsSome
@@ -50,7 +51,7 @@ module Authenticate =
let run (creds : UsernamePin) =
task {
let! cred = AuthToken.get creds
let! cred = AuthToken.get creds CancellationToken.None
Console.WriteLine cred.AccessToken
match cred.ExpiryTime with

View File

@@ -1,5 +1,6 @@
namespace PureGym.App
open System.Threading
open Argu
open PureGym
@@ -48,7 +49,7 @@ module Fullness =
let run (args : FullnessArgs) =
task {
let! client = Api.make args.Creds
let! client = Api.makeWithoutRefresh CancellationToken.None args.Creds
let! id = GymSelector.canonicalId client args.Gym
let! attendance = client.GetGymAttendance id

View File

@@ -1,5 +1,6 @@
namespace PureGym.App
open System.Threading
open Argu
open PureGym
@@ -44,7 +45,7 @@ module LookupGym =
let run (args : LookupGymArgs) =
task {
let! client = Api.make args.Creds
let! client = Api.makeWithoutRefresh CancellationToken.None args.Creds
let! s = client.GetGym 19
System.Console.WriteLine (string<Gym> s)
return 0

View File

@@ -1,5 +1,6 @@
namespace PureGym.App
open System.Threading
open Argu
open PureGym
@@ -31,7 +32,7 @@ module MemberActivity =
let run (args : MemberActivityArgs) =
task {
let! client = Api.make args.Creds
let! client = Api.makeWithoutRefresh CancellationToken.None args.Creds
let! activity = client.GetMemberActivity ()
let activity = activity.ToMemberActivity ()
System.Console.WriteLine (string<MemberActivityThisMonth> activity)

View File

@@ -16,13 +16,21 @@ module Program =
("Get information about the physical instantiation of a gym",
RequiresAuth (fun auth -> ArgsCrate.make (LookupGymArgs.Parse auth) LookupGym.run))
"all-gyms",
("List information about all gyms",
RequiresAuth (fun auth -> ArgsCrate.make (AllGymsArgs.Parse auth) AllGyms.run))
"fullness",
("Determine how full a gym is",
RequiresAuth (fun auth -> ArgsCrate.make (FullnessArgs.Parse auth) Fullness.run))
"activity",
("Get information about your gym usage",
("Get information about your aggregate gym usage",
RequiresAuth (fun auth -> ArgsCrate.make (MemberActivityArgs.Parse auth) MemberActivity.run))
"sessions",
("Get information about your individual sessions",
RequiresAuth (fun auth -> ArgsCrate.make (SessionsArgs.Parse auth) Sessions.run))
|]
|> Map.ofArray

View File

@@ -1,8 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<NuGetAuditMode>all</NuGetAuditMode>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
@@ -13,7 +15,9 @@
<Compile Include="Authenticate.fs" />
<Compile Include="Fullness.fs" />
<Compile Include="LookupGym.fs" />
<Compile Include="AllGyms.fs" />
<Compile Include="MemberActivity.fs" />
<Compile Include="Sessions.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
@@ -22,7 +26,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Argu" Version="6.1.1" />
<PackageReference Include="Argu" Version="6.2.5" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>
</Project>

50
PureGym.App/Sessions.fs Normal file
View File

@@ -0,0 +1,50 @@
namespace PureGym.App
open System.Threading
open Argu
open PureGym
open System
type SessionsArgsFragment =
| [<Mandatory ; EqualsAssignmentOrSpaced>] From_Date of string
| [<Mandatory ; EqualsAssignmentOrSpaced>] To_Date of string
interface IArgParserTemplate with
member s.Usage =
match s with
| From_Date _ -> "start of date range (inclusive) for query, which needs to parse as a DateTime"
| To_Date _ -> "end of date range (inclusive) for query, which needs to parse as a DateTime"
type SessionsArgs =
{
Creds : Auth
FromDate : DateOnly
ToDate : DateOnly
}
static member Parse
(auth : Auth)
(args : SessionsArgsFragment ParseResults)
: Result<SessionsArgs, ArguParseException>
=
let fromDate = args.GetResult SessionsArgsFragment.From_Date
let toDate = args.GetResult SessionsArgsFragment.To_Date
{
Creds = auth
FromDate = DateOnly.Parse fromDate
ToDate = DateOnly.Parse toDate
}
|> Ok
[<RequireQualifiedAccess>]
module Sessions =
let run (args : SessionsArgs) =
task {
let! client = Api.makeWithoutRefresh CancellationToken.None args.Creds
let! activity = client.GetSessions (args.FromDate, args.ToDate)
System.Console.WriteLine (string<Sessions> activity)
return 0
}

View File

@@ -1,23 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="TestSurface.fs" />
<Compile Include="TestJson.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ApiSurface" Version="4.0.12" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
<PackageReference Include="ApiSurface" Version="5.0.1" />
<PackageReference Include="FsCheck" Version="3.3.1" />
<PackageReference Include="FsUnit" Version="7.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="NUnit" Version="4.4.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0"/>
</ItemGroup>
<ItemGroup>

317
PureGym.Test/TestJson.fs Normal file
View File

@@ -0,0 +1,317 @@
namespace PureGym.Test
open System
open System.IO
open System.Reflection.Metadata
open System.Reflection.PortableExecutable
open System.Text.Json
open System.Text.Json.Nodes
open System.Text.Json.Serialization
open NUnit.Framework
open FsUnitTyped
open PureGym
[<TestFixture>]
module TestJson =
let gymOpeningHoursCases =
[
"""{"openingHours": [], "isAlwaysOpen": false}""",
{
GymOpeningHours.OpeningHours = []
IsAlwaysOpen = false
}
"""{"openingHours": ["something"], "isAlwaysOpen": false}""",
{
GymOpeningHours.OpeningHours = [ "something" ]
IsAlwaysOpen = false
}
]
|> List.map TestCaseData
[<TestCaseSource(nameof (gymOpeningHoursCases))>]
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
[<TestCaseSource(nameof (gymAccessOptionsCases))>]
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
[<TestCaseSource(nameof (gymLocationCases))>]
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
[<TestCaseSource(nameof (gymAddressCases))>]
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 = Some "2021-04-12T00:00:00+01 Europe/London"
}
[ ovalJson, oval ] |> List.map TestCaseData
[<TestCaseSource(nameof (gymCases))>]
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
[<TestCaseSource(nameof memberCases)>]
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 = Some 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
[<TestCaseSource(nameof gymAttendanceCases)>]
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
[<TestCaseSource(nameof memberActivityDtoCases)>]
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
[<TestCaseSource(nameof sessionsCases)>]
let ``Sessions JSON parse`` (json : string, expected : Sessions) =
json
|> fun o -> JsonNode.Parse (o, Nullable (JsonNodeOptions (PropertyNameCaseInsensitive = true)))
|> Sessions.jsonParse
|> shouldEqual expected

View File

@@ -2,308 +2,44 @@ namespace PureGym
open System
open System.Net.Http
open System.Text.Json.Serialization
open System.Threading
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"
[<JsonRequired>]
AddressLine1 : string
/// E.g. "Units 4, 4A, 5 And 5A"
AddressLine2 : string
/// E.g. "Kennington Park"
AddressLine3 : string
/// E.g. "LONDON"
[<JsonRequired>]
Town : string
County : string
/// E.g. "SW9 6DE"
[<JsonRequired>]
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"
[<JsonRequired>]
Name : string
/// This gym's ID in the PureGym system, e.g. 19
[<JsonRequired>]
Id : int
/// I don't know what this status is. Please tell me if you know!
[<JsonRequired>]
Status : int
/// Postal address of this gym
[<JsonRequired>]
Address : GymAddress
/// Phone number of this gym, e.g. "+44 1234 567890"
[<JsonRequired>]
PhoneNumber : string
/// Contact email address for this gym's staff
[<JsonRequired>]
EmailAddress : string
/// When this gym is open
[<JsonRequired>]
GymOpeningHours : GymOpeningHours
/// How a human can physically authenticate when they physically enter this gym
[<JsonRequired>]
AccessOptions : GymAccessOptions
/// Where this gym is physically located
[<JsonRequired>]
Location : GymLocation
/// The IANA time zone this gym observes, e.g. "Europe/London"
[<JsonRequired>]
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<GymOpeningHours> this.GymOpeningHours}
%s{string<GymAccessOptions> 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.
[<JsonRequired>]
Description : string
/// How many people are in the gym as of this statistics snapshot
[<JsonRequired>]
TotalPeopleInGym : int
/// How many people are in classes at the gym as of this statistics snapshot
[<JsonRequired>]
TotalPeopleInClasses : int
/// E.g. " or fewer"
TotalPeopleSuffix : string
[<JsonRequired>]
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 =
{
[<JsonRequired>]
TotalDuration : int
[<JsonRequired>]
AverageDuration : int
[<JsonRequired>]
TotalVisits : int
[<JsonRequired>]
TotalClasses : int
[<JsonRequired>]
IsEstimated : bool
[<JsonRequired>]
LastRefreshed : DateTime
}
member this.ToMemberActivity () =
{
TotalDurationMinutes = this.TotalDuration
AverageDurationMinutes = this.AverageDuration
TotalVisits = this.TotalVisits
TotalClasses = this.TotalClasses
IsEstimated = this.IsEstimated
LastRefreshed = this.LastRefreshed
}
/// The PureGym REST API. You probably want to instantiate one of these with `Api.make`.
[<Header("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")>]
type IPureGymApi =
/// Get the complete list of all gyms known to PureGym.
[<Get "v1/gyms/">]
abstract GetGyms : unit -> Task<Gym list>
/// Get information about the PureGym human whose credentials this client is authenticated with.
[<Get "v1/member">]
abstract GetMember : unit -> Task<Member>
/// Get information about how full the given gym currently is. The gym ID can be found from `GetGyms`.
[<Get "v1/gyms/{gym_id}/attendance">]
abstract GetGymAttendance : [<Path "gym_id">] gymId : int -> Task<GymAttendance>
/// Get information about a specific gym.
[<Get "v1/gyms/{gym_id}">]
abstract GetGym : [<Path "gym_id">] gymId : int -> Task<Gym>
/// Get information about the activities logged against the currently authenticated PureGym human.
[<Get "v1/member/activity">]
abstract GetMemberActivity : unit -> Task<MemberActivityDto>
// [<Get "v1/member/activity/history">]
// abstract GetMemberActivityAll : unit -> Task<string>
/// Methods for interacting with the PureGym REST API.
[<RequireQualifiedAccess>]
module Api =
/// Create a REST client, authenticated as the specified user.
let make (auth : Auth) : IPureGymApi Task =
task {
/// Create a REST client, authenticated as the specified user. Creds will be refreshed if possible as long as
/// the returned disposable is not disposed.
let make (auth : Auth) : (IPureGymApi * IDisposable) Task =
let cache, getToken =
match auth with
| Auth.Token t ->
{ new IDisposable with
member _.Dispose () = ()
},
fun () -> t
| Auth.User cred ->
let cache = new Cache<_> (AuthToken.get cred, _.ExpiryTime)
cache :> _, (fun () -> Async.RunSynchronously (cache.GetCurrentValue ()))
async {
let client = new HttpClient ()
return PureGymApi.make (getToken >> _.AccessToken >> sprintf "Bearer %s") client, cache
}
|> Async.StartAsTask
/// Create a REST client, authenticated as the specified user. Do not refresh creds.
let makeWithoutRefresh (ct : CancellationToken) (auth : Auth) : IPureGymApi Task =
async {
let! token =
match auth with
| Auth.Token t -> Task.FromResult<_> t
| Auth.User cred -> AuthToken.get cred
| Auth.Token t -> async.Return t
| Auth.User cred -> Async.AwaitTask (AuthToken.get cred ct)
let client = new HttpClient ()
client.BaseAddress <- Uri "https://capi.puregym.com/api"
client.DefaultRequestHeaders.Authorization <-
Headers.AuthenticationHeaderValue ("Bearer", token.AccessToken)
client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
return RestClient.For<IPureGymApi> client
return PureGymApi.make (fun () -> $"Bearer %s{token.AccessToken}") client
}
|> Async.StartAsTask

View File

@@ -5,16 +5,22 @@ open System.Collections.Generic
open System.Net.Http
open System.Text.Json
open System.Text.Json.Serialization
open System.Threading
open System.Threading.Tasks
// System.Text.Json does not support internal F# records as of .NET 8, presumably because it can't find the constructor.
// So we end up with this gruesome soup of C#.
// TODO(net8): make this internal
/// An internal type. Don't use it.
type AuthResponseRaw [<JsonConstructor>] (access_token : string, expires_in : int, token_type : string, scope : string)
=
/// Don't use this internal type.
member _.access_token = access_token
/// Don't use this internal type.
member _.expires_in = expires_in
/// Don't use this internal type.
member _.token_type = token_type
/// Don't use this internal type.
member _.scope = scope
/// A token which can be used to authenticate with the PureGym API.
@@ -67,8 +73,9 @@ module AuthToken =
let private options = JsonSerializerOptions (IncludeFields = true)
/// Get an AuthToken for the given user email address with the given eight-digit PureGym PIN.
let get (creds : UsernamePin) : Task<AuthToken> =
task {
let get (creds : UsernamePin) (ct : CancellationToken) : Task<AuthToken> =
async {
let! ct = Async.CancellationToken
use client = new HttpClient ()
client.BaseAddress <- Uri "https://auth.puregym.com"
client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
@@ -84,14 +91,21 @@ module AuthToken =
|> List.map KeyValuePair
use content = new FormUrlEncodedContent (request)
let! response = client.PostAsync (Uri "https://auth.puregym.com/connect/token", content)
let! response =
Async.AwaitTask (
client.PostAsync (Uri "https://auth.puregym.com/connect/token", content, cancellationToken = ct)
)
if response.IsSuccessStatusCode then
let! content = response.Content.ReadAsStreamAsync ()
let! response = JsonSerializer.DeserializeAsync<AuthResponseRaw> (content, options)
// let! response = JsonSerializer.DeserializeAsync<AuthResponseRaw> (content, options)
let! content = Async.AwaitTask (response.Content.ReadAsStreamAsync ct)
let! response =
Async.AwaitTask (JsonSerializer.DeserializeAsync<AuthResponseRaw>(content, options, ct).AsTask ())
return AuthToken.Parse response
else
let! content = response.Content.ReadAsStringAsync ()
let! content = Async.AwaitTask (response.Content.ReadAsStringAsync ct)
return failwithf $"bad status code: %+A{response.StatusCode}\n%s{content}"
}
|> fun a -> Async.StartAsTask (a, cancellationToken = ct)

85
PureGym/Cache.fs Normal file
View File

@@ -0,0 +1,85 @@
namespace PureGym
open System
open System.Threading
open System.Threading.Tasks
type private CacheMessage<'a> =
| TriggerUpdate
| UpdateStored of 'a Task
| Get of AsyncReplyChannel<'a>
| Quit of AsyncReplyChannel<unit>
type internal Cache<'a> (obtainNew : CancellationToken -> 'a Task, expiry : 'a -> DateTime option) =
let cts = new CancellationTokenSource ()
let initialValue = obtainNew cts.Token
let rec handle (value : 'a Task) (mailbox : MailboxProcessor<CacheMessage<'a>>) : Async<unit> =
async {
let! message = mailbox.Receive ()
match message with
| Quit channel ->
channel.Reply ()
return ()
| CacheMessage.UpdateStored newValue -> return! handle newValue mailbox
| CacheMessage.TriggerUpdate ->
async {
let! a = Async.AwaitTask (obtainNew cts.Token)
let expiry = expiry a
match expiry with
| None -> return ()
| Some expiry ->
// a bit sloppy but :shrug:
do! Async.Sleep ((expiry - DateTime.Now) - TimeSpan.FromMinutes 1.0)
try
mailbox.Post CacheMessage.TriggerUpdate
with _ ->
// Post during shutdown sequence: drop it on the floor
()
return ()
}
|> fun a -> Async.Start (a, cancellationToken = cts.Token)
return! handle value mailbox
| CacheMessage.Get reply ->
let! valueAwaited = Async.AwaitTask value
reply.Reply valueAwaited
return! handle value mailbox
}
let mailbox = new MailboxProcessor<_> (handle initialValue)
do
mailbox.Start ()
mailbox.Post CacheMessage.TriggerUpdate
let isDisposing = ref 0
let hasDisposed = TaskCompletionSource<unit> ()
member this.GetCurrentValue () =
try
mailbox.PostAndAsyncReply CacheMessage.Get
with
// TODO I think this is the right exception...
| :? InvalidOperationException ->
raise (ObjectDisposedException (nameof (Cache)))
interface IDisposable with
member _.Dispose () =
if Interlocked.Increment isDisposing = 1 then
mailbox.PostAndReply CacheMessage.Quit
(mailbox :> IDisposable).Dispose ()
// We can't terminate the CTS until the mailbox has processed all client requests.
// Otherwise we terminate the mailbox's state Task before it has finished querying that
// task on behalf of clients.
cts.Cancel ()
cts.Dispose ()
hasDisposed.SetResult ()
else
hasDisposed.Task.Result

43
PureGym/Client.fs Normal file
View File

@@ -0,0 +1,43 @@
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`.
[<WoofWare.Myriad.Plugins.HttpClient>]
[<Header("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")>]
[<BaseAddress "https://capi.puregym.com/api/">]
type IPureGymApi =
[<Header "Authorization">]
abstract AuthHeader : string
/// Get the complete list of all gyms known to PureGym.
[<Get "v1/gyms/">]
abstract GetGyms : ?ct : CancellationToken -> Task<Gym list>
/// Get information about the PureGym human whose credentials this client is authenticated with.
[<Get "v1/member">]
abstract GetMember : ?ct : CancellationToken -> Task<Member>
/// Get information about how full the given gym currently is. The gym ID can be found from `GetGyms`.
[<Get "v1/gyms/{gym_id}/attendance">]
abstract GetGymAttendance : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<GymAttendance>
/// Get information about a specific gym.
[<Get "v1/gyms/{gym_id}">]
abstract GetGym : [<Path "gym_id">] gymId : int * ?ct : CancellationToken -> Task<Gym>
/// Get information about the activities logged against the currently authenticated PureGym human.
[<Get "v1/member/activity">]
abstract GetMemberActivity : ?ct : CancellationToken -> Task<MemberActivityDto>
/// Get information about the individual visits to all PureGyms the currently-authenticated PureGym human has made.
[<Get "v2/gymSessions/member">]
abstract GetSessions :
[<Query>] fromDate : DateOnly * [<Query>] toDate : DateOnly * ?ct : CancellationToken -> Task<Sessions>
// [<Get "v1/member/activity/history">]
// abstract GetMemberActivityAll : ?ct: CancellationToken -> Task<string>

366
PureGym/Dto.fs Normal file
View File

@@ -0,0 +1,366 @@
namespace PureGym
open System
open System.Text.Json.Serialization
/// Describes the opening hours of a given gym.
[<WoofWare.Myriad.Plugins.JsonParse>]
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
[<WoofWare.Myriad.Plugins.JsonParse>]
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
[<WoofWare.Myriad.Plugins.JsonParse>]
type GymLocation =
{
/// Measured in degrees
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
Longitude : float
/// Measured in degrees
[<JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)>]
Latitude : float
}
/// The postal address of a gym
[<WoofWare.Myriad.Plugins.JsonParse>]
type GymAddress =
{
/// E.g. "Canterbury Court"
[<JsonRequired>]
AddressLine1 : string
/// E.g. "Units 4, 4A, 5 And 5A"
AddressLine2 : string option
/// E.g. "Kennington Park"
AddressLine3 : string option
/// E.g. "LONDON"
[<JsonRequired>]
Town : string
/// Never seen this in the wild, sorry
County : string option
/// E.g. "SW9 6DE"
[<JsonRequired>]
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
[<WoofWare.Myriad.Plugins.JsonParse>]
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"
[<JsonRequired>]
Name : string
/// This gym's ID in the PureGym system, e.g. 19
[<JsonRequired>]
Id : int
/// I don't know what this status is. Please tell me if you know!
[<JsonRequired>]
Status : int
/// Postal address of this gym
[<JsonRequired>]
Address : GymAddress
/// Phone number of this gym, e.g. "+44 1234 567890"
[<JsonRequired>]
PhoneNumber : string
/// Contact email address for this gym's staff
[<JsonRequired>]
EmailAddress : string
/// When this gym is open
[<JsonRequired>]
GymOpeningHours : GymOpeningHours
/// How a human can physically authenticate when they physically enter this gym
[<JsonRequired>]
AccessOptions : GymAccessOptions
/// Where this gym is physically located
[<JsonRequired>]
Location : GymLocation
/// The IANA time zone this gym observes, e.g. "Europe/London"
[<JsonRequired>]
TimeZone : string
/// This is a date-time in the format yyyy-MM-ddTHH:mm:ss+01 Europe/London
ReopenDate : string option
}
/// 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<GymOpeningHours> this.GymOpeningHours}
%s{string<GymAccessOptions> this.AccessOptions}
"""
/// A human member of PureGym
[<WoofWare.Myriad.Plugins.JsonParse>]
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
[<JsonPropertyName "dateofBirth">]
DateOfBirth : DateOnly
/// This user's phone number, human-readable
MobileNumber : string
/// This user's registered home postcode
[<JsonPropertyName "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
[<WoofWare.Myriad.Plugins.JsonParse>]
type GymAttendance =
{
/// This appears always to be just equal to TotalPeopleInGym, but a string.
[<JsonRequired>]
Description : string
/// How many people are in the gym as of this statistics snapshot
[<JsonRequired>]
TotalPeopleInGym : int
/// How many people are in classes at the gym as of this statistics snapshot
TotalPeopleInClasses : int option
/// 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).
[<JsonRequired>]
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 =
match this.TotalPeopleInClasses with
| None
| Some 0 -> ""
| Some totalPeopleInClasses -> $"\n%i{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.
[<WoofWare.Myriad.Plugins.JsonParse>]
type MemberActivityDto =
{
[<JsonRequired>]
TotalDuration : int
[<JsonRequired>]
AverageDuration : int
[<JsonRequired>]
TotalVisits : int
[<JsonRequired>]
TotalClasses : int
[<JsonRequired>]
IsEstimated : bool
[<JsonRequired>]
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.
[<WoofWare.Myriad.Plugins.JsonParse>]
type SessionsAggregate =
{
/// Number of gym "activities" within some query-defined time period; presumably this is like classes?
/// It's always 0 for me.
[<JsonPropertyName "Activities">]
Activities : int
/// Number of visits to the gym within some query-defined time period.
[<JsonPropertyName "Visits">]
Visits : int
/// In minutes: total time spent in gym during the query-defined time period.
[<JsonPropertyName "Duration">]
Duration : int
}
/// The DTO for gym info returned from the Sessions endpoint.
[<WoofWare.Myriad.Plugins.JsonParse>]
type VisitGym =
{
// Omitting Location, GymAccess, ContactInfo, TimeZone because these were all null for me
/// The PureGym ID of this gym, e.g. 19
[<JsonPropertyName "Id">]
Id : int
/// E.g. "London Oval", the canonical name of this gym
[<JsonPropertyName "Name">]
Name : string
/// For some reason this always seems to be "Blocked"
[<JsonPropertyName "Status">]
Status : string
}
/// Summary of a single visit to a gym.
[<WoofWare.Myriad.Plugins.JsonParse>]
type Visit =
{
// Omitted Name because it always was null for me
/// Whether the Duration field is estimated.
[<JsonPropertyName "IsDurationEstimated">]
IsDurationEstimated : bool
/// When the visit began.
[<JsonPropertyName "StartTime">]
StartTime : DateTime
/// In minutes.
[<JsonPropertyName "Duration">]
Duration : int
/// Which gym was visited
[<JsonPropertyName "Gym">]
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.
[<WoofWare.Myriad.Plugins.JsonParse>]
type SessionsSummary =
{
/// Aggregate stats for gym visits within the query-dependent time period.
[<JsonPropertyName "Total">]
Total : SessionsAggregate
/// Aggregate stats for gym visits "this week", whatever that means to PureGym.
[<JsonPropertyName "ThisWeek">]
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.
[<WoofWare.Myriad.Plugins.JsonParse>]
type Sessions =
{
/// Aggregated summary over some time period.
[<JsonPropertyName "Summary">]
Summary : SessionsSummary
/// List of all individual visits made within some time period.
[<JsonPropertyName "Visits">]
Visits : Visit list
}
/// Human-readable non-round-trip representation.
override this.ToString () =
let summary = string<SessionsSummary> this.Summary
let visits = this.Visits |> Seq.map string<Visit> |> String.concat "\n"
$"%s{summary}\n%s{visits}"

235
PureGym/GeneratedClient.fs Normal file
View File

@@ -0,0 +1,235 @@
//------------------------------------------------------------------------------
// 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.
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix) ; RequireQualifiedAccess>]
module PureGymApi =
/// Create a REST client. The input functions will be re-evaluated on every HTTP request to obtain the required values for the corresponding header properties.
let make (authHeader : unit -> string) (client : System.Net.Http.HttpClient) : IPureGymApi =
{ new IPureGymApi with
member _.AuthHeader : string = authHeader ()
member this.GetGyms (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://capi.puregym.com/api/"
| v -> v),
System.Uri ("v1/gyms/", System.UriKind.Relative)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("Authorization", this.AuthHeader.ToString ())
do httpMessage.Headers.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return jsonNode.AsArray () |> Seq.map (fun elt -> Gym.jsonParse elt) |> List.ofSeq
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member this.GetMember (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://capi.puregym.com/api/"
| v -> v),
System.Uri ("v1/member", System.UriKind.Relative)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("Authorization", this.AuthHeader.ToString ())
do httpMessage.Headers.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return Member.jsonParse jsonNode
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member this.GetGymAttendance (gymId : int, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://capi.puregym.com/api/"
| v -> v),
System.Uri (
"v1/gyms/{gym_id}/attendance"
.Replace ("{gym_id}", gymId.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("Authorization", this.AuthHeader.ToString ())
do httpMessage.Headers.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return GymAttendance.jsonParse jsonNode
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member this.GetGym (gymId : int, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://capi.puregym.com/api/"
| v -> v),
System.Uri (
"v1/gyms/{gym_id}"
.Replace ("{gym_id}", gymId.ToString () |> System.Uri.EscapeDataString),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("Authorization", this.AuthHeader.ToString ())
do httpMessage.Headers.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return Gym.jsonParse jsonNode
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member this.GetMemberActivity (ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://capi.puregym.com/api/"
| v -> v),
System.Uri ("v1/member/activity", System.UriKind.Relative)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("Authorization", this.AuthHeader.ToString ())
do httpMessage.Headers.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return MemberActivityDto.jsonParse jsonNode
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
member this.GetSessions (fromDate : DateOnly, toDate : DateOnly, ct : CancellationToken option) =
async {
let! ct = Async.CancellationToken
let uri =
System.Uri (
(match client.BaseAddress with
| null -> System.Uri "https://capi.puregym.com/api/"
| v -> v),
System.Uri (
("v2/gymSessions/member"
+ (if "v2/gymSessions/member".IndexOf (char 63) >= 0 then
"&"
else
"?")
+ "fromDate="
+ ((fromDate.ToString "yyyy-MM-dd") |> System.Uri.EscapeDataString)
+ "&toDate="
+ ((toDate.ToString "yyyy-MM-dd") |> System.Uri.EscapeDataString)),
System.UriKind.Relative
)
)
let httpMessage =
new System.Net.Http.HttpRequestMessage (
Method = System.Net.Http.HttpMethod.Get,
RequestUri = uri
)
do httpMessage.Headers.Add ("Authorization", this.AuthHeader.ToString ())
do httpMessage.Headers.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
let! response = client.SendAsync (httpMessage, ct) |> Async.AwaitTask
let response = response.EnsureSuccessStatusCode ()
let! responseStream = response.Content.ReadAsStreamAsync ct |> Async.AwaitTask
let! jsonNode =
System.Text.Json.Nodes.JsonNode.ParseAsync (responseStream, cancellationToken = ct)
|> Async.AwaitTask
return Sessions.jsonParse jsonNode
}
|> (fun a -> Async.StartAsTask (a, ?cancellationToken = ct))
}

1003
PureGym/GeneratedDto.fs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -21,14 +21,17 @@ module GymSelector =
let canonicalId (client : IPureGymApi) (gym : GymSelector) : int Task =
match gym with
| GymSelector.Home ->
task {
let! self = client.GetMember ()
async {
let! ct = Async.CancellationToken
let! self = Async.AwaitTask (client.GetMember ct)
return self.HomeGymId
}
|> Async.StartAsTask
| GymSelector.Id i -> Task.FromResult<_> i
| GymSelector.Name name ->
task {
let! allGyms = client.GetGyms ()
async {
let! ct = Async.CancellationToken
let! allGyms = Async.AwaitTask (client.GetGyms ct)
if allGyms.IsEmpty then
return failwith "PureGym API returned no gyms!"
@@ -46,3 +49,4 @@ module GymSelector =
return bestGym.Id
}
|> Async.StartAsTask

View File

@@ -1,24 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
<WarnOn>FS3559</WarnOn>
<WoofWareMyriadPluginVersion>4.0.9</WoofWareMyriadPluginVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="String.fs" />
<Compile Include="Auth.fs" />
<Compile Include="Dto.fs" />
<Compile Include="GeneratedDto.fs">
<MyriadFile>Dto.fs</MyriadFile>
</Compile>
<Compile Include="Client.fs" />
<Compile Include="GeneratedClient.fs">
<MyriadFile>Client.fs</MyriadFile>
</Compile>
<Compile Include="Cache.fs" />
<Compile Include="Api.fs" />
<Compile Include="GymSelector.fs" />
<EmbeddedResource Include="SurfaceBaseline.txt" />
<EmbeddedResource Include="version.json" />
</ItemGroup>
<ItemGroup>
<MyriadSdkGenerator Include="$(NuGetPackageRoot)/woofware.myriad.plugins/$(WoofWareMyriadPluginVersion)/lib/net6.0/WoofWare.Myriad.Plugins.dll" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RestEase" Version="1.6.4" />
<PackageReference Update="FSharp.Core" Version="6.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.3" />
<PackageReference Include="Fastenshtein" Version="1.0.0.8" />
<PackageReference Update="FSharp.Core" Version="6.0.1" />
<PackageReference Include="System.Text.Json" Version="8.0.0" />
<PackageReference Include="Fastenshtein" Version="1.0.10" />
<PackageReference Include="Myriad.Sdk" Version="0.8.3" PrivateAssets="all" />
<PackageReference Include="WoofWare.Myriad.Plugins" Version="$(WoofWareMyriadPluginVersion)" PrivateAssets="all" />
<PackageReference Include="WoofWare.Myriad.Plugins.Attributes" Version="3.6.6" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,6 @@
PureGym.Api inherit obj
PureGym.Api.make [static method]: PureGym.Auth -> PureGym.IPureGymApi System.Threading.Tasks.Task
PureGym.Api.make [static method]: PureGym.Auth -> (PureGym.IPureGymApi * IDisposable) System.Threading.Tasks.Task
PureGym.Api.makeWithoutRefresh [static method]: System.Threading.CancellationToken -> 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
@@ -10,6 +11,7 @@ PureGym.Auth+Token.Item [property]: [read-only] PureGym.AuthToken
PureGym.Auth+User inherit PureGym.Auth
PureGym.Auth+User.get_Item [method]: unit -> PureGym.UsernamePin
PureGym.Auth+User.Item [property]: [read-only] PureGym.UsernamePin
PureGym.Auth.Equals [method]: (PureGym.Auth, System.Collections.IEqualityComparer) -> bool
PureGym.Auth.get_IsToken [method]: unit -> bool
PureGym.Auth.get_IsUser [method]: unit -> bool
PureGym.Auth.get_Tag [method]: unit -> int
@@ -31,17 +33,19 @@ PureGym.AuthResponseRaw.token_type [property]: [read-only] string
PureGym.AuthToken inherit obj, implements PureGym.AuthToken System.IEquatable, System.Collections.IStructuralEquatable, PureGym.AuthToken System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.AuthToken..ctor [constructor]: (string, System.DateTime option)
PureGym.AuthToken.AccessToken [property]: [read-only] string
PureGym.AuthToken.Equals [method]: (PureGym.AuthToken, System.Collections.IEqualityComparer) -> bool
PureGym.AuthToken.ExpiryTime [property]: [read-only] System.DateTime option
PureGym.AuthToken.get_AccessToken [method]: unit -> string
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.get [static method]: PureGym.UsernamePin -> System.Threading.CancellationToken -> PureGym.AuthToken System.Threading.Tasks.Task
PureGym.AuthTokenModule.ofBearerToken [static method]: string -> PureGym.AuthToken
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..ctor [constructor]: (string, int, int, PureGym.GymAddress, string, string, PureGym.GymOpeningHours, PureGym.GymAccessOptions, PureGym.GymLocation, string, string option)
PureGym.Gym.AccessOptions [property]: [read-only] PureGym.GymAccessOptions
PureGym.Gym.Address [property]: [read-only] PureGym.GymAddress
PureGym.Gym.EmailAddress [property]: [read-only] string
PureGym.Gym.Equals [method]: (PureGym.Gym, System.Collections.IEqualityComparer) -> bool
PureGym.Gym.get_AccessOptions [method]: unit -> PureGym.GymAccessOptions
PureGym.Gym.get_Address [method]: unit -> PureGym.GymAddress
PureGym.Gym.get_EmailAddress [method]: unit -> string
@@ -50,7 +54,7 @@ PureGym.Gym.get_Id [method]: unit -> int
PureGym.Gym.get_Location [method]: unit -> PureGym.GymLocation
PureGym.Gym.get_Name [method]: unit -> string
PureGym.Gym.get_PhoneNumber [method]: unit -> string
PureGym.Gym.get_ReopenDate [method]: unit -> string
PureGym.Gym.get_ReopenDate [method]: unit -> string option
PureGym.Gym.get_Status [method]: unit -> int
PureGym.Gym.get_TimeZone [method]: unit -> string
PureGym.Gym.GymOpeningHours [property]: [read-only] PureGym.GymOpeningHours
@@ -58,61 +62,78 @@ PureGym.Gym.Id [property]: [read-only] int
PureGym.Gym.Location [property]: [read-only] PureGym.GymLocation
PureGym.Gym.Name [property]: [read-only] string
PureGym.Gym.PhoneNumber [property]: [read-only] string
PureGym.Gym.ReopenDate [property]: [read-only] string
PureGym.Gym.ReopenDate [property]: [read-only] string option
PureGym.Gym.Status [property]: [read-only] int
PureGym.Gym.TimeZone [property]: [read-only] string
PureGym.GymAccessOptions inherit obj, implements PureGym.GymAccessOptions System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymAccessOptions System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.GymAccessOptions..ctor [constructor]: (bool, bool)
PureGym.GymAccessOptions.Equals [method]: (PureGym.GymAccessOptions, System.Collections.IEqualityComparer) -> bool
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.Equals [method]: (PureGym.GymAddress, System.Collections.IEqualityComparer) -> bool
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 option, 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.Equals [method]: (PureGym.GymAttendance, System.Collections.IEqualityComparer) -> bool
PureGym.GymAttendance.get_AttendanceTime [method]: unit -> System.DateTime
PureGym.GymAttendance.get_Description [method]: unit -> string
PureGym.GymAttendance.get_IsApproximate [method]: unit -> bool
PureGym.GymAttendance.get_LastRefreshed [method]: unit -> System.DateTime
PureGym.GymAttendance.get_LastRefreshedPeopleInClasses [method]: unit -> System.DateTime
PureGym.GymAttendance.get_MaximumCapacity [method]: unit -> int
PureGym.GymAttendance.get_TotalPeopleInClasses [method]: unit -> int
PureGym.GymAttendance.get_TotalPeopleInClasses [method]: unit -> int option
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.TotalPeopleInClasses [property]: [read-only] int option
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.Equals [method]: (PureGym.GymLocation, System.Collections.IEqualityComparer) -> bool
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.Equals [method]: (PureGym.GymOpeningHours, System.Collections.IEqualityComparer) -> bool
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
@@ -124,6 +145,7 @@ PureGym.GymSelector+Tags inherit obj
PureGym.GymSelector+Tags.Home [static field]: int = 2
PureGym.GymSelector+Tags.Id [static field]: int = 0
PureGym.GymSelector+Tags.Name [static field]: int = 1
PureGym.GymSelector.Equals [method]: (PureGym.GymSelector, System.Collections.IEqualityComparer) -> bool
PureGym.GymSelector.get_Home [static method]: unit -> PureGym.GymSelector
PureGym.GymSelector.get_IsHome [method]: unit -> bool
PureGym.GymSelector.get_IsId [method]: unit -> bool
@@ -138,17 +160,21 @@ PureGym.GymSelector.NewName [static method]: string -> PureGym.GymSelector
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 5 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 - interface with 8 member(s)
PureGym.IPureGymApi.AuthHeader [property]: [read-only] string
PureGym.IPureGymApi.get_AuthHeader [method]: unit -> string
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
PureGym.Member.DateOfBirth [property]: [read-only] System.DateOnly
PureGym.Member.EmailAddress [property]: [read-only] string
PureGym.Member.Equals [method]: (PureGym.Member, System.Collections.IEqualityComparer) -> bool
PureGym.Member.FirstName [property]: [read-only] string
PureGym.Member.get_CompoundMemberId [method]: unit -> string
PureGym.Member.get_DateOfBirth [method]: unit -> System.DateOnly
@@ -179,6 +205,7 @@ PureGym.Member.SuspendedReason [property]: [read-only] int
PureGym.MemberActivityDto inherit obj, implements PureGym.MemberActivityDto System.IEquatable, System.Collections.IStructuralEquatable, PureGym.MemberActivityDto System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.MemberActivityDto..ctor [constructor]: (int, int, int, int, bool, System.DateTime)
PureGym.MemberActivityDto.AverageDuration [property]: [read-only] int
PureGym.MemberActivityDto.Equals [method]: (PureGym.MemberActivityDto, System.Collections.IEqualityComparer) -> bool
PureGym.MemberActivityDto.get_AverageDuration [method]: unit -> int
PureGym.MemberActivityDto.get_IsEstimated [method]: unit -> bool
PureGym.MemberActivityDto.get_LastRefreshed [method]: unit -> System.DateTime
@@ -191,9 +218,12 @@ 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
PureGym.MemberActivityThisMonth.Equals [method]: (PureGym.MemberActivityThisMonth, System.Collections.IEqualityComparer) -> bool
PureGym.MemberActivityThisMonth.get_AverageDurationMinutes [method]: unit -> int
PureGym.MemberActivityThisMonth.get_IsEstimated [method]: unit -> bool
PureGym.MemberActivityThisMonth.get_LastRefreshed [method]: unit -> System.DateTime
@@ -205,9 +235,67 @@ 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]: (unit -> string) -> 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.Equals [method]: (PureGym.Sessions, System.Collections.IEqualityComparer) -> bool
PureGym.Sessions.get_Summary [method]: unit -> PureGym.SessionsSummary
PureGym.Sessions.get_Visits [method]: unit -> PureGym.Visit list
PureGym.Sessions.Summary [property]: [read-only] PureGym.SessionsSummary
PureGym.Sessions.Visits [property]: [read-only] PureGym.Visit list
PureGym.SessionsAggregate inherit obj, implements PureGym.SessionsAggregate System.IEquatable, System.Collections.IStructuralEquatable, PureGym.SessionsAggregate System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.SessionsAggregate..ctor [constructor]: (int, int, int)
PureGym.SessionsAggregate.Activities [property]: [read-only] int
PureGym.SessionsAggregate.Duration [property]: [read-only] int
PureGym.SessionsAggregate.Equals [method]: (PureGym.SessionsAggregate, System.Collections.IEqualityComparer) -> bool
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.Equals [method]: (PureGym.SessionsSummary, System.Collections.IEqualityComparer) -> bool
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.Equals [method]: (PureGym.UsernamePin, System.Collections.IEqualityComparer) -> bool
PureGym.UsernamePin.get_Pin [method]: unit -> string
PureGym.UsernamePin.get_Username [method]: unit -> string
PureGym.UsernamePin.Pin [property]: [read-only] string
PureGym.UsernamePin.Username [property]: [read-only] string
PureGym.Visit inherit obj, implements PureGym.Visit System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Visit System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.Visit..ctor [constructor]: (bool, System.DateTime, int, PureGym.VisitGym)
PureGym.Visit.Duration [property]: [read-only] int
PureGym.Visit.Equals [method]: (PureGym.Visit, System.Collections.IEqualityComparer) -> bool
PureGym.Visit.get_Duration [method]: unit -> int
PureGym.Visit.get_Gym [method]: unit -> PureGym.VisitGym
PureGym.Visit.get_IsDurationEstimated [method]: unit -> bool
PureGym.Visit.get_StartTime [method]: unit -> System.DateTime
PureGym.Visit.Gym [property]: [read-only] PureGym.VisitGym
PureGym.Visit.IsDurationEstimated [property]: [read-only] bool
PureGym.Visit.StartTime [property]: [read-only] System.DateTime
PureGym.VisitGym inherit obj, implements PureGym.VisitGym System.IEquatable, System.Collections.IStructuralEquatable, PureGym.VisitGym System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.VisitGym..ctor [constructor]: (int, string, string)
PureGym.VisitGym.Equals [method]: (PureGym.VisitGym, System.Collections.IEqualityComparer) -> bool
PureGym.VisitGym.get_Id [method]: unit -> int
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
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

0
PureGym/myriad.toml Normal file
View File

View File

@@ -1,5 +1,5 @@
{
"version": "1.0",
"version": "6.0",
"publicReleaseRefSpec": [
"^refs/heads/main$"
],

13
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -20,15 +20,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1696981262,
"narHash": "sha256-YaCOjdqhbjBeyMjxlgFWt4XD/b9pGKWURgS3uEwNLtc=",
"lastModified": 1757068644,
"narHash": "sha256-NOrUtIhTkIIumj1E/Rsv1J37Yi3xGStISEo8tZm3KW4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a2b87a4f66f309d2f4b789fd0457f5fc5db0a9a6",
"rev": "8eb28adfa3dc4de28e792e3bf49fcf9007ca8ac9",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}

View File

@@ -1,12 +1,12 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils = {
url = "github:numtide/flake-utils";
};
};
outputs = inputs @ {
outputs = {
self,
nixpkgs,
flake-utils,
@@ -14,13 +14,14 @@
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
deps = builtins.fromJSON (builtins.readFile ./nix/deps.json);
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.dotnetCorePackages.sdk_9_0;
dotnet-runtime = pkgs.dotnetCorePackages.runtime_9_0;
version = "0.1";
dotnetTool = toolName: toolVersion: sha256:
dotnetTool = dllOverride: toolName: toolVersion: hash:
pkgs.stdenvNoCC.mkDerivation rec {
name = toolName;
version = toolVersion;
@@ -28,49 +29,32 @@
src = pkgs.fetchNuGet {
pname = name;
version = version;
sha256 = sha256;
installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
hash = hash;
installPhase = ''mkdir -p $out/bin && cp -r tools/*/any/* $out/bin'';
};
installPhase = ''
installPhase = let
dll =
if isNull dllOverride
then name
else dllOverride;
in ''
runHook preInstall
mkdir -p "$out/lib"
cp -r ./bin/* "$out/lib"
makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${name}.dll"
makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --set DOTNET_HOST_PATH "${dotnet-sdk}/bin/dotnet" --add-flags "$out/lib/${dll}.dll"
runHook postInstall
'';
};
fantomas = dotnetTool "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version "sha256-83RodORaC3rkYfbFMHsYLEtl0+8+akZXcKoSJdgwuUo=";
fantomas = dotnetTool null "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version (builtins.head (builtins.filter (elem: elem.pname == "fantomas") deps)).hash;
in {
packages = {
fantomas = fantomas;
fetchDeps = let
flags = [];
runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms;
in
pkgs.writeShellScriptBin "fetch-${pname}-deps" (builtins.readFile (pkgs.substituteAll {
src = ./nix/fetchDeps.sh;
pname = pname;
binPath = pkgs.lib.makeBinPath [pkgs.coreutils dotnet-sdk (pkgs.nuget-to-nix.override {inherit dotnet-sdk;})];
projectFiles = toString (pkgs.lib.toList projectFile);
testProjectFiles = toString (pkgs.lib.toList testProjectFile);
rids = pkgs.lib.concatStringsSep "\" \"" runtimeIds;
packages = dotnet-sdk.packages;
storeSrc = pkgs.srcOnly {
src = ./.;
pname = pname;
version = version;
};
}));
default = pkgs.buildDotnetModule {
pname = pname;
inherit pname version projectFile testProjectFile dotnet-sdk dotnet-runtime;
name = "puregym";
version = version;
src = ./.;
projectFile = projectFile;
nugetDeps = ./nix/deps.nix;
nugetDeps = ./nix/deps.json; # `nix build .#default.fetch-deps && ./result nix/deps.json` and put the result here
doCheck = true;
dotnet-sdk = dotnet-sdk;
dotnet-runtime = dotnet-runtime;
};
};
apps = {
@@ -80,13 +64,7 @@
};
};
devShells.default = pkgs.mkShell {
buildInputs =
[pkgs.alejandra pkgs.dotnet-sdk_7 pkgs.python3 pkgs.nodePackages.markdown-link-check]
++ (
if pkgs.stdenv.isDarwin
then [pkgs.darwin.apple_sdk.frameworks.CoreServices]
else []
);
buildInputs = [pkgs.alejandra dotnet-sdk pkgs.python3 pkgs.nodePackages.markdown-link-check];
};
checks = {
alejandra = pkgs.stdenvNoCC.mkDerivation {

377
nix/deps.json Normal file
View File

@@ -0,0 +1,377 @@
[
{
"pname": "ApiSurface",
"version": "5.0.1",
"hash": "sha256-0GMXEMFgWbbE2OGxW+6h4zGgQHg+IZy1aI13Dn97xSU="
},
{
"pname": "Argu",
"version": "6.2.5",
"hash": "sha256-5HcZcvco4e8+hgLhzlxk7ZmFVLtZL9LVr7LbmXsLmNU="
},
{
"pname": "fantomas",
"version": "6.3.15",
"hash": "sha256-Gjw7MxjUNckMWSfnOye4UTe5fZWnor6RHCls3PNsuG8="
},
{
"pname": "Fantomas.Core",
"version": "6.1.1",
"hash": "sha256-FcTLHQFvKkQY/kV08jhhy/St/+FmXpp3epp/R3zUXMA="
},
{
"pname": "Fantomas.FCS",
"version": "6.1.1",
"hash": "sha256-NuZ8msPEHYA8T3EYREB28F1RcNgUU8V54eg2+UttYxw="
},
{
"pname": "Fastenshtein",
"version": "1.0.10",
"hash": "sha256-9qE1zKJhfRvx7X/66MAk2+F7pwrd/2EKKl7r5qjTPCk="
},
{
"pname": "FsCheck",
"version": "3.3.1",
"hash": "sha256-k65ksdOSOGz+meRUUND+yuqJtm5ChaKuaxmRIdKzx2Y="
},
{
"pname": "FSharp.Core",
"version": "6.0.1",
"hash": "sha256-Ehsgt3nCJijpaVuJguC1TPVEKSkJd6PSc07D2ZQSemI="
},
{
"pname": "FsUnit",
"version": "7.1.0",
"hash": "sha256-HHuIEocJrm6PSiTJeMWaYDsPYow8A/NFthU7sgB88sk="
},
{
"pname": "Microsoft.ApplicationInsights",
"version": "2.23.0",
"hash": "sha256-5sf3bg7CZZjHseK+F3foOchEhmVeioePxMZVvS6Rjb0="
},
{
"pname": "Microsoft.AspNetCore.App.Ref",
"version": "6.0.36",
"hash": "sha256-9jDkWbjw/nd8yqdzVTagCuqr6owJ/DUMi4BlUZT4hWU="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
"version": "6.0.36",
"hash": "sha256-JQULJyF0ivLoUU1JaFfK/HHg+/qzpN7V2RR2Cc+WlQ4="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
"version": "6.0.36",
"hash": "sha256-zUsVIpV481vMLAXaLEEUpEMA9/f1HGOnvaQnaWdzlyY="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
"version": "6.0.36",
"hash": "sha256-2seqZcz0JeUjkzh3QcGa9TcJ4LUafpFjTRk+Nm8T6T0="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
"version": "6.0.36",
"hash": "sha256-yxLafxiBKkvfkDggPk0P9YZIHBkDJOsFTO7/V9mEHuU="
},
{
"pname": "Microsoft.CodeCoverage",
"version": "17.14.1",
"hash": "sha256-f8QytG8GvRoP47rO2KEmnDLxIpyesaq26TFjDdW40Gs="
},
{
"pname": "Microsoft.NET.Test.Sdk",
"version": "17.14.1",
"hash": "sha256-mZUzDFvFp7x1nKrcnRd0hhbNu5g8EQYt8SKnRgdhT/A="
},
{
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
"version": "6.0.36",
"hash": "sha256-9lC/LYnthYhjkWWz2kkFCvlA5LJOv11jdt59SDnpdy0="
},
{
"pname": "Microsoft.NETCore.App.Host.linux-x64",
"version": "6.0.36",
"hash": "sha256-VFRDzx7LJuvI5yzKdGmw/31NYVbwHWPKQvueQt5xc10="
},
{
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
"version": "6.0.36",
"hash": "sha256-DaSWwYACJGolEBuMhzDVCj/rQTdDt061xCVi+gyQnuo="
},
{
"pname": "Microsoft.NETCore.App.Host.osx-x64",
"version": "6.0.36",
"hash": "sha256-FrRny9EI6HKCKQbu6mcLj5w4ooSRrODD4Vj2ZMGnMd4="
},
{
"pname": "Microsoft.NETCore.App.Ref",
"version": "6.0.36",
"hash": "sha256-9LZgVoIFF8qNyUu8kdJrYGLutMF/cL2K82HN2ywwlx8="
},
{
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
"version": "6.0.36",
"hash": "sha256-k3rxvUhCEU0pVH8KgEMtkPiSOibn+nBh+0zT2xIfId8="
},
{
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
"version": "6.0.36",
"hash": "sha256-U8wJ2snSDFqeAgDVLXjnniidC7Cr5aJ1/h/BMSlyu0c="
},
{
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
"version": "6.0.36",
"hash": "sha256-UfLcrL2Gj/OLz0s92Oo+OCJeDpZFAcQLPLiSNND8D5Y="
},
{
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
"version": "6.0.36",
"hash": "sha256-0xIJYFzxdMcnCj3wzkFRQZSnQcPHzPHMzePRIOA3oJs="
},
{
"pname": "Microsoft.NETCore.Platforms",
"version": "1.1.0",
"hash": "sha256-FeM40ktcObQJk4nMYShB61H/E8B7tIKfl9ObJ0IOcCM="
},
{
"pname": "Microsoft.NETCore.Platforms",
"version": "1.1.1",
"hash": "sha256-8hLiUKvy/YirCWlFwzdejD2Db3DaXhHxT7GSZx/znJg="
},
{
"pname": "Microsoft.NETCore.Targets",
"version": "1.1.0",
"hash": "sha256-0AqQ2gMS8iNlYkrD+BxtIg7cXMnr9xZHtKAuN4bjfaQ="
},
{
"pname": "Microsoft.NETCore.Targets",
"version": "1.1.3",
"hash": "sha256-WLsf1NuUfRWyr7C7Rl9jiua9jximnVvzy6nk2D2bVRc="
},
{
"pname": "Microsoft.Testing.Extensions.Telemetry",
"version": "1.7.3",
"hash": "sha256-Z6WsY2FCUbNnT5HJd7IOrfOvqknVXp6PWzTVeb0idVg="
},
{
"pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions",
"version": "1.7.3",
"hash": "sha256-PTee04FHyTHx/gF5NLckXuVje807G51MzkPrZ1gkgCw="
},
{
"pname": "Microsoft.Testing.Extensions.VSTestBridge",
"version": "1.7.3",
"hash": "sha256-8d+wZmucfSO7PsviHjVxYB4q6NcjgxvnCUpLePq35sM="
},
{
"pname": "Microsoft.Testing.Platform",
"version": "1.7.3",
"hash": "sha256-cavX11P5o9rooqC3ZHw5h002OKRg2ZNR/VaRwpNTQYA="
},
{
"pname": "Microsoft.Testing.Platform.MSBuild",
"version": "1.7.3",
"hash": "sha256-cREl529UQ/c5atT8KimMgrgNdy6MrAd0sBGT8sXRRPM="
},
{
"pname": "Microsoft.TestPlatform.AdapterUtilities",
"version": "17.13.0",
"hash": "sha256-Vr+3Tad/h/nk7f/5HMExn3HvCGFCarehFAzJSfCBaOc="
},
{
"pname": "Microsoft.TestPlatform.ObjectModel",
"version": "17.13.0",
"hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
},
{
"pname": "Microsoft.TestPlatform.ObjectModel",
"version": "17.14.1",
"hash": "sha256-QMf6O+w0IT+16Mrzo7wn+N20f3L1/mDhs/qjmEo1rYs="
},
{
"pname": "Microsoft.TestPlatform.TestHost",
"version": "17.14.1",
"hash": "sha256-1cxHWcvHRD7orQ3EEEPPxVGEkTpxom1/zoICC9SInJs="
},
{
"pname": "Myriad.Core",
"version": "0.8.3",
"hash": "sha256-vBOxfq8QriX/yUtaXN69rEQaY/psRNJWxqATLidrt2g="
},
{
"pname": "Myriad.Sdk",
"version": "0.8.3",
"hash": "sha256-7O397WKhskKOvE3MkJT37BvxorDWngDR6gTUogtDZ2M="
},
{
"pname": "Newtonsoft.Json",
"version": "13.0.1",
"hash": "sha256-K2tSVW4n4beRPzPu3rlVaBEMdGvWSv/3Q1fxaDh4Mjo="
},
{
"pname": "Newtonsoft.Json",
"version": "13.0.3",
"hash": "sha256-hy/BieY4qxBWVVsDqqOPaLy1QobiIapkbrESm6v2PHc="
},
{
"pname": "NuGet.Common",
"version": "6.14.0",
"hash": "sha256-jDOwt3veI1GSG8CfBnf2+dJxD3E/Nmlc+vHtD4J76Ms="
},
{
"pname": "NuGet.Configuration",
"version": "6.14.0",
"hash": "sha256-1PN9s6fhCw3wd2260U6hQ4vG3jIvcG8GIn1oQgxMXA0="
},
{
"pname": "NuGet.Frameworks",
"version": "6.14.0",
"hash": "sha256-3ViM3R1ucQMEL2hQYsivT86kI6veMQK2xDsiAcFcVQk="
},
{
"pname": "NuGet.Packaging",
"version": "6.14.0",
"hash": "sha256-Yafbnxs3maj55bJ1oKQiZ0QkntFUzXdhorL94YEUOhY="
},
{
"pname": "NuGet.Protocol",
"version": "6.14.0",
"hash": "sha256-uLDKfs+QN1MdnuQtTES8qfNzzsmYKM6XB9pwJc4G+eo="
},
{
"pname": "NuGet.Versioning",
"version": "6.14.0",
"hash": "sha256-DqdOJgsphKxSvqB8b60zNPCaiLfbiu3WnUJ/90feLrY="
},
{
"pname": "NUnit",
"version": "4.4.0",
"hash": "sha256-5geF5QOF+X/WkuCEgkPVKH4AdKx4U0olpU07S8+G3nU="
},
{
"pname": "NUnit3TestAdapter",
"version": "5.1.0",
"hash": "sha256-5z470sFjV67wGHaw8KfmSloIAYe81Dokp0f8I6zXsDc="
},
{
"pname": "RestEase",
"version": "1.6.4",
"hash": "sha256-FFmqFwlHhIln46k56Z8KM1G+xuPEh/bceKCQnJcdcdc="
},
{
"pname": "runtime.any.System.Runtime",
"version": "4.3.0",
"hash": "sha256-qwhNXBaJ1DtDkuRacgHwnZmOZ1u9q7N8j0cWOLYOELM="
},
{
"pname": "runtime.native.System",
"version": "4.3.0",
"hash": "sha256-ZBZaodnjvLXATWpXXakFgcy6P+gjhshFXmglrL5xD5Y="
},
{
"pname": "runtime.unix.System.Private.Uri",
"version": "4.3.0",
"hash": "sha256-c5tXWhE/fYbJVl9rXs0uHh3pTsg44YD1dJvyOA0WoMs="
},
{
"pname": "System.Collections.Immutable",
"version": "8.0.0",
"hash": "sha256-F7OVjKNwpqbUh8lTidbqJWYi476nsq9n+6k0+QVRo3w="
},
{
"pname": "System.Configuration.ConfigurationManager",
"version": "4.4.0",
"hash": "sha256-+8wGYllXnIxRzy9dLhZFB88GoPj8ivYXS0KUfcivT8I="
},
{
"pname": "System.Diagnostics.DiagnosticSource",
"version": "5.0.0",
"hash": "sha256-6mW3N6FvcdNH/pB58pl+pFSCGWgyaP4hfVtC/SMWDV4="
},
{
"pname": "System.Diagnostics.DiagnosticSource",
"version": "7.0.0",
"hash": "sha256-9Wk8cHSkjKtqkN6xW7KnXoQVtF/VNbKeBq79WqDesMs="
},
{
"pname": "System.Formats.Asn1",
"version": "6.0.0",
"hash": "sha256-KaMHgIRBF7Nf3VwOo+gJS1DcD+41cJDPWFh+TDQ8ee8="
},
{
"pname": "System.Memory",
"version": "4.5.5",
"hash": "sha256-EPQ9o1Kin7KzGI5O3U3PUQAZTItSbk9h/i4rViN3WiI="
},
{
"pname": "System.Private.Uri",
"version": "4.3.0",
"hash": "sha256-fVfgcoP4AVN1E5wHZbKBIOPYZ/xBeSIdsNF+bdukIRM="
},
{
"pname": "System.Reflection.Metadata",
"version": "8.0.0",
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
},
{
"pname": "System.Runtime",
"version": "4.3.1",
"hash": "sha256-R9T68AzS1PJJ7v6ARz9vo88pKL1dWqLOANg4pkQjkA0="
},
{
"pname": "System.Runtime.CompilerServices.Unsafe",
"version": "6.0.0",
"hash": "sha256-bEG1PnDp7uKYz/OgLOWs3RWwQSVYm+AnPwVmAmcgp2I="
},
{
"pname": "System.Security.Cryptography.Pkcs",
"version": "6.0.4",
"hash": "sha256-2e0aRybote+OR66bHaNiYpF//4fCiaO3zbR2e9GABUI="
},
{
"pname": "System.Security.Cryptography.ProtectedData",
"version": "4.4.0",
"hash": "sha256-Ri53QmFX8I8UH0x4PikQ1ZA07ZSnBUXStd5rBfGWFOE="
},
{
"pname": "System.Text.Encodings.Web",
"version": "8.0.0",
"hash": "sha256-IUQkQkV9po1LC0QsqrilqwNzPvnc+4eVvq+hCvq8fvE="
},
{
"pname": "System.Text.Json",
"version": "8.0.0",
"hash": "sha256-XFcCHMW1u2/WujlWNHaIWkbW1wn8W4kI0QdrwPtWmow="
},
{
"pname": "System.Text.Json",
"version": "8.0.5",
"hash": "sha256-yKxo54w5odWT6nPruUVsaX53oPRe+gKzGvLnnxtwP68="
},
{
"pname": "System.Text.Json",
"version": "9.0.0",
"hash": "sha256-aM5Dh4okLnDv940zmoFAzRmqZre83uQBtGOImJpoIqk="
},
{
"pname": "TypeEquality",
"version": "0.3.0",
"hash": "sha256-V50xAOzzyUJrY+MYPRxtnqW5MVeATXCes89wPprv1r4="
},
{
"pname": "WoofWare.Myriad.Plugins",
"version": "4.0.9",
"hash": "sha256-VWpStkuvdFZWsEs/tC0mjChneFgxWw+1YETH+3aCoz4="
},
{
"pname": "WoofWare.Myriad.Plugins.Attributes",
"version": "3.6.6",
"hash": "sha256-68T5JQNp4V0DDad0I3snVh8BCe7rz11mLyvm60hxwaA="
},
{
"pname": "WoofWare.Whippet.Fantomas",
"version": "0.3.1",
"hash": "sha256-i5oiqcrxzM90Ocuq5MIu2Ha5lV0aYu5nCvuwmFqp6NA="
}
]

View File

@@ -1,364 +0,0 @@
# 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";
sha256 = "sha256-83RodORaC3rkYfbFMHsYLEtl0+8+akZXcKoSJdgwuUo=";
})
(fetchNuGet {
pname = "ApiSurface";
version = "4.0.12";
sha256 = "0v56sv4cz8bgrfqjjg0q96619qs9dvvi0a6lp7hzz2mi82i1inmq";
})
(fetchNuGet {
pname = "coverlet.collector";
version = "3.2.0";
sha256 = "1qxpv8v10p5wn162lzdm193gdl6c5f81zadj8h889dprlnj3g8yr";
})
(fetchNuGet {
pname = "FSharp.Core";
version = "6.0.0";
sha256 = "1hjhvr39c1vpgrdmf8xln5q86424fqkvy9nirkr29vl2461d2039";
})
(fetchNuGet {
pname = "FSharp.Core";
version = "7.0.400";
sha256 = "1pl6iqqcpm9djfn7f6ms5j1xbcyz00nb808qd6pmsjrnylflalgp";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Ref";
version = "6.0.22";
sha256 = "0fqpl1fr213b4fb3c6xw3fy6669yxqcp1bzcnayw80yrskw8lpxs";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
version = "6.0.22";
sha256 = "1xvqqc7bzj764g3scp0saqxlfiv866crgi8chz57vhjp9sgd61jw";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
version = "7.0.11";
sha256 = "0hmsqy4yc3023mcp5rg0h59yv3f8cnjhxw1g4i8md67vm5y04lfv";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
version = "6.0.22";
sha256 = "1gcv99y295fnhy12fyx8wqvbhbj6mz8p5bm66ppwdxb3zykjg2l8";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
version = "7.0.11";
sha256 = "18sk9wka8z5354ca77q43hi0615yjssdjbyi0hqq92w6zmg43vgc";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
version = "6.0.22";
sha256 = "1ib0x1w33wqy7lgzjf14dvgx981xpjffjqd800d7wgxisgmakrmr";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
version = "7.0.11";
sha256 = "1j0zbd4rmmd3ylgixsvyj145g2r6px6b9d9k4yxxg6d61x90c165";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
version = "6.0.22";
sha256 = "026r38a7by7wdfd3virjdaah3y2sjjmnabgf5l25vdnwpwc7c31d";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
version = "7.0.11";
sha256 = "0wxw7vgygg6hqzq479n0pfjizr69wq7ja03a0qh8bma8b9q2mn6f";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
version = "6.0.22";
sha256 = "0ygdqsd312kqpykwb0k2942n45q1w3yn1nia6m1ahf7b74926qb5";
})
(fetchNuGet {
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
version = "7.0.11";
sha256 = "05ywwfn5lzx6y999f7gwmablkxi2zvska4sg20ihmjzp3xakcmk0";
})
(fetchNuGet {
pname = "Microsoft.CodeCoverage";
version = "17.6.0";
sha256 = "02s98d8nwz5mg4mymcr86qdamy71a29g2091xg452czmd3s3x2di";
})
(fetchNuGet {
pname = "Microsoft.NET.Test.Sdk";
version = "17.6.0";
sha256 = "1bnwpwg7k72z06027ip4yi222863r8sv14ck9nj8h64ckiw2r256";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-arm64";
version = "6.0.22";
sha256 = "0gri1gqznm5c8fsb6spqb3j88a3b0br0iy50y66fh4hz9wc4fwzm";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-arm64";
version = "7.0.11";
sha256 = "03nkxjn4wq30rw0163rqi8sngfxmcvwgm0wg7sgyb1cdh0q1ai68";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-x64";
version = "6.0.22";
sha256 = "0k1i74wn6j7nq0bd8m6jrpl65wda6qc9pglppvz4ybk0n2ab1rbi";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.linux-x64";
version = "7.0.11";
sha256 = "12hh69sr4wf8sjcw3q71vky51sn854ffahbq6rgz3njzvbvc0dbj";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-arm64";
version = "6.0.22";
sha256 = "0166gwarhhnary19lf80ff33bkx00mkm24f17bc8j6v7g3a7zvq6";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-x64";
version = "6.0.22";
sha256 = "038bjwk201p2kzs3jflrkhlnszf7cwalafq0nvs2v8bp7jlnx5ib";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.osx-x64";
version = "7.0.11";
sha256 = "1j1k735gkwba93n5yck87wppfpsbny979hppcygwrk81myf3fv03";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.win-x64";
version = "6.0.22";
sha256 = "1bjy3zmrmaq97xp0f3nzs3ax330ji632avrfpg8xz4vc5p8s1xpc";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Host.win-x64";
version = "7.0.11";
sha256 = "0ifshdx19bgnbgynbk6iy6gybnxmp63nylrn7068x66hvcavh7kh";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Ref";
version = "6.0.22";
sha256 = "0km8184kma8kgz7iyl3j6apj1n7vskzdhzmq3myy3y36ysqrb4wf";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
version = "6.0.22";
sha256 = "01gbl9dgky4h7ijxryz3527l39v23lkcvk4fs4w91ra4pris2n8p";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
version = "7.0.11";
sha256 = "1gzwc96fs222ddia0k1924cn7gxm2a4anqgcxhmavx56x76wsy6f";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
version = "6.0.22";
sha256 = "09gfqdxbh36bjx20fw9k94b9qa9bwffhrq0ldwn834mx31bgrfs8";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
version = "7.0.11";
sha256 = "0vxza49wwiia0d3m887yiaprp3xnax2bgzhj5bf080b4ayapzkf9";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
version = "6.0.22";
sha256 = "1x7wclv93q8wp7rip5nwnsxbqcami92yilvzbp0yn42ddkw177ds";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
version = "7.0.11";
sha256 = "15b62hxrpfy19xvyxlyligixxpa9sysfgi47xi4imx5055fhwphh";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
version = "6.0.22";
sha256 = "1sq1ygsrpv2sl85wrs8382wgkjic0zylaj1y8kcvhczcmkpk3wr5";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
version = "7.0.11";
sha256 = "018qf23b0jixfh3fm74zqaakk01qx6yq21gk2mdn68b0xhnvlzma";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.win-x64";
version = "6.0.22";
sha256 = "1nn254xv1hi5c4rg38fbfkln3031vv545lv9f4df31i8c1yfzz24";
})
(fetchNuGet {
pname = "Microsoft.NETCore.App.Runtime.win-x64";
version = "7.0.11";
sha256 = "12xmw2kcpf5rh8sv4y0mqzp917f7q8g4mfh5navqw4jmnxyb26qq";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Platforms";
version = "1.1.0";
sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm";
})
(fetchNuGet {
pname = "Microsoft.NETCore.Platforms";
version = "2.0.0";
sha256 = "1fk2fk2639i7nzy58m9dvpdnzql4vb8yl8vr19r2fp8lmj9w2jr0";
})
(fetchNuGet {
pname = "Microsoft.TestPlatform.ObjectModel";
version = "17.6.0";
sha256 = "1rz22chnis11dwjrqrcvvmfw80fi2a7756a7ahwy6jlnr250zr61";
})
(fetchNuGet {
pname = "Microsoft.TestPlatform.TestHost";
version = "17.6.0";
sha256 = "16vpicp4q2kbpgr3qwpsxg7srabxqszx23x6smjvvrvz7qmr5v8i";
})
(fetchNuGet {
pname = "NETStandard.Library";
version = "2.0.0";
sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy";
})
(fetchNuGet {
pname = "Newtonsoft.Json";
version = "13.0.1";
sha256 = "0fijg0w6iwap8gvzyjnndds0q4b8anwxxvik7y8vgq97dram4srb";
})
(fetchNuGet {
pname = "NuGet.Common";
version = "6.6.1";
sha256 = "1q7k5rqwchxgs5pnrn22d1rkdb7l2qblvsb9hy046ll69i71vv45";
})
(fetchNuGet {
pname = "NuGet.Configuration";
version = "6.6.1";
sha256 = "0pw4ikd8784iya920wxigacqn5g2v0zlpwxjlswyq5mnj2ha7gpk";
})
(fetchNuGet {
pname = "NuGet.Frameworks";
version = "5.11.0";
sha256 = "0wv26gq39hfqw9md32amr5771s73f5zn1z9vs4y77cgynxr73s4z";
})
(fetchNuGet {
pname = "NuGet.Frameworks";
version = "6.6.1";
sha256 = "1zq79mklzq7qyiyhcv3w8pznw6rq1ddcl8fvy7j1c6n8qh3mglhx";
})
(fetchNuGet {
pname = "NuGet.Packaging";
version = "6.6.1";
sha256 = "1lmx8kgpg220q8kic4wm8skccj53cbkdqggirq9js34gnxxi9b88";
})
(fetchNuGet {
pname = "NuGet.Protocol";
version = "6.6.1";
sha256 = "01n8cw114npvzfk3m3803lb8plk0wm1zg496gpq9az8hw20nmd8g";
})
(fetchNuGet {
pname = "NuGet.Versioning";
version = "6.6.1";
sha256 = "0n2p05y8ciw6jc5s238rlnx6q4dgxvm14v06pcd84ji5j1iirc30";
})
(fetchNuGet {
pname = "NUnit";
version = "3.13.3";
sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6";
})
(fetchNuGet {
pname = "NUnit.Analyzers";
version = "3.6.1";
sha256 = "16dw5375k2wyhiw9x387y7pjgq6zms30y036qb8z7idx4lxw9yi9";
})
(fetchNuGet {
pname = "NUnit3TestAdapter";
version = "4.4.2";
sha256 = "1n2jlc16vjdd81cb1by4qbp75sq73zsjz5w3zc61ssmbdci1q2ri";
})
(fetchNuGet {
pname = "RestEase";
version = "1.6.4";
sha256 = "1mvi3nbrr450g3fgd1y4wg3bwl9k1agyjfd9wdkqk12714bsln8l";
})
(fetchNuGet {
pname = "System.Formats.Asn1";
version = "5.0.0";
sha256 = "1axc8z0839yvqi2cb63l73l6d9j6wd20lsbdymwddz9hvrsgfwpn";
})
(fetchNuGet {
pname = "System.IO.Abstractions";
version = "4.2.13";
sha256 = "0s784iphsmj4vhkrzq9q3w39vsn76w44zclx3hsygsw458zbyh4y";
})
(fetchNuGet {
pname = "System.IO.FileSystem.AccessControl";
version = "4.5.0";
sha256 = "1gq4s8w7ds1sp8f9wqzf8nrzal40q5cd2w4pkf4fscrl2ih3hkkj";
})
(fetchNuGet {
pname = "System.Reflection.Metadata";
version = "1.6.0";
sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4";
})
(fetchNuGet {
pname = "System.Runtime.CompilerServices.Unsafe";
version = "6.0.0";
sha256 = "0qm741kh4rh57wky16sq4m0v05fxmkjjr87krycf5vp9f0zbahbc";
})
(fetchNuGet {
pname = "System.Security.AccessControl";
version = "4.5.0";
sha256 = "1wvwanz33fzzbnd2jalar0p0z3x0ba53vzx1kazlskp7pwyhlnq0";
})
(fetchNuGet {
pname = "System.Security.Cryptography.Cng";
version = "5.0.0";
sha256 = "06hkx2za8jifpslkh491dfwzm5dxrsyxzj5lsc0achb6yzg4zqlw";
})
(fetchNuGet {
pname = "System.Security.Cryptography.Pkcs";
version = "5.0.0";
sha256 = "0hb2mndac3xrw3786bsjxjfh19bwnr991qib54k6wsqjhjyyvbwj";
})
(fetchNuGet {
pname = "System.Security.Cryptography.ProtectedData";
version = "4.4.0";
sha256 = "1q8ljvqhasyynp94a1d7jknk946m20lkwy2c3wa8zw2pc517fbj6";
})
(fetchNuGet {
pname = "System.Security.Principal.Windows";
version = "4.5.0";
sha256 = "0rmj89wsl5yzwh0kqjgx45vzf694v9p92r4x4q6yxldk1cv1hi86";
})
(fetchNuGet {
pname = "System.Text.Encodings.Web";
version = "6.0.0";
sha256 = "06n9ql3fmhpjl32g3492sj181zjml5dlcc5l76xq2h38c4f87sai";
})
(fetchNuGet {
pname = "System.Text.Encodings.Web";
version = "7.0.0";
sha256 = "1151hbyrcf8kyg1jz8k9awpbic98lwz9x129rg7zk1wrs6vjlpxl";
})
(fetchNuGet {
pname = "System.Text.Json";
version = "6.0.0";
sha256 = "1si2my1g0q0qv1hiqnji4xh9wd05qavxnzj9dwgs23iqvgjky0gl";
})
(fetchNuGet {
pname = "System.Text.Json";
version = "7.0.3";
sha256 = "0zjrnc9lshagm6kdb9bdh45dmlnkpwcpyssa896sda93ngbmj8k9";
})
]

View File

@@ -1,73 +0,0 @@
#!/bin/bash
# This file was adapted from
# https://github.com/NixOS/nixpkgs/blob/b981d811453ab84fb3ea593a9b33b960f1ab9147/pkgs/build-support/dotnet/build-dotnet-module/default.nix#L173
set -euo pipefail
export PATH="@binPath@"
for arg in "$@"; do
case "$arg" in
--keep-sources|-k)
keepSources=1
shift
;;
--help|-h)
echo "usage: $0 [--keep-sources] [--help] <output path>"
echo " <output path> The path to write the lockfile to. A temporary file is used if this is not set"
echo " --keep-sources Don't remove temporary directories upon exit, useful for debugging"
echo " --help Show this help message"
exit
;;
esac
done
tmp=$(mktemp -td "@pname@-tmp-XXXXXX")
export tmp
HOME=$tmp/home
exitTrap() {
test -n "${ranTrap-}" && return
ranTrap=1
if test -n "${keepSources-}"; then
echo -e "Path to the source: $tmp/src\nPath to the fake home: $tmp/home"
else
rm -rf "$tmp"
fi
# Since mktemp is used this will be empty if the script didnt succesfully complete
if ! test -s "$depsFile"; then
rm -rf "$depsFile"
fi
}
trap exitTrap EXIT INT TERM
dotnetRestore() {
local -r project="${1-}"
local -r rid="$2"
dotnet restore "${project-}" \
-p:ContinuousIntegrationBuild=true \
-p:Deterministic=true \
--packages "$tmp/nuget_pkgs" \
--runtime "$rid" \
--no-cache \
--force
}
declare -a projectFiles=( @projectFiles@ )
declare -a testProjectFiles=( @testProjectFiles@ )
export DOTNET_NOLOGO=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
depsFile=$(realpath "${1:-$(mktemp -t "@pname@-deps-XXXXXX.nix")}")
mkdir -p "$tmp/nuget_pkgs"
storeSrc="@storeSrc@"
src="$tmp/src"
cp -rT "$storeSrc" "$src"
chmod -R +w "$src"
cd "$src"
echo "Restoring project..."
rids=("@rids@")
for rid in "${rids[@]}"; do
(( ${#projectFiles[@]} == 0 )) && dotnetRestore "" "$rid"
for project in "${projectFiles[@]-}" "${testProjectFiles[@]-}"; do
dotnetRestore "$project" "$rid"
done
done
echo "Successfully restored project"
echo "Writing lockfile..."
echo -e "# This file was automatically generated by passthru.fetch-deps.\n# Please don't edit it manually, your changes might get overwritten!\n" > "$depsFile"
nuget-to-nix "$tmp/nuget_pkgs" "@packages@" >> "$depsFile"
echo "Successfully wrote lockfile to $depsFile"