diff --git a/server/internal/shared/objectstorage/aliyun.go b/server/internal/shared/objectstorage/aliyun.go index 6212ef2..27eb9cc 100644 --- a/server/internal/shared/objectstorage/aliyun.go +++ b/server/internal/shared/objectstorage/aliyun.go @@ -141,6 +141,9 @@ func (c *aliyunClient) GetBytes(ctx context.Context, objectKey string) ([]byte, zap.String("object_key", objectKey), zap.Error(err), ) + if isAliyunObjectNotFound(err) { + return nil, fmt.Errorf("%w: %s", ErrObjectNotFound, objectKey) + } return nil, fmt.Errorf("get aliyun object: %w", err) } defer reader.Close() @@ -158,6 +161,16 @@ func (c *aliyunClient) GetBytes(ctx context.Context, objectKey string) ([]byte, return data, nil } +func isAliyunObjectNotFound(err error) bool { + serviceErr, ok := err.(oss.ServiceError) + if !ok { + return false + } + return serviceErr.StatusCode == 404 || + serviceErr.Code == "NoSuchKey" || + serviceErr.Code == "NoSuchObject" +} + func (c *aliyunClient) Exists(ctx context.Context, objectKey string) (bool, error) { if err := c.Validate(); err != nil { return false, err diff --git a/server/internal/shared/objectstorage/client.go b/server/internal/shared/objectstorage/client.go index d693fc1..28e6078 100644 --- a/server/internal/shared/objectstorage/client.go +++ b/server/internal/shared/objectstorage/client.go @@ -12,6 +12,7 @@ import ( ) var ErrNotConfigured = errors.New("object storage is not configured") +var ErrObjectNotFound = errors.New("object storage object not found") type Client interface { Validate() error diff --git a/server/internal/shared/objectstorage/minio.go b/server/internal/shared/objectstorage/minio.go index 7664473..159dfec 100644 --- a/server/internal/shared/objectstorage/minio.go +++ b/server/internal/shared/objectstorage/minio.go @@ -119,6 +119,9 @@ func (c *minioClient) GetBytes(ctx context.Context, objectKey string) ([]byte, e zap.String("object_key", objectKey), zap.Error(err), ) + if isMinIOObjectNotFound(err) { + return nil, fmt.Errorf("%w: %s", ErrObjectNotFound, objectKey) + } return nil, fmt.Errorf("get minio object: %w", err) } defer reader.Close() @@ -130,11 +133,21 @@ func (c *minioClient) GetBytes(ctx context.Context, objectKey string) ([]byte, e zap.String("object_key", objectKey), zap.Error(err), ) + if isMinIOObjectNotFound(err) { + return nil, fmt.Errorf("%w: %s", ErrObjectNotFound, objectKey) + } return nil, fmt.Errorf("read minio object: %w", err) } return data, nil } +func isMinIOObjectNotFound(err error) bool { + responseErr := minio.ToErrorResponse(err) + return responseErr.StatusCode == 404 || + responseErr.Code == "NoSuchKey" || + responseErr.Code == "NoSuchObject" +} + func (c *minioClient) Exists(ctx context.Context, objectKey string) (bool, error) { if err := c.Validate(); err != nil { return false, err diff --git a/server/internal/tenant/app/cache_support.go b/server/internal/tenant/app/cache_support.go index f850164..f6440f1 100644 --- a/server/internal/tenant/app/cache_support.go +++ b/server/internal/tenant/app/cache_support.go @@ -277,6 +277,10 @@ func kolPromptDetailCachePrefix(tenantID int64) string { return fmt.Sprintf("kol:prompt:detail:%d:", tenantID) } +func kolPromptContentCacheKey(tenantID, promptID int64) string { + return fmt.Sprintf("kol:prompt:content:%d:%d", tenantID, promptID) +} + func kolPromptListCachePrefix(tenantID int64) string { return fmt.Sprintf("kol:prompt:list:%d:", tenantID) } @@ -291,6 +295,7 @@ func kolSubscriptionPromptSchemaCachePrefix(tenantID int64) string { func invalidateKolPromptCaches(ctx context.Context, c sharedcache.Cache, tenantID int64) { deleteCachePrefix(ctx, c, kolPromptDetailCachePrefix(tenantID)) + deleteCachePrefix(ctx, c, fmt.Sprintf("kol:prompt:content:%d:", tenantID)) deleteCachePrefix(ctx, c, kolPromptListCachePrefix(tenantID)) deleteCachePrefix(ctx, c, kolSubscriptionPromptListCachePrefix(tenantID)) deleteCachePrefix(ctx, c, kolSubscriptionPromptSchemaCachePrefix(tenantID)) @@ -299,6 +304,7 @@ func invalidateKolPromptCaches(ctx context.Context, c sharedcache.Cache, tenantI func InvalidateKolPromptCaches(ctx context.Context, c sharedcache.Cache) { deleteCachePrefix(ctx, c, "kol:prompt:detail:") + deleteCachePrefix(ctx, c, "kol:prompt:content:") deleteCachePrefix(ctx, c, "kol:prompt:list:") deleteCachePrefix(ctx, c, "kol:subscription_prompt:list:") deleteCachePrefix(ctx, c, "kol:subscription_prompt:schema:") diff --git a/server/internal/tenant/app/kol_generation_service.go b/server/internal/tenant/app/kol_generation_service.go index 5d7145b..ba658bb 100644 --- a/server/internal/tenant/app/kol_generation_service.go +++ b/server/internal/tenant/app/kol_generation_service.go @@ -9,6 +9,7 @@ import ( "time" "github.com/jackc/pgx/v5/pgxpool" + "golang.org/x/sync/singleflight" "github.com/geo-platform/tenant-api/internal/shared/auth" sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache" @@ -95,6 +96,7 @@ type KolGenerationService struct { asset *KolPromptAsset rabbitMQ *rabbitmq.Client cache sharedcache.Cache + cacheGroup singleflight.Group } func NewKolGenerationService( @@ -223,14 +225,13 @@ func (s *KolGenerationService) enqueue(ctx context.Context, input kolGenerationE return nil, response.ErrInternal(50097, "kol_generation_card_config_decode_failed", err.Error()) } - if prompt.PromptAssetKey == nil || *prompt.PromptAssetKey == "" { - return nil, response.ErrInternal(50098, "kol_generation_prompt_asset_missing", "published prompt content not found") - } - - content, err := s.asset.Get(ctx, *prompt.PromptAssetKey) + content, err := s.loadPromptContent(ctx, prompt) if err != nil { return nil, response.ErrInternal(50098, "kol_generation_prompt_asset_load_failed", err.Error()) } + if strings.TrimSpace(content) == "" { + return nil, response.ErrInternal(50098, "kol_generation_prompt_content_missing", "published prompt content not found") + } renderedPrompt, err := RenderPrompt(content, *schema, variables) if err != nil { @@ -512,13 +513,22 @@ func (s *KolGenerationService) loadAuthorizedPrompt(ctx context.Context, actor a if err != nil { return nil, nil, response.ErrInternal(50110, "kol_prompt_query_failed", err.Error()) } - if prompt == nil || prompt.PromptAssetKey == nil || *prompt.PromptAssetKey == "" { + if prompt == nil || !s.promptHasContent(ctx, prompt) { return nil, nil, response.ErrInternal(50111, "kol_prompt_content_missing", "published prompt content not found") } return row, prompt, nil } +func (s *KolGenerationService) loadPromptContent(ctx context.Context, prompt *repository.KolPrompt) (string, error) { + return loadKolPromptContent(ctx, s.cache, &s.cacheGroup, s.promptRepo, s.asset, prompt) +} + +func (s *KolGenerationService) promptHasContent(ctx context.Context, prompt *repository.KolPrompt) bool { + content, err := s.loadPromptContent(ctx, prompt) + return err == nil && strings.TrimSpace(content) != "" +} + func HandleKolGenerationTaskFailure( ctx context.Context, pool *pgxpool.Pool, diff --git a/server/internal/tenant/app/kol_prompt_content_loader.go b/server/internal/tenant/app/kol_prompt_content_loader.go new file mode 100644 index 0000000..6f1b028 --- /dev/null +++ b/server/internal/tenant/app/kol_prompt_content_loader.go @@ -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 +} diff --git a/server/internal/tenant/app/kol_prompt_service.go b/server/internal/tenant/app/kol_prompt_service.go index 34493d8..a10687b 100644 --- a/server/internal/tenant/app/kol_prompt_service.go +++ b/server/internal/tenant/app/kol_prompt_service.go @@ -8,6 +8,7 @@ import ( "time" "github.com/jackc/pgx/v5/pgxpool" + "golang.org/x/sync/singleflight" "github.com/geo-platform/tenant-api/internal/shared/auth" sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache" @@ -68,6 +69,7 @@ type KolPromptService struct { subRepo repository.KolSubscriptionRepository asset *KolPromptAsset cache sharedcache.Cache + cacheGroup singleflight.Group } func NewKolPromptService( @@ -210,13 +212,6 @@ func (s *KolPromptService) Save(ctx context.Context, actor auth.Actor, promptID sortOrder = *input.SortOrder } - const currentRevisionNo = 1 - - assetKey, err := s.asset.Put(ctx, actor.TenantID, promptID, currentRevisionNo, input.Content) - if err != nil { - return nil, response.ErrInternal(50074, "kol_prompt_asset_store_failed", err.Error()) - } - schemaJSON, err := JSONEncodeSchema(input.Schema) if err != nil { return nil, response.ErrBadRequest(40065, "kol_prompt_schema_invalid", err.Error()) @@ -229,6 +224,7 @@ func (s *KolPromptService) Save(ctx context.Context, actor auth.Actor, promptID publishedRevisionNo := prompt.PublishedRevisionNo if prompt.Status == "active" { + const currentRevisionNo = 1 revNo := currentRevisionNo publishedRevisionNo = &revNo } @@ -239,7 +235,7 @@ func (s *KolPromptService) Save(ctx context.Context, actor auth.Actor, promptID Name: name, PlatformHint: normalizeKolOptionalString(input.PlatformHint), SortOrder: sortOrder, - PromptAssetKey: assetKey, + PromptContent: input.Content, SchemaJSON: schemaJSON, CardConfigJSON: cardConfigJSON, Status: prompt.Status, @@ -283,13 +279,6 @@ func (s *KolPromptService) Publish(ctx context.Context, actor auth.Actor, prompt sortOrder = *input.SortOrder } - const publishedRevisionNo = 1 - - assetKey, err := s.asset.Put(ctx, actor.TenantID, promptID, publishedRevisionNo, input.Content) - if err != nil { - return nil, response.ErrInternal(50074, "kol_prompt_asset_store_failed", err.Error()) - } - schemaJSON, err := JSONEncodeSchema(input.Schema) if err != nil { return nil, response.ErrBadRequest(40065, "kol_prompt_schema_invalid", err.Error()) @@ -309,13 +298,14 @@ func (s *KolPromptService) Publish(ctx context.Context, actor auth.Actor, prompt promptTx := repository.NewKolPromptRepository(tx) subTx := repository.NewKolSubscriptionRepository(tx) + const publishedRevisionNo = 1 if err := promptTx.Publish(ctx, repository.PublishKolPromptInput{ ID: promptID, TenantID: actor.TenantID, Name: name, PlatformHint: normalizeKolOptionalString(input.PlatformHint), SortOrder: sortOrder, - PromptAssetKey: assetKey, + PromptContent: input.Content, SchemaJSON: schemaJSON, CardConfigJSON: cardConfigJSON, PublishedRevisionNo: publishedRevisionNo, @@ -359,14 +349,12 @@ func (s *KolPromptService) Activate(ctx context.Context, actor auth.Actor, promp promptTx := repository.NewKolPromptRepository(tx) subTx := repository.NewKolSubscriptionRepository(tx) - switch { - case prompt.PromptAssetKey != nil && strings.TrimSpace(*prompt.PromptAssetKey) != "": - if err := promptTx.SetPublishedRevision(ctx, actor.TenantID, promptID, 1, actor.UserID); err != nil { - return nil, response.ErrInternal(50090, "kol_prompt_activate_failed", err.Error()) - } - default: + if !s.promptHasContent(ctx, prompt) { return nil, errKolPromptContentEmpty } + if err := promptTx.SetPublishedRevision(ctx, actor.TenantID, promptID, 1, actor.UserID); err != nil { + return nil, response.ErrInternal(50090, "kol_prompt_activate_failed", err.Error()) + } if err := s.grantPromptToActiveSubscribers(ctx, subTx, pkg.ID, promptID); err != nil { return nil, err @@ -484,7 +472,7 @@ func (s *KolPromptService) buildPromptDetail(ctx context.Context, prompt *reposi UpdatedAt: prompt.UpdatedAt, } - if len(prompt.SchemaJSON) == 0 && len(prompt.CardConfigJSON) == 0 && (prompt.PromptAssetKey == nil || strings.TrimSpace(*prompt.PromptAssetKey) == "") { + if len(prompt.SchemaJSON) == 0 && len(prompt.CardConfigJSON) == 0 && !includeContent { return detail, nil } @@ -500,8 +488,8 @@ func (s *KolPromptService) buildPromptDetail(ctx context.Context, prompt *reposi detail.LatestSchemaJSON = schema detail.LatestCardConfigJSON = cardConfig - if includeContent && prompt.PromptAssetKey != nil && strings.TrimSpace(*prompt.PromptAssetKey) != "" { - content, err := s.asset.Get(ctx, *prompt.PromptAssetKey) + if includeContent { + content, err := s.loadPromptContent(ctx, prompt) if err != nil { return nil, response.ErrInternal(50087, "kol_prompt_asset_load_failed", err.Error()) } @@ -511,6 +499,15 @@ func (s *KolPromptService) buildPromptDetail(ctx context.Context, prompt *reposi return detail, nil } +func (s *KolPromptService) promptHasContent(ctx context.Context, prompt *repository.KolPrompt) bool { + content, err := s.loadPromptContent(ctx, prompt) + return err == nil && strings.TrimSpace(content) != "" +} + +func (s *KolPromptService) loadPromptContent(ctx context.Context, prompt *repository.KolPrompt) (string, error) { + return loadKolPromptContent(ctx, s.cache, &s.cacheGroup, s.promptRepo, s.asset, prompt) +} + func (s *KolPromptService) grantPromptToActiveSubscribers( ctx context.Context, subRepo repository.KolSubscriptionRepository, diff --git a/server/internal/tenant/app/schedule_task_service.go b/server/internal/tenant/app/schedule_task_service.go index e830111..146f76e 100644 --- a/server/internal/tenant/app/schedule_task_service.go +++ b/server/internal/tenant/app/schedule_task_service.go @@ -24,14 +24,15 @@ import ( ) type ScheduleTaskService struct { - pool *pgxpool.Pool - auditLogs *auditlog.AsyncWriter - cache sharedcache.Cache - cacheGroup singleflight.Group + pool *pgxpool.Pool + auditLogs *auditlog.AsyncWriter + promptAsset *KolPromptAsset + cache sharedcache.Cache + cacheGroup singleflight.Group } -func NewScheduleTaskService(pool *pgxpool.Pool, auditLogs *auditlog.AsyncWriter) *ScheduleTaskService { - return &ScheduleTaskService{pool: pool, auditLogs: auditLogs} +func NewScheduleTaskService(pool *pgxpool.Pool, auditLogs *auditlog.AsyncWriter, promptAsset *KolPromptAsset) *ScheduleTaskService { + return &ScheduleTaskService{pool: pool, auditLogs: auditLogs, promptAsset: promptAsset} } func (s *ScheduleTaskService) WithCache(c sharedcache.Cache) *ScheduleTaskService { @@ -1006,7 +1007,14 @@ func (s *ScheduleTaskService) validateKolScheduleGenerationInput(ctx context.Con if err != nil { return input, response.ErrInternal(50110, "kol_prompt_query_failed", err.Error()) } - if prompt == nil || prompt.PromptAssetKey == nil || *prompt.PromptAssetKey == "" { + if prompt == nil { + return input, response.ErrInternal(50111, "kol_prompt_content_missing", "published prompt content not found") + } + content, err := loadKolPromptContent(ctx, s.cache, &s.cacheGroup, repository.NewKolPromptRepository(s.pool), s.promptAsset, prompt) + if err != nil { + return input, response.ErrInternal(50110, "kol_prompt_query_failed", err.Error()) + } + if strings.TrimSpace(content) == "" { return input, response.ErrInternal(50111, "kol_prompt_content_missing", "published prompt content not found") } schema, err := decodeKolSchema(prompt.SchemaJSON) diff --git a/server/internal/tenant/repository/kol_prompt_repo.go b/server/internal/tenant/repository/kol_prompt_repo.go index cd3f081..ad8b937 100644 --- a/server/internal/tenant/repository/kol_prompt_repo.go +++ b/server/internal/tenant/repository/kol_prompt_repo.go @@ -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 } diff --git a/server/internal/tenant/transport/schedule_task_handler.go b/server/internal/tenant/transport/schedule_task_handler.go index 628b718..288f439 100644 --- a/server/internal/tenant/transport/schedule_task_handler.go +++ b/server/internal/tenant/transport/schedule_task_handler.go @@ -16,7 +16,7 @@ type ScheduleTaskHandler struct { } func NewScheduleTaskHandler(a *bootstrap.App) *ScheduleTaskHandler { - return &ScheduleTaskHandler{svc: app.NewScheduleTaskService(a.DB, a.AuditLogs).WithCache(a.Cache)} + return &ScheduleTaskHandler{svc: app.NewScheduleTaskService(a.DB, a.AuditLogs, a.KolPromptAsset).WithCache(a.Cache)} } func (h *ScheduleTaskHandler) List(c *gin.Context) { diff --git a/server/migrations/20260524103000_store_kol_prompt_content_in_db.down.sql b/server/migrations/20260524103000_store_kol_prompt_content_in_db.down.sql new file mode 100644 index 0000000..a5fbb59 --- /dev/null +++ b/server/migrations/20260524103000_store_kol_prompt_content_in_db.down.sql @@ -0,0 +1,5 @@ +ALTER TABLE kol_prompt_revisions + DROP COLUMN IF EXISTS prompt_content; + +ALTER TABLE kol_prompts + DROP COLUMN IF EXISTS prompt_content; diff --git a/server/migrations/20260524103000_store_kol_prompt_content_in_db.up.sql b/server/migrations/20260524103000_store_kol_prompt_content_in_db.up.sql new file mode 100644 index 0000000..6cb34e3 --- /dev/null +++ b/server/migrations/20260524103000_store_kol_prompt_content_in_db.up.sql @@ -0,0 +1,5 @@ +ALTER TABLE kol_prompts + ADD COLUMN IF NOT EXISTS prompt_content TEXT; + +ALTER TABLE kol_prompt_revisions + ADD COLUMN IF NOT EXISTS prompt_content TEXT;