Files
geo/server/internal/tenant/app/kol_prompt_content_loader.go
T
root db95b8e4ee 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>
2026-05-24 01:47:51 +08:00

81 lines
1.8 KiB
Go

package app
import (
"context"
"errors"
"strings"
"golang.org/x/sync/singleflight"
sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache"
"github.com/geo-platform/tenant-api/internal/shared/objectstorage"
"github.com/geo-platform/tenant-api/internal/tenant/repository"
)
func loadKolPromptContent(
ctx context.Context,
cache sharedcache.Cache,
group *singleflight.Group,
promptRepo repository.KolPromptRepository,
asset *KolPromptAsset,
prompt *repository.KolPrompt,
) (string, error) {
if prompt == nil {
return "", nil
}
loader := func(loadCtx context.Context) (string, error) {
return loadKolPromptContentUncached(loadCtx, promptRepo, asset, prompt)
}
if cache == nil || group == nil {
return loader(ctx)
}
return sharedcache.LoadJSON(
ctx,
cache,
group,
kolPromptContentCacheKey(prompt.TenantID, prompt.ID),
defaultCacheTTL(),
loader,
)
}
func loadKolPromptContentUncached(
ctx context.Context,
promptRepo repository.KolPromptRepository,
asset *KolPromptAsset,
prompt *repository.KolPrompt,
) (string, error) {
if prompt == nil {
return "", nil
}
if promptRepo != nil {
content, err := promptRepo.GetContentByID(ctx, prompt.TenantID, prompt.ID)
if err != nil {
return "", err
}
if content != nil && strings.TrimSpace(*content) != "" {
return *content, nil
}
}
if prompt.PromptContent != nil && strings.TrimSpace(*prompt.PromptContent) != "" {
return *prompt.PromptContent, nil
}
if prompt.PromptAssetKey == nil || strings.TrimSpace(*prompt.PromptAssetKey) == "" || asset == nil {
return "", nil
}
content, err := asset.Get(ctx, strings.TrimSpace(*prompt.PromptAssetKey))
if err != nil {
if errors.Is(err, objectstorage.ErrObjectNotFound) || errors.Is(err, objectstorage.ErrNotConfigured) {
return "", nil
}
return "", err
}
return content, nil
}