feat(tenant/kol): let KOLs self-subscribe to their own published packages

Add /kol/manage/packages/:id/self-subscription POST/DELETE on the tenant
side so a KOL can grant themselves access to their own published package
without going through the marketplace approval flow. Reject self-subscribe
through the marketplace with a clear error, expose owned_by_current_tenant
on package responses, and surface a self_subscription block on KolPackage
Response. The admin-web KOL workspace gets 订阅自己 / 取消订阅 entries with
matching messaging in the marketplace detail view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 22:06:57 +08:00
parent 088dbb0ec7
commit 4dd8a1bb0e
9 changed files with 472 additions and 77 deletions
@@ -40,7 +40,7 @@ func NewKolManageHandler(a *bootstrap.App, assets *AssetHandler) *KolManageHandl
return &KolManageHandler{
profileSvc: profileSvc,
pkgSvc: app.NewKolPackageService(profileSvc, a.KolPackages, a.KolPrompts).WithCache(a.Cache),
pkgSvc: app.NewKolPackageService(profileSvc, a.KolPackages, a.KolPrompts, a.KolSubscriptions).WithCache(a.Cache),
promptSvc: app.NewKolPromptService(a.DB, profileSvc, a.KolPackages, a.KolPrompts, a.KolSubscriptions, a.KolPromptAsset).WithCache(a.Cache),
assistSvc: app.NewKolAssistService(a.DB, profileSvc, a.KolAssists, a.LLM, a.RabbitMQ, a.Config.LLM, a.Logger).WithCache(a.Cache),
assets: assets,
@@ -249,6 +249,46 @@ func (h *KolManageHandler) ArchivePackage(c *gin.Context) {
response.Success(c, data)
}
func (h *KolManageHandler) SelfSubscribePackage(c *gin.Context) {
id, ok := parseInt64Param(c, "id", "package id")
if !ok {
return
}
actor, ok := actorFromRequest(c)
if !ok {
return
}
data, err := h.pkgSvc.SelfSubscribe(c.Request.Context(), actor, id)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *KolManageHandler) CancelSelfSubscription(c *gin.Context) {
id, ok := parseInt64Param(c, "id", "package id")
if !ok {
return
}
actor, ok := actorFromRequest(c)
if !ok {
return
}
data, err := h.pkgSvc.CancelSelfSubscription(c.Request.Context(), actor, id)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, data)
}
func (h *KolManageHandler) ListPrompts(c *gin.Context) {
packageID, ok := parseInt64Param(c, "id", "package id")
if !ok {
+2 -8
View File
@@ -108,6 +108,8 @@ func RegisterRoutes(app *bootstrap.App) {
kolManage.DELETE("/packages/:id", kolManageHandler.DeletePackage)
kolManage.PUT("/packages/:id/publish", kolManageHandler.PublishPackage)
kolManage.PUT("/packages/:id/archive", kolManageHandler.ArchivePackage)
kolManage.POST("/packages/:id/self-subscription", kolManageHandler.SelfSubscribePackage)
kolManage.DELETE("/packages/:id/self-subscription", kolManageHandler.CancelSelfSubscription)
kolManage.GET("/packages/:id/prompts", kolManageHandler.ListPrompts)
kolManage.POST("/packages/:id/prompts", kolManageHandler.CreatePrompt)
kolManage.GET("/prompts/:id", kolManageHandler.GetPromptLatest)
@@ -139,14 +141,6 @@ func RegisterRoutes(app *bootstrap.App) {
kolSubscriptions.GET("/subscription-prompts/:id/schema", kolMarketplaceHandler.SubscriptionPromptSchema)
kolSubscriptions.POST("/subscription-prompts/:id/generate", kolMarketplaceHandler.Generate)
// V1 compromise: these admin operations live on tenant-api and are gated by
// tenant_role=tenant_admin until the platform-api/module is built.
kolAdmin := tenantProtected.Group("/kol/admin")
kolAdminHandler := NewKolAdminHandler(app)
kolAdmin.PUT("/subscriptions/:id/approve", kolAdminHandler.ApproveSubscription)
kolAdmin.POST("/subscriptions", kolAdminHandler.ManualBind)
kolAdmin.PUT("/subscriptions/:id/revoke", kolAdminHandler.RevokeSubscription)
articles := tenantProtected.Group("/articles")
artHandler := NewArticleHandler(app)
articles.GET("", artHandler.List)