goshort/internal/service/short/shortservice.go
Gustavo Maronato f06e933a80
Some checks failed
Build / build (push) Failing after 6m2s
remove unused go stuff
2023-08-30 21:33:57 -03:00

176 lines
4.2 KiB
Go

package shortservice
import (
"context"
"errors"
"fmt"
"net/url"
"regexp"
"git.maronato.dev/maronato/goshort/internal/errs"
"git.maronato.dev/maronato/goshort/internal/storage"
"git.maronato.dev/maronato/goshort/internal/storage/models"
randomutil "git.maronato.dev/maronato/goshort/internal/util/random"
)
const (
// DefaultShortLength is the default length of the short URL.
DefaultShortLength = 5
// MinShortLength is the minimum length of the short URL.
MinShortLength = 4
// MaxShortLength is the maximum length of the short URL.
MaxShortLength = 20
)
type ShortService struct {
db storage.Storage
}
func NewShortService(db storage.Storage) *ShortService {
return &ShortService{db: db}
}
func (s *ShortService) FindShort(ctx context.Context, name string) (*models.Short, error) {
// Check if the short is valid
err := ShortNameIsValid(name)
if err != nil {
return &models.Short{}, fmt.Errorf("could not validate short: %w", err)
}
// Get the short from storage
short, err := s.db.FindShort(ctx, name)
if err != nil {
return short, fmt.Errorf("could not get short from storage: %w", err)
}
return short, nil
}
func (s *ShortService) FindShortByID(ctx context.Context, id string) (*models.Short, error) {
// Check if the ID is valid
if !models.LooksLikeID(id) {
return &models.Short{}, errs.ErrInvalidShort
}
// Get the short from storage
short, err := s.db.FindShortByID(ctx, id)
if err != nil {
return short, fmt.Errorf("could not get short from storage: %w", err)
}
return short, nil
}
// ShortenURL shortens a URL with a random short name.
func (s *ShortService) generateUnusedShortName(ctx context.Context) (string, error) {
// Generate a random short URL
for {
shortName := randomutil.GenerateRandomShort(DefaultShortLength)
if _, err := s.FindShort(ctx, shortName); err != nil {
if errors.Is(err, errs.ErrShortDoesNotExist) {
return shortName, nil
}
return "", fmt.Errorf("failed to generate unused short name: %w", err)
}
}
}
func (s *ShortService) Shorten(ctx context.Context, short *models.Short) (*models.Short, error) {
// Check if the short is empty
if short.Name == "" {
// Generate a random short name
newName, err := s.generateUnusedShortName(ctx)
if err != nil {
return &models.Short{}, fmt.Errorf("failed to shorten URL: %w", err)
}
short.Name = newName
}
// make sure the short is valid
err := ShortIsValid(short)
if err != nil {
return &models.Short{}, fmt.Errorf("could not validate short: %w", err)
}
// Save the short in storage
newShort, err := s.db.CreateShort(ctx, short)
if err != nil {
return &models.Short{}, fmt.Errorf("could not save short in storage: %w", err)
}
return newShort, nil
}
func (s *ShortService) ListShorts(ctx context.Context, user *models.User) ([]*models.Short, error) {
// Find shorts
shorts, err := s.db.ListShorts(ctx, user)
if err != nil {
return shorts, fmt.Errorf("could not get shorts from storage: %w", err)
}
return shorts, nil
}
func (s *ShortService) DeleteShort(ctx context.Context, short *models.Short) error {
// Delete short
err := s.db.DeleteShort(ctx, short)
if err != nil {
return fmt.Errorf("failed to delete short: %w", err)
}
return nil
}
var shortRegex = regexp.MustCompile(
fmt.Sprintf("^%s$",
fmt.Sprintf("[a-zA-Z0-9_-]{%d,%d}", MinShortLength, MaxShortLength)),
)
func ShortNameIsValid(name string) error {
if !shortRegex.MatchString(name) {
return errs.Errorf(
fmt.Sprintf(
"short must use only letters, numbers, underscores and dashes, and be between %d and %d characters long",
MinShortLength,
MaxShortLength,
),
errs.ErrInvalidShort,
)
}
return nil
}
func ShortURLIsValid(shortURL string) error {
parsedURL, err := url.ParseRequestURI(shortURL)
if err != nil {
return errs.Errorf("invalid URL", errs.ErrInvalidShort)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return errs.Errorf("invalid URL scheme", errs.ErrInvalidShort)
}
return nil
}
// ShortIsValid checks if the short is valid.
func ShortIsValid(short *models.Short) error {
// Check if the short name is valid
err := ShortNameIsValid(short.Name)
if err != nil {
return err
}
// Check if the URL is valid
err = ShortURLIsValid(short.URL)
if err != nil {
return err
}
return nil
}