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 }