Add ServiceCatalogItem type and services CRUD API endpoints (list, create, update, delete). Extend Policy type to include services array with domain, upstream_ip, proxy_ip, and ports metadata. Add ServicesPage component with table view and create/edit modals for managing service definitions. Include service name, domain, proxy, and upstream columns with port parsing logic. Integrate service selection
153 lines
4.3 KiB
Go
153 lines
4.3 KiB
Go
package servicecatalog
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
type Repository interface {
|
|
List(ctx context.Context) ([]Service, error)
|
|
Create(ctx context.Context, input CreateRequest) (Service, error)
|
|
Update(ctx context.Context, serviceID uuid.UUID, input UpdateRequest) (Service, error)
|
|
Delete(ctx context.Context, serviceID uuid.UUID) error
|
|
ByIDs(ctx context.Context, ids []uuid.UUID) ([]Service, error)
|
|
}
|
|
|
|
type PGRepository struct {
|
|
db *pgxpool.Pool
|
|
}
|
|
|
|
func NewPGRepository(db *pgxpool.Pool) *PGRepository {
|
|
return &PGRepository{db: db}
|
|
}
|
|
|
|
func (r *PGRepository) List(ctx context.Context) ([]Service, error) {
|
|
rows, err := r.db.Query(ctx, `
|
|
select id, name, description, domain, host(upstream_ip), host(proxy_ip), ports, is_active
|
|
from services
|
|
where deleted_at is null
|
|
order by name asc
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []Service
|
|
for rows.Next() {
|
|
var item Service
|
|
if err := rows.Scan(&item.ID, &item.Name, &item.Description, &item.Domain, &item.UpstreamIP, &item.ProxyIP, &item.Ports, &item.IsActive); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return items, rows.Err()
|
|
}
|
|
|
|
func (r *PGRepository) Create(ctx context.Context, input CreateRequest) (Service, error) {
|
|
row := r.db.QueryRow(ctx, `
|
|
insert into services (id, name, description, domain, upstream_ip, proxy_ip, ports, is_active)
|
|
values ($1, $2, $3, $4, $5::inet, $6::inet, $7::integer[], coalesce($8, true))
|
|
returning id, name, description, domain, host(upstream_ip), host(proxy_ip), ports, is_active
|
|
`, uuid.New(), input.Name, input.Description, input.Domain, input.UpstreamIP, input.ProxyIP, normalizePorts(input.Ports), input.IsActive)
|
|
|
|
var item Service
|
|
err := row.Scan(&item.ID, &item.Name, &item.Description, &item.Domain, &item.UpstreamIP, &item.ProxyIP, &item.Ports, &item.IsActive)
|
|
return item, err
|
|
}
|
|
|
|
func (r *PGRepository) Update(ctx context.Context, serviceID uuid.UUID, input UpdateRequest) (Service, error) {
|
|
var ports *[]int
|
|
if input.Ports != nil {
|
|
normalized := normalizePorts(input.Ports)
|
|
ports = &normalized
|
|
}
|
|
|
|
row := r.db.QueryRow(ctx, `
|
|
update services
|
|
set
|
|
name = coalesce($2, name),
|
|
description = coalesce($3, description),
|
|
domain = coalesce($4, domain),
|
|
upstream_ip = coalesce($5::inet, upstream_ip),
|
|
proxy_ip = coalesce($6::inet, proxy_ip),
|
|
ports = coalesce($7::integer[], ports),
|
|
is_active = coalesce($8, is_active),
|
|
updated_at = now()
|
|
where id = $1 and deleted_at is null
|
|
returning id, name, description, domain, host(upstream_ip), host(proxy_ip), ports, is_active
|
|
`, serviceID, input.Name, input.Description, input.Domain, input.UpstreamIP, input.ProxyIP, ports, input.IsActive)
|
|
|
|
var item Service
|
|
err := row.Scan(&item.ID, &item.Name, &item.Description, &item.Domain, &item.UpstreamIP, &item.ProxyIP, &item.Ports, &item.IsActive)
|
|
return item, err
|
|
}
|
|
|
|
func (r *PGRepository) Delete(ctx context.Context, serviceID uuid.UUID) error {
|
|
_, err := r.db.Exec(ctx, `
|
|
update services
|
|
set deleted_at = now(), updated_at = now()
|
|
where id = $1 and deleted_at is null
|
|
`, serviceID)
|
|
return err
|
|
}
|
|
|
|
func (r *PGRepository) ByIDs(ctx context.Context, ids []uuid.UUID) ([]Service, error) {
|
|
if len(ids) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
rows, err := r.db.Query(ctx, `
|
|
select id, name, description, domain, host(upstream_ip), host(proxy_ip), ports, is_active
|
|
from services
|
|
where deleted_at is null and id = any($1::uuid[])
|
|
order by name asc
|
|
`, ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []Service
|
|
for rows.Next() {
|
|
var item Service
|
|
if err := rows.Scan(&item.ID, &item.Name, &item.Description, &item.Domain, &item.UpstreamIP, &item.ProxyIP, &item.Ports, &item.IsActive); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(items) == 0 {
|
|
return nil, errors.New("services not found")
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func normalizePorts(ports []int) []int {
|
|
if len(ports) == 0 {
|
|
return []int{80, 443}
|
|
}
|
|
|
|
seen := make(map[int]struct{}, len(ports))
|
|
result := make([]int, 0, len(ports))
|
|
for _, port := range ports {
|
|
if port <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[port]; ok {
|
|
continue
|
|
}
|
|
seen[port] = struct{}{}
|
|
result = append(result, port)
|
|
}
|
|
if len(result) == 0 {
|
|
return []int{80, 443}
|
|
}
|
|
return result
|
|
}
|