package bootstrap import ( "context" "fmt" "haixun-backend/internal/config" libcrypto "haixun-backend/internal/library/crypto" libmongo "haixun-backend/internal/library/mongo" brandrepo "haixun-backend/internal/model/brand/repository" contentformularepo "haixun-backend/internal/model/content_formula/repository" cmatrixrepo "haixun-backend/internal/model/content_matrix/repository" contentopsrepo "haixun-backend/internal/model/content_ops/repository" copydraftrepo "haixun-backend/internal/model/copy_draft/repository" copymissionrepo "haixun-backend/internal/model/copy_mission/repository" crmcontactrepo "haixun-backend/internal/model/crm_contact/repository" jobrepo "haixun-backend/internal/model/job/repository" jobusecase "haixun-backend/internal/model/job/usecase" kgrepo "haixun-backend/internal/model/knowledge_graph/repository" memberrepo "haixun-backend/internal/model/member/repository" mentioninboxrepo "haixun-backend/internal/model/mention_inbox/repository" outreachdraftrepo "haixun-backend/internal/model/outreach_draft/repository" ownpostformularepo "haixun-backend/internal/model/own_post_formula/repository" permissionrepo "haixun-backend/internal/model/permission/repository" permissionuc "haixun-backend/internal/model/permission/usecase" personarepo "haixun-backend/internal/model/persona/repository" placementtopicrepo "haixun-backend/internal/model/placement_topic/repository" publishanalyticsrepo "haixun-backend/internal/model/publish_analytics/repository" publishguardrepo "haixun-backend/internal/model/publish_guard/repository" publishinventoryrepo "haixun-backend/internal/model/publish_inventory/repository" publishqueuerepo "haixun-backend/internal/model/publish_queue/repository" publishqueueeventrepo "haixun-backend/internal/model/publish_queue_event/repository" scanpostrepo "haixun-backend/internal/model/scan_post/repository" settingrepo "haixun-backend/internal/model/setting/repository" stylepresetrepo "haixun-backend/internal/model/style_preset/repository" threadsaccountrepo "haixun-backend/internal/model/threads_account/repository" ) type InitOptions struct { TenantID string AdminEmail string AdminPass string DisplayName string } type InitReport struct { IndexesEnsured bool PermissionsSeeded bool RolePermissionsSeeded bool AdminUID string AdminCreated bool } func Init(ctx context.Context, cfg config.Config, opts InitOptions) (*InitReport, error) { if cfg.Mongo.URI == "" || cfg.Mongo.Database == "" { return nil, fmt.Errorf("mongo URI and database are required") } if opts.TenantID == "" { return nil, fmt.Errorf("tenant_id is required") } if opts.AdminEmail == "" || opts.AdminPass == "" { return nil, fmt.Errorf("admin email and password are required") } mongoClient, err := libmongo.NewClient(ctx, cfg.Mongo) if err != nil { return nil, fmt.Errorf("connect mongo: %w", err) } defer func() { _ = mongoClient.Close(ctx) }() db := mongoClient.Database() report := &InitReport{} // cipher 只用於資料加解密;EnsureIndexes 不會用到,但 secrets repo 建構子需要它。 secretsCipher, err := libcrypto.New(cfg.Secrets.EncryptionKey) if err != nil { return nil, fmt.Errorf("init secrets cipher: %w", err) } settingRepository := settingrepo.NewMongoRepository(db) memberRepository := memberrepo.NewMongoRepository(db) permissionRepository := permissionrepo.NewMongoPermissionRepository(db) rolePermissionRepository := permissionrepo.NewMongoRolePermissionRepository(db) jobTemplateRepository := jobrepo.NewMongoTemplateRepository(db) jobRunRepository := jobrepo.NewMongoRunRepository(db) jobScheduleRepository := jobrepo.NewMongoScheduleRepository(db) jobEventRepository := jobrepo.NewMongoEventRepository(db) repos := []struct { name string fn func(context.Context) error }{ {"settings", settingRepository.EnsureIndexes}, {"members", memberRepository.EnsureIndexes}, {"permissions", permissionRepository.EnsureIndexes}, {"role_permissions", rolePermissionRepository.EnsureIndexes}, {"job_templates", jobTemplateRepository.EnsureIndexes}, {"job_runs", jobRunRepository.EnsureIndexes}, {"job_schedules", jobScheduleRepository.EnsureIndexes}, {"job_events", jobEventRepository.EnsureIndexes}, {"copy_missions", copymissionrepo.NewMongoRepository(db).EnsureIndexes}, {"copy_drafts", copydraftrepo.NewMongoRepository(db).EnsureIndexes}, {"scan_posts", scanpostrepo.NewMongoRepository(db).EnsureIndexes}, {"outreach_drafts", outreachdraftrepo.NewMongoRepository(db).EnsureIndexes}, {"content_matrix", cmatrixrepo.NewMongoRepository(db).EnsureIndexes}, {"knowledge_graph", kgrepo.NewMongoRepository(db).EnsureIndexes}, {"personas", personarepo.NewMongoRepository(db).EnsureIndexes}, {"brands", brandrepo.NewMongoRepository(db).EnsureIndexes}, {"placement_topics", placementtopicrepo.NewMongoRepository(db).EnsureIndexes}, {"threads_accounts", threadsaccountrepo.NewMongoRepository(db).EnsureIndexes}, {"threads_account_secrets", threadsaccountrepo.NewSecretsMongoRepository(db, secretsCipher).EnsureIndexes}, {"publish_analytics", publishanalyticsrepo.NewMongoRepository(db).EnsureIndexes}, {"publish_queue", publishqueuerepo.NewMongoRepository(db).EnsureIndexes}, {"publish_inventory", publishinventoryrepo.NewMongoRepository(db).EnsureIndexes}, {"publish_guard", publishguardrepo.NewMongoRepository(db).EnsureIndexes}, {"publish_queue_events", publishqueueeventrepo.NewMongoRepository(db).EnsureIndexes}, {"style_presets", stylepresetrepo.NewMongoRepository(db).EnsureIndexes}, {"own_post_formulas", ownpostformularepo.NewMongoRepository(db).EnsureIndexes}, {"content_formulas", contentformularepo.NewMongoRepository(db).EnsureIndexes}, {"content_ops", contentopsrepo.NewMongoRepository(db).EnsureIndexes}, {"mention_inbox", mentioninboxrepo.NewMongoRepository(db).EnsureIndexes}, {"crm_contacts", crmcontactrepo.NewMongoRepository(db).EnsureIndexes}, } for _, repo := range repos { if err := repo.fn(ctx); err != nil { return nil, fmt.Errorf("ensure %s indexes: %w", repo.name, err) } } report.IndexesEnsured = true jobUseCase := jobusecase.NewUseCase(jobTemplateRepository, jobRunRepository, jobScheduleRepository, jobEventRepository, nil) jobTemplates := []struct { name string fn func(context.Context) error }{ {"demo", jobUseCase.EnsureDemoTemplate}, {"style_8d", jobUseCase.EnsureStyle8DTemplate}, {"expand_graph", jobUseCase.EnsureExpandGraphTemplate}, {"placement_scan", jobUseCase.EnsurePlacementScanTemplate}, {"scan_viral", jobUseCase.EnsureScanViralTemplate}, {"generate_outreach_draft", jobUseCase.EnsureGenerateOutreachDraftTemplate}, {"refresh_threads_token", jobUseCase.EnsureRefreshThreadsTokenTemplate}, {"publish_analytics", jobUseCase.EnsurePublishAnalyticsTemplate}, } for _, template := range jobTemplates { if err := template.fn(ctx); err != nil { return nil, fmt.Errorf("seed %s job template: %w", template.name, err) } } permissionUseCase := permissionuc.NewUseCase(permissionRepository, rolePermissionRepository) if err := permissionUseCase.EnsureDefaultPermissions(ctx); err != nil { return nil, fmt.Errorf("seed permissions catalog: %w", err) } if cfg.Permission.SeedAdminRegister { if err := permissionUseCase.EnsureAdminRegisterPermission(ctx); err != nil { return nil, fmt.Errorf("seed admin register permission: %w", err) } } report.PermissionsSeeded = true if err := permissionUseCase.EnsureDefaultRolePermissions(ctx, opts.TenantID); err != nil { return nil, fmt.Errorf("seed role permissions: %w", err) } report.RolePermissionsSeeded = true admin, created, err := EnsureAdminMember(ctx, memberRepository, AdminOptions{ TenantID: opts.TenantID, Email: opts.AdminEmail, Password: opts.AdminPass, DisplayName: opts.DisplayName, }) if err != nil { return nil, err } report.AdminUID = admin.UID report.AdminCreated = created return report, nil }