ts-trainingpeaks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +358 -0
- package/package.json +45 -0
- package/src/TrainingPeaks.ts +674 -0
- package/src/browser.ts +558 -0
- package/src/http.ts +265 -0
- package/src/index.ts +5 -0
- package/src/types.ts +319 -0
package/src/http.ts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client for TrainingPeaks API
|
|
3
|
+
* Handles cookies, authentication, and API requests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TPCookie } from './types'
|
|
7
|
+
|
|
8
|
+
export interface HttpClientOptions {
|
|
9
|
+
baseUrl?: string
|
|
10
|
+
timeout?: number
|
|
11
|
+
userAgent?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class HttpClient {
|
|
15
|
+
private cookies: Map<string, TPCookie> = new Map()
|
|
16
|
+
private baseUrl: string
|
|
17
|
+
private timeout: number
|
|
18
|
+
private userAgent: string
|
|
19
|
+
|
|
20
|
+
constructor(options: HttpClientOptions = {}) {
|
|
21
|
+
this.baseUrl = options.baseUrl || 'https://home.trainingpeaks.com'
|
|
22
|
+
this.timeout = options.timeout || 30000
|
|
23
|
+
this.userAgent = options.userAgent || 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setCookies(cookies: TPCookie[]): void {
|
|
27
|
+
for (const cookie of cookies) {
|
|
28
|
+
const key = `${cookie.domain || ''}:${cookie.name}`
|
|
29
|
+
this.cookies.set(key, cookie)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getCookies(): TPCookie[] {
|
|
34
|
+
return Array.from(this.cookies.values())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
clearCookies(): void {
|
|
38
|
+
this.cookies.clear()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private getCookieHeader(url: string): string {
|
|
42
|
+
const parsed = new URL(url)
|
|
43
|
+
const matching: TPCookie[] = []
|
|
44
|
+
|
|
45
|
+
for (const cookie of this.cookies.values()) {
|
|
46
|
+
// Check domain match
|
|
47
|
+
if (cookie.domain) {
|
|
48
|
+
const domain = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain
|
|
49
|
+
if (!parsed.hostname.endsWith(domain) && parsed.hostname !== domain) {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check path match
|
|
55
|
+
if (cookie.path && !parsed.pathname.startsWith(cookie.path)) {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check secure
|
|
60
|
+
if (cookie.secure && parsed.protocol !== 'https:') {
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check expiration
|
|
65
|
+
if (cookie.expires && cookie.expires < new Date()) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
matching.push(cookie)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return matching.map(c => `${c.name}=${c.value}`).join('; ')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private extractCookies(response: Response, url: string): void {
|
|
76
|
+
const setCookies = response.headers.getSetCookie()
|
|
77
|
+
const parsedUrl = new URL(url)
|
|
78
|
+
|
|
79
|
+
for (const cookieStr of setCookies) {
|
|
80
|
+
const cookie = this.parseCookie(cookieStr, parsedUrl.hostname)
|
|
81
|
+
if (cookie) {
|
|
82
|
+
const key = `${cookie.domain || ''}:${cookie.name}`
|
|
83
|
+
this.cookies.set(key, cookie)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private parseCookie(cookieStr: string, defaultDomain: string): TPCookie | null {
|
|
89
|
+
const parts = cookieStr.split(';').map(p => p.trim())
|
|
90
|
+
const [nameValue, ...attrs] = parts
|
|
91
|
+
|
|
92
|
+
if (!nameValue || !nameValue.includes('=')) return null
|
|
93
|
+
|
|
94
|
+
const [name, ...valueParts] = nameValue.split('=')
|
|
95
|
+
const value = valueParts.join('=')
|
|
96
|
+
|
|
97
|
+
const cookie: TPCookie = {
|
|
98
|
+
name: name.trim(),
|
|
99
|
+
value: value.trim(),
|
|
100
|
+
domain: defaultDomain,
|
|
101
|
+
path: '/',
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const attr of attrs) {
|
|
105
|
+
const [key, ...attrValueParts] = attr.split('=')
|
|
106
|
+
const attrKey = key.trim().toLowerCase()
|
|
107
|
+
const attrValue = attrValueParts.join('=').trim()
|
|
108
|
+
|
|
109
|
+
switch (attrKey) {
|
|
110
|
+
case 'domain':
|
|
111
|
+
cookie.domain = attrValue
|
|
112
|
+
break
|
|
113
|
+
case 'path':
|
|
114
|
+
cookie.path = attrValue
|
|
115
|
+
break
|
|
116
|
+
case 'expires':
|
|
117
|
+
cookie.expires = new Date(attrValue)
|
|
118
|
+
break
|
|
119
|
+
case 'max-age':
|
|
120
|
+
const maxAge = parseInt(attrValue, 10)
|
|
121
|
+
if (maxAge) cookie.expires = new Date(Date.now() + maxAge * 1000)
|
|
122
|
+
break
|
|
123
|
+
case 'secure':
|
|
124
|
+
cookie.secure = true
|
|
125
|
+
break
|
|
126
|
+
case 'httponly':
|
|
127
|
+
cookie.httpOnly = true
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return cookie
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async request<T>(
|
|
136
|
+
method: string,
|
|
137
|
+
path: string,
|
|
138
|
+
options: {
|
|
139
|
+
body?: unknown
|
|
140
|
+
headers?: Record<string, string>
|
|
141
|
+
params?: Record<string, string | number | boolean | undefined>
|
|
142
|
+
} = {}
|
|
143
|
+
): Promise<T> {
|
|
144
|
+
let url = path.startsWith('http') ? path : `${this.baseUrl}${path}`
|
|
145
|
+
|
|
146
|
+
// Add query params
|
|
147
|
+
if (options.params) {
|
|
148
|
+
const searchParams = new URLSearchParams()
|
|
149
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
150
|
+
if (value !== undefined) {
|
|
151
|
+
searchParams.set(key, String(value))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const queryString = searchParams.toString()
|
|
155
|
+
if (queryString) {
|
|
156
|
+
url += (url.includes('?') ? '&' : '?') + queryString
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const headers: Record<string, string> = {
|
|
161
|
+
'User-Agent': this.userAgent,
|
|
162
|
+
'Accept': 'application/json, text/plain, */*',
|
|
163
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
164
|
+
...options.headers,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add cookies
|
|
168
|
+
const cookieHeader = this.getCookieHeader(url)
|
|
169
|
+
if (cookieHeader) {
|
|
170
|
+
headers['Cookie'] = cookieHeader
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Add body
|
|
174
|
+
let body: string | undefined
|
|
175
|
+
if (options.body !== undefined) {
|
|
176
|
+
if (typeof options.body === 'object') {
|
|
177
|
+
headers['Content-Type'] = headers['Content-Type'] || 'application/json'
|
|
178
|
+
body = JSON.stringify(options.body)
|
|
179
|
+
}
|
|
180
|
+
else if (typeof options.body === 'string') {
|
|
181
|
+
body = options.body
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const controller = new AbortController()
|
|
186
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const response = await fetch(url, {
|
|
190
|
+
method,
|
|
191
|
+
headers,
|
|
192
|
+
body,
|
|
193
|
+
signal: controller.signal,
|
|
194
|
+
redirect: 'follow',
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
clearTimeout(timeoutId)
|
|
198
|
+
|
|
199
|
+
// Extract cookies from response
|
|
200
|
+
this.extractCookies(response, url)
|
|
201
|
+
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
const text = await response.text()
|
|
204
|
+
throw new Error(`HTTP ${response.status}: ${text}`)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const contentType = response.headers.get('content-type')
|
|
208
|
+
if (contentType?.includes('application/json')) {
|
|
209
|
+
return await response.json() as T
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return await response.text() as T
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
clearTimeout(timeoutId)
|
|
216
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
217
|
+
throw new Error(`Request timeout: ${url}`)
|
|
218
|
+
}
|
|
219
|
+
throw error
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
|
|
224
|
+
return this.request<T>('GET', path, { params })
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {
|
|
228
|
+
return this.request<T>('POST', path, { body, headers })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async put<T>(path: string, body?: unknown): Promise<T> {
|
|
232
|
+
return this.request<T>('PUT', path, { body })
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async delete<T>(path: string): Promise<T> {
|
|
236
|
+
return this.request<T>('DELETE', path)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Save cookies to disk
|
|
240
|
+
async saveCookies(path: string): Promise<void> {
|
|
241
|
+
const cookies = this.getCookies()
|
|
242
|
+
await Bun.write(path, JSON.stringify(cookies, null, 2))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Load cookies from disk
|
|
246
|
+
async loadCookies(path: string): Promise<void> {
|
|
247
|
+
try {
|
|
248
|
+
const file = Bun.file(path)
|
|
249
|
+
const content = await file.text()
|
|
250
|
+
const cookies = JSON.parse(content) as TPCookie[]
|
|
251
|
+
|
|
252
|
+
// Convert date strings back to Date objects
|
|
253
|
+
for (const cookie of cookies) {
|
|
254
|
+
if (cookie.expires) {
|
|
255
|
+
cookie.expires = new Date(cookie.expires)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.setCookies(cookies)
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// File doesn't exist or is invalid, ignore
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
export interface TPCredentials {
|
|
2
|
+
username: string
|
|
3
|
+
password: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface TPTokens {
|
|
7
|
+
accessToken: string
|
|
8
|
+
refreshToken?: string
|
|
9
|
+
expiresAt?: number
|
|
10
|
+
userId?: number
|
|
11
|
+
accountType?: 'athlete' | 'coach'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TPCookie {
|
|
15
|
+
name: string
|
|
16
|
+
value: string
|
|
17
|
+
domain?: string
|
|
18
|
+
path?: string
|
|
19
|
+
expires?: Date
|
|
20
|
+
secure?: boolean
|
|
21
|
+
httpOnly?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TPAthlete {
|
|
25
|
+
Id: number
|
|
26
|
+
UserId?: number
|
|
27
|
+
FirstName: string
|
|
28
|
+
LastName: string
|
|
29
|
+
Email?: string
|
|
30
|
+
DateOfBirth?: string
|
|
31
|
+
Gender?: string
|
|
32
|
+
City?: string
|
|
33
|
+
State?: string
|
|
34
|
+
Country?: string
|
|
35
|
+
Weight?: number
|
|
36
|
+
ProfilePhotoUrl?: string
|
|
37
|
+
IsCoach?: boolean
|
|
38
|
+
IsPremium?: boolean
|
|
39
|
+
ThresholdPower?: number
|
|
40
|
+
ThresholdHeartRate?: number
|
|
41
|
+
ThresholdPace?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TPCoachAthlete extends TPAthlete {
|
|
45
|
+
AthleteId: number
|
|
46
|
+
CoachId: number
|
|
47
|
+
Status: string
|
|
48
|
+
LastActivity?: string
|
|
49
|
+
CompliancePercent?: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface TPAthleteGroup {
|
|
53
|
+
id: number
|
|
54
|
+
coachId: number
|
|
55
|
+
name: string
|
|
56
|
+
athleteIds: number[]
|
|
57
|
+
isDefault: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TPWorkout {
|
|
61
|
+
workoutId: number
|
|
62
|
+
athleteId: number
|
|
63
|
+
workoutDay: string
|
|
64
|
+
title: string
|
|
65
|
+
description?: string
|
|
66
|
+
coachComments?: string
|
|
67
|
+
workoutComments?: unknown[]
|
|
68
|
+
workoutTypeValueId: number
|
|
69
|
+
completed: boolean | null
|
|
70
|
+
// Planned metrics
|
|
71
|
+
totalTimePlanned?: number
|
|
72
|
+
distancePlanned?: number
|
|
73
|
+
tssPlanned?: number
|
|
74
|
+
ifPlanned?: number
|
|
75
|
+
velocityPlanned?: number
|
|
76
|
+
caloriesPlanned?: number
|
|
77
|
+
energyPlanned?: number
|
|
78
|
+
elevationGainPlanned?: number
|
|
79
|
+
// Actual metrics
|
|
80
|
+
totalTime?: number
|
|
81
|
+
distance?: number
|
|
82
|
+
energy?: number
|
|
83
|
+
calories?: number
|
|
84
|
+
// Heart rate
|
|
85
|
+
heartRateAverage?: number
|
|
86
|
+
heartRateMinimum?: number
|
|
87
|
+
heartRateMaximum?: number
|
|
88
|
+
// Power
|
|
89
|
+
powerAverage?: number
|
|
90
|
+
powerMaximum?: number
|
|
91
|
+
normalizedPowerActual?: number
|
|
92
|
+
normalizedSpeedActual?: number
|
|
93
|
+
if?: number
|
|
94
|
+
tssActual?: number
|
|
95
|
+
// Cadence
|
|
96
|
+
cadenceAverage?: number
|
|
97
|
+
cadenceMaximum?: number
|
|
98
|
+
// Speed
|
|
99
|
+
velocityAverage?: number
|
|
100
|
+
velocityMaximum?: number
|
|
101
|
+
// Elevation
|
|
102
|
+
elevationGain?: number
|
|
103
|
+
elevationLoss?: number
|
|
104
|
+
elevationMinimum?: number
|
|
105
|
+
elevationAverage?: number
|
|
106
|
+
elevationMaximum?: number
|
|
107
|
+
// Temperature
|
|
108
|
+
tempMin?: number
|
|
109
|
+
tempAvg?: number
|
|
110
|
+
tempMax?: number
|
|
111
|
+
// RPE/Feeling
|
|
112
|
+
rpe?: number
|
|
113
|
+
feeling?: number
|
|
114
|
+
// Compliance
|
|
115
|
+
complianceDurationPercent?: number
|
|
116
|
+
complianceDistancePercent?: number
|
|
117
|
+
complianceTssPercent?: number
|
|
118
|
+
// Other
|
|
119
|
+
structure?: TPWorkoutStructureResponse
|
|
120
|
+
userTags?: string[] | null
|
|
121
|
+
syncedTo?: string[]
|
|
122
|
+
orderOnDay?: number
|
|
123
|
+
personalRecordCount?: number
|
|
124
|
+
lastModifiedDate?: string
|
|
125
|
+
isLocked?: boolean | null
|
|
126
|
+
startTime?: string | null
|
|
127
|
+
startTimePlanned?: string | null
|
|
128
|
+
sharedWorkoutInformationKey?: string
|
|
129
|
+
publicSettingValue?: number
|
|
130
|
+
code?: string | null
|
|
131
|
+
equipmentBikeId?: number | null
|
|
132
|
+
equipmentShoeId?: number | null
|
|
133
|
+
poolLengthOptionId?: number | null
|
|
134
|
+
workoutSubTypeId?: number | null
|
|
135
|
+
workoutDeviceSource?: string | null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface TPWorkoutStructureResponse {
|
|
139
|
+
structure: TPWorkoutStructureStep[]
|
|
140
|
+
polyline?: number[][]
|
|
141
|
+
primaryLengthMetric?: string
|
|
142
|
+
primaryIntensityMetric?: string
|
|
143
|
+
primaryIntensityTargetOrRange?: string
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface TPWorkoutStructureStep {
|
|
147
|
+
type: 'step' | 'repetition'
|
|
148
|
+
name?: string
|
|
149
|
+
length: {
|
|
150
|
+
value: number
|
|
151
|
+
unit: string
|
|
152
|
+
}
|
|
153
|
+
steps?: TPWorkoutStructureStep[]
|
|
154
|
+
targets?: {
|
|
155
|
+
minValue?: number
|
|
156
|
+
maxValue?: number
|
|
157
|
+
}[]
|
|
158
|
+
intensityClass?: string
|
|
159
|
+
openDuration?: boolean
|
|
160
|
+
begin?: number
|
|
161
|
+
end?: number
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Workout type IDs returned by API
|
|
165
|
+
export const TPWorkoutTypeIds = {
|
|
166
|
+
Swim: 1,
|
|
167
|
+
Bike: 2,
|
|
168
|
+
Run: 3,
|
|
169
|
+
Brick: 4,
|
|
170
|
+
XTrain: 5,
|
|
171
|
+
Strength: 6,
|
|
172
|
+
Custom: 7,
|
|
173
|
+
Walk: 8,
|
|
174
|
+
Hike: 9,
|
|
175
|
+
Row: 10,
|
|
176
|
+
Ski: 11,
|
|
177
|
+
Other: 100,
|
|
178
|
+
} as const
|
|
179
|
+
|
|
180
|
+
export type TPWorkoutTypeId = typeof TPWorkoutTypeIds[keyof typeof TPWorkoutTypeIds]
|
|
181
|
+
|
|
182
|
+
export type TPWorkoutType =
|
|
183
|
+
| 'Swim'
|
|
184
|
+
| 'Bike'
|
|
185
|
+
| 'Run'
|
|
186
|
+
| 'Brick'
|
|
187
|
+
| 'XTrain'
|
|
188
|
+
| 'Strength'
|
|
189
|
+
| 'Custom'
|
|
190
|
+
| 'Walk'
|
|
191
|
+
| 'Hike'
|
|
192
|
+
| 'Row'
|
|
193
|
+
| 'Ski'
|
|
194
|
+
| 'Other'
|
|
195
|
+
|
|
196
|
+
export function getWorkoutTypeName(typeId: number): TPWorkoutType {
|
|
197
|
+
const map: Record<number, TPWorkoutType> = {
|
|
198
|
+
1: 'Swim',
|
|
199
|
+
2: 'Bike',
|
|
200
|
+
3: 'Run',
|
|
201
|
+
4: 'Brick',
|
|
202
|
+
5: 'XTrain',
|
|
203
|
+
6: 'Strength',
|
|
204
|
+
7: 'Custom',
|
|
205
|
+
8: 'Walk',
|
|
206
|
+
9: 'Hike',
|
|
207
|
+
10: 'Row',
|
|
208
|
+
11: 'Ski',
|
|
209
|
+
}
|
|
210
|
+
return map[typeId] || 'Other'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface TPDailySummary {
|
|
214
|
+
Date: string
|
|
215
|
+
TssTotal?: number
|
|
216
|
+
DurationTotal?: number
|
|
217
|
+
DistanceTotal?: number
|
|
218
|
+
CaloriesTotal?: number
|
|
219
|
+
WorkoutsPlanned?: number
|
|
220
|
+
WorkoutsCompleted?: number
|
|
221
|
+
CompliancePercent?: number
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface TPMetrics {
|
|
225
|
+
Date: string
|
|
226
|
+
Weight?: number
|
|
227
|
+
RestingHeartRate?: number
|
|
228
|
+
HrvRmssd?: number
|
|
229
|
+
SleepHours?: number
|
|
230
|
+
SleepQuality?: number
|
|
231
|
+
Fatigue?: number
|
|
232
|
+
Mood?: number
|
|
233
|
+
Motivation?: number
|
|
234
|
+
Stress?: number
|
|
235
|
+
Soreness?: number
|
|
236
|
+
Injury?: number
|
|
237
|
+
Steps?: number
|
|
238
|
+
CaloriesConsumed?: number
|
|
239
|
+
Comments?: string
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface TPCalendarDay {
|
|
243
|
+
Date: string
|
|
244
|
+
Workouts: TPWorkout[]
|
|
245
|
+
Metrics?: TPMetrics
|
|
246
|
+
Notes?: string[]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface TPPerformanceChart {
|
|
250
|
+
StartDate: string
|
|
251
|
+
EndDate: string
|
|
252
|
+
Ctl: number[] // Chronic Training Load (fitness)
|
|
253
|
+
Atl: number[] // Acute Training Load (fatigue)
|
|
254
|
+
Tsb: number[] // Training Stress Balance (form)
|
|
255
|
+
Tss: number[] // Training Stress Score
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface TPActivityFile {
|
|
259
|
+
Id: number
|
|
260
|
+
FileName: string
|
|
261
|
+
FileType: string
|
|
262
|
+
UploadDate: string
|
|
263
|
+
ProcessedDate?: string
|
|
264
|
+
Status: string
|
|
265
|
+
WorkoutId?: number
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface TPUploadResult {
|
|
269
|
+
Success: boolean
|
|
270
|
+
FileId?: number
|
|
271
|
+
WorkoutId?: number
|
|
272
|
+
Message?: string
|
|
273
|
+
Errors?: string[]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface TPSearchParams {
|
|
277
|
+
startDate?: Date
|
|
278
|
+
endDate?: Date
|
|
279
|
+
workoutTypes?: TPWorkoutType[]
|
|
280
|
+
completed?: boolean
|
|
281
|
+
limit?: number
|
|
282
|
+
offset?: number
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface TPLibraryWorkout {
|
|
286
|
+
Id: number
|
|
287
|
+
Title: string
|
|
288
|
+
Description?: string
|
|
289
|
+
WorkoutType: TPWorkoutType
|
|
290
|
+
Duration?: number
|
|
291
|
+
Distance?: number
|
|
292
|
+
Tss?: number
|
|
293
|
+
Structure?: TPWorkoutStructureResponse
|
|
294
|
+
Tags?: string[]
|
|
295
|
+
IsPublic?: boolean
|
|
296
|
+
CreatedDate?: string
|
|
297
|
+
ModifiedDate?: string
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface TPPlan {
|
|
301
|
+
Id: number
|
|
302
|
+
Name: string
|
|
303
|
+
Description?: string
|
|
304
|
+
StartDate?: string
|
|
305
|
+
EndDate?: string
|
|
306
|
+
AthleteId?: number
|
|
307
|
+
CoachId?: number
|
|
308
|
+
Status: string
|
|
309
|
+
Weeks?: TPPlanWeek[]
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface TPPlanWeek {
|
|
313
|
+
WeekNumber: number
|
|
314
|
+
StartDate: string
|
|
315
|
+
EndDate: string
|
|
316
|
+
Description?: string
|
|
317
|
+
PlannedTss?: number
|
|
318
|
+
PlannedDuration?: number
|
|
319
|
+
}
|