feat: add API services for templates, stats, integrations, and calendar

This commit is contained in:
Blake Ridgway
2026-02-12 10:07:50 -06:00
parent 26993a04fb
commit a379f142a0
3 changed files with 200 additions and 80 deletions

View File

@@ -62,26 +62,26 @@ api.interceptors.response.use(
export const calendarApi = {
async getWorkouts() {
const { data } = await api.get('/protected/workouts')
const { data } = await api.get('/api/protected/workouts')
return data
},
async getWorkoutsByMonth(year, month) {
const { data } = await api.get('/protected/workouts/month', {
const { data } = await api.get('/api/protected/workouts/month', {
params: { year, month }
})
return data
},
async getWorkoutsByWeek(year, week) {
const { data } = await api.get('/protected/workouts/week', {
const { data } = await api.get('/api/protected/workouts/week', {
params: { year, week }
})
return data
},
async getWorkoutsByRange(startDate, endDate) {
const { data } = await api.get('/protected/workouts/range', {
const { data } = await api.get('/api/protected/workouts/range', {
params: {
start: startDate,
end: endDate
@@ -91,29 +91,29 @@ export const calendarApi = {
},
async createWorkout(workout) {
const { data } = await api.post('/protected/workouts', workout)
const { data } = await api.post('/api/protected/workouts', workout)
return data
},
async updateWorkout(workoutId, workout) {
const { data } = await api.put(`/protected/workouts?id=${workoutId}`, workout)
const { data } = await api.put(`/api/protected/workouts?id=${workoutId}`, workout)
return data
},
async rescheduleWorkout(workoutId, newDate) {
const { data } = await api.put(`/protected/workouts/reschedule?id=${workoutId}`, {
const { data } = await api.put(`/api/protected/workouts/reschedule?id=${workoutId}`, {
scheduled_date: newDate
})
return data
},
async deleteWorkout(workoutId) {
const { data } = await api.delete(`/protected/workouts?id=${workoutId}`)
const { data } = await api.delete(`/api/protected/workouts?id=${workoutId}`)
return data
},
async scheduleFromTemplate(templateId, templateName, templateType, duration, scheduledDate, workoutData = null) {
const { data } = await api.post('/protected/workouts/schedule-template', {
const { data } = await api.post('/api/protected/workouts/schedule-template', {
template_id: templateId,
template_name: templateName,
template_type: templateType,
@@ -130,7 +130,7 @@ export const calendarApi = {
if (scheduledDate) {
formData.append('scheduled_date', scheduledDate)
}
const { data } = await api.post('/protected/workouts/upload', formData, {
const { data } = await api.post('/api/protected/workouts/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
@@ -139,7 +139,7 @@ export const calendarApi = {
},
async exportICS(startDate, endDate) {
const response = await api.get('/protected/workouts/export/ics', {
const response = await api.get('/api/protected/workouts/export/ics', {
params: { start: startDate, end: endDate },
responseType: 'blob'
})
@@ -147,50 +147,77 @@ export const calendarApi = {
},
async getWorkoutTypes() {
const { data } = await api.get('/protected/workout-types')
const { data } = await api.get('/api/protected/workout-types')
return data
},
async completeWorkout(workoutId, completionData) {
const { data } = await api.post(`/protected/workouts/complete?id=${workoutId}`, completionData)
const { data } = await api.post(`/api/protected/workouts/complete?id=${workoutId}`, completionData)
return data
},
async importActivity(file, opts = {}) {
const formData = new FormData()
formData.append('file', file)
if (opts.title) formData.append('title', opts.title)
if (opts.workout_id) formData.append('workout_id', String(opts.workout_id))
if (opts.equipment_id) formData.append('equipment_id', String(opts.equipment_id))
if (opts.notes) formData.append('notes', opts.notes)
const { data } = await api.post('/api/protected/workouts/import', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
return data
},
async exportFIT(workoutId) {
const response = await api.get(`/api/protected/workouts/export/fit?id=${workoutId}`, {
responseType: 'blob'
})
return response.data
},
async exportZWO(workoutId) {
const response = await api.get(`/api/protected/workouts/export/zwo?id=${workoutId}`, {
responseType: 'blob'
})
return response.data
}
}
export const recurringApi = {
async create(schedule) {
const { data } = await api.post('/protected/workouts/recurring', schedule)
const { data } = await api.post('/api/protected/workouts/recurring', schedule)
return data
},
async list() {
const { data } = await api.get('/protected/workouts/recurring')
const { data } = await api.get('/api/protected/workouts/recurring')
return data
},
async delete(scheduleId, deleteFuture = true) {
const { data } = await api.delete(`/protected/workouts/recurring?id=${scheduleId}&delete_future=${deleteFuture}`)
const { data } = await api.delete(`/api/protected/workouts/recurring?id=${scheduleId}&delete_future=${deleteFuture}`)
return data
}
}
export const sessionApi = {
async getSessions() {
const { data } = await api.get('/protected/sessions', {
const { data } = await api.get('/api/protected/sessions', {
headers: { 'X-Refresh-Token': localStorage.getItem('refresh_token') }
})
return data.sessions || []
},
async revokeSession(sessionId) {
const { data } = await api.delete(`/protected/sessions/${sessionId}`, {
const { data } = await api.delete(`/api/protected/sessions/${sessionId}`, {
headers: { 'X-Refresh-Token': localStorage.getItem('refresh_token') }
})
return data
},
async revokeAllOtherSessions() {
const { data } = await api.delete('/protected/sessions/all', {
const { data } = await api.delete('/api/protected/sessions/all', {
headers: { 'X-Refresh-Token': localStorage.getItem('refresh_token') }
})
return data
@@ -199,32 +226,32 @@ export const sessionApi = {
export const onboardingApi = {
async getStatus() {
const { data } = await api.get('/protected/onboarding/status')
const { data } = await api.get('/api/protected/onboarding/status')
return data
},
async saveProfile(profileData) {
const { data } = await api.post('/protected/onboarding/profile', profileData)
const { data } = await api.post('/api/protected/onboarding/profile', profileData)
return data
},
async saveMetrics(metricsData) {
const { data } = await api.post('/protected/onboarding/metrics', metricsData)
const { data } = await api.post('/api/protected/onboarding/metrics', metricsData)
return data
},
async submitFTPTest(testData) {
const { data } = await api.post('/protected/onboarding/ftp-test', testData)
const { data } = await api.post('/api/protected/onboarding/ftp-test', testData)
return data
},
async getFTPHistory() {
const { data } = await api.get('/protected/onboarding/ftp-history')
const { data } = await api.get('/api/protected/onboarding/ftp-history')
return data
},
async calculateHRZones(maxHR, restingHR) {
const { data } = await api.post('/protected/onboarding/hr-zones', {
const { data } = await api.post('/api/protected/onboarding/hr-zones', {
max_hr: maxHR,
resting_hr: restingHR
})
@@ -232,7 +259,7 @@ export const onboardingApi = {
},
async estimateFTP(fitnessLevel, weight) {
const { data } = await api.post('/protected/onboarding/estimate-ftp', {
const { data } = await api.post('/api/protected/onboarding/estimate-ftp', {
fitness_level: fitnessLevel,
weight: weight
})
@@ -240,148 +267,158 @@ export const onboardingApi = {
},
async estimateMaxHR(age) {
const { data } = await api.post('/protected/onboarding/estimate-max-hr', { age })
const { data } = await api.post('/api/protected/onboarding/estimate-max-hr', { age })
return data
},
async completeEquipmentStep() {
const { data } = await api.post('/protected/onboarding/equipment')
const { data } = await api.post('/api/protected/onboarding/equipment')
return data
},
async savePreferences(preferences) {
const { data } = await api.post('/protected/onboarding/preferences', preferences)
const { data } = await api.post('/api/protected/onboarding/preferences', preferences)
return data
},
async complete() {
const { data } = await api.post('/protected/onboarding/complete')
const { data } = await api.post('/api/protected/onboarding/complete')
return data
},
async skip() {
const { data } = await api.post('/protected/onboarding/skip')
const { data } = await api.post('/api/protected/onboarding/skip')
return data
}
}
export const equipmentApi = {
async create(equipment) {
const { data } = await api.post('/protected/equipment', equipment)
const { data } = await api.post('/api/protected/equipment', equipment)
return data
},
async list() {
const { data } = await api.get('/protected/equipment')
const { data } = await api.get('/api/protected/equipment')
return data
},
async update(equipmentId, equipment) {
const { data } = await api.put(`/protected/equipment?id=${equipmentId}`, equipment)
const { data } = await api.put(`/api/protected/equipment?id=${equipmentId}`, equipment)
return data
},
async delete(equipmentId) {
const { data } = await api.delete(`/protected/equipment?id=${equipmentId}`)
const { data } = await api.delete(`/api/protected/equipment?id=${equipmentId}`)
return data
},
async getTrainingZones() {
const { data } = await api.get('/protected/zones')
const { data } = await api.get('/api/protected/zones')
return data
},
async recordService(equipmentId) {
const { data } = await api.post(`/api/protected/equipment/service?id=${equipmentId}`)
return data
},
async getServiceStatus(equipmentId) {
const { data } = await api.get(`/api/protected/equipment/service-status?id=${equipmentId}`)
return data
}
}
export const teamsApi = {
async create(team) {
const { data } = await api.post('/protected/teams', team)
const { data } = await api.post('/api/protected/teams', team)
return data
},
async list() {
const { data } = await api.get('/protected/teams')
const { data } = await api.get('/api/protected/teams')
return data.teams || []
},
async get(teamId) {
const { data } = await api.get(`/protected/teams/${teamId}`)
const { data } = await api.get(`/api/protected/teams/${teamId}`)
return data
},
async update(teamId, team) {
const { data } = await api.put(`/protected/teams/${teamId}`, team)
const { data } = await api.put(`/api/protected/teams/${teamId}`, team)
return data
},
async delete(teamId) {
const { data } = await api.delete(`/protected/teams/${teamId}`)
const { data } = await api.delete(`/api/protected/teams/${teamId}`)
return data
},
async getMembers(teamId) {
const { data } = await api.get(`/protected/teams/${teamId}/members`)
const { data } = await api.get(`/api/protected/teams/${teamId}/members`)
return data.members || []
},
async updateMemberRole(teamId, userId, role) {
const { data } = await api.put(`/protected/teams/${teamId}/members/${userId}/role`, { role })
const { data } = await api.put(`/api/protected/teams/${teamId}/members/${userId}/role`, { role })
return data
},
async removeMember(teamId, userId) {
const { data } = await api.delete(`/protected/teams/${teamId}/members/${userId}`)
const { data } = await api.delete(`/api/protected/teams/${teamId}/members/${userId}`)
return data
},
async leave(teamId) {
const { data } = await api.delete(`/protected/teams/${teamId}/leave`)
const { data } = await api.delete(`/api/protected/teams/${teamId}/leave`)
return data
},
async sendInvite(teamId, email, role) {
const { data } = await api.post(`/protected/teams/${teamId}/invite`, { email, role })
const { data } = await api.post(`/api/protected/teams/${teamId}/invite`, { email, role })
return data
},
async getTeamInvites(teamId) {
const { data } = await api.get(`/protected/teams/${teamId}/invites`)
const { data } = await api.get(`/api/protected/teams/${teamId}/invites`)
return data.invites || []
},
async cancelInvite(teamId, inviteId) {
const { data } = await api.delete(`/protected/teams/${teamId}/invites/${inviteId}`)
const { data } = await api.delete(`/api/protected/teams/${teamId}/invites/${inviteId}`)
return data
},
async getMyInvites() {
const { data } = await api.get('/protected/invites')
const { data } = await api.get('/api/protected/invites')
return data.invites || []
},
async acceptInvite(token) {
const { data } = await api.post('/protected/invites/accept', { token })
const { data } = await api.post('/api/protected/invites/accept', { token })
return data
},
async declineInvite(token) {
const { data } = await api.post('/protected/invites/decline', { token })
const { data } = await api.post('/api/protected/invites/decline', { token })
return data
}
}
export const coachingApi = {
async getAthletes() {
const { data } = await api.get('/protected/coaching/athletes')
const { data } = await api.get('/api/protected/coaching/athletes')
return data.athletes || []
},
async getCoaches() {
const { data } = await api.get('/protected/coaching/coaches')
const { data } = await api.get('/api/protected/coaching/coaches')
return data.coaches || []
},
async requestCoaching(coachId, note = null) {
const { data } = await api.post('/protected/coaching/request', {
const { data } = await api.post('/api/protected/coaching/request', {
coach_id: coachId,
note: note
})
@@ -389,41 +426,41 @@ export const coachingApi = {
},
async acceptRequest(relationId) {
const { data } = await api.post('/protected/coaching/accept', {
const { data } = await api.post('/api/protected/coaching/accept', {
relation_id: relationId
})
return data
},
async endRelationship(athleteId) {
const { data } = await api.delete(`/protected/coaching/${athleteId}`)
const { data } = await api.delete(`/api/protected/coaching/${athleteId}`)
return data
}
}
export const oauthApi = {
async getAccounts() {
const { data } = await api.get('/protected/oauth/accounts')
const { data } = await api.get('/api/protected/oauth/accounts')
return data.accounts || []
},
async linkStrava(code) {
const { data } = await api.post('/protected/oauth/link/strava', { code })
const { data } = await api.post('/api/protected/oauth/link/strava', { code })
return data
},
async linkGoogle() {
const { data } = await api.post('/protected/oauth/link/google')
const { data } = await api.post('/api/protected/oauth/link/google')
return data
},
async unlink(provider) {
const { data } = await api.delete(`/protected/oauth/unlink/${provider}`)
const { data } = await api.delete(`/api/protected/oauth/unlink/${provider}`)
return data
},
async syncStrava() {
const { data } = await api.post('/protected/oauth/strava/sync')
const { data } = await api.post('/api/protected/oauth/strava/sync')
return data
},
@@ -431,4 +468,87 @@ export const oauthApi = {
getStravaAuthUrl: () => `${API_BASE_URL}/api/oauth/strava`
}
export const statsApi = {
async getSummary() {
const { data } = await api.get('/api/protected/stats/summary')
return data
},
async getWeeklyStats(weeks = 12) {
const { data } = await api.get('/api/protected/stats/weekly', { params: { weeks } })
return data
},
async getMonthlyStats(months = 12) {
const { data } = await api.get('/api/protected/stats/monthly', { params: { months } })
return data
},
async getPersonalBests() {
const { data } = await api.get('/api/protected/stats/personal-bests')
return data
}
}
export const templatesApi = {
async list(category = '') {
const params = {}
if (category) params.category = category
const { data } = await api.get('/api/protected/workout-templates', { params })
return data
},
async getDetail(id) {
const { data } = await api.get(`/api/protected/workout-templates/detail?id=${id}`)
return data
},
async createFromTemplate(payload) {
const { data } = await api.post('/api/protected/workouts/from-template', payload)
return data
}
}
export const integrationsApi = {
async garminAuth() {
const { data } = await api.get('/api/protected/garmin/auth')
return data
},
async garminStatus() {
const { data } = await api.get('/api/protected/garmin/status')
return data
},
async garminDisconnect() {
const { data } = await api.delete('/api/protected/garmin/disconnect')
return data
},
async pushToGarmin(workoutId) {
const { data } = await api.post(`/api/protected/workouts/push/garmin?id=${workoutId}`)
return data
},
async wahooAuth() {
const { data } = await api.get('/api/protected/wahoo/auth')
return data
},
async wahooStatus() {
const { data } = await api.get('/api/protected/wahoo/status')
return data
},
async wahooDisconnect() {
const { data } = await api.delete('/api/protected/wahoo/disconnect')
return data
},
async pushToWahoo(workoutId) {
const { data } = await api.post(`/api/protected/workouts/push/wahoo?id=${workoutId}`)
return data
}
}
export default api

View File

@@ -3,19 +3,19 @@ import api from './api'
export const workoutLibraryApi = {
// Get workout types, categories, and difficulties
async getTypes() {
const { data } = await api.get('/protected/library/types')
const { data } = await api.get('/api/protected/library/types')
return data
},
// Get system-provided workouts
async getSystemWorkouts() {
const { data } = await api.get('/protected/library/system')
const { data } = await api.get('/api/protected/library/system')
return data
},
// Browse public workouts (paginated)
async browseWorkouts(page = 1, pageSize = 20) {
const { data } = await api.get('/protected/library/browse', {
const { data } = await api.get('/api/protected/library/browse', {
params: { page, page_size: pageSize }
})
return data
@@ -23,7 +23,7 @@ export const workoutLibraryApi = {
// Search/filter workouts
async searchWorkouts(params = {}) {
const { data } = await api.get('/protected/library/search', {
const { data } = await api.get('/api/protected/library/search', {
params: {
q: params.search || undefined,
type: params.type || undefined,
@@ -38,73 +38,73 @@ export const workoutLibraryApi = {
// Get workouts by type
async getWorkoutsByType(type) {
const { data } = await api.get(`/protected/library/type/${type}`)
const { data } = await api.get(`/api/protected/library/type/${type}`)
return data
},
// Get workouts by category
async getWorkoutsByCategory(category) {
const { data } = await api.get(`/protected/library/category/${category}`)
const { data } = await api.get(`/api/protected/library/category/${category}`)
return data
},
// Get single workout details
async getWorkout(workoutId) {
const { data } = await api.get(`/protected/library/${workoutId}`)
const { data } = await api.get(`/api/protected/library/${workoutId}`)
return data
},
// Get current user's custom workouts
async getUserWorkouts() {
const { data } = await api.get('/protected/library/mine')
const { data } = await api.get('/api/protected/library/mine')
return data
},
// Get user's favorited workouts
async getFavorites() {
const { data } = await api.get('/protected/library/favorites')
const { data } = await api.get('/api/protected/library/favorites')
return data
},
// Create custom workout
async createWorkout(workout) {
const { data } = await api.post('/protected/library', workout)
const { data } = await api.post('/api/protected/library', workout)
return data
},
// Update user's workout
async updateWorkout(workoutId, workout) {
const { data } = await api.put(`/protected/library/${workoutId}`, workout)
const { data } = await api.put(`/api/protected/library/${workoutId}`, workout)
return data
},
// Delete user's workout
async deleteWorkout(workoutId) {
const { data } = await api.delete(`/protected/library/${workoutId}`)
const { data } = await api.delete(`/api/protected/library/${workoutId}`)
return data
},
// Mark workout as used
async recordUsage(workoutId) {
const { data } = await api.post(`/protected/library/${workoutId}/use`)
const { data } = await api.post(`/api/protected/library/${workoutId}/use`)
return data
},
// Add to favorites
async addFavorite(workoutId) {
const { data } = await api.post(`/protected/library/${workoutId}/favorite`)
const { data } = await api.post(`/api/protected/library/${workoutId}/favorite`)
return data
},
// Remove from favorites
async removeFavorite(workoutId) {
const { data } = await api.delete(`/protected/library/${workoutId}/favorite`)
const { data } = await api.delete(`/api/protected/library/${workoutId}/favorite`)
return data
},
// Rate workout (1-5 stars + optional comment)
async rateWorkout(workoutId, rating, comment = null) {
const { data } = await api.post(`/protected/library/${workoutId}/rate`, {
const { data } = await api.post(`/api/protected/library/${workoutId}/rate`, {
rating,
comment: comment || undefined
})
@@ -113,7 +113,7 @@ export const workoutLibraryApi = {
// Schedule a library workout to a specific date
async scheduleToCalendar(workout, scheduledDate) {
const { data } = await api.post('/protected/workouts/schedule-template', {
const { data } = await api.post('/api/protected/workouts/schedule-template', {
template_id: workout.id,
template_name: workout.name,
template_type: workout.type,

View File

@@ -125,7 +125,7 @@ export const useAuthStore = defineStore('auth', () => {
error.value = null
try {
const { data } = await api.get('/protected/profile')
const { data } = await api.get('/api/protected/profile')
return data
} catch (err) {
error.value = err.response?.data?.error || 'Failed to fetch profile'
@@ -140,7 +140,7 @@ export const useAuthStore = defineStore('auth', () => {
error.value = null
try {
const { data } = await api.put('/protected/profile', profileData)
const { data } = await api.put('/api/protected/profile', profileData)
return data
} catch (err) {
error.value = err.response?.data?.error || 'Failed to update profile'