182 lines
3.8 KiB
Go
182 lines
3.8 KiB
Go
package link
|
|
|
|
import (
|
|
"cgnolink/pkg/cgnolink/database"
|
|
"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)
|
|
GetAll(limit int, offset int) (Links, error)
|
|
Update(link *Link) error
|
|
DeleteById(id string) error
|
|
}
|
|
|
|
type PgRepository struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
func NewRepository(pool *pgxpool.Pool) Repository {
|
|
return &PgRepository{pool: 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, 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.Password, 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, password, creation_time
|
|
FROM links
|
|
WHERE id = $1
|
|
`
|
|
|
|
database.LogPoolState(r.pool, "Finding link by ID")
|
|
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) GetAll(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, password, creation_time
|
|
FROM links
|
|
LIMIT $1
|
|
OFFSET $2
|
|
`
|
|
|
|
database.LogPoolState(r.pool, "Getting all links")
|
|
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, password = $3
|
|
WHERE id = $4
|
|
`
|
|
|
|
database.LogPoolState(r.pool, "Updating link")
|
|
_, err := r.pool.Exec(ctx, sql, link.Name, link.RedirectURL.String(), link.Password, 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
|
|
`
|
|
|
|
database.LogPoolState(r.pool, "Deleting link")
|
|
_, 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, &entity.Password, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
case pgx.Rows:
|
|
if err := v.Scan(&entity.Id, &entity.Name, &urlStr, &entity.Password, &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
|
|
}
|