mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 07:06:36 +08:00
refactor(migration): delete pkg/migrator, move SQL to per-plugin embed
BREAKING: pkg/migrator/ deleted entirely. Migration SQL files are now owned by each plugin in its own migrations/ directory. Architecture: - Delete pkg/migrator/ (26 global SQL files + ClickHouse migration) - Move global SQL to per-plugin migrations/ with go:embed + Register() - Rewrite cmd/app.go gooseEngine: uses Inject[DBService] for DB, iterates all plugin-registered MigrationEntry, runs goose.Up per entry - core.MigrationEngine.Migrate signature changed: *Context instead of context.Context, so engine can resolve services via IoC Per-plugin migration ownership: auth/ → w_access_tokens, w_auth_sources, w_external_accounts user/ → w_users (seed system user) admin/ → w_system_configs, w_templates (seeds) upload/ → w_uploads, w_upload_stats message_gateway/ → w_push_*, w_message_* risk_control/ → w_user_access_logs (PG + ClickHouse) driver_asynq_cron/ → w_schedules driver_asynq_worker/ → w_task_executions Dependencies: - cmd/banner.go: removed migration report display (migrations are automatic) - cmd/reset_passwd.go: removed PreRun migrator.Migrate() call - go.mod: clickhouse-go kept (used by plugins/infra/database/clickhouse.go)
This commit is contained in:
+63
-7
@@ -4,12 +4,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/Rain-kl/Wavelet/pkg/config"
|
||||
"github.com/Rain-kl/Wavelet/pkg/migrator"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/admin"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/cap"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/plugins/infra/database"
|
||||
"github.com/Rain-kl/Wavelet/plugins/infra/logger"
|
||||
"github.com/Rain-kl/Wavelet/plugins/infra/storage"
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
// newWaveletApp creates a core.App wired with Wavelet platform infrastructure, domain plugins, and profile drivers.
|
||||
@@ -56,7 +58,7 @@ func newWaveletApp(profile core.Profile) *core.App {
|
||||
system.New(),
|
||||
)
|
||||
|
||||
// 3. Bind Goose migration engine (wraps pkg/migrator.Migrate)
|
||||
// 3. Bind Goose migration engine
|
||||
app.SetMigrationEngine(&gooseEngine{})
|
||||
|
||||
// 4. Mount runtime drivers for each aspect
|
||||
@@ -69,11 +71,65 @@ func newWaveletApp(profile core.Profile) *core.App {
|
||||
return app
|
||||
}
|
||||
|
||||
// gooseEngine implements core.MigrationEngine by wrapping pkg/migrator.Migrate.
|
||||
// It runs all centrally managed Goose SQL migrations during app startup.
|
||||
// gooseEngine implements core.MigrationEngine by iterating all plugin-registered
|
||||
// migration entries and applying each plugin's migrations against the shared DB.
|
||||
//
|
||||
// Each plugin owns its own `migrations/*.sql` directory, embedded via go:embed
|
||||
// and registered via ctx.Migrations().Register(pluginID, embedFS). The engine
|
||||
// resolves DBService from the IoC container and runs every entry in order.
|
||||
type gooseEngine struct{}
|
||||
|
||||
func (e *gooseEngine) Migrate(_ context.Context, _ []core.MigrationEntry) error {
|
||||
_ = migrator.Migrate()
|
||||
func (e *gooseEngine) Migrate(ctx *core.Context, entries []core.MigrationEntry) error {
|
||||
if len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve DBService from the IoC container.
|
||||
var dbSvc contracts.DBService
|
||||
if err := core.Using[contracts.DBService](ctx, func(svc contracts.DBService) {
|
||||
dbSvc = svc
|
||||
}); err != nil {
|
||||
return fmt.Errorf("migration: resolve DBService: %w", err)
|
||||
}
|
||||
|
||||
gormDB := dbSvc.GORM()
|
||||
if gormDB == nil {
|
||||
return fmt.Errorf("migration: DBService.GORM() returned nil")
|
||||
}
|
||||
|
||||
sqlDB, err := gormDB.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("migration: get underlying %s from GORM: %w", dialectName(), err)
|
||||
}
|
||||
|
||||
dialect := dialectName()
|
||||
for _, entry := range entries {
|
||||
log.Printf("[migrate] applying %s migrations (%s)", entry.PluginID, entry.Dir)
|
||||
|
||||
goose.SetBaseFS(entry.FS)
|
||||
if err := goose.SetDialect(dialect); err != nil {
|
||||
return fmt.Errorf("migration %s: set dialect %q: %w", entry.PluginID, dialect, err)
|
||||
}
|
||||
|
||||
dir := entry.Dir
|
||||
if dir == "" {
|
||||
dir = "migrations"
|
||||
}
|
||||
|
||||
if err := goose.Up(sqlDB, dir); err != nil {
|
||||
return fmt.Errorf("migration %s: apply %w", entry.PluginID, err)
|
||||
}
|
||||
|
||||
log.Printf("[migrate] %s migrations applied", entry.PluginID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dialectName returns the goose dialect based on the configured database engine.
|
||||
func dialectName() string {
|
||||
if !config.Config.Database.Enabled {
|
||||
return "sqlite3"
|
||||
}
|
||||
return "postgres"
|
||||
}
|
||||
+1
-17
@@ -14,14 +14,11 @@ import (
|
||||
|
||||
"github.com/Rain-kl/Wavelet/pkg/buildinfo"
|
||||
"github.com/Rain-kl/Wavelet/pkg/config"
|
||||
"github.com/Rain-kl/Wavelet/pkg/migrator"
|
||||
)
|
||||
|
||||
//nolint:unused // startup banner formatting utilities
|
||||
type startupState struct {
|
||||
mode string
|
||||
relationalDB migrator.Report
|
||||
clickHouseDB migrator.Report
|
||||
listensForHTTP bool
|
||||
}
|
||||
|
||||
@@ -42,8 +39,6 @@ func formatStartupBanner(state startupState) string {
|
||||
fmt.Sprintf(" Environment: %s", config.Config.App.Env),
|
||||
fmt.Sprintf(" Runtime: %s/%s (%s)", runtime.GOOS, runtime.GOARCH, runtime.Version()),
|
||||
fmt.Sprintf(" Build time: %s", buildTime()),
|
||||
fmt.Sprintf(" Database: %s", formatMigration(state.relationalDB)),
|
||||
fmt.Sprintf(" Analytics: %s", formatMigration(state.clickHouseDB)),
|
||||
}
|
||||
if state.listensForHTTP {
|
||||
lines = append(lines, fmt.Sprintf(" Listening: http://%s", config.Config.App.Addr))
|
||||
@@ -57,15 +52,4 @@ func buildTime() string {
|
||||
return "development build"
|
||||
}
|
||||
return buildinfo.BuildTime
|
||||
}
|
||||
|
||||
func formatMigration(report migrator.Report) string {
|
||||
if !report.Enabled {
|
||||
return "disabled"
|
||||
}
|
||||
state := "up to date"
|
||||
if report.Applied {
|
||||
state = "upgraded"
|
||||
}
|
||||
return fmt.Sprintf("%s (version %d, %s)", report.Backend, report.Version, state)
|
||||
}
|
||||
}
|
||||
+2
-12
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/Rain-kl/Wavelet/pkg/buildinfo"
|
||||
"github.com/Rain-kl/Wavelet/pkg/config"
|
||||
"github.com/Rain-kl/Wavelet/pkg/migrator"
|
||||
)
|
||||
|
||||
func TestFormatStartupBanner(t *testing.T) {
|
||||
@@ -30,14 +29,7 @@ func TestFormatStartupBanner(t *testing.T) {
|
||||
config.Config.App.Addr = ":3000"
|
||||
|
||||
banner := formatStartupBanner(startupState{
|
||||
mode: "API",
|
||||
relationalDB: migrator.Report{
|
||||
Backend: "PostgreSQL",
|
||||
Enabled: true,
|
||||
Version: 202607150003,
|
||||
Applied: true,
|
||||
},
|
||||
clickHouseDB: migrator.Report{Backend: "ClickHouse"},
|
||||
mode: "API",
|
||||
listensForHTTP: true,
|
||||
})
|
||||
|
||||
@@ -45,8 +37,6 @@ func TestFormatStartupBanner(t *testing.T) {
|
||||
"Wavelet v3.2.1",
|
||||
"Environment: production",
|
||||
"Build time: 2026-07-13T08:00:00Z",
|
||||
"Database: PostgreSQL (version 202607150003, upgraded)",
|
||||
"Analytics: disabled",
|
||||
"Listening: http://:3000",
|
||||
"Mode: API",
|
||||
} {
|
||||
@@ -54,4 +44,4 @@ func TestFormatStartupBanner(t *testing.T) {
|
||||
t.Errorf("banner missing %q:\n%s", want, banner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -13,7 +13,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/pkg/migrator"
|
||||
userdomain "github.com/Rain-kl/Wavelet/plugins/domain/user"
|
||||
"github.com/Rain-kl/Wavelet/plugins/infra/database"
|
||||
|
||||
@@ -46,12 +45,12 @@ func generateRandomPassword(length int) (string, error) {
|
||||
var resetPasswdCmd = &cobra.Command{
|
||||
Use: "reset-passwd",
|
||||
Short: "重置指定账号密码",
|
||||
PreRun: func(_ *cobra.Command, _ []string) {
|
||||
migrator.Migrate()
|
||||
},
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Ensure database is initialized
|
||||
database.DB(ctx)
|
||||
|
||||
var username string
|
||||
if usernameFlag != "" {
|
||||
username = usernameFlag
|
||||
|
||||
Reference in New Issue
Block a user