package postgres import ( "context" "database/sql" "encoding/json" "fmt" "time" "github.com/google/uuid" "github.com/ocdp/cluster-service/internal/domain/entity" "github.com/ocdp/cluster-service/internal/domain/repository" ) type InstanceRepository struct { db *DB } func NewInstanceRepository(db *DB) repository.InstanceRepository { return &InstanceRepository{db: db} } func (r *InstanceRepository) Create(ctx context.Context, instance *entity.Instance) error { if instance.ID == "" { instance.ID = uuid.New().String() } valuesJSON, err := json.Marshal(instance.Values) if err != nil { return fmt.Errorf("failed to marshal values: %w", err) } query := ` INSERT INTO instances (id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version, description, values, values_yaml, status, status_reason, last_operation, last_error, revision, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20) ` _, err = r.db.conn.ExecContext(ctx, query, instance.ID, instance.WorkspaceID, instance.OwnerID, instance.ClusterID, instance.Name, instance.Namespace, instance.RegistryID, instance.Repository, instance.Chart, instance.Version, instance.Description, valuesJSON, instance.ValuesYAML, instance.Status, instance.StatusReason, instance.LastOperation, instance.LastError, instance.Revision, instance.CreatedAt, instance.UpdatedAt, ) if err != nil { return fmt.Errorf("failed to create instance: %w", err) } return nil } func (r *InstanceRepository) GetByID(ctx context.Context, id string) (*entity.Instance, error) { return r.get(ctx, "id = $1", id) } func (r *InstanceRepository) GetByClusterAndName(ctx context.Context, clusterID, name string) (*entity.Instance, error) { query := ` SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version, description, values, values_yaml, status, status_reason, last_operation, last_error, revision, created_at, updated_at FROM instances WHERE cluster_id = $1 AND name = $2 ` rows, err := r.db.conn.QueryContext(ctx, query, clusterID, name) if err != nil { return nil, fmt.Errorf("failed to get instance: %w", err) } defer rows.Close() if !rows.Next() { return nil, entity.ErrInstanceNotFound } return r.scanInstance(rows) } func (r *InstanceRepository) get(ctx context.Context, where string, arg interface{}) (*entity.Instance, error) { query := fmt.Sprintf(` SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version, description, values, values_yaml, status, status_reason, last_operation, last_error, revision, created_at, updated_at FROM instances WHERE %s `, where) rows, err := r.db.conn.QueryContext(ctx, query, arg) if err != nil { return nil, fmt.Errorf("failed to get instance: %w", err) } defer rows.Close() if !rows.Next() { return nil, entity.ErrInstanceNotFound } return r.scanInstance(rows) } func (r *InstanceRepository) Update(ctx context.Context, instance *entity.Instance) error { instance.UpdatedAt = time.Now() valuesJSON, err := json.Marshal(instance.Values) if err != nil { return fmt.Errorf("failed to marshal values: %w", err) } query := ` UPDATE instances SET workspace_id = $1, owner_id = $2, cluster_id = $3, name = $4, namespace = $5, registry_id = $6, repository = $7, chart = $8, version = $9, description = $10, values = $11, values_yaml = $12, status = $13, status_reason = $14, last_operation = $15, last_error = $16, revision = $17, updated_at = $18 WHERE id = $19 ` result, err := r.db.conn.ExecContext(ctx, query, instance.WorkspaceID, instance.OwnerID, instance.ClusterID, instance.Name, instance.Namespace, instance.RegistryID, instance.Repository, instance.Chart, instance.Version, instance.Description, valuesJSON, instance.ValuesYAML, instance.Status, instance.StatusReason, instance.LastOperation, instance.LastError, instance.Revision, instance.UpdatedAt, instance.ID, ) if err != nil { return fmt.Errorf("failed to update instance: %w", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get affected rows: %w", err) } if rows == 0 { return entity.ErrInstanceNotFound } return nil } func (r *InstanceRepository) Delete(ctx context.Context, id string) error { result, err := r.db.conn.ExecContext(ctx, `DELETE FROM instances WHERE id = $1`, id) if err != nil { return fmt.Errorf("failed to delete instance: %w", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get affected rows: %w", err) } if rows == 0 { return entity.ErrInstanceNotFound } return nil } func (r *InstanceRepository) ListByCluster(ctx context.Context, clusterID string) ([]*entity.Instance, error) { return r.list(ctx, "WHERE cluster_id = $1", clusterID) } func (r *InstanceRepository) List(ctx context.Context) ([]*entity.Instance, error) { return r.list(ctx, "", nil) } func (r *InstanceRepository) list(ctx context.Context, where string, arg interface{}) ([]*entity.Instance, error) { query := ` SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version, description, values, values_yaml, status, status_reason, last_operation, last_error, revision, created_at, updated_at FROM instances ` + where + ` ORDER BY created_at DESC ` var rows *sql.Rows var err error if where == "" { rows, err = r.db.conn.QueryContext(ctx, query) } else { rows, err = r.db.conn.QueryContext(ctx, query, arg) } if err != nil { return nil, fmt.Errorf("failed to list instances: %w", err) } defer rows.Close() instances := make([]*entity.Instance, 0) for rows.Next() { instance, err := r.scanInstance(rows) if err != nil { return nil, err } instances = append(instances, instance) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("rows iteration error: %w", err) } return instances, nil } type instanceScanner interface { Scan(dest ...interface{}) error } func (r *InstanceRepository) scanInstance(scanner instanceScanner) (*entity.Instance, error) { instance := &entity.Instance{} var ( valuesJSON []byte statusReason sql.NullString lastOperation sql.NullString lastError sql.NullString ) err := scanner.Scan( &instance.ID, &instance.WorkspaceID, &instance.OwnerID, &instance.ClusterID, &instance.Name, &instance.Namespace, &instance.RegistryID, &instance.Repository, &instance.Chart, &instance.Version, &instance.Description, &valuesJSON, &instance.ValuesYAML, &instance.Status, &statusReason, &lastOperation, &lastError, &instance.Revision, &instance.CreatedAt, &instance.UpdatedAt, ) if err != nil { return nil, fmt.Errorf("failed to scan instance: %w", err) } if len(valuesJSON) > 0 { if err := json.Unmarshal(valuesJSON, &instance.Values); err != nil { return nil, fmt.Errorf("failed to unmarshal values: %w", err) } } if statusReason.Valid { instance.StatusReason = statusReason.String } if lastOperation.Valid { instance.LastOperation = entity.InstanceOperation(lastOperation.String) } if lastError.Valid { instance.LastError = lastError.String } return instance, nil }