feat(ops/site-mappings): add repository CRUD for site_domain_mappings
Add SiteDomainMappingRepository with list/get/create/update/set-active/ delete to back the ops console management UI on top of the now-global site_domain_mappings table.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/ops/domain"
|
||||
)
|
||||
|
||||
var ErrSiteDomainMappingNotFound = errors.New("site domain mapping not found")
|
||||
|
||||
type SiteDomainMappingRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewSiteDomainMappingRepository(pool *pgxpool.Pool) *SiteDomainMappingRepository {
|
||||
return &SiteDomainMappingRepository{pool: pool}
|
||||
}
|
||||
|
||||
const siteDomainMappingColumns = `id, registrable_domain, site_key, site_name, is_active, created_at, updated_at`
|
||||
|
||||
type SiteDomainMappingFilter struct {
|
||||
Keyword string
|
||||
IsActive *bool
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type CreateSiteDomainMappingInput struct {
|
||||
RegistrableDomain string
|
||||
SiteKey string
|
||||
SiteName string
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
type UpdateSiteDomainMappingInput struct {
|
||||
RegistrableDomain string
|
||||
SiteKey string
|
||||
SiteName string
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
func scanSiteDomainMapping(row pgx.Row) (*domain.SiteDomainMapping, error) {
|
||||
var item domain.SiteDomainMapping
|
||||
err := row.Scan(
|
||||
&item.ID,
|
||||
&item.RegistrableDomain,
|
||||
&item.SiteKey,
|
||||
&item.SiteName,
|
||||
&item.IsActive,
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrSiteDomainMappingNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (r *SiteDomainMappingRepository) List(ctx context.Context, f SiteDomainMappingFilter) ([]domain.SiteDomainMapping, int64, error) {
|
||||
args := []any{}
|
||||
where := "1=1"
|
||||
|
||||
if f.Keyword != "" {
|
||||
args = append(args, "%"+f.Keyword+"%")
|
||||
where += fmt.Sprintf(" AND (registrable_domain ILIKE $%d OR site_key ILIKE $%d OR site_name ILIKE $%d)",
|
||||
len(args), len(args), len(args))
|
||||
}
|
||||
if f.IsActive != nil {
|
||||
args = append(args, *f.IsActive)
|
||||
where += fmt.Sprintf(" AND is_active = $%d", len(args))
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := r.pool.QueryRow(ctx, "SELECT COUNT(*) FROM site_domain_mappings WHERE "+where, args...).Scan(&total); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
limit := f.Limit
|
||||
if limit <= 0 || limit > 200 {
|
||||
limit = 20
|
||||
}
|
||||
offset := f.Offset
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
args = append(args, limit, offset)
|
||||
limitArg := len(args) - 1
|
||||
offsetArg := len(args)
|
||||
|
||||
rows, err := r.pool.Query(ctx, fmt.Sprintf(`
|
||||
SELECT %s
|
||||
FROM site_domain_mappings
|
||||
WHERE %s
|
||||
ORDER BY is_active DESC, registrable_domain ASC, id DESC
|
||||
LIMIT $%d OFFSET $%d`, siteDomainMappingColumns, where, limitArg, offsetArg), args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]domain.SiteDomainMapping, 0, limit)
|
||||
for rows.Next() {
|
||||
item, scanErr := scanSiteDomainMapping(rows)
|
||||
if scanErr != nil {
|
||||
return nil, 0, scanErr
|
||||
}
|
||||
items = append(items, *item)
|
||||
}
|
||||
return items, total, rows.Err()
|
||||
}
|
||||
|
||||
func (r *SiteDomainMappingRepository) GetByID(ctx context.Context, id int64) (*domain.SiteDomainMapping, error) {
|
||||
return scanSiteDomainMapping(r.pool.QueryRow(ctx, `
|
||||
SELECT `+siteDomainMappingColumns+`
|
||||
FROM site_domain_mappings
|
||||
WHERE id = $1`, id))
|
||||
}
|
||||
|
||||
func (r *SiteDomainMappingRepository) Create(ctx context.Context, in CreateSiteDomainMappingInput) (*domain.SiteDomainMapping, error) {
|
||||
return scanSiteDomainMapping(r.pool.QueryRow(ctx, `
|
||||
INSERT INTO site_domain_mappings (registrable_domain, site_key, site_name, is_active)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING `+siteDomainMappingColumns,
|
||||
in.RegistrableDomain, in.SiteKey, in.SiteName, in.IsActive))
|
||||
}
|
||||
|
||||
func (r *SiteDomainMappingRepository) Update(ctx context.Context, id int64, in UpdateSiteDomainMappingInput) (*domain.SiteDomainMapping, error) {
|
||||
return scanSiteDomainMapping(r.pool.QueryRow(ctx, `
|
||||
UPDATE site_domain_mappings
|
||||
SET registrable_domain = $1,
|
||||
site_key = $2,
|
||||
site_name = $3,
|
||||
is_active = $4,
|
||||
updated_at = NOW()
|
||||
WHERE id = $5
|
||||
RETURNING `+siteDomainMappingColumns,
|
||||
in.RegistrableDomain, in.SiteKey, in.SiteName, in.IsActive, id))
|
||||
}
|
||||
|
||||
func (r *SiteDomainMappingRepository) SetActive(ctx context.Context, id int64, active bool) (*domain.SiteDomainMapping, error) {
|
||||
return scanSiteDomainMapping(r.pool.QueryRow(ctx, `
|
||||
UPDATE site_domain_mappings
|
||||
SET is_active = $1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
RETURNING `+siteDomainMappingColumns, active, id))
|
||||
}
|
||||
|
||||
func (r *SiteDomainMappingRepository) Delete(ctx context.Context, id int64) error {
|
||||
cmd, err := r.pool.Exec(ctx, `DELETE FROM site_domain_mappings WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.RowsAffected() == 0 {
|
||||
return ErrSiteDomainMappingNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user