Add existing codebase

This commit is contained in:
Andrey Chervyakov 2021-03-13 01:43:08 +06:00
commit c23a68347b
16 changed files with 807 additions and 0 deletions

26
link/dto.go Normal file
View file

@ -0,0 +1,26 @@
package link
import (
"net/url"
"time"
)
type CreationModel struct {
Id string `json:"id"`
Name string `json:"name"`
RedirectURL string `json:"redirectUrl"`
}
func (m *CreationModel) MapModelToEntity() (*Link, error) {
u, err := url.Parse(m.RedirectURL)
if err != nil {
return nil, err
}
return &Link{
Id: m.Id,
Name: m.Name,
RedirectURL: *u,
CreationTime: time.Now().UTC(),
}, nil
}

13
link/entity.go Normal file
View file

@ -0,0 +1,13 @@
package link
import (
"net/url"
"time"
)
type Link struct {
Id string
Name string
RedirectURL url.URL
CreationTime time.Time
}

63
link/handlers.go Normal file
View file

@ -0,0 +1,63 @@
package link
import (
"encoding/json"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/labstack/echo/v4"
"net/http"
)
func creationHandler(c echo.Context, pool *pgxpool.Pool) error {
var model CreationModel
if err := json.NewDecoder(c.Request().Body).Decode(&model); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid data format.")
}
entity, err := model.MapModelToEntity()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid URL.")
}
rep := PgRepository{pool: pool}
link, err := rep.FindById(entity.Id)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError)
}
if link != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Link with given ID already exists.")
}
if err = rep.Save(entity); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError)
}
return c.NoContent(http.StatusCreated)
}
func redirectHandler(c echo.Context, pool *pgxpool.Pool) error {
linkId := c.Param("id")
rep := PgRepository{pool: pool}
link, err := rep.FindById(linkId)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError)
}
if link == nil {
return echo.NewHTTPError(http.StatusNotFound, "Link with given ID was not found.")
}
return c.Redirect(http.StatusSeeOther, link.RedirectURL.String())
}
func AddHandlers(s *echo.Echo, pool *pgxpool.Pool) {
s.POST("", func(ctx echo.Context) error {
return creationHandler(ctx, pool)
})
s.GET("/:id", func(ctx echo.Context) error {
return redirectHandler(ctx, pool)
})
}

95
link/repository.go Normal file
View file

@ -0,0 +1,95 @@
package link
import (
"context"
"errors"
"github.com/jackc/pgtype"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
"net/url"
)
type Repository interface {
Save(link *Link) error
FindById(id string) (*Link, error)
DeleteById(id string) error
}
type PgRepository struct {
pool *pgxpool.Pool
}
func (r *PgRepository) Save(link *Link) error {
ctx := context.Background()
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
sql := `
INSERT INTO links (id, name, redirect_url, creation_time)
VALUES ($1, $2, $3, $4::timestamp)
`
_, err = tx.Exec(ctx, sql, link.Id, link.Name, link.RedirectURL.String(), link.CreationTime.Format("2006-01-02 15:04:05"))
if err != nil {
_ = tx.Rollback(ctx)
return err
}
if err = tx.Commit(ctx); err != nil {
return err
}
return nil
}
func (r *PgRepository) FindById(id string) (*Link, error) {
ctx := context.Background()
tx, err := r.pool.Begin(ctx)
if err != nil {
return nil, err
}
sql := `
SELECT id, name, redirect_url, creation_time
FROM links
WHERE id = $1
`
entity, err := mapRowToEntity(tx.QueryRow(ctx, sql, id))
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
} else {
_ = tx.Rollback(ctx)
return nil, err
}
}
if err = tx.Commit(ctx); err != nil {
return nil, err
}
return entity, nil
}
func mapRowToEntity(r pgx.Row) (*Link, error) {
var entity Link
var urlStr string
var t pgtype.Timestamp
if err := r.Scan(&entity.Id, &entity.Name, &urlStr, &t); err != nil {
return nil, err
}
u, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
entity.RedirectURL = *u
entity.CreationTime = t.Time
return &entity, nil
}