- backend: NestJS service with exercise/plan data - miniprogram: WeChat mini program client - exclude node_modules, dist, runtime data-store, local env
87 lines
2.4 KiB
TypeScript
87 lines
2.4 KiB
TypeScript
import {
|
|
RawExercise,
|
|
Exercise,
|
|
ExerciseSummary,
|
|
ExerciseType,
|
|
} from '../exercises.interface';
|
|
import {
|
|
BODY_PART_LABELS,
|
|
EQUIPMENT_LABELS,
|
|
MUSCLE_LABELS,
|
|
labelOf,
|
|
} from '../data/labels';
|
|
|
|
const MEDIA_BASE_URL = (
|
|
process.env.MEDIA_BASE_URL || 'https://cdn.jsdelivr.net/gh/hasaneyldrm/exercises-dataset@main'
|
|
).replace(/\/$/, '');
|
|
|
|
function toMediaUrl(path: string): string {
|
|
if (!path) return '';
|
|
if (/^https?:\/\//.test(path)) return path;
|
|
return `${MEDIA_BASE_URL}/${path}`;
|
|
}
|
|
|
|
function deriveType(raw: RawExercise): ExerciseType {
|
|
if (raw.body_part === 'cardio') return 'cardio';
|
|
const hay = `${raw.name} ${raw.equipment} ${raw.category || ''}`.toLowerCase();
|
|
const flexKeywords = ['stretch', 'yoga', 'mobility', 'pilates', 'flexibility', 'foam roll'];
|
|
if (flexKeywords.some((k) => hay.includes(k))) return 'flexibility';
|
|
return 'strength';
|
|
}
|
|
|
|
const TYPE_LABELS: Record<ExerciseType, string> = {
|
|
strength: '力量',
|
|
cardio: '有氧',
|
|
flexibility: '柔韧',
|
|
};
|
|
|
|
export function normalize(raw: RawExercise): Exercise {
|
|
const type = deriveType(raw);
|
|
const zh = raw.instructions?.zh || '';
|
|
const en = raw.instructions?.en || '';
|
|
const zhSteps = raw.instruction_steps?.zh || [];
|
|
const enSteps = raw.instruction_steps?.en || [];
|
|
|
|
return {
|
|
id: raw.id,
|
|
name: raw.name,
|
|
bodyPart: raw.body_part,
|
|
bodyPartLabel: labelOf(BODY_PART_LABELS, raw.body_part),
|
|
equipment: raw.equipment,
|
|
equipmentLabel: labelOf(EQUIPMENT_LABELS, raw.equipment),
|
|
target: raw.target,
|
|
targetLabel: labelOf(MUSCLE_LABELS, raw.target),
|
|
muscleGroup: raw.muscle_group,
|
|
muscleGroupLabel: labelOf(MUSCLE_LABELS, raw.muscle_group),
|
|
secondaryMuscles: raw.secondary_muscles || [],
|
|
secondaryMusclesLabels: (raw.secondary_muscles || []).map(
|
|
(m) => labelOf(MUSCLE_LABELS, m),
|
|
),
|
|
type,
|
|
typeLabel: TYPE_LABELS[type],
|
|
image: toMediaUrl(raw.image),
|
|
gifUrl: toMediaUrl(raw.gif_url),
|
|
instructions: { zh, en },
|
|
instructionSteps: { zh: zhSteps, en: enSteps },
|
|
attribution: raw.attribution,
|
|
mediaId: raw.media_id,
|
|
};
|
|
}
|
|
|
|
export function toSummary(ex: Exercise): ExerciseSummary {
|
|
return {
|
|
id: ex.id,
|
|
name: ex.name,
|
|
bodyPart: ex.bodyPart,
|
|
bodyPartLabel: ex.bodyPartLabel,
|
|
equipment: ex.equipment,
|
|
equipmentLabel: ex.equipmentLabel,
|
|
target: ex.target,
|
|
targetLabel: ex.targetLabel,
|
|
type: ex.type,
|
|
typeLabel: ex.typeLabel,
|
|
image: ex.image,
|
|
gifUrl: ex.gifUrl,
|
|
};
|
|
}
|