feat: split account management
This commit is contained in:
@@ -135,8 +135,7 @@ func TestCreatorRouteValidationCoverage(t *testing.T) {
|
||||
{http.MethodPost, "/api/creator/strategies/missing/disable"},
|
||||
{http.MethodDelete, "/api/creator/strategies/missing"},
|
||||
{http.MethodPut, "/api/creator/strategies/missing"},
|
||||
{http.MethodPost, "/api/creator/competitors/preview"},
|
||||
{http.MethodPost, "/api/creator/competitors"},
|
||||
{http.MethodPost, "/api/creator/competitor-share-jobs"},
|
||||
{http.MethodPost, "/api/creator/competitors/missing/pause"},
|
||||
{http.MethodPost, "/api/creator/competitors/missing/resume"},
|
||||
{http.MethodPost, "/api/creator/competitors/missing/sync"},
|
||||
|
||||
@@ -273,36 +273,45 @@ func registerCreatorWithServices(app *fiber.App, store *creator.Store, phaseASto
|
||||
}
|
||||
return c.JSON(items)
|
||||
})
|
||||
app.Post("/api/creator/competitors/preview", func(c fiber.Ctx) error {
|
||||
var input competitorShareRequest
|
||||
if err := decodeCreator(c, &input); err != nil {
|
||||
return creatorError(c, err)
|
||||
}
|
||||
preview, err := previewCompetitorShare(c.Context(), store, phaseAStore, hubStore, input.AccountID, input.Platform, input.ShareURL)
|
||||
app.Get("/api/creator/competitor-share-jobs", func(c fiber.Ctx) error {
|
||||
items, err := store.ListCompetitorShareJobs(c.Context(), c.Query("platform"), c.Query("status"))
|
||||
if err != nil {
|
||||
return creatorError(c, err)
|
||||
}
|
||||
return c.JSON(preview)
|
||||
return c.JSON(items)
|
||||
})
|
||||
app.Post("/api/creator/competitors", func(c fiber.Ctx) error {
|
||||
var request competitorShareRequest
|
||||
app.Get("/api/creator/competitor-share-jobs/:id", func(c fiber.Ctx) error {
|
||||
item, err := store.GetCompetitorShareJob(c.Context(), c.Params("id"))
|
||||
if err != nil {
|
||||
return creatorError(c, err)
|
||||
}
|
||||
return c.JSON(item)
|
||||
})
|
||||
app.Post("/api/creator/competitor-share-jobs", func(c fiber.Ctx) error {
|
||||
var request creator.CompetitorShareJobInput
|
||||
if err := decodeCreator(c, &request); err != nil {
|
||||
return creatorError(c, err)
|
||||
}
|
||||
preview, err := previewCompetitorShare(c.Context(), store, phaseAStore, hubStore, request.AccountID, request.Platform, request.ShareURL)
|
||||
platform, err := competitorSharePlatform(request.ShareURL)
|
||||
if err != nil {
|
||||
return creatorError(c, err)
|
||||
}
|
||||
input := preview.input()
|
||||
input.Tags = request.Tags
|
||||
if err := validateXiaohongshuCompetitor(input); err != nil {
|
||||
return creatorError(c, err)
|
||||
if request.Platform != "" && request.Platform != platform {
|
||||
return creatorError(c, creator.ErrInvalid)
|
||||
}
|
||||
item, err := store.CreateCompetitor(c.Context(), input)
|
||||
request.Platform = platform
|
||||
item, err := store.CreateCompetitorShareJob(c.Context(), request)
|
||||
if err != nil {
|
||||
return creatorError(c, err)
|
||||
}
|
||||
return c.Status(fiber.StatusCreated).JSON(item)
|
||||
return c.Status(fiber.StatusAccepted).JSON(item)
|
||||
})
|
||||
app.Post("/api/creator/competitor-share-jobs/:id/retry", func(c fiber.Ctx) error {
|
||||
item, err := store.RetryCompetitorShareJob(c.Context(), c.Params("id"))
|
||||
if err != nil {
|
||||
return creatorError(c, err)
|
||||
}
|
||||
return c.Status(fiber.StatusAccepted).JSON(item)
|
||||
})
|
||||
app.Get("/api/creator/competitors/:id", func(c fiber.Ctx) error {
|
||||
item, err := store.GetCompetitor(c.Context(), c.Params("id"))
|
||||
@@ -1524,13 +1533,6 @@ func (browser creatorGatewayBrowser) Media(ctx context.Context, target, destinat
|
||||
return writeCreatorMedia(destination, data)
|
||||
}
|
||||
|
||||
type competitorShareRequest struct {
|
||||
AccountID string `json:"account_id"`
|
||||
Platform string `json:"platform"`
|
||||
ShareURL string `json:"share_url"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type competitorSharePreview struct {
|
||||
AccountID string `json:"account_id"`
|
||||
Platform string `json:"platform"`
|
||||
@@ -1952,6 +1954,49 @@ func syncCreatorCompetitorWithClaim(ctx context.Context, store *creator.Store, p
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func processCompetitorShareJob(ctx context.Context, store *creator.Store, phaseAStore *accountdomain.Store, hubStore *hub.Store, jobID string) error {
|
||||
for {
|
||||
job, leaseToken, claimed, err := store.ClaimCompetitorShareJob(ctx, jobID, time.Now().UTC())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !claimed {
|
||||
return nil
|
||||
}
|
||||
|
||||
preview, processErr := previewCompetitorShare(ctx, store, phaseAStore, hubStore, "", job.Platform, job.ShareURL)
|
||||
competitorID := ""
|
||||
if processErr == nil {
|
||||
input := preview.input()
|
||||
input.Tags = job.Tags
|
||||
if processErr = validateXiaohongshuCompetitor(input); processErr == nil {
|
||||
var competitor creator.Competitor
|
||||
competitor, processErr = store.UpsertCompetitor(ctx, input)
|
||||
competitorID = competitor.ID
|
||||
}
|
||||
}
|
||||
if processErr == nil {
|
||||
if err := store.MarkCompetitorShareJob(ctx, job.ID, leaseToken, creator.CompetitorShareJobSucceeded, competitorID, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{"job_id": job.ID, "competitor_id": competitorID, "attempts": job.Attempts}).Info("creator competitor share job completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
status := creator.CompetitorShareJobQueued
|
||||
if job.Attempts >= creator.MaxCompetitorShareJobAttempts {
|
||||
status = creator.CompetitorShareJobFailed
|
||||
}
|
||||
if err := store.MarkCompetitorShareJob(ctx, job.ID, leaseToken, status, "", processErr.Error()); err != nil {
|
||||
return errors.Join(processErr, err)
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{"job_id": job.ID, "attempts": job.Attempts, "status": status}).WithError(processErr).Warn("creator competitor share job attempt failed")
|
||||
if status == creator.CompetitorShareJobFailed || ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runCreatorScheduleOnce(ctx context.Context, store *creator.Store, phaseAStore *accountdomain.Store, hubStore *hub.Store) error {
|
||||
if store == nil {
|
||||
return creator.ErrUnavailable
|
||||
@@ -1961,6 +2006,15 @@ func runCreatorScheduleOnce(ctx context.Context, store *creator.Store, phaseASto
|
||||
return err
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
shareJobs, err := store.ListDueCompetitorShareJobs(ctx, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, job := range shareJobs {
|
||||
if err := processCompetitorShareJob(ctx, store, phaseAStore, hubStore, job.ID); err != nil {
|
||||
logrus.WithError(err).WithField("job_id", job.ID).Warn("creator competitor share job failed")
|
||||
}
|
||||
}
|
||||
competitors, err := store.ListDueCompetitors(ctx, now)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -52,8 +52,7 @@ func TestCreatorWriteRoutesRejectMalformedInputBeforeStoreAccess(t *testing.T) {
|
||||
{method: http.MethodPost, path: "/api/creator/relations"},
|
||||
{method: http.MethodPost, path: "/api/creator/accounts/account-1/strategies"},
|
||||
{method: http.MethodPut, path: "/api/creator/strategies/strategy-1"},
|
||||
{method: http.MethodPost, path: "/api/creator/competitors/preview"},
|
||||
{method: http.MethodPost, path: "/api/creator/competitors"},
|
||||
{method: http.MethodPost, path: "/api/creator/competitor-share-jobs"},
|
||||
{method: http.MethodPut, path: "/api/creator/competitors/competitor-1"},
|
||||
{method: http.MethodPost, path: "/api/creator/competitors/competitor-1/sync"},
|
||||
{method: http.MethodPost, path: "/api/creator/xiaohongshu/search"},
|
||||
|
||||
Reference in New Issue
Block a user