diff --git a/server/internal/ops/repository/site_domain_mapping.go b/server/internal/ops/repository/site_domain_mapping.go new file mode 100644 index 0000000..c9e76a1 --- /dev/null +++ b/server/internal/ops/repository/site_domain_mapping.go @@ -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 +}