frontend/app/lib/api.ts
Nathan Lamy 81d73fd99d
All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m31s
feat: add BJRepas credentials
2025-08-26 00:26:30 +02:00

520 lines
11 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { DateTime, Duration } from "luxon";
const BASE_URL = import.meta.env.VITE_API_URL;
const makePostRequest = async (
url: string,
body: object,
error = "Une erreur est survenue",
method = "POST"
) => {
const response = await fetch(BASE_URL + url, {
method,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(body),
credentials: "include", // Include cookies for authentication
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || error);
return data?.data || data;
};
const makeRequest = async (url: string, error = "Une erreur est survenue") => {
const response = await fetch(BASE_URL + url, { credentials: "include" });
const data = await response.json();
if (!response.ok) throw new Error(data.error || error);
return data?.data || data;
};
/**
* === AUTH API ===
*/
export const requestLogin = async (email: string, token: string) => {
return makePostRequest(
"/auth/request",
{ email, token },
"Échec de la demande de connexion"
);
};
export const verifyOtp = async ({
otpCode,
email,
}: {
otpCode: string;
email: string;
}) => {
return makePostRequest(
"/auth/verify",
{ email, code: otpCode },
"Code de vérification invalide"
);
};
export const getClasses = async () => {
try {
const res = await fetch("/classes.json");
return res.json();
} catch (error) {
console.error("Error fetching classes:", error);
return [];
}
};
export const registerUser = async (
userId: number,
className: string,
token: string
) => {
return makePostRequest(
"/auth/register",
{ userId, className, token },
"Échec de l'inscription"
);
};
export const getNames = async (className: string) => {
return makeRequest(
`/auth/autocomplete?className=${encodeURIComponent(className)}`,
"Échec de la récupération des noms"
);
};
/**
* === USER API ===
*/
const fetchUser = async () => {
return makeRequest(
"/users/@me",
"Échec de la récupération des informations utilisateur"
);
};
const defaultUser = {
id: 0,
firstName: "",
lastName: "",
fullName: "",
email: "",
className: "",
preferences: [] as UserPreferences,
};
export type User = typeof defaultUser;
export const AUTH_ERROR = "Unauthorized access";
export const useUser = () => {
const { data, ...props } = useQuery({
queryKey: ["user"],
queryFn: fetchUser,
staleTime: Duration.fromObject({
minutes: 5, // 5 minutes
}).toMillis(),
gcTime: Duration.fromObject({
days: 3, // 3 days
}).toMillis(),
});
return {
user: (data ? Object.assign(defaultUser, data) : defaultUser) as User,
...props,
};
};
export const logout = async () => {
return makePostRequest("/auth/logout", {}, "Échec de la déconnexion", "POST");
};
export const updateUserPreferences = async (
preferences: { name: string; emoji: string; color: string }[]
) => {
return makePostRequest(
"/users/@me",
{ preferences },
"Échec de la mise à jour des préférences utilisateur",
"POST"
);
};
interface UserPreference {
name: string;
emoji: string;
color: string;
}
export type UserPreferences = UserPreference[];
/**
* === COLLES API ===
*/
export const getColles = async (startDate: DateTime) => {
return makeRequest(
`/colles?startDate=${startDate.toISODate()}`,
"Échec de la récupération des colles"
);
};
export interface Colle {
relatives: Colle[];
id: number;
date: string; // ISO date string
subject: {
id: number;
name: string;
};
examiner: {
id: number;
name: string;
};
room: {
id: number;
name: string;
};
student: User;
grade?: number; // Nullable grade
bjsecret?: string; // Optional field
bjid?: string; // Optional field
content?: string; // Optional field
comment?: string; // Optional field
attachments?: Attachment[]; // Optional field, array of attachment URLs
}
interface CollePayload {
classColles: Colle[];
studentColles: Colle[];
favoriteColles: Colle[];
healthyUntil: Date;
lastSync: Date;
}
export const useColles = (startDate: DateTime) => {
// Check if the start date is the current week
// This is used to determine if the data is "actual" or not
const isActual = DateTime.now()
.startOf("week")
.equals(startDate.startOf("week"));
const options = isActual
? {
refetchOnWindowFocus: true,
refetchOnReconnect: true,
staleTime: 0,
gcTime: 0,
}
: {
staleTime: Duration.fromObject({
hours: 1, // 1 hour
}).toMillis(),
gcTime: Duration.fromObject({
days: 3, // 3 days
}).toMillis(),
};
const { data, ...props } = useQuery({
queryKey: ["colles", startDate.toISODate()],
queryFn: () => getColles(startDate),
...options,
});
const mergedData = Object.assign(
{
classColles: [],
studentColles: [],
favoriteColles: [],
healthyUntil: new Date(0),
lastSync: new Date(0),
},
data || {}
) as CollePayload;
return {
...mergedData,
...props,
};
};
const fetchColle = async (id: number) => {
return makeRequest(`/colles/${id}`, "Échec de la récupération de la colle");
};
const defaultColle = {
id: 0,
date: "",
subject: {
id: 0,
name: "",
},
examiner: {
id: 0,
name: "",
},
room: {
id: 0,
name: "",
},
student: defaultUser,
attachments: [],
};
export const useColle = (id: number) => {
const { data, ...props } = useQuery({
queryKey: ["colle", id],
queryFn: () => fetchColle(id),
staleTime: Duration.fromObject({
seconds: 30, // 30 seconds
}).toMillis(),
gcTime: Duration.fromObject({
days: 3, // 3 days
}).toMillis(),
});
return {
colle: (data || defaultColle) as Colle,
...props,
};
};
export const refreshColle = async (id: number) => {
return makePostRequest(
`/colles/${id}/refresh`,
{},
"Échec de la demande de rafraîchissement de la colle",
"POST"
);
};
export interface Attachment {
path: string;
name: string;
}
/**
* === SUBJECTS API ===
*/
export const getSubjects = async () => {
return makeRequest("/subjects", "Échec de la récupération des matières");
};
export const useSubjects = () => {
const { data, ...props } = useQuery({
queryKey: ["subjects"],
queryFn: getSubjects,
staleTime: Duration.fromObject({
hours: 1, // 1 day
}).toMillis(),
gcTime: Duration.fromObject({
days: 3, // 3 days
}).toMillis(),
});
return {
subjects: (data as string[]) || [],
...props,
};
};
/**
* === NOTIFICATIONS API ===
*/
export const subscribe = async (data: any) => {
return makePostRequest(
"/notifications",
data,
"Échec de l'abonnement aux notifications"
);
};
export const unsubscribe = async (id: string) => {
return makePostRequest(
`/notifications/${id}/unsubscribe`,
{},
"Échec de la désinscription des notifications"
);
};
export const updateSubscription = async (id: string, events: string[]) => {
return makePostRequest(
`/notifications/${id}`,
{ events },
"Échec de la mise à jour de l'abonnement aux notifications"
);
};
export const getNotifications = async () => {
return makeRequest(
"/notifications",
"Échec de la récupération des notifications"
);
};
export interface Event {
id: string;
enabled: boolean;
}
interface Subscription {
id: string;
device: string;
events: Event[];
enabled: boolean;
}
export const useNotifications = () => {
const { data, ...props } = useQuery({
queryKey: ["notifications"],
queryFn: getNotifications,
staleTime: Duration.fromObject({
hours: 0, // 1 hour
}).toMillis(),
gcTime: Duration.fromObject({
days: 3, // 3 days
}).toMillis(),
});
return {
notifications: (data as Subscription[]) || [],
...props,
};
};
export const testNotification = async (id: string) => {
return makePostRequest(
`/notifications/${id}/test`,
{},
"Échec de l'envoi de la notification de test"
);
};
/**
* === GRADES API ===
*/
export const getGrades = async (period: string) => {
return makeRequest(
`/grades?period=${encodeURIComponent(period)}`,
"Échec de la récupération des notes"
);
};
export const useGrades = (period: string) => {
const { data, ...props } = useQuery({
queryKey: ["grades", period],
queryFn: () => getGrades(period),
staleTime: Duration.fromObject({
hours: 0, // 1 hour
}).toMillis(),
gcTime: Duration.fromObject({
days: 3, // 3 days
}).toMillis(),
});
const averages = (data?.averages as PeriodResult[]) || [];
const subjects = (data?.subjects as string[]) || [];
const grades = (data?.grades as Grade[]) || [];
return {
averages,
subjects,
grades,
...props,
};
};
interface SubjectPerformance {
subject: string;
average: number;
}
export interface PeriodResult {
period: string;
average: number;
}
export interface Grade {
subject: string;
date: number; // timestamp
grade: string;
}
export const getAverages = async (period: string) => {
return makeRequest(
`/averages?period=${encodeURIComponent(period)}`,
"Échec de la récupération des moyennes"
);
};
export const useAverages = (period: string) => {
const { data, ...props } = useQuery({
queryKey: ["averages", period],
queryFn: () => getAverages(period),
staleTime: Duration.fromObject({
hours: 0, // 1 hour
}).toMillis(),
gcTime: Duration.fromObject({
days: 3, // 3 days
}).toMillis(),
});
const subjectAverages = (data?.subjectAverages as SubjectPerformance[]) || [];
const globalAverage = data?.globalAverage || 0;
return {
subjectAverages,
globalAverage,
...props,
};
};
/**
* === REPAS API ===
*/
const fetchMeals = async () => {
return makeRequest(`/meals`, "Échec de la récupération des menus");
};
export const useMeals = () => {
const { data, ...props } = useQuery({
queryKey: ["meals"],
queryFn: fetchMeals,
staleTime: Duration.fromObject({
hours: 0, // 6 hours
}).toMillis(),
gcTime: Duration.fromObject({
days: 1, // 1 day
}).toMillis(),
});
return {
meals: (data as Meal[]) || [],
...props,
};
};
export interface Meal {
id: number;
date: string;
name: string;
courses: { name: string; description: string }[];
submittable: boolean;
isRegistered?: boolean; // Optional field to indicate if the user is registered for this meal
}
export const registerForMeal = async (
mealId: number,
username: string,
password: string
) => {
return makePostRequest(
`/meals/${mealId}`,
{ username, password },
"Échec de l'inscription au repas",
"POST"
);
};
export const testCredentials = async (username: string, password: string) => {
return makePostRequest(
`/auth/test`,
{ username, password },
"Échec de la vérification des identifiants",
"POST"
);
};
export const getCredentialsStatus = async () => {
return makeRequest(
`/auth/status`,
"Échec de la récupération du statut des identifiants"
);
};