diff --git a/server/internal/bootstrap/bootstrap.go b/server/internal/bootstrap/bootstrap.go index 2586bc6..2fb176e 100644 --- a/server/internal/bootstrap/bootstrap.go +++ b/server/internal/bootstrap/bootstrap.go @@ -25,6 +25,7 @@ import ( "github.com/geo-platform/tenant-api/internal/shared/response" "github.com/geo-platform/tenant-api/internal/shared/retrieval" "github.com/geo-platform/tenant-api/internal/shared/stream" + tenantapp "github.com/geo-platform/tenant-api/internal/tenant/app" "github.com/geo-platform/tenant-api/internal/tenant/prompts" "github.com/geo-platform/tenant-api/internal/tenant/repository" ) @@ -47,6 +48,9 @@ type App struct { GenerationStreams *stream.GenerationHub Cache cache.Cache KolProfiles repository.KolProfileRepository + KolPackages repository.KolPackageRepository + KolPrompts repository.KolPromptRepository + KolPromptAsset *tenantapp.KolPromptAsset } func New(configPath string) (*App, error) { @@ -99,6 +103,9 @@ func New(configPath string) (*App, error) { generationStreams := stream.NewGenerationHub(mqClient) appCache := cache.New(cfg.Cache.Driver, rdb) kolProfiles := repository.NewKolProfileRepository(pool) + kolPackages := repository.NewKolPackageRepository(pool) + kolPrompts := repository.NewKolPromptRepository(pool) + kolPromptAsset := tenantapp.NewKolPromptAsset(objectStorageClient) if cfg.Server.Mode == "release" { gin.SetMode(gin.ReleaseMode) @@ -153,6 +160,9 @@ func New(configPath string) (*App, error) { GenerationStreams: generationStreams, Cache: appCache, KolProfiles: kolProfiles, + KolPackages: kolPackages, + KolPrompts: kolPrompts, + KolPromptAsset: kolPromptAsset, }, nil } diff --git a/server/internal/tenant/transport/kol_manage_handler.go b/server/internal/tenant/transport/kol_manage_handler.go new file mode 100644 index 0000000..318d682 --- /dev/null +++ b/server/internal/tenant/transport/kol_manage_handler.go @@ -0,0 +1,340 @@ +package transport + +import ( + "strconv" + + "github.com/gin-gonic/gin" + + "github.com/geo-platform/tenant-api/internal/bootstrap" + "github.com/geo-platform/tenant-api/internal/shared/auth" + "github.com/geo-platform/tenant-api/internal/shared/response" + "github.com/geo-platform/tenant-api/internal/tenant/app" +) + +type KolManageHandler struct { + profileSvc *app.KolProfileService + pkgSvc *app.KolPackageService + promptSvc *app.KolPromptService +} + +func NewKolManageHandler(a *bootstrap.App) *KolManageHandler { + profileSvc := app.NewKolProfileService(a.KolProfiles) + + return &KolManageHandler{ + profileSvc: profileSvc, + pkgSvc: app.NewKolPackageService(profileSvc, a.KolPackages, a.KolPrompts), + promptSvc: app.NewKolPromptService(a.DB, profileSvc, a.KolPackages, a.KolPrompts, a.KolPromptAsset), + } +} + +func (h *KolManageHandler) GetProfile(c *gin.Context) { + actor, ok := actorFromRequest(c) + if !ok { + return + } + + profile, err := h.profileSvc.RequireActiveKol(c.Request.Context(), actor) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, app.NewKolProfileResponse(profile)) +} + +func (h *KolManageHandler) ListPackages(c *gin.Context) { + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.pkgSvc.List(c.Request.Context(), actor) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) CreatePackage(c *gin.Context) { + var req app.CreateKolPackageInput + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.pkgSvc.Create(c.Request.Context(), actor, req) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) UpdatePackage(c *gin.Context) { + id, ok := parseInt64Param(c, "id", "package id") + if !ok { + return + } + + var req app.UpdateKolPackageInput + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.pkgSvc.Update(c.Request.Context(), actor, id, req) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) DeletePackage(c *gin.Context) { + id, ok := parseInt64Param(c, "id", "package id") + if !ok { + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + if err := h.pkgSvc.Delete(c.Request.Context(), actor, id); err != nil { + response.Error(c, err) + return + } + + response.Success(c, nil) +} + +func (h *KolManageHandler) PublishPackage(c *gin.Context) { + id, ok := parseInt64Param(c, "id", "package id") + if !ok { + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.pkgSvc.Publish(c.Request.Context(), actor, id) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) ArchivePackage(c *gin.Context) { + id, ok := parseInt64Param(c, "id", "package id") + if !ok { + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.pkgSvc.Archive(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 { + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.promptSvc.ListByPackage(c.Request.Context(), actor, packageID) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) CreatePrompt(c *gin.Context) { + packageID, ok := parseInt64Param(c, "id", "package id") + if !ok { + return + } + + var req app.CreatePromptInput + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) + return + } + req.PackageID = packageID + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.promptSvc.CreatePrompt(c.Request.Context(), actor, req) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) GetPromptLatest(c *gin.Context) { + promptID, ok := parseInt64Param(c, "id", "prompt id") + if !ok { + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.promptSvc.GetForOwner(c.Request.Context(), actor, promptID) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) UpdatePromptMetadata(c *gin.Context) { + promptID, ok := parseInt64Param(c, "id", "prompt id") + if !ok { + return + } + + var req app.UpdateKolPromptInput + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.promptSvc.UpdateMetadata(c.Request.Context(), actor, promptID, req) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) DeletePrompt(c *gin.Context) { + promptID, ok := parseInt64Param(c, "id", "prompt id") + if !ok { + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + if err := h.promptSvc.Delete(c.Request.Context(), actor, promptID); err != nil { + response.Error(c, err) + return + } + + response.Success(c, nil) +} + +func (h *KolManageHandler) SaveDraft(c *gin.Context) { + promptID, ok := parseInt64Param(c, "id", "prompt id") + if !ok { + return + } + + var req app.SaveDraftInput + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, response.ErrBadRequest(40001, "invalid_params", err.Error())) + return + } + req.PromptID = promptID + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + data, err := h.promptSvc.SaveDraft(c.Request.Context(), actor, req) + if err != nil { + response.Error(c, err) + return + } + + response.Success(c, data) +} + +func (h *KolManageHandler) PublishPrompt(c *gin.Context) { + promptID, ok := parseInt64Param(c, "id", "prompt id") + if !ok { + return + } + + actor, ok := actorFromRequest(c) + if !ok { + return + } + + if err := h.promptSvc.Publish(c.Request.Context(), actor, promptID); err != nil { + response.Error(c, err) + return + } + + response.Success(c, gin.H{"ok": true}) +} + +func actorFromRequest(c *gin.Context) (auth.Actor, bool) { + actor, ok := auth.ActorFromCtx(c.Request.Context()) + if !ok { + response.Error(c, response.ErrUnauthorized(40100, "not_authenticated", "not authenticated")) + return auth.Actor{}, false + } + return actor, true +} + +func parseInt64Param(c *gin.Context, key, label string) (int64, bool) { + id, err := strconv.ParseInt(c.Param(key), 10, 64) + if err != nil { + response.Error(c, response.ErrBadRequest(40001, "invalid_id", label+" must be a number")) + return 0, false + } + return id, true +} diff --git a/server/internal/tenant/transport/router.go b/server/internal/tenant/transport/router.go index 0ceef0c..7dd4053 100644 --- a/server/internal/tenant/transport/router.go +++ b/server/internal/tenant/transport/router.go @@ -55,6 +55,23 @@ func RegisterRoutes(app *bootstrap.App) { templates.GET("/:id/gen_outline_task_result", tplHandler.GetOutlineTaskResult) templates.POST("/:id/generate", tplHandler.Generate) + kolManage := protected.Group("/tenant/kol/manage") + kolManageHandler := NewKolManageHandler(app) + kolManage.GET("/profile", kolManageHandler.GetProfile) + kolManage.GET("/packages", kolManageHandler.ListPackages) + kolManage.POST("/packages", kolManageHandler.CreatePackage) + kolManage.PUT("/packages/:id", kolManageHandler.UpdatePackage) + kolManage.DELETE("/packages/:id", kolManageHandler.DeletePackage) + kolManage.PUT("/packages/:id/publish", kolManageHandler.PublishPackage) + kolManage.PUT("/packages/:id/archive", kolManageHandler.ArchivePackage) + kolManage.GET("/packages/:id/prompts", kolManageHandler.ListPrompts) + kolManage.POST("/packages/:id/prompts", kolManageHandler.CreatePrompt) + kolManage.GET("/prompts/:id", kolManageHandler.GetPromptLatest) + kolManage.PUT("/prompts/:id", kolManageHandler.UpdatePromptMetadata) + kolManage.DELETE("/prompts/:id", kolManageHandler.DeletePrompt) + kolManage.POST("/prompts/:id/draft", kolManageHandler.SaveDraft) + kolManage.POST("/prompts/:id/publish", kolManageHandler.PublishPrompt) + articles := protected.Group("/tenant/articles") artHandler := NewArticleHandler(app) articles.GET("", artHandler.List)