feat(kol): repository layer for all KOL tables

This commit is contained in:
2026-04-17 08:17:22 +08:00
parent 2c58fc591e
commit 3d4842e983
8 changed files with 1341 additions and 0 deletions
@@ -58,6 +58,14 @@ func timeFromTimestamp(value pgtype.Timestamptz) time.Time {
return value.Time
}
func optionalTime(value pgtype.Timestamptz) *time.Time {
if !value.Valid {
return nil
}
t := value.Time
return &t
}
func pgText(value *string) pgtype.Text {
if value == nil {
return pgtype.Text{}
@@ -0,0 +1,118 @@
package repository
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type KolAssistTask struct {
ID string
TenantID int64
KolProfileID int64
PromptID *int64
OperatorID int64
TaskType string
Status string
RequestJSON []byte
ResultJSON []byte
ErrorMessage *string
StartedAt *time.Time
CompletedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type CreateKolAssistInput struct {
ID string
TenantID int64
KolProfileID int64
PromptID *int64
OperatorID int64
TaskType string
RequestJSON []byte
}
type KolAssistRepository interface {
Create(ctx context.Context, input CreateKolAssistInput) error
Get(ctx context.Context, tenantID int64, id string) (*KolAssistTask, error)
MarkStarted(ctx context.Context, tenantID int64, id string) error
MarkCompleted(ctx context.Context, tenantID int64, id string, resultJSON []byte) error
MarkFailed(ctx context.Context, tenantID int64, id string, errorMsg string) error
}
type kolAssistRepository struct {
q generated.Querier
}
func NewKolAssistRepository(db generated.DBTX) KolAssistRepository {
return &kolAssistRepository{q: newQuerier(db)}
}
func (r *kolAssistRepository) Create(ctx context.Context, input CreateKolAssistInput) error {
return r.q.CreateKolAssistTask(ctx, generated.CreateKolAssistTaskParams{
ID: input.ID,
TenantID: input.TenantID,
KolProfileID: input.KolProfileID,
PromptID: pgInt8(input.PromptID),
OperatorID: input.OperatorID,
TaskType: input.TaskType,
RequestJson: input.RequestJSON,
})
}
func (r *kolAssistRepository) Get(ctx context.Context, tenantID int64, id string) (*KolAssistTask, error) {
row, err := r.q.GetKolAssistTask(ctx, generated.GetKolAssistTaskParams{
ID: id,
TenantID: tenantID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &KolAssistTask{
ID: row.ID,
TenantID: row.TenantID,
KolProfileID: row.KolProfileID,
PromptID: nullableInt64(row.PromptID),
OperatorID: row.OperatorID,
TaskType: row.TaskType,
Status: row.Status,
RequestJSON: row.RequestJson,
ResultJSON: row.ResultJson,
ErrorMessage: nullableText(row.ErrorMessage),
StartedAt: optionalTime(row.StartedAt),
CompletedAt: optionalTime(row.CompletedAt),
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}, nil
}
func (r *kolAssistRepository) MarkStarted(ctx context.Context, tenantID int64, id string) error {
return r.q.MarkKolAssistStarted(ctx, generated.MarkKolAssistStartedParams{
ID: id,
TenantID: tenantID,
})
}
func (r *kolAssistRepository) MarkCompleted(ctx context.Context, tenantID int64, id string, resultJSON []byte) error {
return r.q.MarkKolAssistCompleted(ctx, generated.MarkKolAssistCompletedParams{
ResultJson: resultJSON,
ID: id,
TenantID: tenantID,
})
}
func (r *kolAssistRepository) MarkFailed(ctx context.Context, tenantID int64, id string, errorMsg string) error {
return r.q.MarkKolAssistFailed(ctx, generated.MarkKolAssistFailedParams{
ErrorMessage: pgText(&errorMsg),
ID: id,
TenantID: tenantID,
})
}
@@ -0,0 +1,145 @@
package repository
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type PublishedKolPackage struct {
ID int64
TenantID int64
KolProfileID int64
Name string
Description *string
CoverURL *string
Industry *string
Tags []byte
PriceNote *string
Status string
CreatedAt time.Time
UpdatedAt time.Time
KolDisplayName string
KolAvatarURL *string
KolBio *string
PromptCount int64
SubscriberCount int64
}
type PublicKolPrompt struct {
ID int64
Name string
PlatformHint *string
Status string
PublishedRevisionNo *int
CreatedAt time.Time
}
type ListPublishedFilter struct {
Industry *string
Keyword *string
Offset int
Limit int
}
type KolMarketplaceRepository interface {
ListPublished(ctx context.Context, filter ListPublishedFilter) ([]PublishedKolPackage, error)
GetPublished(ctx context.Context, id int64) (*PublishedKolPackage, error)
ListPublicPrompts(ctx context.Context, packageID int64) ([]PublicKolPrompt, error)
}
type kolMarketplaceRepository struct {
q generated.Querier
}
func NewKolMarketplaceRepository(db generated.DBTX) KolMarketplaceRepository {
return &kolMarketplaceRepository{q: newQuerier(db)}
}
func (r *kolMarketplaceRepository) ListPublished(ctx context.Context, filter ListPublishedFilter) ([]PublishedKolPackage, error) {
limit := filter.Limit
if limit <= 0 {
limit = 20
}
rows, err := r.q.ListPublishedKolPackages(ctx, generated.ListPublishedKolPackagesParams{
Industry: pgText(filter.Industry),
Keyword: pgText(filter.Keyword),
Offset: int32(filter.Offset),
Limit: int32(limit),
})
if err != nil {
return nil, err
}
result := make([]PublishedKolPackage, len(rows))
for i, row := range rows {
result[i] = PublishedKolPackage{
ID: row.ID,
TenantID: row.TenantID,
KolProfileID: row.KolProfileID,
Name: row.Name,
Description: nullableText(row.Description),
CoverURL: nullableText(row.CoverUrl),
Industry: nullableText(row.Industry),
Tags: row.Tags,
PriceNote: nullableText(row.PriceNote),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
KolDisplayName: row.KolDisplayName,
KolAvatarURL: nullableText(row.KolAvatarUrl),
PromptCount: row.PromptCount,
SubscriberCount: row.SubscriberCount,
}
}
return result, nil
}
func (r *kolMarketplaceRepository) GetPublished(ctx context.Context, id int64) (*PublishedKolPackage, error) {
row, err := r.q.GetPublishedKolPackage(ctx, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &PublishedKolPackage{
ID: row.ID,
TenantID: row.TenantID,
KolProfileID: row.KolProfileID,
Name: row.Name,
Description: nullableText(row.Description),
CoverURL: nullableText(row.CoverUrl),
Industry: nullableText(row.Industry),
Tags: row.Tags,
PriceNote: nullableText(row.PriceNote),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
KolDisplayName: row.KolDisplayName,
KolAvatarURL: nullableText(row.KolAvatarUrl),
KolBio: nullableText(row.KolBio),
}, nil
}
func (r *kolMarketplaceRepository) ListPublicPrompts(ctx context.Context, packageID int64) ([]PublicKolPrompt, error) {
rows, err := r.q.ListPublicPromptsForPackage(ctx, packageID)
if err != nil {
return nil, err
}
result := make([]PublicKolPrompt, len(rows))
for i, row := range rows {
result[i] = PublicKolPrompt{
ID: row.ID,
Name: row.Name,
PlatformHint: nullableText(row.PlatformHint),
Status: row.Status,
PublishedRevisionNo: nullableIntFromInt4(row.PublishedRevisionNo),
CreatedAt: timeFromTimestamp(row.CreatedAt),
}
}
return result, nil
}
@@ -0,0 +1,192 @@
package repository
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type KolPackage struct {
ID int64
TenantID int64
KolProfileID int64
Name string
Description *string
CoverURL *string
Industry *string
Tags []byte
PriceNote *string
Status string
CreatedAt time.Time
UpdatedAt time.Time
}
type CreateKolPackageInput struct {
TenantID int64
KolProfileID int64
Name string
Description *string
CoverURL *string
Industry *string
Tags []byte
PriceNote *string
}
type UpdateKolPackageInput struct {
ID int64
Name string
Description *string
CoverURL *string
Industry *string
Tags []byte
PriceNote *string
}
type KolPackageRepository interface {
Create(ctx context.Context, input CreateKolPackageInput) (*KolPackage, error)
GetByID(ctx context.Context, tenantID, id int64) (*KolPackage, error)
ListByProfile(ctx context.Context, tenantID, profileID int64) ([]KolPackage, error)
ListPackageIDsByProfile(ctx context.Context, tenantID, profileID int64) ([]int64, error)
Update(ctx context.Context, tenantID int64, input UpdateKolPackageInput) error
UpdateStatus(ctx context.Context, tenantID, id int64, status string) error
SoftDelete(ctx context.Context, tenantID, id int64) error
}
type kolPackageRepository struct {
q generated.Querier
}
func NewKolPackageRepository(db generated.DBTX) KolPackageRepository {
return &kolPackageRepository{q: newQuerier(db)}
}
func (r *kolPackageRepository) Create(ctx context.Context, input CreateKolPackageInput) (*KolPackage, error) {
row, err := r.q.CreateKolPackage(ctx, generated.CreateKolPackageParams{
TenantID: input.TenantID,
KolProfileID: input.KolProfileID,
Name: input.Name,
Description: pgText(input.Description),
CoverUrl: pgText(input.CoverURL),
Industry: pgText(input.Industry),
Tags: input.Tags,
PriceNote: pgText(input.PriceNote),
})
if err != nil {
return nil, err
}
return &KolPackage{
ID: row.ID,
TenantID: row.TenantID,
KolProfileID: row.KolProfileID,
Name: row.Name,
Description: nullableText(row.Description),
CoverURL: nullableText(row.CoverUrl),
Industry: nullableText(row.Industry),
Tags: row.Tags,
PriceNote: nullableText(row.PriceNote),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}, nil
}
func (r *kolPackageRepository) GetByID(ctx context.Context, tenantID, id int64) (*KolPackage, error) {
row, err := r.q.GetKolPackageByID(ctx, generated.GetKolPackageByIDParams{
ID: id,
TenantID: tenantID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &KolPackage{
ID: row.ID,
TenantID: row.TenantID,
KolProfileID: row.KolProfileID,
Name: row.Name,
Description: nullableText(row.Description),
CoverURL: nullableText(row.CoverUrl),
Industry: nullableText(row.Industry),
Tags: row.Tags,
PriceNote: nullableText(row.PriceNote),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}, nil
}
func (r *kolPackageRepository) ListByProfile(ctx context.Context, tenantID, profileID int64) ([]KolPackage, error) {
rows, err := r.q.ListKolPackagesByProfile(ctx, generated.ListKolPackagesByProfileParams{
KolProfileID: profileID,
TenantID: tenantID,
})
if err != nil {
return nil, err
}
result := make([]KolPackage, len(rows))
for i, row := range rows {
result[i] = KolPackage{
ID: row.ID,
TenantID: row.TenantID,
KolProfileID: row.KolProfileID,
Name: row.Name,
Description: nullableText(row.Description),
CoverURL: nullableText(row.CoverUrl),
Industry: nullableText(row.Industry),
Tags: row.Tags,
PriceNote: nullableText(row.PriceNote),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}
}
return result, nil
}
func (r *kolPackageRepository) ListPackageIDsByProfile(ctx context.Context, tenantID, profileID int64) ([]int64, error) {
ids, err := r.q.ListKolPackageIDsByProfile(ctx, generated.ListKolPackageIDsByProfileParams{
KolProfileID: profileID,
TenantID: tenantID,
})
if err != nil {
return nil, err
}
if ids == nil {
return []int64{}, nil
}
return ids, nil
}
func (r *kolPackageRepository) Update(ctx context.Context, tenantID int64, input UpdateKolPackageInput) error {
return r.q.UpdateKolPackage(ctx, generated.UpdateKolPackageParams{
Name: input.Name,
Description: pgText(input.Description),
CoverUrl: pgText(input.CoverURL),
Industry: pgText(input.Industry),
Tags: input.Tags,
PriceNote: pgText(input.PriceNote),
ID: input.ID,
TenantID: tenantID,
})
}
func (r *kolPackageRepository) UpdateStatus(ctx context.Context, tenantID, id int64, status string) error {
return r.q.UpdateKolPackageStatus(ctx, generated.UpdateKolPackageStatusParams{
Status: status,
ID: id,
TenantID: tenantID,
})
}
func (r *kolPackageRepository) SoftDelete(ctx context.Context, tenantID, id int64) error {
return r.q.SoftDeleteKolPackage(ctx, generated.SoftDeleteKolPackageParams{
ID: id,
TenantID: tenantID,
})
}
@@ -0,0 +1,102 @@
package repository
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type KolProfile struct {
ID int64
TenantID int64
UserID int64
DisplayName string
Bio *string
AvatarURL *string
MarketEnabled bool
Status string
CreatedAt time.Time
UpdatedAt time.Time
}
type UpdateKolProfileInput struct {
ID int64
DisplayName string
Bio *string
AvatarURL *string
}
type KolProfileRepository interface {
GetByUser(ctx context.Context, tenantID, userID int64) (*KolProfile, error)
GetByID(ctx context.Context, id int64) (*KolProfile, error)
Update(ctx context.Context, tenantID int64, input UpdateKolProfileInput) error
}
type kolProfileRepository struct {
q generated.Querier
}
func NewKolProfileRepository(db generated.DBTX) KolProfileRepository {
return &kolProfileRepository{q: newQuerier(db)}
}
func (r *kolProfileRepository) GetByUser(ctx context.Context, tenantID, userID int64) (*KolProfile, error) {
row, err := r.q.GetKolProfileByUser(ctx, generated.GetKolProfileByUserParams{
UserID: userID,
TenantID: tenantID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &KolProfile{
ID: row.ID,
TenantID: row.TenantID,
UserID: row.UserID,
DisplayName: row.DisplayName,
Bio: nullableText(row.Bio),
AvatarURL: nullableText(row.AvatarUrl),
MarketEnabled: row.MarketEnabled,
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}, nil
}
func (r *kolProfileRepository) GetByID(ctx context.Context, id int64) (*KolProfile, error) {
row, err := r.q.GetKolProfileByID(ctx, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &KolProfile{
ID: row.ID,
TenantID: row.TenantID,
UserID: row.UserID,
DisplayName: row.DisplayName,
Bio: nullableText(row.Bio),
AvatarURL: nullableText(row.AvatarUrl),
MarketEnabled: row.MarketEnabled,
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}, nil
}
func (r *kolProfileRepository) Update(ctx context.Context, tenantID int64, input UpdateKolProfileInput) error {
return r.q.UpdateKolProfile(ctx, generated.UpdateKolProfileParams{
DisplayName: input.DisplayName,
Bio: pgText(input.Bio),
AvatarUrl: pgText(input.AvatarURL),
ID: input.ID,
TenantID: tenantID,
})
}
@@ -0,0 +1,323 @@
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
CreatedBy int64
UpdatedBy int64
CreatedAt time.Time
UpdatedAt time.Time
}
type KolPromptRevision struct {
ID int64
TenantID int64
PromptID int64
RevisionNo int
PromptAssetKey string
SchemaJSON []byte
CardConfigJSON []byte
CreatedBy int64
CreatedAt 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 CreateKolPromptRevisionInput struct {
TenantID int64
PromptID int64
RevisionNo int
PromptAssetKey string
SchemaJSON []byte
CardConfigJSON []byte
CreatedBy 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
SetDraftRevision(ctx context.Context, tenantID, promptID int64, revNo int, updatedBy int64) 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
CreateRevision(ctx context.Context, input CreateKolPromptRevisionInput) (*KolPromptRevision, error)
GetRevision(ctx context.Context, tenantID, promptID int64, revNo int) (*KolPromptRevision, error)
NextRevisionNo(ctx context.Context, tenantID, promptID int64) (int, error)
}
type kolPromptRepository struct {
q generated.Querier
}
func NewKolPromptRepository(db generated.DBTX) KolPromptRepository {
return &kolPromptRepository{q: newQuerier(db)}
}
func mapKolPrompt(row generated.GetKolPromptByIDRow) *KolPrompt {
return &KolPrompt{
ID: row.ID,
TenantID: row.TenantID,
PackageID: row.PackageID,
Name: row.Name,
PlatformHint: nullableText(row.PlatformHint),
Status: row.Status,
SortOrder: int(row.SortOrder),
PublishedRevisionNo: nullableIntFromInt4(row.PublishedRevisionNo),
DraftRevisionNo: nullableIntFromInt4(row.DraftRevisionNo),
CreatedBy: row.CreatedBy,
UpdatedBy: row.UpdatedBy,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}
}
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 &KolPrompt{
ID: row.ID,
TenantID: row.TenantID,
PackageID: row.PackageID,
Name: row.Name,
PlatformHint: nullableText(row.PlatformHint),
Status: row.Status,
SortOrder: int(row.SortOrder),
PublishedRevisionNo: nullableIntFromInt4(row.PublishedRevisionNo),
DraftRevisionNo: nullableIntFromInt4(row.DraftRevisionNo),
CreatedBy: row.CreatedBy,
UpdatedBy: row.UpdatedBy,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}, nil
}
func (r *kolPromptRepository) GetByID(ctx context.Context, tenantID, id int64) (*KolPrompt, error) {
row, err := r.q.GetKolPromptByID(ctx, generated.GetKolPromptByIDParams{
ID: id,
TenantID: tenantID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return mapKolPrompt(row), nil
}
func (r *kolPromptRepository) ListByPackage(ctx context.Context, tenantID, packageID int64) ([]KolPrompt, error) {
rows, err := r.q.ListKolPromptsByPackage(ctx, generated.ListKolPromptsByPackageParams{
PackageID: packageID,
TenantID: tenantID,
})
if err != nil {
return nil, err
}
result := make([]KolPrompt, len(rows))
for i, row := range rows {
result[i] = KolPrompt{
ID: row.ID,
TenantID: row.TenantID,
PackageID: row.PackageID,
Name: row.Name,
PlatformHint: nullableText(row.PlatformHint),
Status: row.Status,
SortOrder: int(row.SortOrder),
PublishedRevisionNo: nullableIntFromInt4(row.PublishedRevisionNo),
DraftRevisionNo: nullableIntFromInt4(row.DraftRevisionNo),
CreatedBy: row.CreatedBy,
UpdatedBy: row.UpdatedBy,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}
}
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) SetDraftRevision(ctx context.Context, tenantID, promptID int64, revNo int, updatedBy int64) error {
return r.q.UpdateKolPromptDraftRevision(ctx, generated.UpdateKolPromptDraftRevisionParams{
RevisionNo: pgtype.Int4{Int32: int32(revNo), Valid: true},
UpdatedBy: updatedBy,
ID: promptID,
TenantID: tenantID,
})
}
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 (r *kolPromptRepository) CreateRevision(ctx context.Context, input CreateKolPromptRevisionInput) (*KolPromptRevision, error) {
row, err := r.q.CreateKolPromptRevision(ctx, generated.CreateKolPromptRevisionParams{
TenantID: input.TenantID,
PromptID: input.PromptID,
RevisionNo: int32(input.RevisionNo),
PromptAssetKey: input.PromptAssetKey,
SchemaJson: input.SchemaJSON,
CardConfigJson: input.CardConfigJSON,
CreatedBy: input.CreatedBy,
})
if err != nil {
return nil, err
}
return &KolPromptRevision{
ID: row.ID,
TenantID: input.TenantID,
PromptID: row.PromptID,
RevisionNo: int(row.RevisionNo),
PromptAssetKey: row.PromptAssetKey,
SchemaJSON: row.SchemaJson,
CardConfigJSON: row.CardConfigJson,
CreatedBy: row.CreatedBy,
CreatedAt: timeFromTimestamp(row.CreatedAt),
}, nil
}
func (r *kolPromptRepository) GetRevision(ctx context.Context, tenantID, promptID int64, revNo int) (*KolPromptRevision, error) {
row, err := r.q.GetKolPromptRevision(ctx, generated.GetKolPromptRevisionParams{
PromptID: promptID,
RevisionNo: int32(revNo),
TenantID: tenantID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &KolPromptRevision{
ID: row.ID,
TenantID: tenantID,
PromptID: row.PromptID,
RevisionNo: int(row.RevisionNo),
PromptAssetKey: row.PromptAssetKey,
SchemaJSON: row.SchemaJson,
CardConfigJSON: row.CardConfigJson,
CreatedBy: row.CreatedBy,
CreatedAt: timeFromTimestamp(row.CreatedAt),
}, nil
}
func (r *kolPromptRepository) NextRevisionNo(ctx context.Context, tenantID, promptID int64) (int, error) {
next, err := r.q.NextKolPromptRevisionNo(ctx, generated.NextKolPromptRevisionNoParams{
PromptID: promptID,
TenantID: tenantID,
})
if err != nil {
return 0, err
}
return int(next), nil
}
func nullableIntFromInt4(value pgtype.Int4) *int {
if !value.Valid {
return nil
}
v := int(value.Int32)
return &v
}
@@ -0,0 +1,255 @@
package repository
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type KolSubscription struct {
ID int64
TenantID int64
PackageID int64
Status string
ApprovedBy *int64
StartAt *time.Time
EndAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type KolSubscriptionPrompt struct {
ID int64
TenantID int64
SubscriptionID int64
PackageID int64
PromptID int64
Status string
GrantedAt *time.Time
RevokedAt *time.Time
PromptName string
PlatformHint *string
PackageName string
PublishedRevisionNo *int
// Fields below only populated by GetSubscriptionPromptByID.
SubscriptionStatus string
SubscriptionEndAt *time.Time
PromptStatus string
PackageStatus string
}
type CreateAccessRowInput struct {
TenantID int64
SubscriptionID int64
PackageID int64
PromptID int64
}
type KolSubscriptionRepository interface {
Create(ctx context.Context, tenantID, packageID int64) (*KolSubscription, error)
GetByID(ctx context.Context, tenantID, id int64) (*KolSubscription, error)
GetActiveForTenant(ctx context.Context, tenantID, packageID int64) (*KolSubscription, error)
ListByTenant(ctx context.Context, tenantID int64) ([]KolSubscription, error)
ListActiveSubscribersForPackage(ctx context.Context, packageID int64) ([]KolSubscription, error)
Approve(ctx context.Context, id, tenantID, approvedBy int64, endAt *time.Time) error
RevokeByTenant(ctx context.Context, id, tenantID int64) error
CreateAccessRow(ctx context.Context, input CreateAccessRowInput) error
ListSubscriptionPromptsByTenant(ctx context.Context, tenantID int64) ([]KolSubscriptionPrompt, error)
GetSubscriptionPromptByID(ctx context.Context, tenantID, id int64) (*KolSubscriptionPrompt, error)
RevokeAccessRowsBySubscription(ctx context.Context, subscriptionID, tenantID int64) error
RevokeAccessRowsByPrompt(ctx context.Context, promptID, tenantID int64) error
}
type kolSubscriptionRepository struct {
q generated.Querier
}
func NewKolSubscriptionRepository(db generated.DBTX) KolSubscriptionRepository {
return &kolSubscriptionRepository{q: newQuerier(db)}
}
func mapKolSubscription(row generated.KolSubscription) KolSubscription {
startAt := optionalTime(row.StartAt)
endAt := optionalTime(row.EndAt)
return KolSubscription{
ID: row.ID,
TenantID: row.TenantID,
PackageID: row.PackageID,
Status: row.Status,
ApprovedBy: nullableInt64(row.ApprovedBy),
StartAt: startAt,
EndAt: endAt,
CreatedAt: timeFromTimestamp(row.CreatedAt),
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
}
}
func (r *kolSubscriptionRepository) Create(ctx context.Context, tenantID, packageID int64) (*KolSubscription, error) {
row, err := r.q.CreateKolSubscription(ctx, generated.CreateKolSubscriptionParams{
TenantID: tenantID,
PackageID: packageID,
})
if err != nil {
return nil, err
}
sub := mapKolSubscription(row)
return &sub, nil
}
func (r *kolSubscriptionRepository) GetByID(ctx context.Context, tenantID, id int64) (*KolSubscription, error) {
row, err := r.q.GetKolSubscriptionByID(ctx, generated.GetKolSubscriptionByIDParams{
ID: id,
TenantID: tenantID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
sub := mapKolSubscription(row)
return &sub, nil
}
func (r *kolSubscriptionRepository) GetActiveForTenant(ctx context.Context, tenantID, packageID int64) (*KolSubscription, error) {
row, err := r.q.GetActiveKolSubscription(ctx, generated.GetActiveKolSubscriptionParams{
TenantID: tenantID,
PackageID: packageID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
sub := mapKolSubscription(row)
return &sub, nil
}
func (r *kolSubscriptionRepository) ListByTenant(ctx context.Context, tenantID int64) ([]KolSubscription, error) {
rows, err := r.q.ListKolSubscriptions(ctx, tenantID)
if err != nil {
return nil, err
}
result := make([]KolSubscription, len(rows))
for i, row := range rows {
result[i] = mapKolSubscription(row)
}
return result, nil
}
func (r *kolSubscriptionRepository) ListActiveSubscribersForPackage(ctx context.Context, packageID int64) ([]KolSubscription, error) {
rows, err := r.q.ListActiveSubscribersForPackage(ctx, packageID)
if err != nil {
return nil, err
}
result := make([]KolSubscription, len(rows))
for i, row := range rows {
result[i] = mapKolSubscription(row)
}
return result, nil
}
func (r *kolSubscriptionRepository) Approve(ctx context.Context, id, tenantID, approvedBy int64, endAt *time.Time) error {
return r.q.ApproveKolSubscription(ctx, generated.ApproveKolSubscriptionParams{
ApprovedBy: pgInt8(&approvedBy),
EndAt: pgTimestamp(endAt),
ID: id,
TenantID: tenantID,
})
}
func (r *kolSubscriptionRepository) RevokeByTenant(ctx context.Context, id, tenantID int64) error {
return r.q.RevokeKolSubscription(ctx, generated.RevokeKolSubscriptionParams{
ID: id,
TenantID: tenantID,
})
}
func (r *kolSubscriptionRepository) CreateAccessRow(ctx context.Context, input CreateAccessRowInput) error {
return r.q.CreateSubscriptionPrompt(ctx, generated.CreateSubscriptionPromptParams{
TenantID: input.TenantID,
SubscriptionID: input.SubscriptionID,
PackageID: input.PackageID,
PromptID: input.PromptID,
})
}
func (r *kolSubscriptionRepository) ListSubscriptionPromptsByTenant(ctx context.Context, tenantID int64) ([]KolSubscriptionPrompt, error) {
rows, err := r.q.ListSubscriptionPromptsByTenant(ctx, tenantID)
if err != nil {
return nil, err
}
result := make([]KolSubscriptionPrompt, len(rows))
for i, row := range rows {
grantedAt := optionalTime(row.GrantedAt)
revokedAt := optionalTime(row.RevokedAt)
result[i] = KolSubscriptionPrompt{
ID: row.ID,
TenantID: row.TenantID,
SubscriptionID: row.SubscriptionID,
PackageID: row.PackageID,
PromptID: row.PromptID,
Status: row.Status,
GrantedAt: grantedAt,
RevokedAt: revokedAt,
PromptName: row.PromptName,
PlatformHint: nullableText(row.PlatformHint),
PackageName: row.PackageName,
PublishedRevisionNo: nullableIntFromInt4(row.PublishedRevisionNo),
}
}
return result, nil
}
func (r *kolSubscriptionRepository) GetSubscriptionPromptByID(ctx context.Context, tenantID, id int64) (*KolSubscriptionPrompt, error) {
row, err := r.q.GetSubscriptionPromptByID(ctx, generated.GetSubscriptionPromptByIDParams{
ID: id,
TenantID: tenantID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
grantedAt := optionalTime(row.GrantedAt)
revokedAt := optionalTime(row.RevokedAt)
subEndAt := optionalTime(row.SubscriptionEndAt)
return &KolSubscriptionPrompt{
ID: row.ID,
TenantID: row.TenantID,
SubscriptionID: row.SubscriptionID,
PackageID: row.PackageID,
PromptID: row.PromptID,
Status: row.Status,
GrantedAt: grantedAt,
RevokedAt: revokedAt,
PromptName: row.PromptName,
PlatformHint: nullableText(row.PlatformHint),
PackageName: row.PackageName,
PublishedRevisionNo: nullableIntFromInt4(row.PublishedRevisionNo),
SubscriptionStatus: row.SubscriptionStatus,
SubscriptionEndAt: subEndAt,
PromptStatus: row.PromptStatus,
PackageStatus: row.PackageStatus,
}, nil
}
func (r *kolSubscriptionRepository) RevokeAccessRowsBySubscription(ctx context.Context, subscriptionID, tenantID int64) error {
return r.q.RevokeSubscriptionPromptsBySubscription(ctx, generated.RevokeSubscriptionPromptsBySubscriptionParams{
SubscriptionID: subscriptionID,
TenantID: tenantID,
})
}
func (r *kolSubscriptionRepository) RevokeAccessRowsByPrompt(ctx context.Context, promptID, tenantID int64) error {
return r.q.RevokeSubscriptionPromptsByPrompt(ctx, generated.RevokeSubscriptionPromptsByPromptParams{
PromptID: promptID,
TenantID: tenantID,
})
}
@@ -0,0 +1,198 @@
package repository
import (
"context"
"time"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type KolUsageLog struct {
ID int64
TenantID int64
GenerationTaskID *int64
Status string
CreatedAt time.Time
}
type KolUsageByTaskRecord struct {
ID int64
TenantID int64
PackageID int64
PromptID int64
Status string
CreatedAt time.Time
}
type CreateKolUsageLogInput struct {
TenantID int64
SubscriptionID *int64
SubscriptionPromptID *int64
PackageID int64
PromptID int64
PromptRevisionNo int
GenerationTaskID *int64
VariablesSnapshot []byte
SchemaSnapshot []byte
RenderedPromptHash *string
}
type KolDashboardOverview struct {
TotalSubs int64
MonthSubs int64
TotalUsage int64
MonthUsage int64
FailureCount int64
}
type KolDashboardTrendPoint struct {
Day time.Time
Usages int64
}
type KolUsagePackageStats struct {
PackageID int64
Total int64
Completed int64
Failed int64
}
type KolUsageRepository interface {
Create(ctx context.Context, input CreateKolUsageLogInput) (*KolUsageLog, error)
MarkCompletedByTask(ctx context.Context, tenantID, taskID, articleID int64) error
MarkFailedByTask(ctx context.Context, tenantID, taskID int64, errorMsg string) error
GetByGenerationTask(ctx context.Context, tenantID, taskID int64) (*KolUsageByTaskRecord, error)
DashboardOverview(ctx context.Context, packageIDs []int64) (*KolDashboardOverview, error)
DashboardTrend(ctx context.Context, packageIDs []int64, periodDays int) ([]KolDashboardTrendPoint, error)
PerPackageStats(ctx context.Context, packageIDs []int64) ([]KolUsagePackageStats, error)
}
type kolUsageRepository struct {
q generated.Querier
}
func NewKolUsageRepository(db generated.DBTX) KolUsageRepository {
return &kolUsageRepository{q: newQuerier(db)}
}
func (r *kolUsageRepository) Create(ctx context.Context, input CreateKolUsageLogInput) (*KolUsageLog, error) {
row, err := r.q.CreateKolUsageLog(ctx, generated.CreateKolUsageLogParams{
TenantID: input.TenantID,
SubscriptionID: pgInt8(input.SubscriptionID),
SubscriptionPromptID: pgInt8(input.SubscriptionPromptID),
PackageID: input.PackageID,
PromptID: input.PromptID,
PromptRevisionNo: int32(input.PromptRevisionNo),
GenerationTaskID: pgInt8(input.GenerationTaskID),
VariablesSnapshot: input.VariablesSnapshot,
SchemaSnapshot: input.SchemaSnapshot,
RenderedPromptHash: pgText(input.RenderedPromptHash),
})
if err != nil {
return nil, err
}
return &KolUsageLog{
ID: row.ID,
TenantID: row.TenantID,
GenerationTaskID: nullableInt64(row.GenerationTaskID),
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
}, nil
}
func (r *kolUsageRepository) MarkCompletedByTask(ctx context.Context, tenantID, taskID, articleID int64) error {
return r.q.MarkKolUsageCompletedByTask(ctx, generated.MarkKolUsageCompletedByTaskParams{
ArticleID: pgInt8(&articleID),
GenerationTaskID: pgInt8(&taskID),
TenantID: tenantID,
})
}
func (r *kolUsageRepository) MarkFailedByTask(ctx context.Context, tenantID, taskID int64, errorMsg string) error {
return r.q.MarkKolUsageFailedByTask(ctx, generated.MarkKolUsageFailedByTaskParams{
ErrorMessage: pgText(&errorMsg),
GenerationTaskID: pgInt8(&taskID),
TenantID: tenantID,
})
}
func (r *kolUsageRepository) GetByGenerationTask(ctx context.Context, tenantID, taskID int64) (*KolUsageByTaskRecord, error) {
row, err := r.q.GetKolUsageByGenerationTask(ctx, generated.GetKolUsageByGenerationTaskParams{
GenerationTaskID: pgInt8(&taskID),
TenantID: tenantID,
})
if err != nil {
// Callers use errors.Is(err, pgx.ErrNoRows) for "not found"; preserve err semantics.
return nil, err
}
return &KolUsageByTaskRecord{
ID: row.ID,
TenantID: row.TenantID,
PackageID: row.PackageID,
PromptID: row.PromptID,
Status: row.Status,
CreatedAt: timeFromTimestamp(row.CreatedAt),
}, nil
}
func (r *kolUsageRepository) DashboardOverview(ctx context.Context, packageIDs []int64) (*KolDashboardOverview, error) {
if len(packageIDs) == 0 {
return &KolDashboardOverview{}, nil
}
row, err := r.q.KolDashboardOverview(ctx, packageIDs)
if err != nil {
return nil, err
}
return &KolDashboardOverview{
TotalSubs: row.TotalSubs,
MonthSubs: row.MonthSubs,
TotalUsage: row.TotalUsage,
MonthUsage: row.MonthUsage,
FailureCount: row.FailureCount,
}, nil
}
func (r *kolUsageRepository) DashboardTrend(ctx context.Context, packageIDs []int64, periodDays int) ([]KolDashboardTrendPoint, error) {
if len(packageIDs) == 0 {
return []KolDashboardTrendPoint{}, nil
}
rows, err := r.q.KolDashboardTrend(ctx, generated.KolDashboardTrendParams{
PackageIds: packageIDs,
PeriodDays: int32(periodDays),
})
if err != nil {
return nil, err
}
result := make([]KolDashboardTrendPoint, len(rows))
for i, row := range rows {
var day time.Time
if row.D.Valid {
day = row.D.Time
}
result[i] = KolDashboardTrendPoint{
Day: day,
Usages: row.Usages,
}
}
return result, nil
}
func (r *kolUsageRepository) PerPackageStats(ctx context.Context, packageIDs []int64) ([]KolUsagePackageStats, error) {
if len(packageIDs) == 0 {
return []KolUsagePackageStats{}, nil
}
rows, err := r.q.KolUsageCountByPackage(ctx, packageIDs)
if err != nil {
return nil, err
}
result := make([]KolUsagePackageStats, len(rows))
for i, row := range rows {
result[i] = KolUsagePackageStats{
PackageID: row.PackageID,
Total: row.Total,
Completed: row.Completed,
Failed: row.Failed,
}
}
return result, nil
}