diff --git a/PureGym.App/Program.fs b/PureGym.App/Program.fs
index 7443d71..ae84bc6 100644
--- a/PureGym.App/Program.fs
+++ b/PureGym.App/Program.fs
@@ -21,8 +21,12 @@ module Program =
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
diff --git a/PureGym.App/PureGym.App.fsproj b/PureGym.App/PureGym.App.fsproj
index 1e026b5..0a7b218 100644
--- a/PureGym.App/PureGym.App.fsproj
+++ b/PureGym.App/PureGym.App.fsproj
@@ -14,6 +14,7 @@
+
diff --git a/PureGym.App/Sessions.fs b/PureGym.App/Sessions.fs
new file mode 100644
index 0000000..4e641f6
--- /dev/null
+++ b/PureGym.App/Sessions.fs
@@ -0,0 +1,49 @@
+namespace PureGym.App
+
+open Argu
+open PureGym
+open System
+
+type SessionsArgsFragment =
+ | [] From_Date of string
+ | [] 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 : DateTime
+ ToDate : DateTime
+ }
+
+ static member Parse
+ (auth : Auth)
+ (args : SessionsArgsFragment ParseResults)
+ : Result
+ =
+ let fromDate = args.GetResult SessionsArgsFragment.From_Date
+ let toDate = args.GetResult SessionsArgsFragment.To_Date
+
+ {
+ Creds = auth
+ FromDate = DateTime.Parse fromDate
+ ToDate = DateTime.Parse toDate
+ }
+ |> Ok
+
+[]
+module Sessions =
+
+ let run (args : SessionsArgs) =
+ task {
+ let! client = Api.make args.Creds
+ let! activity = client.GetSessions args.FromDate args.ToDate
+
+ System.Console.WriteLine (string activity)
+ return 0
+ }
diff --git a/PureGym/Api.fs b/PureGym/Api.fs
index a98a6e8..6ff4793 100644
--- a/PureGym/Api.fs
+++ b/PureGym/Api.fs
@@ -260,6 +260,74 @@ type MemberActivityDto =
LastRefreshed = this.LastRefreshed
}
+type SessionsAggregate =
+ {
+ /// Number of gym "activities" within some query-defined time period; presumably this is like classes?
+ /// It's always 0 for me.
+ Activities : int
+ /// Number of visits to the gym within some query-defined time period.
+ Visits : int
+ /// In minutes: total time spent in gym during the query-defined time period.
+ Duration : int
+ }
+
+/// The DTO for gym info returned from the Sessions endpoint.
+type VisitGym =
+ {
+ // Omitting Location, GymAccess, ContactInfo, TimeZone because these were all null for me
+ /// The PureGym ID of this gym, e.g. 19
+ Id : int
+ /// E.g. "London Oval", the canonical name of this gym
+ Name : string
+ /// For some reason this always seems to be "Blocked"
+ Status : string
+ }
+
+/// Summary of a single visit to a gym.
+type Visit =
+ {
+ // Omitted Name because it always was null for me
+ /// Whether the Duration field is estimated.
+ IsDurationEstimated : bool
+ /// When the visit began.
+ StartTime : DateTime
+ /// In minutes.
+ Duration : int
+ /// Which gym was visited
+ Gym : VisitGym
+ }
+
+ /// Human-readable non-round-trip representation.
+ override this.ToString () =
+ let startTime = this.StartTime.ToString "yyyy-MM-dd HH:mm"
+ $"%s{this.Gym.Name}: %s{startTime} (%i{this.Duration} minutes)"
+
+/// Aggregate statistics for gym visits across a time period.
+type SessionsSummary =
+ {
+ /// Aggregate stats for gym visits within the query-dependent time period.
+ Total : SessionsAggregate
+ /// Aggregate stats for gym visits "this week", whatever that means to PureGym.
+ ThisWeek : SessionsAggregate
+ }
+
+ /// Human-readable non-round-trip representation.
+ override this.ToString () =
+ $"%i{this.Total.Visits} visits, totalling %i{this.Total.Duration} minutes"
+
+type Sessions =
+ {
+ Summary : SessionsSummary
+ Visits : Visit list
+ }
+
+ /// Human-readable non-round-trip representation.
+ override this.ToString () =
+ let summary = string this.Summary
+ let visits = this.Visits |> Seq.map string |> String.concat "\n"
+
+ $"%s{summary}\n%s{visits}"
+
/// The PureGym REST API. You probably want to instantiate one of these with `Api.make`.
[]
type IPureGymApi =
@@ -283,6 +351,10 @@ type IPureGymApi =
[]
abstract GetMemberActivity : unit -> Task
+ /// Get information about the individual visits to all PureGyms the currently-authenticated PureGym human has made.
+ []
+ abstract GetSessions : [] fromDate : DateTime -> [] toDate : DateTime -> Task
+
// []
// abstract GetMemberActivityAll : unit -> Task
diff --git a/PureGym/Auth.fs b/PureGym/Auth.fs
index 4aca107..c63c16a 100644
--- a/PureGym/Auth.fs
+++ b/PureGym/Auth.fs
@@ -10,11 +10,16 @@ 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 [] (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.
diff --git a/PureGym/SurfaceBaseline.txt b/PureGym/SurfaceBaseline.txt
index b4ec859..5102d5c 100644
--- a/PureGym/SurfaceBaseline.txt
+++ b/PureGym/SurfaceBaseline.txt
@@ -138,12 +138,13 @@ 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 - interface with 6 member(s)
PureGym.IPureGymApi.GetGym [method]: int -> PureGym.Gym System.Threading.Tasks.Task
PureGym.IPureGymApi.GetGymAttendance [method]: int -> PureGym.GymAttendance System.Threading.Tasks.Task
PureGym.IPureGymApi.GetGyms [method]: unit -> PureGym.Gym list System.Threading.Tasks.Task
PureGym.IPureGymApi.GetMember [method]: unit -> PureGym.Member System.Threading.Tasks.Task
PureGym.IPureGymApi.GetMemberActivity [method]: unit -> PureGym.MemberActivityDto System.Threading.Tasks.Task
+PureGym.IPureGymApi.GetSessions [method]: System.DateTime -> System.DateTime -> PureGym.Sessions System.Threading.Tasks.Task
PureGym.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
@@ -205,9 +206,47 @@ 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.Sessions inherit obj, implements PureGym.Sessions System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Sessions System.IComparable, System.IComparable, System.Collections.IStructuralComparable
+PureGym.Sessions..ctor [constructor]: (PureGym.SessionsSummary, PureGym.Visit list)
+PureGym.Sessions.get_Summary [method]: unit -> PureGym.SessionsSummary
+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.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.SessionsSummary inherit obj, implements PureGym.SessionsSummary System.IEquatable, System.Collections.IStructuralEquatable, PureGym.SessionsSummary System.IComparable, System.IComparable, System.Collections.IStructuralComparable
+PureGym.SessionsSummary..ctor [constructor]: (PureGym.SessionsAggregate, PureGym.SessionsAggregate)
+PureGym.SessionsSummary.get_ThisWeek [method]: unit -> PureGym.SessionsAggregate
+PureGym.SessionsSummary.get_Total [method]: unit -> PureGym.SessionsAggregate
+PureGym.SessionsSummary.ThisWeek [property]: [read-only] PureGym.SessionsAggregate
+PureGym.SessionsSummary.Total [property]: [read-only] PureGym.SessionsAggregate
PureGym.UsernamePin inherit obj, implements PureGym.UsernamePin System.IEquatable, System.Collections.IStructuralEquatable, PureGym.UsernamePin System.IComparable, System.IComparable, System.Collections.IStructuralComparable
PureGym.UsernamePin..ctor [constructor]: (string, string)
PureGym.UsernamePin.get_Pin [method]: unit -> string
PureGym.UsernamePin.get_Username [method]: unit -> string
PureGym.UsernamePin.Pin [property]: [read-only] string
-PureGym.UsernamePin.Username [property]: [read-only] string
\ No newline at end of file
+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.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.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
\ No newline at end of file
diff --git a/PureGym/version.json b/PureGym/version.json
index 5b7dc86..958fb13 100644
--- a/PureGym/version.json
+++ b/PureGym/version.json
@@ -1,5 +1,5 @@
{
- "version": "1.0",
+ "version": "2.0",
"publicReleaseRefSpec": [
"^refs/heads/main$"
],