first commit
This commit is contained in:
36
internal/cache/cache.go
vendored
Normal file
36
internal/cache/cache.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ferdzo/ferurl/utils"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
type Cache struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func NewRedisClient(config utils.RedisConfig) (*Cache, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: utils.RedisUrl(),
|
||||
Password: config.Password,
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Cache{client: client}, nil
|
||||
}
|
||||
|
||||
func (c *Cache) Get(key string) (string, error) {
|
||||
return c.client.Get(ctx, key).Result()
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, value string) error {
|
||||
return c.client.Set(ctx, key, value, 0).Err()
|
||||
}
|
||||
51
internal/db/database.go
Normal file
51
internal/db/database.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ferdzo/ferurl/utils"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
client *pgx.Conn
|
||||
}
|
||||
|
||||
func NewDatabaseClient(config utils.DatabaseConfig) (*Database, error) {
|
||||
conn, err := pgx.Connect(context.Background(), utils.DatabaseUrl())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Database{client: conn}, nil
|
||||
}
|
||||
|
||||
func (d *Database) InsertNewURL(shorturl string, url string) error {
|
||||
timeNow := time.Now()
|
||||
_, err := d.client.Exec(context.Background(), "INSERT INTO urls (shorturl, url, created_at) VALUES ($1, $2, $3)", shorturl, url, timeNow)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert URL into database: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) DeleteURL(shorturl string) error {
|
||||
_, err := d.client.Exec(context.Background(), "DELETE FROM urls WHERE shorturl = $1", shorturl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete URL from database: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) GetURL(shorturl string) (string, error) {
|
||||
var url string
|
||||
err := d.client.QueryRow(context.Background(), "SELECT url FROM urls WHERE shorturl = $1", shorturl).Scan(&url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to retrieve URL from database: %w", err)
|
||||
}
|
||||
return url, nil
|
||||
}
|
||||
7
internal/db/migrations/migrations.go
Normal file
7
internal/db/migrations/migrations.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
func
|
||||
105
internal/shortener/handler.go
Normal file
105
internal/shortener/handler.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package shortener
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ferdzo/ferurl/internal/cache"
|
||||
"github.com/ferdzo/ferurl/internal/db"
|
||||
|
||||
"github.com/ferdzo/ferurl/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
cache *cache.Cache
|
||||
database *db.Database
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewService(redisConfig utils.RedisConfig, databaseConfig utils.DatabaseConfig) (*Service, error) {
|
||||
redisClient, err := cache.NewRedisClient(redisConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
databaseClient, err := db.NewDatabaseClient(databaseConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{cache: redisClient, database: databaseClient}, nil
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) (*Handler, error) {
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("service is nil")
|
||||
}
|
||||
|
||||
return &Handler{service: service}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) CreateShortURL(w http.ResponseWriter, r *http.Request) {
|
||||
longUrl := r.FormValue("url")
|
||||
|
||||
if longUrl == "" {
|
||||
http.Error(w, "Long URL is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !utils.IsValidUrl(longUrl) {
|
||||
http.Error(w, "Invalid URL", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
shortUrl := generateShortUrl(longUrl)
|
||||
|
||||
http.Redirect(w, r, shortUrl, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *Handler) GetUrl(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "key")
|
||||
if id == "" {
|
||||
http.Error(w, "ID is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
shortUrl, err := h.fetchUrl(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, shortUrl, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func generateShortUrl(url string) string {
|
||||
shortUrl := utils.GenerateUrlHash(url)[:7]
|
||||
|
||||
return shortUrl
|
||||
}
|
||||
|
||||
func (h *Handler) fetchUrl(id string) (string, error) {
|
||||
if url, err := h.service.fetchUrlFromCache(id); err == nil {
|
||||
return url, nil
|
||||
}
|
||||
if url, err := h.service.fetchUrlFromDatabase(id); err == nil {
|
||||
return url, nil
|
||||
}
|
||||
return "", fmt.Errorf("URL not found")
|
||||
}
|
||||
|
||||
func (s *Service) fetchUrlFromCache(shortUrl string) (string, error) {
|
||||
if url, err := s.cache.Get(shortUrl); err == nil {
|
||||
return url, nil
|
||||
}
|
||||
return "", fmt.Errorf("URL not found")
|
||||
}
|
||||
|
||||
func (s *Service) fetchUrlFromDatabase(id string) (string, error) {
|
||||
if url, err := s.database.GetURL(id); err == nil {
|
||||
return url, nil
|
||||
}
|
||||
return "", fmt.Errorf("URL not found")
|
||||
}
|
||||
Reference in New Issue
Block a user