From a01f540a296203497126b0828f9311c9c7b2dc5a Mon Sep 17 00:00:00 2001 From: Andrey Chervyakov Date: Sat, 3 Apr 2021 22:25:11 +0600 Subject: [PATCH] Add password protected links --- link/dto.go | 5 ++++ link/entity.go | 1 + link/handlers.go | 5 ++-- link/repository.go | 20 ++++++------- link/service.go | 51 ++++++++++++++++++++++----------- migrations/002_add_password.sql | 13 +++++++++ 6 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 migrations/002_add_password.sql diff --git a/link/dto.go b/link/dto.go index d8b2e68..511f174 100644 --- a/link/dto.go +++ b/link/dto.go @@ -9,18 +9,21 @@ type CreationModel struct { Id string `json:"id"` Name string `json:"name"` RedirectURL string `json:"redirectUrl"` + Password string `json:"password"` } type ResourceModel struct { Id string `json:"id"` Name string `json:"name"` RedirectURL string `json:"redirectUrl"` + Password string `json:"password"` CreationTime int64 `json:"creationTime"` } type UpdateModel struct { Name string `json:"name,omitempty"` RedirectURL string `json:"redirectUrl,omitempty"` + Password string `json:"password"` } func (m *CreationModel) MapModelToEntity() (*Link, error) { @@ -33,6 +36,7 @@ func (m *CreationModel) MapModelToEntity() (*Link, error) { Id: m.Id, Name: m.Name, RedirectURL: *u, + Password: m.Password, CreationTime: time.Now().UTC(), }, nil } @@ -42,6 +46,7 @@ func MapEntityToModel(entity *Link) ResourceModel { Id: entity.Id, Name: entity.Name, RedirectURL: entity.RedirectURL.String(), + Password: entity.Password, CreationTime: entity.CreationTime.Unix(), } } diff --git a/link/entity.go b/link/entity.go index 50f6521..343743e 100644 --- a/link/entity.go +++ b/link/entity.go @@ -9,6 +9,7 @@ type Link struct { Id string Name string RedirectURL url.URL + Password string CreationTime time.Time } diff --git a/link/handlers.go b/link/handlers.go index 912372d..be10ada 100644 --- a/link/handlers.go +++ b/link/handlers.go @@ -11,13 +11,14 @@ import ( func redirectHandler(ctx echo.Context, serv Service) error { linkId := ctx.Param("id") + linkPassword := ctx.QueryParam("password") - link, err := serv.GetById(linkId) + redirectUrl, err := serv.AccessLink(linkId, linkPassword) if err != nil { return err } - return ctx.Redirect(http.StatusSeeOther, link.RedirectURL.String()) + return ctx.Redirect(http.StatusSeeOther, redirectUrl.String()) } func creationHandler(ctx echo.Context, serv Service) error { diff --git a/link/repository.go b/link/repository.go index 8751b3a..f8149d6 100644 --- a/link/repository.go +++ b/link/repository.go @@ -34,12 +34,12 @@ func (r *PgRepository) Save(link *Link) error { defer cancel() sql := ` - INSERT INTO links (id, name, redirect_url, creation_time) - VALUES ($1, $2, $3, $4::timestamp) + INSERT INTO links (id, name, redirect_url, password, creation_time) + VALUES ($1, $2, $3, $4, $5::timestamp) ` database.LogPoolState(r.pool, "Saving link") - _, err := r.pool.Exec(ctx, sql, link.Id, link.Name, link.RedirectURL.String(), link.CreationTime.Format("2006-01-02 15:04:05")) + _, err := r.pool.Exec(ctx, sql, link.Id, link.Name, link.RedirectURL.String(), link.Password, link.CreationTime.Format("2006-01-02 15:04:05")) if err != nil { return err } @@ -52,7 +52,7 @@ func (r *PgRepository) FindById(id string) (*Link, error) { defer cancel() sql := ` - SELECT id, name, redirect_url, creation_time + SELECT id, name, redirect_url, password, creation_time FROM links WHERE id = $1 ` @@ -82,7 +82,7 @@ func (r *PgRepository) GetAll(limit int, offset int) (Links, error) { defer cancel() sql := ` - SELECT id, name, redirect_url, creation_time + SELECT id, name, redirect_url, password, creation_time FROM links LIMIT $1 OFFSET $2 @@ -122,12 +122,12 @@ func (r *PgRepository) Update(link *Link) error { sql := ` UPDATE links - SET name = $1, redirect_url = $2 - WHERE id = $3 + SET name = $1, redirect_url = $2, password = $3 + WHERE id = $4 ` database.LogPoolState(r.pool, "Updating link") - _, err := r.pool.Exec(ctx, sql, link.Name, link.RedirectURL.String(), link.Id) + _, err := r.pool.Exec(ctx, sql, link.Name, link.RedirectURL.String(), link.Password, link.Id) if err != nil { return err } @@ -159,11 +159,11 @@ func mapRowToEntity(r interface{}) (*Link, error) { switch v := r.(type) { case pgx.Row: - if err := v.Scan(&entity.Id, &entity.Name, &urlStr, &t); err != nil { + if err := v.Scan(&entity.Id, &entity.Name, &urlStr, &entity.Password, &t); err != nil { return nil, err } case pgx.Rows: - if err := v.Scan(&entity.Id, &entity.Name, &urlStr, &t); err != nil { + if err := v.Scan(&entity.Id, &entity.Name, &urlStr, &entity.Password, &t); err != nil { return nil, err } default: diff --git a/link/service.go b/link/service.go index b35d7d5..5c4b092 100644 --- a/link/service.go +++ b/link/service.go @@ -3,11 +3,15 @@ package link import ( apperrors "cgnolink/errors" "github.com/patrickmn/go-cache" + "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) Update(data *Link) error DeleteById(id string) error @@ -18,6 +22,21 @@ type PgService struct { 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 == "" || link.Password != password { + return nil, linkNotFoundError + } + } + + return &link.RedirectURL, nil +} + func NewService(rep Repository) Service { return &PgService{ rep: rep, @@ -25,8 +44,8 @@ func NewService(rep Repository) Service { } } -func (s *PgService) Create(link *Link) error { - existingLink, err := s.rep.FindById(link.Id) +func (service *PgService) Create(link *Link) error { + existingLink, err := service.rep.FindById(link.Id) if err != nil { return apperrors.UnknownError{Err: err} } @@ -35,36 +54,36 @@ func (s *PgService) Create(link *Link) error { return apperrors.AlreadyExistsError{Message: "Link with given ID already exists."} } - if err = s.rep.Save(link); err != nil { + if err = service.rep.Save(link); err != nil { return apperrors.UnknownError{Err: err} } return nil } -func (s *PgService) GetById(id string) (*Link, error) { - if v, found := s.cache.Get(id); found { +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 := s.rep.FindById(id) + link, err := service.rep.FindById(id) if err != nil { return nil, apperrors.UnknownError{Err: err} } if link == nil { - return nil, apperrors.NotFoundError{Message: "Link with given ID was not found."} + return nil, linkNotFoundError } - s.cache.Set(id, link, cache.DefaultExpiration) + service.cache.Set(id, link, cache.DefaultExpiration) return link, nil } -func (s *PgService) GetAll(limit int, offset int) (Links, error) { - links, err := s.rep.GetAll(limit, offset) +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} } @@ -72,20 +91,20 @@ func (s *PgService) GetAll(limit int, offset int) (Links, error) { return links, nil } -func (s *PgService) Update(data *Link) error { - if err := s.rep.Update(data); err != nil { +func (service *PgService) Update(data *Link) error { + if err := service.rep.Update(data); err != nil { return apperrors.UnknownError{Err: err} } + service.cache.Delete(data.Id) return nil } -func (s *PgService) DeleteById(id string) error { - s.cache.Delete(id) - - if err := s.rep.DeleteById(id); err != 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 } diff --git a/migrations/002_add_password.sql b/migrations/002_add_password.sql new file mode 100644 index 0000000..715fffb --- /dev/null +++ b/migrations/002_add_password.sql @@ -0,0 +1,13 @@ +ALTER TABLE links +ADD COLUMN password varchar; + +UPDATE links +SET password = ''; + +ALTER TABLE links +ALTER COLUMN password SET not null; + +---- create above / drop below ---- + +ALTER TABLE links +DROP COLUMN password;