Backend is here. Default admin is created if needed.

This commit is contained in:
aodulov
2025-11-19 10:48:37 +02:00
parent 10819cc6f5
commit bb705c8a63
25 changed files with 3662 additions and 944 deletions

View File

@@ -1,137 +1,67 @@
import { WorkoutSession, ExerciseDef, ExerciseType, WorkoutSet, WorkoutPlan } from '../types';
import { updateUserProfile } from './auth';
import { api } from './api';
// Helper to namespace keys
const getKey = (base: string, userId: string) => `${base}_${userId}`;
const SESSIONS_KEY = 'gymflow_sessions';
const EXERCISES_KEY = 'gymflow_exercises'; // Custom exercises are per user
const PLANS_KEY = 'gymflow_plans';
const DEFAULT_EXERCISES: ExerciseDef[] = [
{ id: 'bp', name: 'Жим лежа', type: ExerciseType.STRENGTH },
{ id: 'sq', name: 'Приседания со штангой', type: ExerciseType.STRENGTH },
{ id: 'dl', name: 'Становая тяга', type: ExerciseType.STRENGTH },
{ id: 'pu', name: 'Подтягивания', type: ExerciseType.BODYWEIGHT, bodyWeightPercentage: 100 },
{ id: 'run', name: 'Бег', type: ExerciseType.CARDIO },
{ id: 'plank', name: 'Планка', type: ExerciseType.STATIC, bodyWeightPercentage: 100 },
{ id: 'dip', name: 'Отжимания на брусьях', type: ExerciseType.BODYWEIGHT, bodyWeightPercentage: 100 },
{ id: 'pushup', name: 'Отжимания от пола', type: ExerciseType.BODYWEIGHT, bodyWeightPercentage: 65 },
{ id: 'air_sq', name: 'Приседания (свой вес)', type: ExerciseType.BODYWEIGHT, bodyWeightPercentage: 75 },
];
export const getSessions = (userId: string): WorkoutSession[] => {
export const getSessions = async (userId: string): Promise<WorkoutSession[]> => {
try {
const data = localStorage.getItem(getKey(SESSIONS_KEY, userId));
return data ? JSON.parse(data) : [];
} catch (e) {
return [];
}
};
export const saveSession = (userId: string, session: WorkoutSession): void => {
const sessions = getSessions(userId);
const index = sessions.findIndex(s => s.id === session.id);
if (index >= 0) {
sessions[index] = session;
} else {
sessions.unshift(session);
}
localStorage.setItem(getKey(SESSIONS_KEY, userId), JSON.stringify(sessions));
// Auto-update user weight profile if present in session
if (session.userBodyWeight) {
updateUserProfile(userId, { weight: session.userBodyWeight });
}
};
export const deleteSession = (userId: string, id: string): void => {
let sessions = getSessions(userId);
sessions = sessions.filter(s => s.id !== id);
localStorage.setItem(getKey(SESSIONS_KEY, userId), JSON.stringify(sessions));
};
export const deleteAllUserData = (userId: string) => {
localStorage.removeItem(getKey(SESSIONS_KEY, userId));
localStorage.removeItem(getKey(EXERCISES_KEY, userId));
localStorage.removeItem(getKey(PLANS_KEY, userId));
};
export const getExercises = (userId: string): ExerciseDef[] => {
try {
const data = localStorage.getItem(getKey(EXERCISES_KEY, userId));
const savedExercises: ExerciseDef[] = data ? JSON.parse(data) : [];
// Create a map of saved exercises for easy lookup
const savedMap = new Map(savedExercises.map(ex => [ex.id, ex]));
// Start with defaults
const mergedExercises = DEFAULT_EXERCISES.map(defEx => {
// If user has a saved version of this default exercise (e.g. edited or archived), use that
if (savedMap.has(defEx.id)) {
const saved = savedMap.get(defEx.id)!;
savedMap.delete(defEx.id); // Remove from map so we don't add it again
return saved;
}
return defEx;
});
// Add remaining custom exercises (those that are not overrides of defaults)
return [...mergedExercises, ...Array.from(savedMap.values())];
} catch (e) {
return DEFAULT_EXERCISES;
}
};
export const saveExercise = (userId: string, exercise: ExerciseDef): void => {
try {
const data = localStorage.getItem(getKey(EXERCISES_KEY, userId));
let list: ExerciseDef[] = data ? JSON.parse(data) : [];
const index = list.findIndex(e => e.id === exercise.id);
if (index >= 0) {
list[index] = exercise;
} else {
list.push(exercise);
}
localStorage.setItem(getKey(EXERCISES_KEY, userId), JSON.stringify(list));
} catch {}
};
export const getLastSetForExercise = (userId: string, exerciseId: string): WorkoutSet | undefined => {
const sessions = getSessions(userId);
for (const session of sessions) {
for (let i = session.sets.length - 1; i >= 0; i--) {
if (session.sets[i].exerciseId === exerciseId) {
return session.sets[i];
}
}
}
return undefined;
}
export const getPlans = (userId: string): WorkoutPlan[] => {
try {
const data = localStorage.getItem(getKey(PLANS_KEY, userId));
return data ? JSON.parse(data) : [];
return await api.get('/sessions');
} catch {
return [];
}
};
export const savePlan = (userId: string, plan: WorkoutPlan): void => {
const plans = getPlans(userId);
const index = plans.findIndex(p => p.id === plan.id);
if (index >= 0) {
plans[index] = plan;
} else {
plans.push(plan);
}
localStorage.setItem(getKey(PLANS_KEY, userId), JSON.stringify(plans));
export const saveSession = async (userId: string, session: WorkoutSession): Promise<void> => {
await api.post('/sessions', session);
};
export const deletePlan = (userId: string, id: string): void => {
const plans = getPlans(userId).filter(p => p.id !== id);
localStorage.setItem(getKey(PLANS_KEY, userId), JSON.stringify(plans));
export const deleteSession = async (userId: string, id: string): Promise<void> => {
await api.delete(`/sessions/${id}`);
};
export const deleteAllUserData = (userId: string) => {
// Not implemented in frontend
};
export const getExercises = async (userId: string): Promise<ExerciseDef[]> => {
try {
return await api.get('/exercises');
} catch {
return [];
}
};
export const saveExercise = async (userId: string, exercise: ExerciseDef): Promise<void> => {
await api.post('/exercises', exercise);
};
export const getLastSetForExercise = async (userId: string, exerciseId: string): Promise<WorkoutSet | undefined> => {
// This requires fetching sessions or a specific endpoint.
// For performance, we should probably have an endpoint for this.
// For now, let's fetch sessions and find it client side, or implement endpoint later.
// Given the async nature, we need to change the signature to Promise.
// The caller needs to await this.
const sessions = await getSessions(userId);
for (const session of sessions) {
for (let i = session.sets.length - 1; i >= 0; i--) {
if (session.sets[i].exerciseId === exerciseId) {
return session.sets[i];
}
}
}
return undefined;
}
export const getPlans = async (userId: string): Promise<WorkoutPlan[]> => {
try {
return await api.get('/plans');
} catch {
return [];
}
};
export const savePlan = async (userId: string, plan: WorkoutPlan): Promise<void> => {
await api.post('/plans', plan);
};
export const deletePlan = async (userId: string, id: string): Promise<void> => {
await api.delete(`/plans/${id}`);
};