feat: scope articles by brand

This commit is contained in:
2026-05-20 15:37:25 +08:00
parent 5fb9d0b0dd
commit dd082e2ed1
72 changed files with 3213 additions and 432 deletions
@@ -11,6 +11,7 @@ import (
type CreateArticleInput struct {
TenantID int64
BrandID int64
SourceType string
TemplateID *int64
KolPromptID *int64
@@ -45,6 +46,7 @@ func NewArticleRepository(db generated.DBTX) ArticleRepository {
func (r *articleRepository) CreateArticle(ctx context.Context, input CreateArticleInput) (int64, error) {
return r.q.CreateArticle(ctx, generated.CreateArticleParams{
TenantID: input.TenantID,
BrandID: input.BrandID,
SourceType: input.SourceType,
TemplateID: pgInt8(input.TemplateID),
KolPromptID: pgInt8(input.KolPromptID),
@@ -48,13 +48,14 @@ func (q *Queries) CountArticles(ctx context.Context, arg CountArticlesParams) (i
}
const createArticle = `-- name: CreateArticle :one
INSERT INTO articles (tenant_id, source_type, template_id, kol_prompt_id, generate_status, publish_status)
VALUES ($1, $2, $3, $4, 'pending', 'unpublished')
INSERT INTO articles (tenant_id, brand_id, source_type, template_id, kol_prompt_id, generate_status, publish_status)
VALUES ($1, $2, $3, $4, $5, 'pending', 'unpublished')
RETURNING id
`
type CreateArticleParams struct {
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
SourceType string `json:"source_type"`
TemplateID pgtype.Int8 `json:"template_id"`
KolPromptID pgtype.Int8 `json:"kol_prompt_id"`
@@ -63,6 +64,7 @@ type CreateArticleParams struct {
func (q *Queries) CreateArticle(ctx context.Context, arg CreateArticleParams) (int64, error) {
row := q.db.QueryRow(ctx, createArticle,
arg.TenantID,
arg.BrandID,
arg.SourceType,
arg.TemplateID,
arg.KolPromptID,
@@ -56,6 +56,7 @@ type Article struct {
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
WizardStateJson []byte `json:"wizard_state_json"`
KolPromptID pgtype.Int8 `json:"kol_prompt_id"`
BrandID int64 `json:"brand_id"`
}
type ArticleTemplate struct {
@@ -435,6 +436,21 @@ type KnowledgeChunksMetum struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type KnowledgeDeletedCleanupEvent struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
KnowledgeItemID int64 `json:"knowledge_item_id"`
Status string `json:"status"`
AttemptCount int32 `json:"attempt_count"`
AvailableAt pgtype.Timestamptz `json:"available_at"`
LockedBy pgtype.Text `json:"locked_by"`
LockedAt pgtype.Timestamptz `json:"locked_at"`
LastError pgtype.Text `json:"last_error"`
CompletedAt pgtype.Timestamptz `json:"completed_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type KnowledgeGroup struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
@@ -19,13 +19,13 @@ type Querier interface {
ConfirmReservation(ctx context.Context, arg ConfirmReservationParams) error
CountActiveImagesInFolder(ctx context.Context, arg CountActiveImagesInFolderParams) (int32, error)
CountArticles(ctx context.Context, arg CountArticlesParams) (int64, error)
CountArticlesByTenant(ctx context.Context, tenantID int64) (int64, error)
CountArticlesByTenantAndBrand(ctx context.Context, arg CountArticlesByTenantAndBrandParams) (int64, error)
CountBrandsByTenant(ctx context.Context, tenantID int64) (int64, error)
CountImageAssets(ctx context.Context, arg CountImageAssetsParams) (int32, error)
CountImageReferences(ctx context.Context, arg CountImageReferencesParams) (int32, error)
CountPromptRules(ctx context.Context, arg CountPromptRulesParams) (int64, error)
CountPromptRulesUngrouped(ctx context.Context, tenantID int64) (int64, error)
CountPublishedArticlesByTenant(ctx context.Context, tenantID int64) (int64, error)
CountPublishedArticlesByTenantAndBrand(ctx context.Context, arg CountPublishedArticlesByTenantAndBrandParams) (int64, error)
CountScheduleTasks(ctx context.Context, arg CountScheduleTasksParams) (int64, error)
CountUncategorizedImages(ctx context.Context, tenantID int64) (int32, error)
CreateArticle(ctx context.Context, arg CreateArticleParams) (int64, error)
@@ -86,7 +86,7 @@ type Querier interface {
GetPromptRuleByID(ctx context.Context, arg GetPromptRuleByIDParams) (GetPromptRuleByIDRow, error)
GetPublishedKolPackage(ctx context.Context, id int64) (GetPublishedKolPackageRow, error)
GetQuotaSummary(ctx context.Context, tenantID int64) (int32, error)
GetRecentArticles(ctx context.Context, tenantID int64) ([]GetRecentArticlesRow, error)
GetRecentArticlesByBrand(ctx context.Context, arg GetRecentArticlesByBrandParams) ([]GetRecentArticlesByBrandRow, error)
GetScheduleTaskByID(ctx context.Context, arg GetScheduleTaskByIDParams) (GetScheduleTaskByIDRow, error)
GetSubscriptionPromptByID(ctx context.Context, arg GetSubscriptionPromptByIDParams) (GetSubscriptionPromptByIDRow, error)
GetTemplateByID(ctx context.Context, arg GetTemplateByIDParams) (GetTemplateByIDRow, error)
@@ -11,13 +11,18 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const countArticlesByTenant = `-- name: CountArticlesByTenant :one
const countArticlesByTenantAndBrand = `-- name: CountArticlesByTenantAndBrand :one
SELECT COUNT(*) FROM articles
WHERE tenant_id = $1 AND deleted_at IS NULL
WHERE tenant_id = $1 AND brand_id = $2 AND deleted_at IS NULL
`
func (q *Queries) CountArticlesByTenant(ctx context.Context, tenantID int64) (int64, error) {
row := q.db.QueryRow(ctx, countArticlesByTenant, tenantID)
type CountArticlesByTenantAndBrandParams struct {
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
}
func (q *Queries) CountArticlesByTenantAndBrand(ctx context.Context, arg CountArticlesByTenantAndBrandParams) (int64, error) {
row := q.db.QueryRow(ctx, countArticlesByTenantAndBrand, arg.TenantID, arg.BrandID)
var count int64
err := row.Scan(&count)
return count, err
@@ -35,13 +40,18 @@ func (q *Queries) CountBrandsByTenant(ctx context.Context, tenantID int64) (int6
return count, err
}
const countPublishedArticlesByTenant = `-- name: CountPublishedArticlesByTenant :one
const countPublishedArticlesByTenantAndBrand = `-- name: CountPublishedArticlesByTenantAndBrand :one
SELECT COUNT(*) FROM articles
WHERE tenant_id = $1 AND publish_status = 'success' AND deleted_at IS NULL
WHERE tenant_id = $1 AND brand_id = $2 AND publish_status = 'success' AND deleted_at IS NULL
`
func (q *Queries) CountPublishedArticlesByTenant(ctx context.Context, tenantID int64) (int64, error) {
row := q.db.QueryRow(ctx, countPublishedArticlesByTenant, tenantID)
type CountPublishedArticlesByTenantAndBrandParams struct {
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
}
func (q *Queries) CountPublishedArticlesByTenantAndBrand(ctx context.Context, arg CountPublishedArticlesByTenantAndBrandParams) (int64, error) {
row := q.db.QueryRow(ctx, countPublishedArticlesByTenantAndBrand, arg.TenantID, arg.BrandID)
var count int64
err := row.Scan(&count)
return count, err
@@ -93,8 +103,8 @@ func (q *Queries) GetQuotaSummary(ctx context.Context, tenantID int64) (int32, e
return balance, err
}
const getRecentArticles = `-- name: GetRecentArticles :many
SELECT a.id, a.generate_status, a.publish_status, a.source_type, a.created_at,
const getRecentArticlesByBrand = `-- name: GetRecentArticlesByBrand :many
SELECT a.id, a.brand_id, a.generate_status, a.publish_status, a.source_type, a.created_at,
av.title, av.markdown_content, av.word_count, av.source_label,
t.template_name,
COALESCE(
@@ -111,13 +121,19 @@ LEFT JOIN LATERAL (
ORDER BY created_at DESC
LIMIT 1
) gt ON true
WHERE a.tenant_id = $1 AND a.deleted_at IS NULL
WHERE a.tenant_id = $1 AND a.brand_id = $2 AND a.deleted_at IS NULL
ORDER BY a.created_at DESC
LIMIT 10
`
type GetRecentArticlesRow struct {
type GetRecentArticlesByBrandParams struct {
TenantID int64 `json:"tenant_id"`
BrandID int64 `json:"brand_id"`
}
type GetRecentArticlesByBrandRow struct {
ID int64 `json:"id"`
BrandID int64 `json:"brand_id"`
GenerateStatus string `json:"generate_status"`
PublishStatus string `json:"publish_status"`
SourceType string `json:"source_type"`
@@ -130,17 +146,18 @@ type GetRecentArticlesRow struct {
GenerationMode interface{} `json:"generation_mode"`
}
func (q *Queries) GetRecentArticles(ctx context.Context, tenantID int64) ([]GetRecentArticlesRow, error) {
rows, err := q.db.Query(ctx, getRecentArticles, tenantID)
func (q *Queries) GetRecentArticlesByBrand(ctx context.Context, arg GetRecentArticlesByBrandParams) ([]GetRecentArticlesByBrandRow, error) {
rows, err := q.db.Query(ctx, getRecentArticlesByBrand, arg.TenantID, arg.BrandID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetRecentArticlesRow
var items []GetRecentArticlesByBrandRow
for rows.Next() {
var i GetRecentArticlesRow
var i GetRecentArticlesByBrandRow
if err := rows.Scan(
&i.ID,
&i.BrandID,
&i.GenerateStatus,
&i.PublishStatus,
&i.SourceType,
@@ -45,8 +45,8 @@ WHERE article_id = sqlc.arg(article_id)
ORDER BY version_no DESC;
-- name: CreateArticle :one
INSERT INTO articles (tenant_id, source_type, template_id, kol_prompt_id, generate_status, publish_status)
VALUES (sqlc.arg(tenant_id), sqlc.arg(source_type), sqlc.arg(template_id), sqlc.arg(kol_prompt_id), 'pending', 'unpublished')
INSERT INTO articles (tenant_id, brand_id, source_type, template_id, kol_prompt_id, generate_status, publish_status)
VALUES (sqlc.arg(tenant_id), sqlc.arg(brand_id), sqlc.arg(source_type), sqlc.arg(template_id), sqlc.arg(kol_prompt_id), 'pending', 'unpublished')
RETURNING id;
-- name: CreateArticleVersion :one
@@ -1,17 +1,17 @@
-- name: CountArticlesByTenant :one
-- name: CountArticlesByTenantAndBrand :one
SELECT COUNT(*) FROM articles
WHERE tenant_id = sqlc.arg(tenant_id) AND deleted_at IS NULL;
WHERE tenant_id = sqlc.arg(tenant_id) AND brand_id = sqlc.arg(brand_id) AND deleted_at IS NULL;
-- name: CountPublishedArticlesByTenant :one
-- name: CountPublishedArticlesByTenantAndBrand :one
SELECT COUNT(*) FROM articles
WHERE tenant_id = sqlc.arg(tenant_id) AND publish_status = 'success' AND deleted_at IS NULL;
WHERE tenant_id = sqlc.arg(tenant_id) AND brand_id = sqlc.arg(brand_id) AND publish_status = 'success' AND deleted_at IS NULL;
-- name: CountBrandsByTenant :one
SELECT COUNT(*) FROM brands
WHERE tenant_id = sqlc.arg(tenant_id) AND deleted_at IS NULL;
-- name: GetRecentArticles :many
SELECT a.id, a.generate_status, a.publish_status, a.source_type, a.created_at,
-- name: GetRecentArticlesByBrand :many
SELECT a.id, a.brand_id, a.generate_status, a.publish_status, a.source_type, a.created_at,
av.title, av.markdown_content, av.word_count, av.source_label,
t.template_name,
COALESCE(
@@ -28,7 +28,7 @@ LEFT JOIN LATERAL (
ORDER BY created_at DESC
LIMIT 1
) gt ON true
WHERE a.tenant_id = sqlc.arg(tenant_id) AND a.deleted_at IS NULL
WHERE a.tenant_id = sqlc.arg(tenant_id) AND a.brand_id = sqlc.arg(brand_id) AND a.deleted_at IS NULL
ORDER BY a.created_at DESC
LIMIT 10;
@@ -9,6 +9,7 @@ import (
type WorkspaceRecentArticle struct {
ID int64
BrandID *int64
GenerateStatus string
PublishStatus string
SourceType string
@@ -40,10 +41,10 @@ type WorkspaceKolCard struct {
}
type WorkspaceRepository interface {
CountArticlesByTenant(ctx context.Context, tenantID int64) (int64, error)
CountPublishedArticlesByTenant(ctx context.Context, tenantID int64) (int64, error)
CountArticlesByTenantAndBrand(ctx context.Context, tenantID, brandID int64) (int64, error)
CountPublishedArticlesByTenantAndBrand(ctx context.Context, tenantID, brandID int64) (int64, error)
CountBrandsByTenant(ctx context.Context, tenantID int64) (int64, error)
GetRecentArticles(ctx context.Context, tenantID int64) ([]WorkspaceRecentArticle, error)
GetRecentArticlesByBrand(ctx context.Context, tenantID, brandID int64) ([]WorkspaceRecentArticle, error)
GetQuotaSummary(ctx context.Context, tenantID int64) (int, error)
GetActivePlanForTenant(ctx context.Context, tenantID int64) (*WorkspacePlan, error)
ListKolCards(ctx context.Context, tenantID int64) ([]WorkspaceKolCard, error)
@@ -57,28 +58,39 @@ func NewWorkspaceRepository(db generated.DBTX) WorkspaceRepository {
return &workspaceRepository{q: newQuerier(db)}
}
func (r *workspaceRepository) CountArticlesByTenant(ctx context.Context, tenantID int64) (int64, error) {
return r.q.CountArticlesByTenant(ctx, tenantID)
func (r *workspaceRepository) CountArticlesByTenantAndBrand(ctx context.Context, tenantID, brandID int64) (int64, error) {
return r.q.CountArticlesByTenantAndBrand(ctx, generated.CountArticlesByTenantAndBrandParams{
TenantID: tenantID,
BrandID: brandID,
})
}
func (r *workspaceRepository) CountPublishedArticlesByTenant(ctx context.Context, tenantID int64) (int64, error) {
return r.q.CountPublishedArticlesByTenant(ctx, tenantID)
func (r *workspaceRepository) CountPublishedArticlesByTenantAndBrand(ctx context.Context, tenantID, brandID int64) (int64, error) {
return r.q.CountPublishedArticlesByTenantAndBrand(ctx, generated.CountPublishedArticlesByTenantAndBrandParams{
TenantID: tenantID,
BrandID: brandID,
})
}
func (r *workspaceRepository) CountBrandsByTenant(ctx context.Context, tenantID int64) (int64, error) {
return r.q.CountBrandsByTenant(ctx, tenantID)
}
func (r *workspaceRepository) GetRecentArticles(ctx context.Context, tenantID int64) ([]WorkspaceRecentArticle, error) {
rows, err := r.q.GetRecentArticles(ctx, tenantID)
func (r *workspaceRepository) GetRecentArticlesByBrand(ctx context.Context, tenantID, brandID int64) ([]WorkspaceRecentArticle, error) {
rows, err := r.q.GetRecentArticlesByBrand(ctx, generated.GetRecentArticlesByBrandParams{
TenantID: tenantID,
BrandID: brandID,
})
if err != nil {
return nil, err
}
articles := make([]WorkspaceRecentArticle, 0, len(rows))
for _, row := range rows {
brandID := row.BrandID
articles = append(articles, WorkspaceRecentArticle{
ID: row.ID,
BrandID: &brandID,
GenerateStatus: row.GenerateStatus,
PublishStatus: row.PublishStatus,
SourceType: row.SourceType,