Link Manager

¡Lo publico puede ser confidencial!

¡Hola a todos!
Hoy quiero compartir con vosotros un proyecto en el que he estado trabajando recientemente.

Repositorio Github.

Sitio web demostración

Se trata de una aplicación de gestión de enlaces encriptados construida con Next.js.

En este blog, les contaré sobre los detalles técnicos del proyecto, incluyendo la estructura del proyecto, los endpoints de la API y cómo funciona todo. Además, compartiré algunos datos curiosos y experiencias personales durante el desarrollo.

La estructura del proyecto es bastante estándar para una aplicación Next.js, pero con algunas particularidades interesantes. Aquí les dejo un vistazo rápido:

Vamos por partes, para empezar, la estructura del proyecto.

.env
.eslintrc.json
.gitignore
.next/
package.json
public/
src/
  app/
    components/
    lib/
  api/
    links/
      route.ts
tailwind.config.ts
tsconfig.json

Archivos Clave

  • src/app/page.tsx: Este archivo contiene el componente principal de la aplicación, donde se gestionan los enlaces encriptados.
  • src/app/api/links/route.ts: Aquí se definen los endpoints de la API para gestionar los enlaces.
  • tailwind.config.ts y postcss.config.mjs: Configuración de Tailwind CSS para el estilo de la aplicación.
  • package.json: Contiene las dependencias y scripts del proyecto.

Componentes Principales

  • EncryptedLinkSaver

Este componente es el corazón de la aplicación. Permite a los usuarios guardar, ver y eliminar enlaces encriptados. Aquí hay un fragmento del código:

import React, { useState, useEffect } from 'react'
import CryptoJS from 'crypto-js'
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"
import { Trash2, Eye, EyeOff, Loader2 } from 'lucide-react'
import { useToast } from "@/components/ui/use-toast"

interface EncryptedLink {
  id: number
  encrypted_title: string
  encrypted_url: string
}

export default function EncryptedLinkSaver() {
  // Lógica del componente
}
  • AlertDialog

Utilizamos el componente AlertDialog para confirmar la eliminación de un enlace. Aquí hay un ejemplo de cómo se usa:

<AlertDialog>
  <AlertDialogTrigger asChild>
    <Button variant="outline">
      <Trash2 className="h-4 w-4" />
    </Button>
  </AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you sure?</AlertDialogTitle>
      <AlertDialogDescription>
        This action cannot be undone. This will permanently delete the link.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction onClick={() => deleteLink(link.id)}>
        Delete
      </AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

Endpoints de la API

GET /api/links

Este endpoint obtiene todos los enlaces de un usuario específico.

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const userId = searchParams.get('userId')

  if (!userId) {
    return NextResponse.json({ error: 'User ID is required' }, { status: 400 })
  }

  try {
    const { rows } = await sql`
      SELECT * FROM links 
      WHERE user_id = ${userId} 
      ORDER BY id DESC
    `
    return NextResponse.json(rows)
  } catch (error) {
    console.error('Error fetching links:', error)
    return NextResponse.json({ error: 'Failed to fetch links' }, { status: 500 })
  }
}

POST /api/links

Este endpoint permite agregar un nuevo enlace encriptado.

export async function POST(request: Request) {
  const { userId, encryptedTitle, encryptedUrl } = await request.json()

  if (!userId || !encryptedTitle || !encryptedUrl) {
    return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
  }

  try {
    const { rows } = await sql`
      INSERT INTO links (user_id, encrypted_title, encrypted_url)
      VALUES (${userId}, ${encryptedTitle}, ${encryptedUrl})
      RETURNING id
    `
    return NextResponse.json({ id: rows[0].id, message: 'Link added successfully' })
  } catch (error) {
    console.error('Error adding link:', error)
    return NextResponse.json({ error: 'Failed to add link' }, { status: 500 })
  }
}

DELETE /api/links

Este endpoint elimina un enlace encriptado.

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url)
  const id = searchParams.get('id')
  const userId = searchParams.get('userId')

  if (!id || !userId) {
    return NextResponse.json({ error: 'Link ID and User ID are required' }, { status: 400 })
  }

  try {
    await sql`
      DELETE FROM links 
      WHERE id = ${id} AND user_id = ${userId}
    `
    return NextResponse.json({ message: 'Link deleted successfully' })
  } catch (error) {
    console.error('Error deleting link:', error)
    return NextResponse.json({ error: 'Failed to delete link' }, { status: 500 })
  }
}

Conclusión

Este proyecto ha sido una experiencia increíble. Trabajar con Next.js y Tailwind CSS ha sido muy gratificante, y la funcionalidad de encriptación añade una capa adicional de seguridad que me hace sentir orgulloso del resultado final.

Además, he aprendido mucho sobre la gestión de estados y la interacción con bases de datos PostgreSQL para almacenar los links encriptados.

Espero que este blog les haya dado una buena visión general del proyecto y cómo funciona.

¡Gracias por leer! Si tienen alguna pregunta o comentario, no duden en contactarme. ¡Hasta la próxima!