persona-community-5/pkg/personagen/store_postgres.go
rdev-worker 66ceb7e55f
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
build: /implement-feature persona-generation --requirements 'Implement the g...
2026-02-24 08:13:52 +00:00

147 lines
3.5 KiB
Go

package personagen
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
// ErrPersonaNotFound is returned when a persona ID does not exist.
var ErrPersonaNotFound = errors.New("persona not found")
// personaRow is the database scan target for persona queries.
type personaRow struct {
ID string `db:"id"`
Name string `db:"name"`
Handle string `db:"handle"`
Gender string `db:"gender"`
Description string `db:"description"`
Tags pq.StringArray `db:"tags"`
SpecJSON []byte `db:"spec_json"`
AnchorURL *string `db:"anchor_url"`
AvatarURL *string `db:"avatar_url"`
BannerURL *string `db:"banner_url"`
ImageURLs pq.StringArray `db:"image_urls"`
VideoURLs pq.StringArray `db:"video_urls"`
Status string `db:"status"`
}
func (r *personaRow) toRecord() *PersonaRecord {
rec := &PersonaRecord{
ID: r.ID,
Name: r.Name,
Handle: r.Handle,
Gender: r.Gender,
Description: r.Description,
Tags: []string(r.Tags),
Status: r.Status,
ImageURLs: []string(r.ImageURLs),
VideoURLs: []string(r.VideoURLs),
}
if rec.Tags == nil {
rec.Tags = []string{}
}
if rec.ImageURLs == nil {
rec.ImageURLs = []string{}
}
if rec.VideoURLs == nil {
rec.VideoURLs = []string{}
}
if len(r.SpecJSON) > 0 {
rec.SpecJSON = r.SpecJSON
}
if r.AnchorURL != nil {
rec.AnchorURL = *r.AnchorURL
}
if r.AvatarURL != nil {
rec.AvatarURL = *r.AvatarURL
}
if r.BannerURL != nil {
rec.BannerURL = *r.BannerURL
}
return rec
}
// PostgresPersonaStore implements PersonaStore using PostgreSQL/CockroachDB.
type PostgresPersonaStore struct {
db *sqlx.DB
}
// Compile-time interface check.
var _ PersonaStore = (*PostgresPersonaStore)(nil)
// NewPostgresPersonaStore creates a PersonaStore backed by a SQL database.
func NewPostgresPersonaStore(db *sqlx.DB) *PostgresPersonaStore {
return &PostgresPersonaStore{db: db}
}
func (s *PostgresPersonaStore) GetByID(ctx context.Context, id string) (*PersonaRecord, error) {
var row personaRow
err := s.db.QueryRowxContext(ctx, `
SELECT id, name, handle, gender, description, tags, spec_json,
anchor_url, avatar_url, banner_url, image_urls, video_urls, status
FROM personas WHERE id = $1
`, id).StructScan(&row)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrPersonaNotFound
}
return nil, fmt.Errorf("get persona: %w", err)
}
return row.toRecord(), nil
}
func (s *PostgresPersonaStore) Update(ctx context.Context, p *PersonaRecord) error {
var specJSON []byte
if p.SpecJSON != nil {
specJSON = []byte(p.SpecJSON)
}
var anchorURL, avatarURL, bannerURL *string
if p.AnchorURL != "" {
anchorURL = &p.AnchorURL
}
if p.AvatarURL != "" {
avatarURL = &p.AvatarURL
}
if p.BannerURL != "" {
bannerURL = &p.BannerURL
}
result, err := s.db.ExecContext(ctx, `
UPDATE personas
SET name = $2, handle = $3, tags = $4, spec_json = $5,
anchor_url = $6, avatar_url = $7, banner_url = $8,
image_urls = $9, video_urls = $10, status = $11
WHERE id = $1
`,
p.ID,
p.Name,
p.Handle,
pq.StringArray(p.Tags),
specJSON,
anchorURL,
avatarURL,
bannerURL,
pq.StringArray(p.ImageURLs),
pq.StringArray(p.VideoURLs),
p.Status,
)
if err != nil {
return fmt.Errorf("update persona: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("update persona rows affected: %w", err)
}
if rows == 0 {
return ErrPersonaNotFound
}
return nil
}