code:
"use server";

import { signIn, signOut, auth } from "@/auth";
import { AuthError } from "next-auth";
import type { Player, Pair } from "./definitions";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

type Credentials = {
  username: string;
  password: string;
};

type NewPlayer = Omit<Player, "id" | "is_active" | "created_at" | "updated_at">;
type NewPair = Omit<Pair, "id" | "is_eliminated">;

export async function authenticate(
  prevState: string | undefined,
  formData: Credentials
) {
  try {
    await signIn("credentials", formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case "CredentialsSignin":
          return "Credenciales Invalidas.";
        default:
          return "Ocurrio un error, intenta más tarde";
      }
    }
    throw error;
  }
}

export async function logOut() {
  await signOut({ redirectTo: "/" });
}

export async function createPlayer(playerData: NewPlayer) {
  console.log({ playerData });
  try {
    const session = await auth();
    // console.log({ session });
    if (!session?.user) {
      throw new Error("No autorizado");
    }
    // fetch to create it
    console.log("autorizado create player");
    // console.log({ playerData, session });
    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/players`;
    const response = await fetch(url, {
      method: "POST",
      body: JSON.stringify(playerData),
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    // console.log({ response });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description || "Error al obtener al crear al juagador"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function createPair(pairData: NewPair) {
  // console.log({ pairData });
  try {
    const session = await auth();
    // console.log({ session });
    if (!session?.user) {
      throw new Error("No autorizado");
    }
    console.log("autorizado create pair");
    // fetch to create it
    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/pairs`;
    const response = await fetch(url, {
      method: "POST",
      body: JSON.stringify(pairData),
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    console.log({ response });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData?.description || "Error al crear la pareja");
    }
    const data = await response.json();
    return data;
  } catch (error) {
    if (error instanceof Error) {
      throw error;
    }
    throw new Error("Error desconocido al crear la pareja");
  }
}

