251 lines
5.8 KiB
Go
251 lines
5.8 KiB
Go
package cluster
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"proxui/backend/internal/encryption"
|
|
)
|
|
|
|
type Cluster struct {
|
|
ID string
|
|
Name string
|
|
APIEndpoint string
|
|
TLSFingerprint string
|
|
TokenID string
|
|
TokenSecret string
|
|
Status string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
type StoredCluster struct {
|
|
ID string
|
|
Name string
|
|
APIEndpoint string
|
|
TLSFingerprint string
|
|
EncryptedToken []byte
|
|
TokenID string
|
|
Status string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
type Storage interface {
|
|
Get(ctx context.Context, id string) (StoredCluster, bool, error)
|
|
Upsert(ctx context.Context, cluster StoredCluster) (StoredCluster, error)
|
|
SetStatus(ctx context.Context, id string, status string) (StoredCluster, bool, error)
|
|
}
|
|
|
|
type Repository struct {
|
|
storage Storage
|
|
cipher encryption.Cipher
|
|
}
|
|
|
|
func NewRepository(db *sql.DB, cipher encryption.Cipher) Repository {
|
|
return Repository{
|
|
storage: SQLStorage{db: db},
|
|
cipher: cipher,
|
|
}
|
|
}
|
|
|
|
func NewRepositoryWithStorage(storage Storage, cipher encryption.Cipher) Repository {
|
|
return Repository{
|
|
storage: storage,
|
|
cipher: cipher,
|
|
}
|
|
}
|
|
|
|
func (r Repository) GetCluster(ctx context.Context, id string) (Cluster, bool, error) {
|
|
stored, found, err := r.storage.Get(ctx, id)
|
|
if err != nil || !found {
|
|
return Cluster{}, found, err
|
|
}
|
|
|
|
tokenSecret, err := r.cipher.Decrypt(stored.EncryptedToken)
|
|
if err != nil {
|
|
return Cluster{}, false, err
|
|
}
|
|
|
|
return Cluster{
|
|
ID: stored.ID,
|
|
Name: stored.Name,
|
|
APIEndpoint: stored.APIEndpoint,
|
|
TLSFingerprint: stored.TLSFingerprint,
|
|
TokenID: stored.TokenID,
|
|
TokenSecret: string(tokenSecret),
|
|
Status: stored.Status,
|
|
CreatedAt: stored.CreatedAt,
|
|
}, true, nil
|
|
}
|
|
|
|
func (r Repository) UpsertCluster(ctx context.Context, cluster Cluster) (Cluster, error) {
|
|
if strings.TrimSpace(cluster.TokenSecret) == "" {
|
|
return Cluster{}, fmt.Errorf("token secret is required")
|
|
}
|
|
|
|
encryptedToken, err := r.cipher.Encrypt([]byte(cluster.TokenSecret))
|
|
if err != nil {
|
|
return Cluster{}, err
|
|
}
|
|
|
|
status := strings.TrimSpace(cluster.Status)
|
|
if status == "" {
|
|
status = "active"
|
|
}
|
|
|
|
stored, err := r.storage.Upsert(ctx, StoredCluster{
|
|
ID: strings.TrimSpace(cluster.ID),
|
|
Name: cluster.Name,
|
|
APIEndpoint: cluster.APIEndpoint,
|
|
TLSFingerprint: cluster.TLSFingerprint,
|
|
EncryptedToken: encryptedToken,
|
|
TokenID: cluster.TokenID,
|
|
Status: status,
|
|
})
|
|
if err != nil {
|
|
return Cluster{}, err
|
|
}
|
|
|
|
return Cluster{
|
|
ID: stored.ID,
|
|
Name: stored.Name,
|
|
APIEndpoint: stored.APIEndpoint,
|
|
TLSFingerprint: stored.TLSFingerprint,
|
|
TokenID: stored.TokenID,
|
|
TokenSecret: cluster.TokenSecret,
|
|
Status: stored.Status,
|
|
CreatedAt: stored.CreatedAt,
|
|
}, nil
|
|
}
|
|
|
|
func (r Repository) SetClusterStatus(ctx context.Context, id string, status string) (Cluster, bool, error) {
|
|
stored, found, err := r.storage.SetStatus(ctx, strings.TrimSpace(id), strings.TrimSpace(status))
|
|
if err != nil || !found {
|
|
return Cluster{}, found, err
|
|
}
|
|
|
|
return Cluster{
|
|
ID: stored.ID,
|
|
Name: stored.Name,
|
|
APIEndpoint: stored.APIEndpoint,
|
|
TLSFingerprint: stored.TLSFingerprint,
|
|
TokenID: stored.TokenID,
|
|
Status: stored.Status,
|
|
CreatedAt: stored.CreatedAt,
|
|
}, true, nil
|
|
}
|
|
|
|
type SQLStorage struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
func (s SQLStorage) Get(ctx context.Context, id string) (StoredCluster, bool, error) {
|
|
var cluster StoredCluster
|
|
err := s.db.QueryRowContext(ctx, `
|
|
select id::text, name, api_endpoint, tls_fingerprint, encrypted_token, token_id, status, created_at
|
|
from public.clusters
|
|
where id = $1
|
|
`, id).Scan(
|
|
&cluster.ID,
|
|
&cluster.Name,
|
|
&cluster.APIEndpoint,
|
|
&cluster.TLSFingerprint,
|
|
&cluster.EncryptedToken,
|
|
&cluster.TokenID,
|
|
&cluster.Status,
|
|
&cluster.CreatedAt,
|
|
)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return StoredCluster{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return StoredCluster{}, false, err
|
|
}
|
|
|
|
return cluster, true, nil
|
|
}
|
|
|
|
func (s SQLStorage) Upsert(ctx context.Context, cluster StoredCluster) (StoredCluster, error) {
|
|
var stored StoredCluster
|
|
err := s.db.QueryRowContext(ctx, `
|
|
insert into public.clusters (
|
|
id,
|
|
name,
|
|
api_endpoint,
|
|
tls_fingerprint,
|
|
encrypted_token,
|
|
token_id,
|
|
status
|
|
)
|
|
values (
|
|
coalesce(nullif($1, '')::uuid, gen_random_uuid()),
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6,
|
|
$7
|
|
)
|
|
on conflict (id) do update
|
|
set name = excluded.name,
|
|
api_endpoint = excluded.api_endpoint,
|
|
tls_fingerprint = excluded.tls_fingerprint,
|
|
encrypted_token = excluded.encrypted_token,
|
|
token_id = excluded.token_id,
|
|
status = excluded.status
|
|
returning id::text, name, api_endpoint, tls_fingerprint, encrypted_token, token_id, status, created_at
|
|
`,
|
|
cluster.ID,
|
|
cluster.Name,
|
|
cluster.APIEndpoint,
|
|
cluster.TLSFingerprint,
|
|
cluster.EncryptedToken,
|
|
cluster.TokenID,
|
|
cluster.Status,
|
|
).Scan(
|
|
&stored.ID,
|
|
&stored.Name,
|
|
&stored.APIEndpoint,
|
|
&stored.TLSFingerprint,
|
|
&stored.EncryptedToken,
|
|
&stored.TokenID,
|
|
&stored.Status,
|
|
&stored.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
return StoredCluster{}, err
|
|
}
|
|
|
|
return stored, nil
|
|
}
|
|
|
|
func (s SQLStorage) SetStatus(ctx context.Context, id string, status string) (StoredCluster, bool, error) {
|
|
var stored StoredCluster
|
|
err := s.db.QueryRowContext(ctx, `
|
|
update public.clusters
|
|
set status = $2
|
|
where id = $1
|
|
returning id::text, name, api_endpoint, tls_fingerprint, encrypted_token, token_id, status, created_at
|
|
`, id, status).Scan(
|
|
&stored.ID,
|
|
&stored.Name,
|
|
&stored.APIEndpoint,
|
|
&stored.TLSFingerprint,
|
|
&stored.EncryptedToken,
|
|
&stored.TokenID,
|
|
&stored.Status,
|
|
&stored.CreatedAt,
|
|
)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return StoredCluster{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return StoredCluster{}, false, err
|
|
}
|
|
|
|
return stored, true, nil
|
|
}
|