Best GitHub parte 4
Tags: githubhack23, nextjs, typescript, spanish
Published: 2023-05-15 23:15:30

Hola 👋, en este post terminaremos la parte del backend con **Next JS**.


Para empezar, dentro de nuestra carpeta **api** que se encuentra dentro de la carpeta **pages**, vamos a crear un archivo llamado **search-place.ts**.

Dentro de este archivo agregaremos el siguiente código:

```
import type { NextApiRequest, NextApiResponse } from "next";

import messages from "@/utils/messages";

export default async function handler(req: NextApiRequest,res: NextApiResponse) {
  if (req.method !== "POST") {
    return res.status(404).json(messages.notFound);
  }
}
```

Como te habrás dado cuenta, la estructura es muy parecida a lo que habíamos hecho en el post anterior, pero esta vez vamos de usar el método **GET** usaremos el método **POST**.

Antes de continuar, vamos a instalar una dependencia llamada **Zod** para validar el usuario de GitHub que vamos a resibir.

Esto lo haremos con el siguiente comando:

```
yarn add zod
```

Si estás usando **NPM**:

```
npm install zod
```
Ya instalado, crearemos un archivo llamado **validations.ts**, dentro de nuestra carpeta **utils**, ahí procederemos a escribir el siguiente código:

```
import { z } from "zod";

export const userZodSchema = z.object({
  username: z.string().trim().min(1),
});
```

Lo que estoy haciendo es importar **z** desde **zod** para poder acceder a su propiedad **object**, que nos sirve para poder validar varios datos, pasándole un solo objeto, en lugar de validar dato por dato, también estoy validando un dato como un objeto en caso de que quiera después agregar más datos para validar que vengan del frontend.

La key **username** va a tener la validación del username, en este caso le estoy diciendo con **z.string** que quiero que el dato sea de tipo **string**, con el **trim** le estoy diciendo que si el string recibido tiene espacios se los quite, y con **min(1)** le estoy diciendo que el username no puede estar vacío y debe tener minimo un carácter.

Y ese esquema de validación lo estoy guardando en una constante llamada **userZodSchema**.

Ahora en nuestro archivo **search-place.ts**, procederemos a importar nuestro esquema y agregar el siguiente código después del **if**:

```
const result = userZodSchema.safeParse(req.body);

if (!result.success) {
  return res.status(400).json(messages.error);
}

const { username } = result.data;
```

Lo que hace ese código, es que estamos accediendo a la propiedad **safeParse** que nos crea **zod** en nuestro esquema de validación, que sirve para que si el dato recibido no cumple con las validaciones que le agregamos no nos mande un error, depués va a recibir todo lo que venga del **req.body** de la petición que nos manden a la ruta **/api/search-place** con el método **POST** y guardaremos el resultado en una constante llamada **result**, si el objeto recibido tiene más propiedades aparte del **username**, nuestro esquema de validación directamente las ignorará.

En el **if** lo que estoy haciendo es revisar que la constante **result** no tenga una propiedad llamada **success** en **true**, que significa que el dato recibido no cumple con las validaciones que agregamos,y responda con el mensaje de error de nuestras utilidades que habíamos creado en el post anterior, y si existe la propiedad **success**, entonces de **data** desestructuramos el **username** ya validado.

Ahora en nuestra carpeta **util** vamos a crear un archivo llamado **githubCall.ts** y dentro agregaremos el siguiente código:

```
const githubCall = async(username: string) => {
    const response = await fetch(`https://api.github.com/users/${username}`);
    const user = await response.json();
    return user;
}

export default githubCall;
```

Como podrás ver, es solo una función que recibe el **username** y hace una llamada al **api pública de GitHub** y nos retorna los datos públicos de su perfil de GitHub.

También crearemos un archivo llamado **changeUser.ts** dentro de nuestra carpeta **utils**, y agregaremos el siguiente código:

```
import { PrismaClient } from "@prisma/client";

import type { NewUser } from "./types";

const prisma = new PrismaClient();

const changeUser = async (userID: string, newUser: NewUser) => {
  const updatedUser = await prisma.user.update({
    where: { id: userID },
    data: newUser,
  });

  if (updatedUser) {
    console.log("User Updated");
  } else {
    console.error("Error Updating User");
  }
};

export default changeUser;
```

Esta utilidad nos va a servir para actualizar a los lugares ganadores,  por eso la función esta recibiendo el **userID** y el **newUser**, dentro de la función estamos accediendo a la propiedad **user** de nuestro **user** en **prisma** para actualizar los del usuario por los del nuevo usuario identificándolo por su ID. 

También crearemos un archivo **newUser.ts** dentro de nuestra carpeta **utils** con el siguiente código:

```
import type { NextApiResponse } from "next";

import changeUser from "./changeUser";
import type { GitHubUser, NewUser } from "./types";
import type { User } from "@prisma/client";

const newUser = async (
  res: NextApiResponse,
  user: GitHubUser,
  users: User[]
) => {
  const newUser: NewUser = {
    username: user.login,
    name: user.name,
    url: user.html_url,
    avatar: user.avatar_url,
    total: user.public_repos + user.followers,
  };

  if (newUser.total >= users[0].total) {
    await changeUser(users[0].id, newUser);
    return res.status(200).json({
      message: "Congratulations, you are in the first place",
    });
  } else if (
    newUser.total < users[0].total &&
    newUser.total >= users[1].total
  ) {
    await changeUser(users[1].id, newUser);
    return res.status(200).json({
      message: "Congratulations, you are in the second place",
    });
  } else if (
    newUser.total < users[1].total &&
    newUser.total >= users[2].total
  ) {
    await changeUser(users[2].id, newUser);
    return res.status(200).json({
      message: "Congratulations, you are in the third place",
    });
  } else {
    return res.status(200).json({
      message: "Sorry, you are not in any of the first places",
    });
  }
};

export default newUser;
```

Lo que está haciendo ese código es ver si el nuevo usuario tiene más puntos o menos que cualquiera de los tres usuarios en nuestra base de datos, y si tiene más o igual de puntos lo va a sustituir.


Para finalizar, nuestro archivo **search-place.ts** se tendría que ver de esta forma:

```
import type { NextApiRequest, NextApiResponse } from "next";

import messages from "@/utils/messages";
import { userZodSchema } from "@/utils/validations";
import githubCall from "@/utils/githubCall";
import usersCall from "@/utils/usersCall";
import newUser from "@/utils/newUser";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") {
    return res.status(404).json(messages.notFound);
  }

  const result = userZodSchema.safeParse(req.body);

  if (!result.success) {
    return res.status(400).json(messages.error);
  }

  const { username } = result.data;

  try {
    const user = await githubCall(username);

    if (user.message) {
      return res.status(400).json({ message: "The username not exist" });
    }

    const users = await usersCall();

    return await newUser(res, user, users);
  } catch (error) {
    return res.status(400).json(messages.error);
  }
}
```