link-go/link/repository.go
2021-03-16 13:24:44 +06:00

172 lines
3.3 KiB
Go

package link
import (
"context"
"errors"
"github.com/jackc/pgtype"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
"net/url"
"time"
)
const defaultContextTimeout = 10 * time.Second
type Repository interface {
Save(link *Link) error
FindById(id string) (*Link, error)
FindAll(limit int, offset int) (Links, error)
Update(link *Link) error
DeleteById(id string) error
}
type PgRepository struct {
pool *pgxpool.Pool
}
func (r *PgRepository) Save(link *Link) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
sql := `
INSERT INTO links (id, name, redirect_url, creation_time)
VALUES ($1, $2, $3, $4::timestamp)
`
_, err := r.pool.Exec(ctx, sql, link.Id, link.Name, link.RedirectURL.String(), link.CreationTime.Format("2006-01-02 15:04:05"))
if err != nil {
return err
}
return nil
}
func (r *PgRepository) FindById(id string) (*Link, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
sql := `
SELECT id, name, redirect_url, creation_time
FROM links
WHERE id = $1
`
entity, err := mapRowToEntity(r.pool.QueryRow(ctx, sql, id))
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
} else {
return nil, err
}
}
return entity, nil
}
func (r *PgRepository) FindAll(limit int, offset int) (Links, error) {
if limit < 0 {
return nil, errors.New("limit can't be negative")
}
if offset < 0 {
return nil, errors.New("offset can't be negative")
}
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
sql := `
SELECT id, name, redirect_url, creation_time
FROM links
LIMIT $1
OFFSET $2
`
rows, err := r.pool.Query(ctx, sql, limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
links := make(Links, 0)
for rows.Next() {
link, err := mapRowToEntity(rows)
if err != nil {
return nil, err
}
links = append(links, link)
}
if err = rows.Err(); err != nil {
return nil, err
}
return links, nil
}
func (r *PgRepository) Update(link *Link) error {
if link.Id == "" {
return errors.New("link ID must not be empty")
}
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
sql := `
UPDATE links
SET name = $1, redirect_url = $2
WHERE id = $3
`
_, err := r.pool.Exec(ctx, sql, link.Name, link.RedirectURL.String(), link.Id)
if err != nil {
return err
}
return nil
}
func (r *PgRepository) DeleteById(id string) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
defer cancel()
sql := `
DELETE FROM links WHERE id = $1
`
_, err := r.pool.Exec(ctx, sql, id)
if err != nil {
return err
}
return nil
}
func mapRowToEntity(r interface{}) (*Link, error) {
var entity Link
var urlStr string
var t pgtype.Timestamp
switch v := r.(type) {
case pgx.Row:
if err := v.Scan(&entity.Id, &entity.Name, &urlStr, &t); err != nil {
return nil, err
}
case pgx.Rows:
if err := v.Scan(&entity.Id, &entity.Name, &urlStr, &t); err != nil {
return nil, err
}
default:
return nil, errors.New("unsupported type")
}
u, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
entity.RedirectURL = *u
entity.CreationTime = t.Time
return &entity, nil
}