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:
@@ -0,0 +1,80 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user