UNPKG

trainingpeaks-sdk

Version:
151 lines (150 loc) 5.63 kB
import { ValidationError } from '../../domain/errors/domain-errors.js'; import { WorkoutStructureError, WorkoutValidationError, } from '../../domain/errors/workout-errors.js'; import { calculateWorkoutDuration, createStructuredWorkout, createWorkout, updateWorkout, } from './workout.js'; export const createWorkoutAggregate = (id, name, date, duration, description, distance, activityType, structure, fileContent, fileName) => { let workout; if (structure) { validateWorkoutStructure(structure); const calculatedDuration = calculateWorkoutDuration(structure); if (Math.abs(duration - calculatedDuration) > 1) { throw new WorkoutValidationError(`Provided duration (${duration}s) does not match calculated duration (${calculatedDuration}s)`); } workout = createStructuredWorkout(id, name, date, structure, description, activityType); } else { workout = createWorkout(id, name, date, duration, description, distance, activityType, structure, fileContent, fileName); } return { workout, structure, }; }; export const updateWorkoutAggregate = (aggregate, updates) => { const updatedWorkout = updateWorkout(aggregate.workout, updates); return { ...aggregate, workout: updatedWorkout, }; }; export const updateWorkoutStructure = (aggregate, newStructure) => { validateWorkoutStructure(newStructure); const newDuration = calculateWorkoutDuration(newStructure); const updatedWorkout = { ...aggregate.workout, structure: newStructure, duration: newDuration, updatedAt: new Date(), }; return { workout: updatedWorkout, structure: newStructure, }; }; export const removeWorkoutStructure = (aggregate, newDuration) => { if (newDuration < 0 || !isFinite(newDuration)) { throw new ValidationError('Duration must be a non-negative finite number', 'duration'); } const updatedWorkout = { ...aggregate.workout, structure: undefined, duration: newDuration, updatedAt: new Date(), }; return { workout: updatedWorkout, structure: undefined, }; }; export const addWorkoutFile = (aggregate, fileContent, fileName) => { if (!fileContent || !fileName) { throw new ValidationError('File content and name are required'); } if (fileName.trim().length > 255) { throw new ValidationError('File name cannot exceed 255 characters', 'fileName'); } const updatedWorkout = { ...aggregate.workout, fileContent, fileName: fileName.trim(), updatedAt: new Date(), }; return { ...aggregate, workout: updatedWorkout, }; }; export const removeWorkoutFile = (aggregate) => { const updatedWorkout = { ...aggregate.workout, fileContent: undefined, fileName: undefined, updatedAt: new Date(), }; return { ...aggregate, workout: updatedWorkout, }; }; export const getAggregateDuration = (aggregate) => { if (aggregate.structure) { return calculateWorkoutDuration(aggregate.structure); } return aggregate.workout.duration; }; export const isAggregateConsistent = (aggregate) => { try { if (aggregate.structure) { const calculatedDuration = calculateWorkoutDuration(aggregate.structure); const durationDiff = Math.abs(aggregate.workout.duration - calculatedDuration); if (durationDiff > 1) { return false; } validateWorkoutStructure(aggregate.structure); } if (aggregate.workout.fileContent && !aggregate.workout.fileName) { return false; } if (!aggregate.workout.fileContent && aggregate.workout.fileName) { return false; } return true; } catch { return false; } }; export const getAggregateSummary = (aggregate) => { const { workout, structure } = aggregate; return { id: workout.id, name: workout.name, date: workout.date, duration: getAggregateDuration(aggregate), distance: workout.distance, activityType: workout.activityType, isStructured: Boolean(structure), hasFile: Boolean(workout.fileContent), structureElementCount: structure?.structure.length || 0, isConsistent: isAggregateConsistent(aggregate), }; }; const validateWorkoutStructure = (structure) => { if (!structure.structure || structure.structure.length === 0) { throw new WorkoutStructureError('Workout structure must contain at least one element'); } for (let i = 0; i < structure.structure.length - 1; i++) { const current = structure.structure[i]; const next = structure.structure[i + 1]; if (current && next && current.end > next.begin) { throw new WorkoutStructureError(`Structure elements overlap: element ${i} ends after element ${i + 1} begins`, { elementIndex: i, nextElementIndex: i + 1 }); } } structure.structure.forEach((element, index) => { if (!element.steps || element.steps.length === 0) { throw new WorkoutStructureError(`Structure element ${index} must have at least one step`, { elementIndex: index }); } if (element.end <= element.begin) { throw new WorkoutStructureError(`Structure element ${index} end time must be greater than begin time`, { elementIndex: index, begin: element.begin, end: element.end }); } }); };