// Create groups and Matches
export async function createGroupsMatches() {
  try {
    const session = await auth();
    if (!session?.user) {
      throw new Error("No autorizado");
    }
    // fetch to create it
    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/rounds/init`;
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    console.log({ response });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData?.description || "Error al crear la pareja");
    }
    const data = await response.json();
    revalidatePath("/dashboard/grupos");
    return data;
  } catch (error) {
    if (error instanceof Error) {
      throw error;
    }
    throw new Error("Error desconocido al crear los grupos y martidos");
  }
}

export async function deletePlayerById(id: string) {
  try {
    const session = await auth();
    // console.log({ session });
    if (!session?.user) {
      throw new Error("No autorizado");
    }

    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/players/${id}`;
    const response = await fetch(url, {
      method: "DELETE",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al eliminar el jugador");
    }
    const dataPlayerRemoved = await response.json();
    revalidatePath("/dashboard/jugadores");
    return dataPlayerRemoved;
  } catch (error) {
    throw error;
  }
}

export async function deletePairById(id: string) {
  try {
    const session = await auth();
    // console.log({ session });
    if (!session?.user) {
      throw new Error("No autorizado");
    }

    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/pairs/${id}`;
    const response = await fetch(url, {
      method: "DELETE",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al eliminar la pareja");
    }
    const dataPairRemoved = await response.json();
    revalidatePath("/dashboard/parejas");
    return dataPairRemoved;
  } catch (error) {
    throw error;
  }
}

export async function updatePairById(
  id: string,
  updatePairData: Partial<NewPair>
) {
  try {
    const session = await auth();
    // console.log({ session });
    if (!session?.user) {
      throw new Error("No autorizado");
    }
    console.log(updatePairData);
    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/pairs/${id}`;
    const response = await fetch(url, {
      method: "PUT",
      body: JSON.stringify(updatePairData),
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    console.log({ response });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData?.description || "Error al actualizar pareja");
    }
    const data = await response.json();
    console.log(data);
    revalidatePath("/dashboard/parejas");
    redirect("/dashboard/parejas");
  } catch (error) {
    if (error instanceof Error) {
      throw error;
    }
    throw new Error("Error desconocido al actualizar la pareja");
  }
}

export async function updatePlayerById(
  id: string,
  updatePlayerData: Partial<NewPlayer>
) {
  try {
    const session = await auth();
    // console.log({ session });
    if (!session?.user) {
      throw new Error("No autorizado");
    }
    console.log(updatePlayerData);
    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/players/${id}`;
    const response = await fetch(url, {
      method: "PUT",
      body: JSON.stringify(updatePlayerData),
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    console.log({ response });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description || "Error al actualizar el jugador"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    if (error instanceof Error) {
      throw error;
    }
    throw new Error("Error desconocido al actualizar el jugador");
  }
}

export async function deleteMatchById({
  id,
  idCategory,
}: {
  id: string;
  idCategory: string;
}) {
  try {
    const session = await auth();
    if (!session?.user) {
      throw new Error("No autorizado");
    }

    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/matches/${id}`;
    const response = await fetch(url, {
      method: "DELETE",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al eliminar el partido");
    }
    const dataPairRemoved = await response.json();
    revalidatePath(`/dashboard/partidos/${idCategory}`);
    return dataPairRemoved;
  } catch (error) {
    throw error;
  }
}

export async function updateMatchScorePair({
  idPair,
  matchId,
  idCategory,
  updateMatchData,
}: {
  idPair: string;
  matchId: string;
  idCategory: string;
  updateMatchData: {
    score: number;
    is_winner: boolean;
  };
}) {
  try {
    const session = await auth();
    // console.log({ session });
    if (!session?.user) {
      throw new Error("No autorizado");
    }
    console.log(updateMatchData);
    const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/matches/${matchId}/pairs/${idPair}`;
    const response = await fetch(url, {
      method: "PUT",
      body: JSON.stringify(updateMatchData),
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": session.user.apiKey,
      },
    });
    console.log({ response });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description || "Error al actualizar el partido"
      );
    }
    revalidatePath(`/dashboard/partidos/${idCategory}`);
  } catch (error) {
    if (error instanceof Error) {
      throw error;
    }
    throw new Error("Error desconocido al actualizar el partido");
  }
}


code:
import {
  Group,
  PairsPlayers,
  PairPlayersRelationByGroup,
  MatchesByGroup,
} from "@/app/lib/definitions";
import { auth } from "@/auth";

const URL_API = `${process.env.NEXT_PUBLIC_API_URL}/v1`;

type FetchPlayersProps = {
  apiKey: string;
  page: number;
  search?: string;
  filterBy?: string;
  orderBy?: string;
  sortBy?: string;
};
type FetchPairsProps = {
  apiKey: string;
  page: number;
  registration_category_id?: string;
  orderBy?: string;
  sortBy?: string;
  is_eliminated?: boolean | null;
};

// -------------- Players -------------------
export async function fetchPlayers({
  apiKey,
  page = 1,
  search = "",
  filterBy = "",
  orderBy = "",
  sortBy = "",
}: FetchPlayersProps) {
  const url = `${URL_API}/players?page=${page}&search=${search}&filterBy=${filterBy}&orderBy=${orderBy}&sortBy=${sortBy}&is_active=true`;
  const response = await fetch(url, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": apiKey,
    },
  });
  console.log({ response });
  if (!response.ok) {
    const errorData = await response.json();
    console.log(errorData);
    throw new Error(errorData?.description || "Error al obtener los jugadores");
  }
  const data = await response.json();
  return data;
}

export async function fetchAllPlayers(apiKey: string) {
  const allPlayers = [];

  // Fetch the first page to get the total pages
  const { data: dataPlayersPage1, pagination } = await fetchPlayers({
    page: 1,
    apiKey: apiKey,
  });
  const totalPages = pagination.total;
  allPlayers.push(...dataPlayersPage1);

  // Loop through each page
  for (let i = 2; i <= totalPages; i++) {
    const { data: dataRestPlayersPages } = await fetchPlayers({
      page: i,
      apiKey,
    });
    allPlayers.push(...dataRestPlayersPages);
  }

  return allPlayers;
}

export async function fetchPlayerById({
  idPlayer,
  apiKey,
}: {
  idPlayer: string;
  apiKey: string;
}) {
  const url = `${URL_API}/players/${idPlayer}`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al obtener el jugador");
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

// -------------- Pairs -------------------
export async function fetchPairs({
  apiKey,
  page = 1,
  orderBy = "",
  sortBy = "",
  registration_category_id = "",
  is_eliminated = null,
}: FetchPairsProps) {
  const url = `${URL_API}/pairs/players?page=${page}&orderBy=${orderBy}&sortBy=${sortBy}&registration_category_id=${registration_category_id}&is_eliminated=${is_eliminated}&is_active=true`;
  const response = await fetch(url, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": apiKey,
    },
  });
  // console.log({ response });
  if (!response.ok) {
    const errorData = await response.json();
    console.log(errorData);
    throw new Error(errorData?.description || "Error al obtener las parejas");
  }
  const data = await response.json();
  return data;
}

export async function fetchAllPairs(apiKey: string) {
  const allPairs = [];

  // Fetch the first page to get the total pages
  const { data: dataPairsPage1, pagination } = await fetchPairs({
    page: 1,
    apiKey: apiKey,
  });
  const totalPages = pagination.total;
  allPairs.push(...dataPairsPage1);

  // Loop through each page
  for (let i = 2; i <= totalPages; i++) {
    const { data: dataRestPairsPages } = await fetchPairs({
      page: i,
      apiKey,
    });
    allPairs.push(...dataRestPairsPages);
  }

  return allPairs;
}

export async function fetchPairById({
  idPair,
  apiKey,
}: {
  idPair: string;
  apiKey: string;
}) {
  const url = `${URL_API}/pairs/${idPair}`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al obtener la pareja");
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchPairPlayersById({
  idPair,
  apiKey,
}: {
  idPair: string;
  apiKey: string;
}) {
  const url = `${URL_API}/pairs/${idPair}/players`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description || "Error al obtener los juagdores de la pareja"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

// -------------- Categories -------------------
export async function fetchCategories(apiKey: string) {
  const url = `${URL_API}/categories/registrations`;
  const response = await fetch(url, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": apiKey,
    },
  });
  // console.log({ response });
  if (!response.ok) {
    const errorData = await response.json();
    console.log(errorData);
    throw new Error(
      errorData?.description || "Error al obtener las categorias"
    );
  }
  const data = await response.json();
  return data;
}

export async function fetchCategoryById({
  idCategory,
  apiKey,
}: {
  idCategory: string;
  apiKey: string;
}) {
  const url = `${URL_API}/categories/registrations/${idCategory}`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchStatusCategories(apiKey: string) {
  const url = `${URL_API}/status/matches`;
  const response = await fetch(url, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": apiKey,
    },
  });
  // console.log({ response });
  if (!response.ok) {
    const errorData = await response.json();
    console.log(errorData);
    throw new Error(
      errorData?.description || "Error al obtener los estatus de los partidos"
    );
  }
  const data = await response.json();
  return data;
}
export async function fetchMatchesCategories(apiKey: string) {
  const url = `${URL_API}/categories/matches`;
  const response = await fetch(url, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "X-API-KEY": apiKey,
    },
  });
  // console.log({ response });
  if (!response.ok) {
    const errorData = await response.json();
    console.log(errorData);
    throw new Error(
      errorData?.description ||
        "Error al obtener las categorias de los partidos"
    );
  }
  const data = await response.json();
  return data;
}

// -------------- Others -------------------
export async function fetchCardsData(apiKey: string) {
  const { data: categoriesData } = await fetchCategories(apiKey);
  const [category1, category2] = categoriesData;
  const playersPromise = fetchPlayers({ apiKey, page: 1 });
  const pairsPromise = fetchPairs({ apiKey, page: 1 });
  const pairsSeniorPromise = fetchPairs({
    apiKey,
    page: 1,
    registration_category_id: category1.id,
  });
  const pairsOpenPromise = fetchPairs({
    apiKey,
    page: 1,
    registration_category_id: category2.id,
  });

  const data = await Promise.all([
    playersPromise,
    pairsPromise,
    pairsOpenPromise,
    pairsSeniorPromise,
  ]);
  const numberOfPlayers = data[0].pagination.count;
  const numberOfPairs = data[1].pagination.count;
  const numberOfPairsOpen = data[2].pagination.count;
  const numberOfPairsSenior = data[3].pagination.count;

  return {
    numberOfPlayers,
    numberOfPairs,
    numberOfPairsOpen,
    numberOfPairsSenior,
    // numberOfGroups
    // numberOfMatches
    // numberOfGroupsOpen
    // numberOfGroupsSenior
    // numberOfMatchesOpen
    // numberOfMatchesSenior
  };
}

export async function fetchStatsCardsData(apiKey: string) {
  const { data: categoriesData } = await fetchCategories(apiKey);
  const [category1, category2] = categoriesData;
  const playersPromise = fetchPlayers({ apiKey, page: 1 });
  const pairsPromise = fetchPairs({ apiKey, page: 1 });
  const matchesPromise = fetchMatches(apiKey);

  const pairsSeniorPromise = fetchPairs({
    apiKey,
    page: 1,
    registration_category_id: category1.id,
  });
  const pairsOpenPromise = fetchPairs({
    apiKey,
    page: 1,
    registration_category_id: category2.id,
  });

  const data = await Promise.all([
    playersPromise,
    pairsPromise,
    matchesPromise,
    pairsOpenPromise,
    pairsSeniorPromise,
  ]);
  const numberOfPlayers = data[0].pagination.count;
  const numberOfPairs = data[1].pagination.count;
  const numberOfMatches = data[2].pagination.count;
  const numberOfPairsOpen = data[3].pagination.count;
  const numberOfPairsSenior = data[4].pagination.count;

  return {
    numberOfPlayers,
    numberOfPairs,
    numberOfMatches,
    numberOfPairsOpen,
    numberOfPairsSenior,
  };
}

export async function login(username: string, password: string) {
  const url = `${URL_API}/auth/login`;
  try {
    const response = await fetch(url, {
      method: "POST",
      body: JSON.stringify({
        nickname: username,
        password: password,
      }),
      headers: {
        "Content-Type": "application/json",
      },
    });
    // console.log({response})
    const { data } = await response.json();

    if (response.ok && data.api_key) {
      return {
        apiKey: data.api_key,
      };
    }
  } catch (error) {
    console.error("Failed to fetch user:", error);
    throw new Error("Failed to fetch user.");
  }
}

export async function fetchProfile({
  apiKey,
  idPlayer,
}: {
  apiKey: string;
  idPlayer: string;
}) {
  const url = `${URL_API}/analysis/profiles/${idPlayer}`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description || "Error al obtener el perfil del jugador"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function getApiKey() {
  const session = await auth();
  let apiKey: string;
  // If already authenticated (admin or public user)
  if (session?.user?.apiKey) {
    apiKey = session.user.apiKey;
  } else {
    // Programmatic sign-in with admin credentials
    const data = await login(
      process.env.API_USERNAME ?? "",
      process.env.API_PASSWORD ?? ""
    );
    if (!data?.apiKey) {
      throw new Error("No Autorizado");
    }
    apiKey = data.apiKey;
  }
  return apiKey;
}

// -------------- Tables -------------------
export async function fetchTablePairData(apiKey: string) {
  const dataPairs = await fetchAllPairs(apiKey);

  const tableDataPairs = dataPairs.map((item) => {
    const { pair, players } = item;

    const [player1Info, player2Info] = players;

    return {
      id: pair.id,
      player1: {
        id: player1Info?.player?.id,
        name: player1Info?.player?.fullname ?? "No reconocido",
      },
      player2: {
        id: player2Info?.player?.id,
        name: player2Info?.player?.fullname ?? "No reconocido",
      },
      category: pair?.registration_category?.description ?? "Sin categoria",
    };
  });

  return tableDataPairs;
}

export async function fetchTablePlayerData(apiKey: string) {
  const dataPlayers = await fetchAllPlayers(apiKey);
  const tableData = dataPlayers.map((playerData) => {
    const { id, fullname, city, weight, height } = playerData;
    return {
      id,
      fullname,
      city,
      weight,
      height,
    };
  });
  return tableData;
}

// -------------- Groups -------------------
export async function fetchGroups(apiKey: string) {
  const url = `${URL_API}/groups`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al obtener grupos");
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchGroupById({
  idGroup,
  apiKey,
}: {
  idGroup: string;
  apiKey: string;
}) {
  const url = `${URL_API}/groups/${idGroup}`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al obtener el grupo");
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchGroupsByCategory({
  idCategory,
  apiKey,
}: {
  idCategory: string;
  apiKey: string;
}) {
  const url = `${URL_API}/categories/registrations/${idCategory}/groups`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description || "Error al obtener grupos por categoria"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchPlayersPairsByGroup({
  idGroup,
  apiKey,
}: {
  idGroup: string;
  apiKey: string;
}) {
  const url = `${URL_API}/groups/${idGroup}/pairs/players`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description ||
          "Error al obtener Parejas y Jugadores por Grupo"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchGroupsPairsPlayersByCategory({
  idCategory,
  apiKey,
}: {
  idCategory: string;
  apiKey: string;
}) {
  // get the category info
  const { data: category } = await fetchCategoryById({ idCategory, apiKey });
  // get the groups info by category
  const { data: groups } = await fetchGroupsByCategory({ idCategory, apiKey });

  const groupsInfoByCategoryPromises = groups.map(async (group: Group) => {
    const idGroup = group.id;
    // get the pairs player by group
    const { data: pairsPlayersInfo } = await fetchPlayersPairsByGroup({
      idGroup,
      apiKey,
    });
    // normalize the data to get the needed data
    const pairsList = pairsPlayersInfo.map(
      (pairPlayers: PairPlayersRelationByGroup) => {
        const playersForPair = pairPlayers.pair.players.map(
          (players: PairsPlayers) => {
            const playerInfo = players.player;
            return {
              id: playerInfo.id,
              fullname: playerInfo.fullname,
            };
          }
        );

        return {
          pair: {
            id: pairPlayers.pair.id,
          },
          players: playersForPair,
        };
      }
    );

    return {
      id: idGroup,
      name: group.name,
      description: group.description,
      pairs: pairsList,
    };
  });

  const groupsInfoByCategory = await Promise.all(groupsInfoByCategoryPromises);
  // return the category info and teh groups by category
  return {
    categoryData: {
      id: category.id,
      name: category.name,
      description: category.description,
    },
    groupsData: groupsInfoByCategory,
  };
}

// -------------- Matches -------------------
export async function fetchMatches(apiKey: string) {
  const url = `${URL_API}/matches`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description || "Error al obtener los partidos"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchMatchPairsById({
  idMatch,
  apiKey,
}: {
  idMatch: string;
  apiKey: string;
}) {
  const url = `${URL_API}/matches/${idMatch}/pairs`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(errorData?.description || "Error al obtener el partido");
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchMatchesPlayersPairsByGroup({
  idGroup,
  apiKey,
}: {
  idGroup: string;
  apiKey: string;
}) {
  const url = `${URL_API}/groups/${idGroup}/matches/pairs/players`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description || "Error al obtener partidos por grupo"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchMatchesPairsPlayersByGroup({
  apiKey,
  idGroup,
}: {
  apiKey: string;
  idGroup: string;
}) {
  // get the group info and teh category info by group id
  const { data: group } = await fetchGroupById({ idGroup, apiKey });
  const { registration_category: category } = group;

  const { data: matches } = await fetchMatchesPlayersPairsByGroup({
    idGroup,
    apiKey,
  });

  const matchesInfoByCategoryByGroup = matches.map(
    ({ match }: { match: MatchesByGroup }) => {
      const pairsByMatch = match.pairs.map((pairItem) => {
        const { pair, relationship } = pairItem;

        const playersForPair = pair.players.map((players: PairsPlayers) => {
          const playerInfo = players.player;
          return {
            id: playerInfo.id,
            fullname: playerInfo.fullname,
          };
        });

        return {
          id: pair.id,
          players: playersForPair,
          score: relationship.score,
          isWinner: relationship.is_winner,
        };
      });

      return {
        id: match.id,
        isActive: match.is_active,
        matchCategory: { ...match.match_category },
        matchStatus: { ...match.match_status },
        matchPairs: pairsByMatch,
      };
    }
  );

  return {
    categoryData: {
      id: category.id,
      name: category.name,
      description: category.description,
    },
    groupData: {
      id: group.id,
      name: group.name,
      description: group.description,
    },
    matchesData: matchesInfoByCategoryByGroup,
  };
}

// Table matches
export async function fetchTableMatchesByCategory({
  apiKey,
  idCategory,
}: {
  apiKey: string;
  idCategory: string;
}) {
  const { data: category } = await fetchCategoryById({ idCategory, apiKey });
  // get the groups info by category
  const { data: groups } = await fetchGroupsByCategory({ idCategory, apiKey });

  const matchesInfoByCategoryPromises = groups.map(async (group: Group) => {
    const idGroup = group.id;
    // get the data of matches
    const { data: matches } = await fetchMatchesPlayersPairsByGroup({
      idGroup,
      apiKey,
    });

    const matchesInfoByCategoryByGroup = matches.map(
      ({ match }: { match: MatchesByGroup }) => {
        const pairsByMatch = match.pairs.map((pairItem) => {
          const { pair, relationship } = pairItem;

          const playersForPair = pair.players.map((players: PairsPlayers) => {
            const playerInfo = players.player;
            return {
              id: playerInfo.id,
              fullname: playerInfo.fullname,
            };
          });

          return {
            id: pair.id,
            players: playersForPair,
            score: relationship.score,
            isWinner: relationship.is_winner,
          };
        });

        return {
          id: match.id,
          isActive: match.is_active,
          matchCategory: { ...match.match_category },
          matchStatus: { ...match.match_status },
          matchPairs: pairsByMatch,
        };
      }
    );

    return {
      id: idGroup,
      name: group.name,
      description: group.description,
      matchesInfoByCategoryByGroup,
    };
  });

  const matchesInfoByCategory = await Promise.all(
    matchesInfoByCategoryPromises
  );

  return {
    categoryData: {
      id: category.id,
      name: category.name,
      description: category.description,
    },
    matchesData: matchesInfoByCategory,
  };
}

export async function fetchRankingByGroup({
  apiKey,
  idGroup,
}: {
  apiKey: string;
  idGroup: string;
}) {
  const url = `${URL_API}/groups/${idGroup}/ranking`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description ||
          "Error al obtener Ranking de las parejas y jugadores de un grupo"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

// DATA ANALYSIS
export async function fetchHeatMap(apiKey: string) {
  const url = `${URL_API}/analysis/heatmap`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      console.log(errorData);
      throw new Error(
        errorData?.description || "Error al obtener los partidos"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchParitiesByCategory({
  apiKey,
  idCategory,
}: {
  apiKey: string;
  idCategory: string;
}) {
  const url = `${URL_API}/analysis/parities/${idCategory}`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description ||
          "Error al obtener la paridad de una categoría de inscripción"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchSynergyByCategory({
  apiKey,
  idCategory,
}: {
  apiKey: string;
  idCategory: string;
}) {
  const url = `${URL_API}/analysis/synergies/${idCategory}`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description ||
          "Error al obtener la sinergia que describe el desempeño de las parejas."
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchPointsByGroup({
  apiKey,
  idGroup,
}: {
  apiKey: string;
  idGroup: string;
}) {
  const url = `${URL_API}/analysis/groups/${idGroup}/points`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description ||
          "Error al obtener los puntos realizados en un grupo."
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchPointsChartByCategory({
  apiKey,
  idCategory,
}: {
  apiKey: string;
  idCategory: string;
}) {
  const { data: groups } = await fetchGroupsByCategory({ apiKey, idCategory });
  const pointsGroupsPromises = groups.map(async (group: Group) => {
    const { data: pointsDataChart } = await fetchPointsByGroup({
      apiKey,
      idGroup: group.id,
    });
    return pointsDataChart;
  });

  const pointsGroups = await Promise.all(pointsGroupsPromises);
  return pointsGroups;
}

export async function fetchPointsComparative(apiKey: string) {
  const url = `${URL_API}/analysis/comparisons/points`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description ||
          "Error al obtener los puntos realizados en las categorías de inscripción."
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchSynergiesComparative(apiKey: string) {
  const url = `${URL_API}/analysis/comparisons/synergies`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description ||
          "Error al obtener los puntos realizados en las categorías de inscripción."
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

export async function fetchEmotions(apiKey: string) {
  const url = `${URL_API}/analysis/feelings`;
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
        "X-API-KEY": apiKey,
      },
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(
        errorData?.description || "Error al obtener las emociones"
      );
    }
    const data = await response.json();
    return data;
  } catch (error) {
    throw error;
  }
}

license: mit


code:
export type UserSession = {
  id: string;
  username: string;
  password: string;
  apiKey: string;
  role: string;
  expiresAt: number;
};

export type Player = {
  id: string;
  fullname: string;
  city: string;
  weight: number;
  height: number;
  age: number;
  experience: number;
  is_active: number;
  created_at: string;
  updated_at: string;
};

export type PlayerRelationship = {
  id: string;
  player_id: string;
  pair_id: string;
  created_at: string;
  updated_at: string;
};

export type Pair = {
  id: string;
  players: [string, string];
  is_eliminated: number;
  registration_category_id: string;
};

export type Category = {
  id: string;
  name: string;
  description: string;
  created_at: string;
  updated_at: string;
};

export type PairsPlayers = {
  player: Player;
  relationship: PlayerRelationship;
};

export type PairByGroup = {
  id: string;
  is_eliminated: number;
  is_active: number;
  created_at: string;
  updated_at: string;
  registration_category: Omit<Category, "created_at" | "updated_at">;
  players: PairsPlayers[];
};

export type PairPlayersRelationByGroup = {
  pair: PairByGroup;
};

export type Group = {
  id: string;
  registration_category_id: string;
  name: string;
  description: string;
  is_eliminated: number;
  is_active: number;
  created_at: string;
  updated_at: string;
};

export type MatchStatus = {
  id: string;
  name: string;
  description: string;
};

export type Match = {
  id: string;
  is_active: number;
  created_at: string;
  updated_at: string;
  registration_category: Omit<Category, "created_at" | "updated_at">;
  match_category: MatchStatus;
  match_status: MatchStatus;
};

export type PairPlayersMatchesRelationByGroup = {
  pair: PairByGroup;
  relationship: {
    id: string;
    pair_id: string;
    match_id: string;
    score: number;
    is_winner: number;
    created_at: string;
    updated_at: string;
  };
};

export type MatchesByGroup = Match & {
  pairs: PairPlayersMatchesRelationByGroup[];
};

export type MatchPairPlayers = {
  match: MatchesByGroup;
};

export type GroupByCategory = {
  categoryData: Omit<Category, "created_at" | "updated_at">;
  groupsData: {
    id: string;
    name: string;
    description: string;
    pairs: {
      pair: {
        id: string;
      };
      players: {
        id: string;
        fullname: string;
      }[];
    }[];
  }[];
};

// export type MatchByCategory = {
//   categoryData: Omit<Category, "created_at" | "updated_at">;
//   groupData: Omit<
//     Group,
//     "is_active" | "is_eliminated" | "created_at" | "updated_at"
//   >;
// };

export type MatchByGroup = {
  categoryData: Omit<Category, "created_at" | "updated_at">;
  groupData: {
    id: string;
    name: string;
    description: string;
  };
  matchesData: {
    id: string;
    isActive: number;
    matchCategory: MatchStatus;
    matchStatus: MatchStatus;
    matchPairs: {
      id: string;
      score: number;
      isWinner: number;
      players: {
        id: string;
        fullname: string;
      }[];
    }[];
  }[];
};


code:
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

export function getPlotlyLayoutChart({
  isXaxisVisible = true,
  xAxisTitle = "",
  yAxisTitle = "",
}: {
  isXaxisVisible?: boolean;
  xAxisTitle: string;
  yAxisTitle: string;
}) {
  const layout = {
    template: {
      /* existing template data */
    },
    font: {
      family: "system-ui", // Set font family to Open Sans
      size: 12,
      color: "white",
    },
    // title: {
    //   text: `${titleChart}`,
    //   x: 0.1,
    //   y: 0.9,
    //   yanchor: "top",
    // },
    // annotations: [
    //   {
    //     x: "B",
    //     y: 8.35,
    //     text: "Highest Value",
    //     showarrow: true,
    //     arrowhead: 1,
    //     ax: 0,
    //     ay: -40,
    //   },
    // ],
    // margin: { t: 120 },
    xaxis: {
      visible: isXaxisVisible,
      title: { text: `${xAxisTitle}` },
      showgrid: true,
      gridcolor: "rgba(255, 255, 255, 0.1)",
    },
    yaxis: {
      title: { text: `${yAxisTitle}` },
      showgrid: true,
      gridcolor: "rgba(255, 255, 255, 0.1)",
    },
    images: [
      {
        source: "/logo.png", // URL to a subtle pattern
        xref: "paper",
        yref: "paper",
        x: 1.05,
        y: 1.1,
        sizex: 0.2,
        sizey: 0.2,
        opacity: 0.8, // Make it subtle
        layer: "below",
        xanchor: "right",
        yanchor: "bottom",
      },
    ],
    plot_bgcolor: "rgba(36, 17, 17, 0.8)",
    paper_bgcolor: "#09090b",
  };

  return layout;
}


code:
import LoginForm from "@/app/components/LoginForm";
import SquareBackground from "@/app/components/SquareBackground";
import { Suspense } from "react";

export default function LoginPage() {
  return (

      <SquareBackground />

          Login - TDLF 2025

          Login

        <Suspense>
          <LoginForm />
        </Suspense>

  );
}


code:
import {
  Header,
  Hero,
  Info,
  NeedToKnow,
  HowToPlay,
  Sponsors,
  Agenda,
  Awards,
  PreFooter,
  Footer,
} from "@/app/components/landing";

export default function Home() {
  return (

      <Hero></Hero>
      <Info></Info>
      <NeedToKnow></NeedToKnow>
      <HowToPlay></HowToPlay>
      <Sponsors></Sponsors>
      <Awards></Awards>
      <Agenda></Agenda>
      <PreFooter></PreFooter>

  );
}


code:
import { columnsPublic, PairRow } from "@/app/components/PairsCols";
import TablePairs from "@/app/components/TablePairs";
import { fetchTablePairData, getApiKey } from "@/app/lib/data";
// import { auth } from "@/auth";

export default async function PairsPage() {
  const apiKey = await getApiKey();
  const tableData: PairRow[] = await fetchTablePairData(apiKey);
  return (

            Torneo De Las Fresas

          Tercera edición

            2025

            Tabla de Parejas

            <TablePairs columns={columnsPublic} data={tableData} />

  );
}


code:
.scroll__animate {
  animation-timeline: scroll();
  animation-range: 0 100px;
}

.animation__scrollin {
  view-timeline-name: --scroll-in;
}

.animation__scrollin > li {
  --index0: calc(var(--index-agenda) - 1);
  --reverse-index: calc(var(--numItems) - var(--index0));
  --reverse-index0: calc(var(--reverse-index) - 1);
  --numcards: 6;
  position: sticky;
  top: 15vh;
  padding-block-start: calc(var(--index-agenda) * 1rem);
}

.animation__scrollin > li > div {
  --start-range: calc(var(--index0) / var(--numcards) * 100%);
  --end-range: calc(var(--index-agenda) / var(--numcards) * 100%);
  animation: linear scale forwards;
  animation-timeline: --scroll-in;
  animation-range: exit-crossing var(--start-range) exit-crossing
    var(--end-range);
  will-change: transform;
}

@keyframes scale {
  to {
    transform: scale(calc(1.1 - calc(0.1 * var(--reverse-index))));
  }
}


code:
import type { NextAuthConfig } from "next-auth"

export const authConfig = {
  pages: {
    signIn: '/login'
  },
  callbacks: {
    async authorized({auth, request: {nextUrl}}) {
      const isLoggedIn = !!auth?.user
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
      const isOnLogin = nextUrl.pathname.startsWith('/login')

      if (isOnDashboard) {
        if (isLoggedIn) return true;
        return false; // Redirect unauthenticated users to login page
      }
      if(isOnLogin && isLoggedIn) {
        return Response.redirect(new URL('/dashboard', nextUrl));
      }
      return true;
    }
  },
  providers: []
} satisfies NextAuthConfig


code:
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { authConfig } from "./auth.config";
import { z } from 'zod'
import type { UserSession } from '@/app/lib/definitions'

async function authLogin(username: string, password: string):  Promise<UserSession | undefined> {
  console.log('executing authLogin fn')
  const url = `${process.env.NEXT_PUBLIC_API_URL}/v1/auth/login`;
  try {
    const response = await fetch(url, {
      method: 'POST',
      body: JSON.stringify({
        nickname: username,
        password: password
      }),
      headers: {
        "Content-Type": "application/json"
      }
    })
    // console.log({response})
    const { data } = await response.json()

    if(response.ok && data.api_key ) {
      const responseMe = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/auth/me`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          "X-API-KEY": data.api_key,
        },
      })
      const {data: authMe} = await responseMe.json()

      if( responseMe.ok && authMe.role.name) {
        return {
          id: '1',
          username,
          password,
          apiKey: data.api_key,
          role: authMe.role.name,
          expiresAt: Date.now() + (8 * 60 * 1000)
        }
      }
    }

  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw new Error('Failed to fetch user.');
  }
}

