3ef0807456
- Updated the regex for placeholder matching to support more flexible key formats. - Refactored variable storage to allow both ID and key lookups in rendering. - Improved schema validation to check for duplicate keys and enforce non-empty keys. - Added new utility functions for variable value lookup and display name generation. - Enhanced tests to cover new key-based variable scenarios. - Refactored KolPrompt repository to simplify prompt storage and management, including the removal of obsolete revision handling. - Introduced new API endpoints for saving, activating, and archiving prompts. - Added new utility functions for handling Kol placeholders and platform options in the admin web. - Implemented database migrations to simplify prompt storage structure and ensure data integrity.
381 lines
10 KiB
Go
381 lines
10 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
|
|
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
|
|
)
|
|
|
|
type KolPrompt struct {
|
|
ID int64
|
|
TenantID int64
|
|
PackageID int64
|
|
Name string
|
|
PlatformHint *string
|
|
Status string
|
|
SortOrder int
|
|
PublishedRevisionNo *int
|
|
DraftRevisionNo *int
|
|
PromptAssetKey *string
|
|
SchemaJSON []byte
|
|
CardConfigJSON []byte
|
|
CreatedBy int64
|
|
UpdatedBy int64
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type ActiveKolPrompt struct {
|
|
ID int64
|
|
TenantID int64
|
|
PackageID int64
|
|
PublishedRevisionNo *int
|
|
}
|
|
|
|
type CreateKolPromptInput struct {
|
|
TenantID int64
|
|
PackageID int64
|
|
Name string
|
|
PlatformHint *string
|
|
SortOrder int
|
|
CreatedBy int64
|
|
}
|
|
|
|
type UpdateKolPromptMetadataInput struct {
|
|
ID int64
|
|
TenantID int64
|
|
Name string
|
|
PlatformHint *string
|
|
SortOrder int
|
|
UpdatedBy int64
|
|
}
|
|
|
|
type SaveKolPromptInput struct {
|
|
ID int64
|
|
TenantID int64
|
|
Name string
|
|
PlatformHint *string
|
|
SortOrder int
|
|
PromptAssetKey string
|
|
SchemaJSON []byte
|
|
CardConfigJSON []byte
|
|
Status string
|
|
PublishedRevisionNo *int
|
|
UpdatedBy int64
|
|
}
|
|
|
|
type PublishKolPromptInput struct {
|
|
ID int64
|
|
TenantID int64
|
|
Name string
|
|
PlatformHint *string
|
|
SortOrder int
|
|
PromptAssetKey string
|
|
SchemaJSON []byte
|
|
CardConfigJSON []byte
|
|
PublishedRevisionNo int
|
|
UpdatedBy int64
|
|
}
|
|
|
|
type KolPromptRepository interface {
|
|
Create(ctx context.Context, input CreateKolPromptInput) (*KolPrompt, error)
|
|
GetByID(ctx context.Context, tenantID, id int64) (*KolPrompt, error)
|
|
ListByPackage(ctx context.Context, tenantID, packageID int64) ([]KolPrompt, error)
|
|
ListActiveByPackage(ctx context.Context, tenantID, packageID int64) ([]ActiveKolPrompt, error)
|
|
UpdateMetadata(ctx context.Context, input UpdateKolPromptMetadataInput) error
|
|
Save(ctx context.Context, input SaveKolPromptInput) error
|
|
Publish(ctx context.Context, input PublishKolPromptInput) error
|
|
SetPublishedRevision(ctx context.Context, tenantID, promptID int64, revNo int, updatedBy int64) error
|
|
UpdateStatus(ctx context.Context, tenantID, id int64, status string, updatedBy int64) error
|
|
SoftDelete(ctx context.Context, tenantID, id int64) error
|
|
}
|
|
|
|
type kolPromptRepository struct {
|
|
db generated.DBTX
|
|
q generated.Querier
|
|
}
|
|
|
|
func NewKolPromptRepository(db generated.DBTX) KolPromptRepository {
|
|
return &kolPromptRepository{db: db, q: newQuerier(db)}
|
|
}
|
|
|
|
func mapKolPromptRow(
|
|
id int64,
|
|
tenantID int64,
|
|
packageID int64,
|
|
name string,
|
|
platformHint pgtype.Text,
|
|
status string,
|
|
sortOrder int32,
|
|
publishedRevisionNo pgtype.Int4,
|
|
draftRevisionNo pgtype.Int4,
|
|
promptAssetKey pgtype.Text,
|
|
schemaJSON []byte,
|
|
cardConfigJSON []byte,
|
|
createdBy int64,
|
|
updatedBy int64,
|
|
createdAt pgtype.Timestamptz,
|
|
updatedAt pgtype.Timestamptz,
|
|
) *KolPrompt {
|
|
return &KolPrompt{
|
|
ID: id,
|
|
TenantID: tenantID,
|
|
PackageID: packageID,
|
|
Name: name,
|
|
PlatformHint: nullableText(platformHint),
|
|
Status: status,
|
|
SortOrder: int(sortOrder),
|
|
PublishedRevisionNo: nullableIntFromInt4(publishedRevisionNo),
|
|
DraftRevisionNo: nullableIntFromInt4(draftRevisionNo),
|
|
PromptAssetKey: nullableText(promptAssetKey),
|
|
SchemaJSON: schemaJSON,
|
|
CardConfigJSON: cardConfigJSON,
|
|
CreatedBy: createdBy,
|
|
UpdatedBy: updatedBy,
|
|
CreatedAt: timeFromTimestamp(createdAt),
|
|
UpdatedAt: timeFromTimestamp(updatedAt),
|
|
}
|
|
}
|
|
|
|
func scanKolPrompt(scanner interface{ Scan(...any) error }) (*KolPrompt, error) {
|
|
var (
|
|
id int64
|
|
tenantID int64
|
|
packageID int64
|
|
name string
|
|
platformHint pgtype.Text
|
|
status string
|
|
sortOrder int32
|
|
publishedRevisionNo pgtype.Int4
|
|
draftRevisionNo pgtype.Int4
|
|
promptAssetKey pgtype.Text
|
|
schemaJSON []byte
|
|
cardConfigJSON []byte
|
|
createdBy int64
|
|
updatedBy int64
|
|
createdAt pgtype.Timestamptz
|
|
updatedAt pgtype.Timestamptz
|
|
)
|
|
|
|
if err := scanner.Scan(
|
|
&id,
|
|
&tenantID,
|
|
&packageID,
|
|
&name,
|
|
&platformHint,
|
|
&status,
|
|
&sortOrder,
|
|
&publishedRevisionNo,
|
|
&draftRevisionNo,
|
|
&promptAssetKey,
|
|
&schemaJSON,
|
|
&cardConfigJSON,
|
|
&createdBy,
|
|
&updatedBy,
|
|
&createdAt,
|
|
&updatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mapKolPromptRow(
|
|
id,
|
|
tenantID,
|
|
packageID,
|
|
name,
|
|
platformHint,
|
|
status,
|
|
sortOrder,
|
|
publishedRevisionNo,
|
|
draftRevisionNo,
|
|
promptAssetKey,
|
|
schemaJSON,
|
|
cardConfigJSON,
|
|
createdBy,
|
|
updatedBy,
|
|
createdAt,
|
|
updatedAt,
|
|
), nil
|
|
}
|
|
|
|
func (r *kolPromptRepository) Create(ctx context.Context, input CreateKolPromptInput) (*KolPrompt, error) {
|
|
row, err := r.q.CreateKolPrompt(ctx, generated.CreateKolPromptParams{
|
|
TenantID: input.TenantID,
|
|
PackageID: input.PackageID,
|
|
Name: input.Name,
|
|
PlatformHint: pgText(input.PlatformHint),
|
|
SortOrder: int32(input.SortOrder),
|
|
CreatedBy: input.CreatedBy,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.GetByID(ctx, input.TenantID, row.ID)
|
|
}
|
|
|
|
func (r *kolPromptRepository) GetByID(ctx context.Context, tenantID, id int64) (*KolPrompt, error) {
|
|
row := r.db.QueryRow(ctx, `
|
|
SELECT id, tenant_id, package_id, name, platform_hint, status, sort_order,
|
|
published_revision_no, draft_revision_no, prompt_asset_key, schema_json, card_config_json,
|
|
created_by, updated_by, created_at, updated_at
|
|
FROM kol_prompts
|
|
WHERE id = $1
|
|
AND tenant_id = $2
|
|
AND deleted_at IS NULL
|
|
`, id, tenantID)
|
|
|
|
prompt, err := scanKolPrompt(row)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return prompt, nil
|
|
}
|
|
|
|
func (r *kolPromptRepository) ListByPackage(ctx context.Context, tenantID, packageID int64) ([]KolPrompt, error) {
|
|
rows, err := r.db.Query(ctx, `
|
|
SELECT id, tenant_id, package_id, name, platform_hint, status, sort_order,
|
|
published_revision_no, draft_revision_no, prompt_asset_key, schema_json, card_config_json,
|
|
created_by, updated_by, created_at, updated_at
|
|
FROM kol_prompts
|
|
WHERE package_id = $1
|
|
AND tenant_id = $2
|
|
AND deleted_at IS NULL
|
|
ORDER BY sort_order ASC, created_at ASC
|
|
`, packageID, tenantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
result := make([]KolPrompt, 0)
|
|
for rows.Next() {
|
|
prompt, err := scanKolPrompt(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, *prompt)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *kolPromptRepository) ListActiveByPackage(ctx context.Context, tenantID, packageID int64) ([]ActiveKolPrompt, error) {
|
|
rows, err := r.q.ListActiveKolPromptsByPackage(ctx, generated.ListActiveKolPromptsByPackageParams{
|
|
PackageID: packageID,
|
|
TenantID: tenantID,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]ActiveKolPrompt, len(rows))
|
|
for i, row := range rows {
|
|
result[i] = ActiveKolPrompt{
|
|
ID: row.ID,
|
|
TenantID: row.TenantID,
|
|
PackageID: row.PackageID,
|
|
PublishedRevisionNo: nullableIntFromInt4(row.PublishedRevisionNo),
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *kolPromptRepository) UpdateMetadata(ctx context.Context, input UpdateKolPromptMetadataInput) error {
|
|
return r.q.UpdateKolPromptMetadata(ctx, generated.UpdateKolPromptMetadataParams{
|
|
Name: input.Name,
|
|
PlatformHint: pgText(input.PlatformHint),
|
|
SortOrder: int32(input.SortOrder),
|
|
UpdatedBy: input.UpdatedBy,
|
|
ID: input.ID,
|
|
TenantID: input.TenantID,
|
|
})
|
|
}
|
|
|
|
func (r *kolPromptRepository) Save(ctx context.Context, input SaveKolPromptInput) error {
|
|
publishedRevisionNo := pgtype.Int4{}
|
|
if input.PublishedRevisionNo != nil {
|
|
publishedRevisionNo = pgtype.Int4{Int32: int32(*input.PublishedRevisionNo), Valid: true}
|
|
}
|
|
|
|
_, err := r.db.Exec(ctx, `
|
|
UPDATE kol_prompts
|
|
SET name = $1,
|
|
platform_hint = $2,
|
|
sort_order = $3,
|
|
prompt_asset_key = $4,
|
|
schema_json = $5::jsonb,
|
|
card_config_json = $6::jsonb,
|
|
status = $7,
|
|
published_revision_no = $8,
|
|
updated_by = $9,
|
|
updated_at = NOW()
|
|
WHERE id = $10
|
|
AND tenant_id = $11
|
|
AND deleted_at IS NULL
|
|
`, input.Name, pgText(input.PlatformHint), int32(input.SortOrder), input.PromptAssetKey, input.SchemaJSON, input.CardConfigJSON, input.Status, publishedRevisionNo, input.UpdatedBy, input.ID, input.TenantID)
|
|
return err
|
|
}
|
|
|
|
func (r *kolPromptRepository) Publish(ctx context.Context, input PublishKolPromptInput) error {
|
|
_, err := r.db.Exec(ctx, `
|
|
UPDATE kol_prompts
|
|
SET name = $1,
|
|
platform_hint = $2,
|
|
sort_order = $3,
|
|
prompt_asset_key = $4,
|
|
schema_json = $5::jsonb,
|
|
card_config_json = $6::jsonb,
|
|
published_revision_no = $7,
|
|
draft_revision_no = NULL,
|
|
status = 'active',
|
|
updated_by = $8,
|
|
updated_at = NOW()
|
|
WHERE id = $9
|
|
AND tenant_id = $10
|
|
AND deleted_at IS NULL
|
|
`, input.Name, pgText(input.PlatformHint), int32(input.SortOrder), input.PromptAssetKey, input.SchemaJSON, input.CardConfigJSON, input.PublishedRevisionNo, input.UpdatedBy, input.ID, input.TenantID)
|
|
return err
|
|
}
|
|
|
|
func (r *kolPromptRepository) SetPublishedRevision(ctx context.Context, tenantID, promptID int64, revNo int, updatedBy int64) error {
|
|
return r.q.UpdateKolPromptPublishedRevision(ctx, generated.UpdateKolPromptPublishedRevisionParams{
|
|
RevisionNo: pgtype.Int4{Int32: int32(revNo), Valid: true},
|
|
UpdatedBy: updatedBy,
|
|
ID: promptID,
|
|
TenantID: tenantID,
|
|
})
|
|
}
|
|
|
|
func (r *kolPromptRepository) UpdateStatus(ctx context.Context, tenantID, id int64, status string, updatedBy int64) error {
|
|
return r.q.UpdateKolPromptStatus(ctx, generated.UpdateKolPromptStatusParams{
|
|
Status: status,
|
|
UpdatedBy: updatedBy,
|
|
ID: id,
|
|
TenantID: tenantID,
|
|
})
|
|
}
|
|
|
|
func (r *kolPromptRepository) SoftDelete(ctx context.Context, tenantID, id int64) error {
|
|
return r.q.SoftDeleteKolPrompt(ctx, generated.SoftDeleteKolPromptParams{
|
|
ID: id,
|
|
TenantID: tenantID,
|
|
})
|
|
}
|
|
|
|
func nullableIntFromInt4(value pgtype.Int4) *int {
|
|
if !value.Valid {
|
|
return nil
|
|
}
|
|
v := int(value.Int32)
|
|
return &v
|
|
}
|