2026-06-26 08:37:04 +00:00
|
|
|
|
package bootstrap
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
|
|
"haixun-backend/internal/config"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
libcrypto "haixun-backend/internal/library/crypto"
|
2026-06-26 08:37:04 +00:00
|
|
|
|
libmongo "haixun-backend/internal/library/mongo"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
brandrepo "haixun-backend/internal/model/brand/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
contentformularepo "haixun-backend/internal/model/content_formula/repository"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
cmatrixrepo "haixun-backend/internal/model/content_matrix/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
contentopsrepo "haixun-backend/internal/model/content_ops/repository"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
copydraftrepo "haixun-backend/internal/model/copy_draft/repository"
|
|
|
|
|
|
copymissionrepo "haixun-backend/internal/model/copy_mission/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
crmcontactrepo "haixun-backend/internal/model/crm_contact/repository"
|
2026-06-26 08:37:04 +00:00
|
|
|
|
jobrepo "haixun-backend/internal/model/job/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
jobusecase "haixun-backend/internal/model/job/usecase"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
kgrepo "haixun-backend/internal/model/knowledge_graph/repository"
|
2026-06-26 08:37:04 +00:00
|
|
|
|
memberrepo "haixun-backend/internal/model/member/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
mentioninboxrepo "haixun-backend/internal/model/mention_inbox/repository"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
outreachdraftrepo "haixun-backend/internal/model/outreach_draft/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
ownpostformularepo "haixun-backend/internal/model/own_post_formula/repository"
|
2026-06-26 08:37:04 +00:00
|
|
|
|
permissionrepo "haixun-backend/internal/model/permission/repository"
|
|
|
|
|
|
permissionuc "haixun-backend/internal/model/permission/usecase"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
personarepo "haixun-backend/internal/model/persona/repository"
|
|
|
|
|
|
placementtopicrepo "haixun-backend/internal/model/placement_topic/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
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"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
scanpostrepo "haixun-backend/internal/model/scan_post/repository"
|
2026-06-26 08:37:04 +00:00
|
|
|
|
settingrepo "haixun-backend/internal/model/setting/repository"
|
2026-07-06 06:13:14 +00:00
|
|
|
|
stylepresetrepo "haixun-backend/internal/model/style_preset/repository"
|
2026-06-26 16:02:06 +00:00
|
|
|
|
threadsaccountrepo "haixun-backend/internal/model/threads_account/repository"
|
2026-06-26 08:37:04 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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{}
|
|
|
|
|
|
|
2026-06-26 16:02:06 +00:00
|
|
|
|
// cipher 只用於資料加解密;EnsureIndexes 不會用到,但 secrets repo 建構子需要它。
|
|
|
|
|
|
secretsCipher, err := libcrypto.New(cfg.Secrets.EncryptionKey)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("init secrets cipher: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 08:37:04 +00:00
|
|
|
|
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},
|
2026-06-26 16:02:06 +00:00
|
|
|
|
{"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},
|
2026-07-06 06:13:14 +00:00
|
|
|
|
{"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},
|
2026-06-26 08:37:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-07-06 06:13:14 +00:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 08:37:04 +00:00
|
|
|
|
permissionUseCase := permissionuc.NewUseCase(permissionRepository, rolePermissionRepository)
|
|
|
|
|
|
if err := permissionUseCase.EnsureDefaultPermissions(ctx); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("seed permissions catalog: %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
|
|
|
|
|
|
}
|