export const {auth, signIn, signOut} = NextAuth({
  ...authConfig,
  providers: [Credentials({
    name: 'Credentials',
    credentials: {
      username: {
        label: 'Username',
        type: 'text'
      },
      password: {
        label: 'Password',
        type: 'password'
      }
    },
    async authorize(credentials) {
      const parsedCredentials = z
        .object({ username: z.string().min(2), password: z.string().min(8) })
        .safeParse(credentials);

        if(parsedCredentials.success) {
          const { username, password } = parsedCredentials.data;
          const user = await authLogin(username, password)
          return user ?? null
        }
        console.log('Invalid credentials');
        return null
    },
  })],
  callbacks: {
    async jwt({ token, user }: {token: any, user:any}) {
      // Add the API key to the JWT token
      console.log('jwt function')
      if (user) {
        token.user = user;
      }

      // Check if the API key has expired
      if (token.user.expiresAt && Date.now() > token.user.expiresAt) {
        // console.log('API key expired, refreshing...');
        const refreshedToken = await authLogin(token.user.username as string, token.user.password as string);
        if (refreshedToken) {
          token.user.apiKey = refreshedToken.apiKey;
          token.user.expiresAt = refreshedToken.expiresAt;
        } else {
          console.error('Failed to refresh API key');
          return { ...token, error: 'RefreshApiKeyError' }; // Handle error gracefully
        }
      }
      // console.log({tokenFinal: token})
      return token;
    },
    async session({ session, token }) {
      // Add the API key to the session
      if (token.user) {
        session.user = token.user as string;
      }
      return session;
    },
  },
  session: {
    strategy: "jwt",
  },
  secret: process.env.AUTH_SECRET
})

// write an entire auth config with next-auth to use in nextJs that got this:
// - two apiKey, one for admin and other fo rthe public routes
// - username, expirestAt (8 min), password

// write an example of how to use it in the private routes in the dashboard and how to do it in the public routes and access to the apiKey in each case to pass that apikey to the services of external API

// ok it is possible to execute this code:

//   const session = await auth()
//   if (!session?.user?.apiKey) {
//     throw new Error("Unauthorized")
//   }
// const apikey = session.user.apiKey

// In some kind of layout and got access to the apiKey value in each children page where I need it (for the dashboard and the public routes) or maybe with a middleware with the apiKey and the role as headers?

// ok is possible to do this with cookies?


code:
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

const eslintConfig = [
  ...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;


code:
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export default NextAuth(authConfig).auth;

export const config = {
  // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};


code:
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  typescript: {
    // !! WARN !!
    // Dangerously allow production builds to successfully complete even if
    // your project has type errors.
    // !! WARN !!
    ignoreBuildErrors: true,
  },
};

export default nextConfig;


dev: next dev --turbopack

build: next build

start: next start

lint: next lint