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:
2026-04-29 16:00:49 +08:00
parent 674709c86b
commit eafb157174
@@ -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
}