package link import ( apperrors "cgnolink/errors" "github.com/patrickmn/go-cache" "golang.org/x/crypto/bcrypt" "net/url" ) var linkNotFoundError = apperrors.NotFoundError{Message: "Link with given ID was not found."} type Service interface { Create(link *Link) error GetById(id string) (*Link, error) AccessLink(id string, password string) (*url.URL, error) GetAll(limit int, offset int) (Links, error) UpdateById( id string, data struct { Name string Password string RedirectURL *url.URL }, ) error DeleteById(id string) error } type PgService struct { rep Repository cache *cache.Cache } func (service *PgService) AccessLink(id string, password string) (*url.URL, error) { link, err := service.GetById(id) if err != nil { return nil, err } if link.Password != "" { if password == "" || bcrypt.CompareHashAndPassword([]byte(link.Password), []byte(password)) != nil { return nil, linkNotFoundError } } return &link.RedirectURL, nil } func NewService(rep Repository) Service { return &PgService{ rep: rep, cache: cache.New(cache.NoExpiration, 0), } } func (service *PgService) Create(link *Link) error { existingLink, err := service.rep.FindById(link.Id) if err != nil { return apperrors.UnknownError{Err: err} } if existingLink != nil { return apperrors.AlreadyExistsError{Message: "Link with given ID already exists."} } if link.Password != "" { hashedPassword, err := HashPassword(link.Password) if err != nil { return err } link.Password = hashedPassword } if err = service.rep.Save(link); err != nil { return apperrors.UnknownError{Err: err} } return nil } func (service *PgService) GetById(id string) (*Link, error) { if v, found := service.cache.Get(id); found { if link, ok := v.(*Link); ok { return link, nil } } link, err := service.rep.FindById(id) if err != nil { return nil, apperrors.UnknownError{Err: err} } if link == nil { return nil, linkNotFoundError } service.cache.Set(id, link, cache.DefaultExpiration) return link, nil } func (service *PgService) GetAll(limit int, offset int) (Links, error) { links, err := service.rep.GetAll(limit, offset) if err != nil { return nil, apperrors.UnknownError{Err: err} } return links, nil } func (service *PgService) UpdateById( id string, data struct { Name string Password string RedirectURL *url.URL }, ) error { link, err := service.GetById(id) if err != nil { return err } hasChanges := false switch { case data.Name != "": link.Name = data.Name hasChanges = true case data.RedirectURL != nil: link.RedirectURL = *data.RedirectURL hasChanges = true case data.Password != "": hashedPw, err := HashPassword(data.Password) if err != nil { return err } link.Password = hashedPw hasChanges = true } if hasChanges { if err := service.rep.Update(link); err != nil { return apperrors.UnknownError{Err: err} } service.cache.Delete(link.Id) } return nil } func (service *PgService) DeleteById(id string) error { if err := service.rep.DeleteById(id); err != nil { return apperrors.UnknownError{Err: err} } service.cache.Delete(id) return nil } func HashPassword(password string) (string, error) { hashedPw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", apperrors.UnknownError{Err: err} } return string(hashedPw), nil }