feat(kol): store KOL prompt content in database with object storage fallback
Add prompt_content columns to kol_prompts and kol_prompt_revisions so prompt bodies live alongside their metadata, eliminating an extra round trip to object storage for the common read path while keeping the old asset key as a fallback for legacy rows. Reads go through a singleflight-deduped, cache-backed loader that prefers the database column, falls back to the asset key, and tolerates missing objects via a new objectstorage.ErrObjectNotFound returned by both the Aliyun OSS and MinIO clients on 404/NoSuchKey responses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ type KolPrompt struct {
|
||||
PublishedRevisionNo *int
|
||||
DraftRevisionNo *int
|
||||
PromptAssetKey *string
|
||||
PromptContent *string
|
||||
SchemaJSON []byte
|
||||
CardConfigJSON []byte
|
||||
CreatedBy int64
|
||||
@@ -61,7 +62,8 @@ type SaveKolPromptInput struct {
|
||||
Name string
|
||||
PlatformHint *string
|
||||
SortOrder int
|
||||
PromptAssetKey string
|
||||
PromptAssetKey *string
|
||||
PromptContent string
|
||||
SchemaJSON []byte
|
||||
CardConfigJSON []byte
|
||||
Status string
|
||||
@@ -75,7 +77,8 @@ type PublishKolPromptInput struct {
|
||||
Name string
|
||||
PlatformHint *string
|
||||
SortOrder int
|
||||
PromptAssetKey string
|
||||
PromptAssetKey *string
|
||||
PromptContent string
|
||||
SchemaJSON []byte
|
||||
CardConfigJSON []byte
|
||||
PublishedRevisionNo int
|
||||
@@ -85,6 +88,7 @@ type PublishKolPromptInput struct {
|
||||
type KolPromptRepository interface {
|
||||
Create(ctx context.Context, input CreateKolPromptInput) (*KolPrompt, error)
|
||||
GetByID(ctx context.Context, tenantID, id int64) (*KolPrompt, error)
|
||||
GetContentByID(ctx context.Context, tenantID, id int64) (*string, error)
|
||||
ListByPackage(ctx context.Context, tenantID, packageID int64) ([]KolPrompt, error)
|
||||
CountByPackages(ctx context.Context, tenantID int64, packageIDs []int64) (map[int64]int, error)
|
||||
ListActiveByPackage(ctx context.Context, tenantID, packageID int64) ([]ActiveKolPrompt, error)
|
||||
@@ -116,6 +120,7 @@ func mapKolPromptRow(
|
||||
publishedRevisionNo pgtype.Int4,
|
||||
draftRevisionNo pgtype.Int4,
|
||||
promptAssetKey pgtype.Text,
|
||||
promptContent pgtype.Text,
|
||||
schemaJSON []byte,
|
||||
cardConfigJSON []byte,
|
||||
createdBy int64,
|
||||
@@ -134,6 +139,7 @@ func mapKolPromptRow(
|
||||
PublishedRevisionNo: nullableIntFromInt4(publishedRevisionNo),
|
||||
DraftRevisionNo: nullableIntFromInt4(draftRevisionNo),
|
||||
PromptAssetKey: nullableText(promptAssetKey),
|
||||
PromptContent: nullableText(promptContent),
|
||||
SchemaJSON: schemaJSON,
|
||||
CardConfigJSON: cardConfigJSON,
|
||||
CreatedBy: createdBy,
|
||||
@@ -155,6 +161,7 @@ func scanKolPrompt(scanner interface{ Scan(...any) error }) (*KolPrompt, error)
|
||||
publishedRevisionNo pgtype.Int4
|
||||
draftRevisionNo pgtype.Int4
|
||||
promptAssetKey pgtype.Text
|
||||
promptContent pgtype.Text
|
||||
schemaJSON []byte
|
||||
cardConfigJSON []byte
|
||||
createdBy int64
|
||||
@@ -174,6 +181,7 @@ func scanKolPrompt(scanner interface{ Scan(...any) error }) (*KolPrompt, error)
|
||||
&publishedRevisionNo,
|
||||
&draftRevisionNo,
|
||||
&promptAssetKey,
|
||||
&promptContent,
|
||||
&schemaJSON,
|
||||
&cardConfigJSON,
|
||||
&createdBy,
|
||||
@@ -195,6 +203,7 @@ func scanKolPrompt(scanner interface{ Scan(...any) error }) (*KolPrompt, error)
|
||||
publishedRevisionNo,
|
||||
draftRevisionNo,
|
||||
promptAssetKey,
|
||||
promptContent,
|
||||
schemaJSON,
|
||||
cardConfigJSON,
|
||||
createdBy,
|
||||
@@ -222,7 +231,7 @@ func (r *kolPromptRepository) Create(ctx context.Context, input CreateKolPromptI
|
||||
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,
|
||||
published_revision_no, draft_revision_no, prompt_asset_key, NULL::text AS prompt_content, schema_json, card_config_json,
|
||||
created_by, updated_by, created_at, updated_at
|
||||
FROM kol_prompts
|
||||
WHERE id = $1
|
||||
@@ -240,10 +249,28 @@ WHERE id = $1
|
||||
return prompt, nil
|
||||
}
|
||||
|
||||
func (r *kolPromptRepository) GetContentByID(ctx context.Context, tenantID, id int64) (*string, error) {
|
||||
var content pgtype.Text
|
||||
err := r.db.QueryRow(ctx, `
|
||||
SELECT prompt_content
|
||||
FROM kol_prompts
|
||||
WHERE id = $1
|
||||
AND tenant_id = $2
|
||||
AND deleted_at IS NULL
|
||||
`, id, tenantID).Scan(&content)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nullableText(content), 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,
|
||||
published_revision_no, draft_revision_no, prompt_asset_key, NULL::text AS prompt_content, schema_json, card_config_json,
|
||||
created_by, updated_by, created_at, updated_at
|
||||
FROM kol_prompts
|
||||
WHERE package_id = $1
|
||||
@@ -346,16 +373,17 @@ 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,
|
||||
prompt_content = $5,
|
||||
schema_json = $6::jsonb,
|
||||
card_config_json = $7::jsonb,
|
||||
status = $8,
|
||||
published_revision_no = $9,
|
||||
updated_by = $10,
|
||||
updated_at = NOW()
|
||||
WHERE id = $10
|
||||
AND tenant_id = $11
|
||||
WHERE id = $11
|
||||
AND tenant_id = $12
|
||||
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)
|
||||
`, input.Name, pgText(input.PlatformHint), int32(input.SortOrder), pgText(input.PromptAssetKey), input.PromptContent, input.SchemaJSON, input.CardConfigJSON, input.Status, publishedRevisionNo, input.UpdatedBy, input.ID, input.TenantID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -366,17 +394,18 @@ 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,
|
||||
prompt_content = $5,
|
||||
schema_json = $6::jsonb,
|
||||
card_config_json = $7::jsonb,
|
||||
published_revision_no = $8,
|
||||
draft_revision_no = NULL,
|
||||
status = 'active',
|
||||
updated_by = $8,
|
||||
updated_by = $9,
|
||||
updated_at = NOW()
|
||||
WHERE id = $9
|
||||
AND tenant_id = $10
|
||||
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.PublishedRevisionNo, input.UpdatedBy, input.ID, input.TenantID)
|
||||
`, input.Name, pgText(input.PlatformHint), int32(input.SortOrder), pgText(input.PromptAssetKey), input.PromptContent, input.SchemaJSON, input.CardConfigJSON, input.PublishedRevisionNo, input.UpdatedBy, input.ID, input.TenantID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user