package svc import ( "context" "fmt" "os" "time" "haixun-backend/internal/config" libmongo "haixun-backend/internal/library/mongo" libredis "haixun-backend/internal/library/redis" "haixun-backend/internal/library/validate" "haixun-backend/internal/middleware" aisettings "haixun-backend/internal/model/ai/usecase" authrepodomain "haixun-backend/internal/model/auth/domain/repository" authdomain "haixun-backend/internal/model/auth/domain/usecase" authrepo "haixun-backend/internal/model/auth/repository" authuc "haixun-backend/internal/model/auth/usecase" branddomain "haixun-backend/internal/model/brand/domain/usecase" brandrepo "haixun-backend/internal/model/brand/repository" brandusecase "haixun-backend/internal/model/brand/usecase" cmatrixdomain "haixun-backend/internal/model/content_matrix/domain/usecase" cmatrixrepo "haixun-backend/internal/model/content_matrix/repository" cmatrixusecase "haixun-backend/internal/model/content_matrix/usecase" copydraftdomain "haixun-backend/internal/model/copy_draft/domain/usecase" copydraftrepo "haixun-backend/internal/model/copy_draft/repository" copydraftusecase "haixun-backend/internal/model/copy_draft/usecase" copymissiondomain "haixun-backend/internal/model/copy_mission/domain/usecase" copymissionrepo "haixun-backend/internal/model/copy_mission/repository" copymissionusecase "haixun-backend/internal/model/copy_mission/usecase" jobrepo "haixun-backend/internal/model/job/repository" jobusecase "haixun-backend/internal/model/job/usecase" kgdomain "haixun-backend/internal/model/knowledge_graph/domain/usecase" kgrepo "haixun-backend/internal/model/knowledge_graph/repository" kgusecase "haixun-backend/internal/model/knowledge_graph/usecase" memberdomain "haixun-backend/internal/model/member/domain/usecase" memberrepo "haixun-backend/internal/model/member/repository" memberuc "haixun-backend/internal/model/member/usecase" outreachdraftdomain "haixun-backend/internal/model/outreach_draft/domain/usecase" outreachdraftrepo "haixun-backend/internal/model/outreach_draft/repository" outreachdraftusecase "haixun-backend/internal/model/outreach_draft/usecase" permissiondomain "haixun-backend/internal/model/permission/domain/usecase" permissionrepo "haixun-backend/internal/model/permission/repository" permissionuc "haixun-backend/internal/model/permission/usecase" personadomain "haixun-backend/internal/model/persona/domain/usecase" personarepo "haixun-backend/internal/model/persona/repository" personausecase "haixun-backend/internal/model/persona/usecase" placementusecase "haixun-backend/internal/model/placement/usecase" placementtopicdomain "haixun-backend/internal/model/placement_topic/domain/usecase" placementtopicrepo "haixun-backend/internal/model/placement_topic/repository" placementtopicusecase "haixun-backend/internal/model/placement_topic/usecase" scanpostdomain "haixun-backend/internal/model/scan_post/domain/usecase" scanpostrepo "haixun-backend/internal/model/scan_post/repository" scanpostusecase "haixun-backend/internal/model/scan_post/usecase" settingrepo "haixun-backend/internal/model/setting/repository" settingusecase "haixun-backend/internal/model/setting/usecase" threadsaccountdomain "haixun-backend/internal/model/threads_account/domain/usecase" threadsaccountrepo "haixun-backend/internal/model/threads_account/repository" threadsaccountusecase "haixun-backend/internal/model/threads_account/usecase" jobworker "haixun-backend/internal/worker/job" goredis "github.com/redis/go-redis/v9" "github.com/zeromicro/go-zero/rest" ) type ServiceContext struct { Config config.Config Validator *validate.Validate Mongo *libmongo.Client Redis *goredis.Client Setting settingusecase.UseCase AI aisettings.UseCase Job jobusecase.UseCase AuthToken authdomain.TokenUseCase Member memberdomain.UseCase Permission permissiondomain.UseCase Persona personadomain.UseCase CopyMission copymissiondomain.UseCase Brand branddomain.UseCase PlacementTopic placementtopicdomain.UseCase KnowledgeGraph kgdomain.UseCase Placement placementusecase.UseCase ScanPost scanpostdomain.UseCase OutreachDraft outreachdraftdomain.UseCase ContentMatrix cmatrixdomain.UseCase CopyDraft copydraftdomain.UseCase ThreadsAccount threadsaccountdomain.UseCase // Middlewares mounted per route group via generate/api `middleware:` directive. AuthJWT rest.Middleware MemberAuth rest.Middleware WorkerSecret rest.Middleware stopWorker context.CancelFunc stopScheduler context.CancelFunc stopReaper context.CancelFunc } func NewServiceContext(c config.Config) *ServiceContext { ctx := context.Background() mongoClient, err := libmongo.NewClient(ctx, c.Mongo) if err != nil { panic(err) } redisClient := libredis.NewClient(c.Redis) settingRepository := settingrepo.NewMongoRepository(mongoClient.Database()) if err := settingRepository.EnsureIndexes(ctx); err != nil { panic(err) } settingUseCase := settingusecase.NewUseCase(settingRepository) var tokenRevokeStore authrepodomain.TokenRevokeStore if redisClient != nil { tokenRevokeStore = authrepo.NewRedisTokenRevokeStore(redisClient) } authTokenUseCase := authuc.NewTokenUseCase(c.Auth, tokenRevokeStore) memberRepository := memberrepo.NewMongoRepository(mongoClient.Database()) if err := memberRepository.EnsureIndexes(ctx); err != nil { panic(err) } memberUseCase := memberuc.NewUseCase(memberRepository, authTokenUseCase) permissionRepository := permissionrepo.NewMongoPermissionRepository(mongoClient.Database()) rolePermissionRepository := permissionrepo.NewMongoRolePermissionRepository(mongoClient.Database()) if err := permissionRepository.EnsureIndexes(ctx); err != nil { panic(err) } if err := rolePermissionRepository.EnsureIndexes(ctx); err != nil { panic(err) } permissionUseCase := permissionuc.NewUseCase(permissionRepository, rolePermissionRepository) if err := permissionUseCase.EnsureDefaultPermissions(ctx); err != nil { panic(err) } jobTemplateRepository := jobrepo.NewMongoTemplateRepository(mongoClient.Database()) jobRunRepository := jobrepo.NewMongoRunRepository(mongoClient.Database()) jobScheduleRepository := jobrepo.NewMongoScheduleRepository(mongoClient.Database()) jobEventRepository := jobrepo.NewMongoEventRepository(mongoClient.Database()) jobQueueRepository := jobrepo.NewRedisQueueRepository(redisClient) if err := jobTemplateRepository.EnsureIndexes(ctx); err != nil { panic(err) } if err := jobRunRepository.EnsureIndexes(ctx); err != nil { panic(err) } if err := jobScheduleRepository.EnsureIndexes(ctx); err != nil { panic(err) } if err := jobEventRepository.EnsureIndexes(ctx); err != nil { panic(err) } jobUseCase := jobusecase.NewUseCase(jobTemplateRepository, jobRunRepository, jobScheduleRepository, jobEventRepository, jobQueueRepository) if err := jobUseCase.EnsureDemoTemplate(ctx); err != nil { panic(err) } if err := jobUseCase.EnsureStyle8DTemplate(ctx); err != nil { panic(err) } if err := jobUseCase.EnsureExpandGraphTemplate(ctx); err != nil { panic(err) } if err := jobUseCase.EnsurePlacementScanTemplate(ctx); err != nil { panic(err) } if err := jobUseCase.EnsureScanViralTemplate(ctx); err != nil { panic(err) } if err := jobUseCase.EnsureAnalyzeCopyMissionTemplate(ctx); err != nil { panic(err) } if err := jobUseCase.EnsureGenerateCopyMatrixTemplate(ctx); err != nil { panic(err) } if err := jobUseCase.EnsureGenerateCopyDraftTemplate(ctx); err != nil { panic(err) } copyMissionRepository := copymissionrepo.NewMongoRepository(mongoClient.Database()) if err := copyMissionRepository.EnsureIndexes(ctx); err != nil { panic(err) } copyMissionUseCase := copymissionusecase.NewUseCase(copyMissionRepository) copyDraftRepository := copydraftrepo.NewMongoRepository(mongoClient.Database()) if err := copyDraftRepository.EnsureIndexes(ctx); err != nil { panic(err) } copyDraftUseCase := copydraftusecase.NewUseCase(copyDraftRepository) scanPostRepository := scanpostrepo.NewMongoRepository(mongoClient.Database()) if err := scanPostRepository.EnsureIndexes(ctx); err != nil { panic(err) } scanPostUseCase := scanpostusecase.NewUseCase(scanPostRepository) outreachDraftRepository := outreachdraftrepo.NewMongoRepository(mongoClient.Database()) if err := outreachDraftRepository.EnsureIndexes(ctx); err != nil { panic(err) } outreachDraftUseCase := outreachdraftusecase.NewUseCase(outreachDraftRepository) contentMatrixRepository := cmatrixrepo.NewMongoRepository(mongoClient.Database()) if err := contentMatrixRepository.EnsureIndexes(ctx); err != nil { panic(err) } contentMatrixUseCase := cmatrixusecase.NewUseCase(contentMatrixRepository) knowledgeGraphRepository := kgrepo.NewMongoRepository(mongoClient.Database()) if err := knowledgeGraphRepository.EnsureIndexes(ctx); err != nil { panic(err) } knowledgeGraphUseCase := kgusecase.NewUseCase(knowledgeGraphRepository) personaRepository := personarepo.NewMongoRepository(mongoClient.Database()) if err := personaRepository.EnsureIndexes(ctx); err != nil { panic(err) } personaUseCase := personausecase.NewUseCase(personaRepository) brandRepository := brandrepo.NewMongoRepository(mongoClient.Database()) if err := brandRepository.EnsureIndexes(ctx); err != nil { panic(err) } brandUseCase := brandusecase.NewUseCase(brandRepository) placementTopicRepository := placementtopicrepo.NewMongoRepository(mongoClient.Database()) if err := placementTopicRepository.EnsureIndexes(ctx); err != nil { panic(err) } placementTopicUseCase := placementtopicusecase.NewUseCase( placementTopicRepository, brandRepository, knowledgeGraphUseCase, scanPostRepository, ) threadsAccountRepository := threadsaccountrepo.NewMongoRepository(mongoClient.Database()) threadsAccountSecretsRepository := threadsaccountrepo.NewSecretsMongoRepository(mongoClient.Database()) if err := threadsAccountRepository.EnsureIndexes(ctx); err != nil { panic(err) } if err := threadsAccountSecretsRepository.EnsureIndexes(ctx); err != nil { panic(err) } threadsAccountUseCase := threadsaccountusecase.NewUseCase( threadsAccountRepository, threadsAccountSecretsRepository, memberUseCase, settingUseCase, personaUseCase, ) placementUseCase := placementusecase.NewUseCase(settingUseCase) sc := &ServiceContext{ Config: c, Validator: validate.New(), Mongo: mongoClient, Redis: redisClient, Setting: settingUseCase, AI: aisettings.NewUseCase(), Job: jobUseCase, AuthToken: authTokenUseCase, Member: memberUseCase, Permission: permissionUseCase, Persona: personaUseCase, CopyMission: copyMissionUseCase, Brand: brandUseCase, PlacementTopic: placementTopicUseCase, KnowledgeGraph: knowledgeGraphUseCase, Placement: placementUseCase, ScanPost: scanPostUseCase, OutreachDraft: outreachDraftUseCase, ContentMatrix: contentMatrixUseCase, CopyDraft: copyDraftUseCase, ThreadsAccount: threadsAccountUseCase, } hostname, _ := os.Hostname() if c.JobWorker.Enabled && redisClient != nil { workerType := c.JobWorker.WorkerType if workerType == "" { workerType = "go" } workerID := c.JobWorker.WorkerID if workerID == "" { workerID = fmt.Sprintf("%s-%s-worker", hostname, workerType) } workerCtx, cancel := context.WithCancel(context.Background()) sc.stopWorker = cancel runner := jobworker.NewRunner(workerID, workerType, jobUseCase) jobworker.RegisterExpandGraphHandler(runner, jobworker.ExpandGraphDeps{ Jobs: jobUseCase, Brand: brandUseCase, PlacementTopic: placementTopicUseCase, KnowledgeGraph: knowledgeGraphUseCase, ThreadsAccount: threadsAccountUseCase, Placement: placementUseCase, AI: sc.AI, }) jobworker.RegisterScanPlacementHandler(runner, jobworker.ScanPlacementDeps{ Jobs: jobUseCase, Brand: brandUseCase, PlacementTopic: placementTopicUseCase, KnowledgeGraph: knowledgeGraphUseCase, ScanPost: scanPostUseCase, ThreadsAccount: threadsAccountUseCase, Placement: placementUseCase, }) jobworker.RegisterScanViralHandler(runner, jobworker.ScanViralDeps{ Jobs: jobUseCase, CopyMission: copyMissionUseCase, Persona: personaUseCase, ScanPost: scanPostUseCase, CopyDraft: copyDraftUseCase, ThreadsAccount: threadsAccountUseCase, Placement: placementUseCase, AI: sc.AI, }) jobworker.RegisterAnalyzeCopyMissionHandler(runner, jobworker.AnalyzeCopyMissionDeps{ Jobs: jobUseCase, CopyMission: copyMissionUseCase, Persona: personaUseCase, ThreadsAccount: threadsAccountUseCase, Placement: placementUseCase, AI: sc.AI, }) jobworker.RegisterGenerateCopyMatrixHandler(runner, jobworker.GenerateCopyMatrixDeps{ Jobs: jobUseCase, CopyMission: copyMissionUseCase, Persona: personaUseCase, ScanPost: scanPostUseCase, CopyDraft: copyDraftUseCase, ThreadsAccount: threadsAccountUseCase, AI: sc.AI, }) jobworker.RegisterGenerateCopyDraftHandler(runner, jobworker.GenerateCopyDraftDeps{ Jobs: jobUseCase, CopyMission: copyMissionUseCase, Persona: personaUseCase, ScanPost: scanPostUseCase, CopyDraft: copyDraftUseCase, ThreadsAccount: threadsAccountUseCase, AI: sc.AI, }) go runner.Start(workerCtx) } if c.JobScheduler.Enabled && redisClient != nil { schedulerHolder := fmt.Sprintf("%s-scheduler", hostname) interval := time.Duration(c.JobScheduler.IntervalSeconds) * time.Second if interval <= 0 { interval = time.Minute } schedulerCtx, cancel := context.WithCancel(context.Background()) sc.stopScheduler = cancel scheduler := jobworker.NewScheduler(schedulerHolder, jobUseCase) go scheduler.Start(schedulerCtx, interval) } if c.JobReaper.Enabled && redisClient != nil { interval := time.Duration(c.JobReaper.IntervalSeconds) * time.Second if interval <= 0 { interval = 30 * time.Second } reaperCtx, cancel := context.WithCancel(context.Background()) sc.stopReaper = cancel reaper := jobworker.NewReaper(jobUseCase) go reaper.Start(reaperCtx, interval) } sc.AuthJWT = middleware.NewAuthJWTMiddleware(sc.AuthToken, sc.Config.Auth).Handle sc.MemberAuth = middleware.NewMemberAuthMiddleware(sc.AuthToken, sc.Config.Auth).Handle sc.WorkerSecret = middleware.NewWorkerSecretMiddleware(sc.Config.InternalWorker).Handle return sc } func (sc *ServiceContext) Close(ctx context.Context) { if sc == nil { return } if sc.stopWorker != nil { sc.stopWorker() } if sc.stopScheduler != nil { sc.stopScheduler() } if sc.stopReaper != nil { sc.stopReaper() } _ = sc.Mongo.Close(ctx) if sc.Redis != nil { _ = sc.Redis.Close() } }