Ctrl K
ComunidadGuías…

main.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/galanjabal3/linkas/docs"
	"github.com/galanjabal3/linkas/internal/handler"
	"github.com/galanjabal3/linkas/internal/middleware"
	"github.com/galanjabal3/linkas/internal/repository"
	"github.com/galanjabal3/linkas/internal/service"
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
	"github.com/joho/godotenv"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
)

// @title           Linkas API
// @version         1.0
// @description     A simple, fast URL shortener API built with Go + Gin + PostgreSQL.
// @termsOfService  http://swagger.io/terms/

// @contact.name   Galan Jabal
// @contact.url    https://github.com/galanjabal3
// @contact.email  galanjabal3@gmail.com

// @license.name  MIT
// @license.url   https://opensource.org/licenses/MIT

// @host      localhost:8080
// @BasePath  /

func main() {
	if err := godotenv.Load(); err != nil {
		log.Println("No .env file found, using environment variables")
	}

	port := getEnv("PORT", "8080")
	databaseURL := getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/linkas?sslmode=disable")
	baseURL := getEnv("BASE_URL", "http://localhost:"+port)

	db, err := sqlx.Connect("postgres", databaseURL)
	if err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}
	defer db.Close()

	db.SetMaxOpenConns(25)
	db.SetMaxIdleConns(5)
	db.SetConnMaxLifetime(5 * time.Minute)

	if err := db.Ping(); err != nil {
		log.Fatalf("Failed to ping database: %v", err)
	}
	log.Println("Connected to database")

	repo := repository.NewURLRepository(db)
	svc := service.NewURLService(repo)
	h := handler.NewURLHandler(svc, baseURL)

	limiter := middleware.NewRateLimiter(30, time.Minute)

	router := gin.Default()

	router.Use(middleware.RateLimit(limiter))

	// --- Swagger ---
	router.GET("/doc.json", func(c *gin.Context) {
		c.Header("Content-Type", "application/json")
		c.String(http.StatusOK, docs.SwaggerInfo.ReadDoc())
	})
	router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("/doc.json")))

	// --- API routes ---
	api := router.Group("/api")
	{
		api.POST("/shorten", h.Shorten)
		api.GET("/stats/*slug", h.GetStats)
		api.GET("/qrcode/*slug", h.GetQRCode)
		api.DELETE("/*slug", h.Delete)
		api.GET("/health", h.Health)
	}

	// --- Catch-all for slug redirect (using NoRoute) ---
	router.NoRoute(func(c *gin.Context) {
		path := c.Request.URL.Path
		// Skip known prefixes
		if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "/swagger") || strings.HasPrefix(path, "/doc.json") {
			c.JSON(http.StatusNotFound, gin.H{"error": "not_found"})
			return
		}
		// Strip leading slash
		slug := strings.TrimPrefix(path, "/")
		h.RedirectBySlug(c, slug)
	})

	log.Printf("Linkas running on port %s", port)
	log.Printf("Swagger UI: http://localhost:%s/swagger/index.html", port)
	if err := router.Run(":" + port); err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
}

func getEnv(key, fallback string) string {
	if value, ok := os.LookupEnv(key); ok {
		return value
	}
	return fallback
}

func init() {
	const (
		colorCyan  = "\033[36m"
		colorReset = "\033[0m"
		colorDim   = "\033[2m"
	)

	fmt.Println(colorCyan + `
    ___       __             
   / (_)___  / /______ ______
  / / / __ \/ //_/ __ ` + "`" + `/ ___/
 / / / / / / ,< / /_/ (__  ) 
/_/_/_/ /_/_/|_|\__,_/____/  
` + colorReset)

	fmt.Println(colorDim + "  ────────────────────────────" + colorReset)
	fmt.Println("  🔗  " + colorCyan + "URL Shortener API" + colorReset + colorDim + " v1.0" + colorReset)
	fmt.Println(colorDim + "  ────────────────────────────" + colorReset)
	fmt.Println()
}