198 lines
5.2 KiB
TypeScript
198 lines
5.2 KiB
TypeScript
import { isAxiosError } from "axios";
|
|
import { apiClient, withAuth } from "../api/client";
|
|
import { API_ENDPOINTS } from "../config/api";
|
|
import log from "../utils/logger";
|
|
|
|
export interface FitnessGoal {
|
|
id: string;
|
|
userId: string;
|
|
fitnessProfileId?: string;
|
|
goalType:
|
|
| "weight_target"
|
|
| "strength_milestone"
|
|
| "endurance_target"
|
|
| "flexibility_goal"
|
|
| "habit_building"
|
|
| "custom";
|
|
title: string;
|
|
description?: string;
|
|
targetValue?: number;
|
|
currentValue?: number;
|
|
unit?: string;
|
|
startDate: string;
|
|
targetDate?: string;
|
|
completedDate?: string;
|
|
status: "active" | "completed" | "abandoned" | "paused";
|
|
progress: number;
|
|
priority: "low" | "medium" | "high";
|
|
notes?: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface CreateGoalData {
|
|
goalType: FitnessGoal["goalType"];
|
|
title: string;
|
|
description?: string;
|
|
targetValue?: number;
|
|
currentValue?: number;
|
|
unit?: string;
|
|
targetDate?: string;
|
|
priority?: FitnessGoal["priority"];
|
|
notes?: string;
|
|
}
|
|
|
|
export class FitnessGoalsService {
|
|
async getGoals(
|
|
userId: string,
|
|
token: string | null,
|
|
status?: string,
|
|
): Promise<FitnessGoal[]> {
|
|
try {
|
|
const response = await apiClient.get(API_ENDPOINTS.FITNESS_GOALS.LIST, {
|
|
params: {
|
|
userId,
|
|
...(status && status !== "all" ? { status } : {}),
|
|
},
|
|
...withAuth(token),
|
|
});
|
|
|
|
const result = response.data;
|
|
|
|
// Handle standardized API response format
|
|
// API returns: { success: true, data: [...], meta: {...} }
|
|
if (result.success && result.data) {
|
|
return Array.isArray(result.data) ? result.data : [];
|
|
}
|
|
|
|
// Fallback for legacy format (direct array)
|
|
return Array.isArray(result) ? result : [];
|
|
} catch (error) {
|
|
if (isAxiosError(error) && error.response) {
|
|
throw new Error(`Failed to fetch goals: ${error.response.status}`);
|
|
}
|
|
log.error("Failed to fetch fitness goals", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async createGoal(
|
|
goalData: CreateGoalData,
|
|
token: string | null,
|
|
): Promise<FitnessGoal> {
|
|
try {
|
|
const response = await apiClient.post(
|
|
API_ENDPOINTS.FITNESS_GOALS.CREATE,
|
|
goalData,
|
|
withAuth(token),
|
|
);
|
|
|
|
const result = response.data;
|
|
|
|
// Handle standardized API response format
|
|
if (result.success && result.data) {
|
|
return result.data;
|
|
}
|
|
|
|
// Fallback for legacy format
|
|
return result;
|
|
} catch (error) {
|
|
if (isAxiosError(error)) {
|
|
const message =
|
|
(error.response?.data as { error?: string } | undefined)?.error ||
|
|
(error.response
|
|
? `Failed to create goal: ${error.response.status}`
|
|
: "Failed to create goal");
|
|
throw new Error(message);
|
|
}
|
|
log.error("Failed to create fitness goal", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateGoal(
|
|
id: string,
|
|
updates: Partial<FitnessGoal>,
|
|
token: string | null,
|
|
): Promise<FitnessGoal> {
|
|
try {
|
|
const response = await apiClient.put(
|
|
API_ENDPOINTS.FITNESS_GOALS.UPDATE(id),
|
|
updates,
|
|
withAuth(token),
|
|
);
|
|
|
|
const result = response.data;
|
|
|
|
// Handle standardized API response format
|
|
if (result.success && result.data) {
|
|
return result.data;
|
|
}
|
|
|
|
// Fallback for legacy format
|
|
return result;
|
|
} catch (error) {
|
|
if (isAxiosError(error) && error.response) {
|
|
throw new Error(`Failed to update goal: ${error.response.status}`);
|
|
}
|
|
log.error("Failed to update fitness goal", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateProgress(
|
|
id: string,
|
|
currentValue: number,
|
|
token: string | null,
|
|
): Promise<FitnessGoal> {
|
|
return this.updateGoal(id, { currentValue }, token);
|
|
}
|
|
|
|
async completeGoal(id: string, token: string | null): Promise<FitnessGoal> {
|
|
try {
|
|
const response = await apiClient.post(
|
|
API_ENDPOINTS.FITNESS_GOALS.COMPLETE(id),
|
|
{},
|
|
withAuth(token),
|
|
);
|
|
|
|
const result = response.data;
|
|
|
|
// Note: Complete endpoint returns direct object (legacy format)
|
|
// Handle standardized API response format (if migrated)
|
|
if (result.success && result.data) {
|
|
return result.data;
|
|
}
|
|
|
|
// Fallback for legacy format (current implementation)
|
|
return result;
|
|
} catch (error) {
|
|
if (isAxiosError(error) && error.response) {
|
|
throw new Error(`Failed to complete goal: ${error.response.status}`);
|
|
}
|
|
log.error("Failed to complete fitness goal", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async deleteGoal(id: string, token: string | null): Promise<void> {
|
|
try {
|
|
await apiClient.delete(
|
|
API_ENDPOINTS.FITNESS_GOALS.DELETE(id),
|
|
withAuth(token),
|
|
);
|
|
|
|
// DELETE endpoint returns: { success: true, data: { deleted: true }, meta: {...} }
|
|
// No need to parse the result for void return type
|
|
} catch (error) {
|
|
if (isAxiosError(error) && error.response) {
|
|
throw new Error(`Failed to delete goal: ${error.response.status}`);
|
|
}
|
|
log.error("Failed to delete fitness goal", error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export const fitnessGoalsService = new FitnessGoalsService();
|