diff --git a/cmd/app.go b/cmd/app.go index 3980dff0..ba03d852 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -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" +} \ No newline at end of file diff --git a/cmd/banner.go b/cmd/banner.go index f2779a9c..22b55c16 100644 --- a/cmd/banner.go +++ b/cmd/banner.go @@ -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) -} +} \ No newline at end of file diff --git a/cmd/banner_test.go b/cmd/banner_test.go index de64db75..4ed9c5e3 100644 --- a/cmd/banner_test.go +++ b/cmd/banner_test.go @@ -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) } } -} +} \ No newline at end of file diff --git a/cmd/reset_passwd.go b/cmd/reset_passwd.go index 8f247b28..dcbe9bf9 100644 --- a/cmd/reset_passwd.go +++ b/cmd/reset_passwd.go @@ -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 diff --git a/core/app.go b/core/app.go index 85ef1533..118fe15b 100644 --- a/core/app.go +++ b/core/app.go @@ -212,7 +212,7 @@ func (a *App) ApplyPlugins() error { } // RunMigrations dispatches migration execution across all registered plugin migration entries. -func (a *App) RunMigrations(ctx context.Context) error { +func (a *App) RunMigrations() error { entries := a.ctx.Migrations().Entries() if len(entries) == 0 { return nil @@ -233,7 +233,7 @@ func (a *App) RunMigrations(ctx context.Context) error { return nil } - if err := engine.Migrate(ctx, entries); err != nil { + if err := engine.Migrate(a.ctx, entries); err != nil { return fmt.Errorf("core: migration failed: %w", err) } @@ -275,7 +275,7 @@ func (a *App) Start(ctx ...context.Context) error { } // 2. Run migrations - if err := a.RunMigrations(baseCtx); err != nil { + if err := a.RunMigrations(); err != nil { a.mu.Lock() a.running = false a.mu.Unlock() diff --git a/core/app_test.go b/core/app_test.go index 5de3a3c7..327a3e58 100644 --- a/core/app_test.go +++ b/core/app_test.go @@ -321,7 +321,7 @@ func TestAppStartDriverFailureRollback(t *testing.T) { func TestAppMigrationEngineExecution(t *testing.T) { var migratedEntries []extpoints.MigrationEntry - runner := core.MigrationRunner(func(ctx context.Context, entries []extpoints.MigrationEntry) error { + runner := core.MigrationRunner(func(ctx *core.Context, entries []extpoints.MigrationEntry) error { migratedEntries = entries return nil }) @@ -354,7 +354,7 @@ func TestAppMigrationEngineExecution(t *testing.T) { func TestAppMigrationEngineFromIoCContainer(t *testing.T) { var executed bool - runner := core.MigrationRunner(func(ctx context.Context, entries []extpoints.MigrationEntry) error { + runner := core.MigrationRunner(func(ctx *core.Context, entries []extpoints.MigrationEntry) error { executed = true return nil }) @@ -483,7 +483,7 @@ func TestAppIdempotencyAndErrorStates(t *testing.T) { assert.False(t, app2.IsRunning()) // Migration failure - migFailRunner := core.MigrationRunner(func(ctx context.Context, entries []extpoints.MigrationEntry) error { + migFailRunner := core.MigrationRunner(func(ctx *core.Context, entries []extpoints.MigrationEntry) error { return errors.New("sql migrate error") }) sqlFS := fstest.MapFS{ diff --git a/core/types.go b/core/types.go index f4ceea07..2197a9ca 100644 --- a/core/types.go +++ b/core/types.go @@ -96,15 +96,17 @@ const ( ) // MigrationEngine is the interface for executing database migrations across registered plugins. +// The ctx parameter is the root micro-kernel Context, allowing the engine to resolve +// services from the IoC container via core.Inject or core.Using. type MigrationEngine interface { - Migrate(ctx context.Context, entries []MigrationEntry) error + Migrate(ctx *Context, entries []MigrationEntry) error } // MigrationRunner is a function adapter implementing MigrationEngine. -type MigrationRunner func(ctx context.Context, entries []MigrationEntry) error +type MigrationRunner func(ctx *Context, entries []MigrationEntry) error // Migrate calls the underlying migration function. -func (fn MigrationRunner) Migrate(ctx context.Context, entries []MigrationEntry) error { +func (fn MigrationRunner) Migrate(ctx *Context, entries []MigrationEntry) error { return fn(ctx, entries) } diff --git a/docs/superpowers/plans/2026-08-28-migration-split-plan.md b/docs/superpowers/plans/2026-08-28-migration-split-plan.md new file mode 100644 index 00000000..48abc22a --- /dev/null +++ b/docs/superpowers/plans/2026-08-28-migration-split-plan.md @@ -0,0 +1,326 @@ +# 迁移脚本拆分执行计划 + +## 背景现状 + +| 维度 | 实际状态 | +|---|---| +| 总 SQL 文件 | 26 个全局 (`pkg/migrator/goose/`) + 5 个插件 (`plugins/domain/*/migrations/`) + 1 个 ClickHouse | +| 实际运行的迁移 | **仅 26 个全局文件**(通过 `gooseEngine` → `pkg/migrator.Migrate()`) | +| 插件注册的迁移 | 3 个 (`auth`, `user`, `message_gateway`) — 注册了但被 `gooseEngine` 丢弃 | +| 有迁移文件但未注册的插件 | `admin`(2 个文件,0 个调用) | +| 无迁移文件的插件 | `upload`, `risk_control`, `cap`, `driver_asynq_worker`, `driver_asynq_cron` | +| ClickHouse 迁移 | 1 个文件 (`w_user_access_logs`),通过 `pkg/migrator.MigrateClickHouse()` 单独运行 | + +## 表所有者映射 + +以下列表基于"单一所有者原则",每个表精确映射到一个插件: + +| 表名 | 所有者插件 | 涉及全局迁移 | +|---|---|---| +| `w_users` | `domain/user` | 20260609 (create), 20260614 (seed system user) | +| `w_access_tokens` | `domain/auth` | 20260609 (create), 20260610 (is_admin), 20260611 (rm last_used_at) | +| `w_auth_sources` | `domain/auth` | 20260609 (create) | +| `w_external_accounts` | `domain/auth` | 20260609 (create) | +| `w_system_configs` | `domain/admin` | 20260609→20260611 (rename+seeds×7), 20260613 (TEXT), 20260816 (log_db) | +| `w_templates` | `domain/admin` | 20260609→20260611 (rename) | +| `w_schedules` | `driver_asynq_cron` | 20260610 (create), 20260611 (identity), 20260614 (update cleanup) | +| `w_task_executions` | `driver_asynq_worker` | 20260609→20260611 (rename) | +| `w_uploads` | `domain/upload` | 20260609→20260611 (rename), 20260613 (access_mode), 20260617 (indexes), 20260618 (drop storage_driver) | +| `w_upload_stats` | `domain/upload` | 20260617 (create+backfill) | +| `w_push_events` | `domain/message_gateway` | 20260614 (create), 20260615 (task_type), 20260616 (cleanup) | +| `w_push_histories` | `domain/message_gateway` | 20260614 (create) | +| `w_push_channels` | `domain/message_gateway` | 20260614 (create) | +| `w_message_channels` | `domain/message_gateway` | 20260816 (create) | +| `w_message_bindings` | `domain/message_gateway` | 20260816 (create) | +| `w_message_pairing_codes` | `domain/message_gateway` | 20260816 (create) | +| `w_user_access_logs` | `domain/risk_control` | 20260816 (create) + ClickHouse | + +## 执行步骤(共 8 步) + +--- + +### 步骤 1:创建 Bootstrap 迁移(保留在 `pkg/migrator`) + +**文件**:`pkg/migrator/goose/postgres/00001_bootstrap.sql` + +将以下全局迁移合并为一个 bootstrap 文件: +- **`202606090001_initial_schema.sql`** → 创建 `users`, `auth_sources`, `external_accounts`, `access_tokens`, `system_configs`, `uploads`, `task_executions`, `templates`(全部无前缀旧名) +- **`202606110003_rename_tables_to_w_prefix.sql`** → 全部重命名为 `w_` 前缀 + +**合并后,bootstrap 文件直接创建带 `w_` 前缀的表**,不再需要 rename 步骤: + +```sql +-- +goose Up +CREATE TABLE IF NOT EXISTS w_users ( + id BIGINT PRIMARY KEY, + username VARCHAR(64) UNIQUE, + ... +); +CREATE TABLE IF NOT EXISTS w_access_tokens (...); +CREATE TABLE IF NOT EXISTS w_auth_sources (...); +CREATE TABLE IF NOT EXISTS w_external_accounts (...); +CREATE TABLE IF NOT EXISTS w_system_configs ( + key VARCHAR(64) PRIMARY KEY, + value TEXT NOT NULL, + ... +); +CREATE TABLE IF NOT EXISTS w_uploads (...); +CREATE TABLE IF NOT EXISTS w_task_executions (...); +CREATE TABLE IF NOT EXISTS w_templates (...); +CREATE TABLE IF NOT EXISTS w_schedules (...); +``` + +> **为什么保留在 `pkg/migrator`**:这些是平台的"初始化基座"——无论哪些插件启用,这些表都存在。将 bootstrap 放到 `pkg/migrator` 之下回避了循环依赖问题(例如 `w_system_configs` 属于 admin,但 bootstrap 时 admin 插件尚未 apply)。 + +--- + +### 步骤 2:修复 `gooseEngine` 支持插件迁移 + +**文件**:`cmd/app.go` + +```go +type gooseEngine struct{} + +func (e *gooseEngine) Migrate(_ context.Context, entries []core.MigrationEntry) error { + // 1. 先跑 bootstrap(初始化基座) + _ = migrator.Migrate() + + // 2. 再跑每个插件注册的迁移 + for _, entry := range entries { + gormDB := database.DB(context.Background()) + if gormDB == nil { + continue + } + sqlDB, err := gormDB.DB() + if err != nil { + return err + } + + goose.SetBaseFS(entry.FS) + if err := goose.SetDialect(gooseDialect()); err != nil { + return err + } + dir := entry.Dir + if dir == "" { + dir = "migrations" + } + if err := goose.Up(sqlDB, dir); err != nil { + return fmt.Errorf("migrate %s: %w", entry.PluginID, err) + } + } + + // 3. ClickHouse 迁移 + _ = migrator.MigrateClickHouse() + + return nil +} +``` + +依赖项:`gooseDialect()` 从 `pkg/migrator` 导出。 + +--- + +### 步骤 3:按表所有者拆分迁移到各插件 + +| 全局源文件 | 目标插件 | 迁移文件名 | +|---|---|---| +| `202606100002` (access_tokens is_admin) | `domain/auth` | `migrations/00002_add_access_token_is_admin.sql` | +| `202606110001` (drop last_used_at) | `domain/auth` | `migrations/00003_drop_access_token_last_used_at.sql` | +| `202606140003` (system user seed) | `domain/user` | `migrations/00002_seed_system_user.sql` | +| `202606110004` (file_access_whitelist seed) | `domain/admin` | `migrations/00003_seed_file_access_whitelist.sql` | +| `202606110005` (disk_cache configs seed) | `domain/admin` | `migrations/00004_seed_disk_cache_configs.sql` | +| `202606120002` (update_upstream_repo seed) | `domain/admin` | `migrations/00005_seed_upstream_repo_config.sql` | +| `202606130002` (system_configs value TEXT) | `domain/admin` | `migrations/00006_expand_config_value.sql` | +| `202606130003` (storage_config seed) | `domain/admin` | `migrations/00007_seed_storage_config.sql` | +| `202608160002` (log database configs) | `domain/admin` | `migrations/00008_seed_log_db_configs.sql` | +| `202606120001` (login_session_ttl) | `domain/auth` | `migrations/00004_seed_login_session_ttl.sql` | +| `202606130001` (w_uploads access_mode) | `domain/upload` | `migrations/00001_add_access_mode.sql` | +| `202606170001` (upload indexes) | `domain/upload` | `migrations/00002_add_composite_indexes.sql` | +| `202606170002` (upload stats table) | `domain/upload` | `migrations/00003_create_upload_stats.sql` | +| `202606170003` (backfill stats) | `domain/upload` | `migrations/00004_backfill_upload_stats.sql` | +| `202606180001` (drop storage_driver) | `domain/upload` | `migrations/00005_drop_storage_driver.sql` | +| `202606140001` (push tables) | `domain/message_gateway` | `migrations/00002_create_push_tables.sql` | +| `202606140004` (push channels) | `domain/message_gateway` | `migrations/00003_create_push_channels.sql` | +| `202606150001` (push task_type) | `domain/message_gateway` | `migrations/00004_add_push_task_type.sql` | +| `202606160001` (remove push config) | `domain/message_gateway` | `migrations/00005_remove_push_config.sql` | +| `202608160003` (message gateway tables) | `domain/message_gateway` | `migrations/00006_create_message_tables.sql` | +| `202606100001` (schedules) | `driver_asynq_cron` | `migrations/00001_create_schedules.sql` | +| `202606110002` (schedules identity) | `driver_asynq_cron` | `migrations/00002_alter_schedules_identity.sql` | +| `202606140005` (update cleanup schedule) | `driver_asynq_cron` | `migrations/00003_update_cleanup_schedule.sql` | +| `202608160001` (user access logs) | `domain/risk_control/logstore` | `migrations/00001_create_access_logs.sql` | +| `202608160002` (log_database configs) | `domain/admin` | (合并到 admin 步骤 7) | + +--- + +### 步骤 4:补充缺失的 `go:embed` 和 `Register()` 调用 + +**`plugins/domain/admin/plugin.go`**: +```go +//go:embed migrations/*.sql +var adminMigrations embed.FS + +// 在 Apply() 中: +ctx.Migrations().Register("admin", adminMigrations) +``` + +**`plugins/domain/upload/plugin.go`**: +```go +//go:embed migrations/*.sql +var uploadMigrations embed.FS + +// 在 Apply() 中: +ctx.Migrations().Register("upload", uploadMigrations) +``` + +**`plugins/domain/risk_control/plugin.go`**: +```go +// go:embed 由 logstore 子包自行处理(它已有自己的 moved 文件) +// 在 Apply() 中: +ctx.Migrations().Register("risk_control/logstore", logstoreMigrationFS) +``` + +**`plugins/drivers/driver_asynq_cron/plugin.go`**: +```go +//go:embed migrations/*.sql +var cronMigrations embed.FS + +// 在 Apply() 中: +ctx.Migrations().Register("driver_asynq_cron", cronMigrations) +``` + +--- + +### 步骤 5:解决 Admin 插件迁移与 Bootstrap 的冲突 + +当前 `admin/migrations/00001` 执行 `CREATE TABLE IF NOT EXISTS w_system_configs (...)`,但 bootstrap 已在步骤 1 中创建过这张表。需要: +1. **保持 `IF NOT EXISTS`** 保证幂等性 +2. **从 admin migration 中移除 `w_schedules` 和 `w_task_executions` 的 CREATE**(它们在 bootstrap 中创建,属于 driver 插件) +3. **仅保留 admin 自己的表**:`w_system_configs`, `w_templates` +4. Seed 数据使用 `ON CONFLICT DO NOTHING` 避免重复: + +当前 admin 的 seed 包含 29 个系统配置,其中约 14 个与全局迁移重复。整理后的 admin seed 应: + +```sql +INSERT INTO w_system_configs (...) VALUES + ('cap_login_enabled', 'false', ...), + ('cap_auto_solve', 'true', ...), + -- ... (所有 29 个配置) +ON CONFLICT (key) DO NOTHING; +``` + +> 全局迁移中 `202606110004` 到 `202608160002` 的 7 个种子 INSERT 将被迁移到 admin,全部使用 `ON CONFLICT DO NOTHING`。 + +--- + +### 步骤 6:清理已迁移的全局文件 + +拆分完成后,从 `pkg/migrator/goose/postgres/` 中删除以下文件: + +``` +202606100002_access_token_is_admin.sql +202606100001_create_schedules.sql +202606110001_remove_access_token_last_used_at.sql +202606110002_alter_schedules_id_auto_increment.sql +202606110004_add_file_access_whitelist_config.sql +202606110005_add_disk_cache_configs.sql +202606120001_add_login_session_ttl_config.sql +202606120002_add_update_upstream_repository_config.sql +202606130001_add_upload_access_mode.sql +202606130002_expand_system_config_value.sql +202606130003_add_storage_config.sql +202606140001_create_push_tables.sql +202606140003_add_system_user.sql +202606140004_create_push_channels.sql +202606140005_update_system_cleanup_schedule.sql +202606150001_add_task_type_to_push_events.sql +202606160001_remove_push_config.sql +202606170001_add_upload_composite_indexes.sql +202606170002_create_upload_stats_table.sql +202606170003_backfill_upload_stats.sql +202606180001_drop_upload_storage_driver.sql +202608160001_create_user_access_logs.sql +202608160002_log_database_configs.sql +202608160003_create_message_gateway.sql +``` + +**保留在 `pkg/migrator/goose/postgres/` 的仅限**: +``` +00001_bootstrap.sql (合并后的初始化基座) +``` + +**注意**:`202606110003_rename_tables_to_w_prefix.sql` 也被合并进 bootstrap。`202606090001_initial_schema.sql` 也被合并掉。 + +--- + +### 步骤 7:更新 `pkg/migrator` 导出 `gooseDialect()` + +在 `pkg/migrator/migrator.go` 中将 `gooseDialect()` 和 `migrationDir()` 改为导出,供 `cmd/app.go` 的 `gooseEngine.Migrate()` 引用。 + +--- + +### 步骤 8:验证 + 提交 + +```bash +cd /Users/ryan/Code/Go/Wavelet + +# 1. 编译验证 +go build -mod=mod ./... +go vet ./... + +# 2. 架构门禁验证 +make code-check + +# 3. 验证插件迁移注册完整性 +grep -rn 'go:embed.*migrations' plugins/domain/*/plugin.go plugins/drivers/*/plugin.go +grep -rn 'Migrations()\.Register' plugins/domain/*/plugin.go plugins/drivers/*/plugin.go +# → 每个有 migrations/ 目录的插件既要有 go:embed 又要有 Register() + +# 4. 验证 admin 插件迁移完整性 +grep -rn 'w_schedules\|w_task_executions' plugins/domain/admin/migrations/ +# → 不应有(这些属于 driver 插件) + +# 5. 提交 +git add -A && git commit -m "refactor(migration): split global SQL into per-plugin migrations + +- Merge 26 global SQLs into bootstrap + per-plugin migrations +- Fix gooseEngine to iterate plugin-registered MigrationEntry +- Add go:embed + Register() to admin, upload, risk_control, driver_asynq_cron +- Remove 23 migrated SQL files from pkg/migrator/goose/ +- Keep only bootstrap in pkg/migrator/goose/ +- All CREATE TABLE use IF NOT EXISTS, all INSERT use ON CONFLICT DO NOTHING" +``` + +--- + +## 依赖关系图 + +``` +Bootstrap (pkg/migrator) + ├── 创建 w_users, w_access_tokens, w_auth_sources, w_external_accounts + ├── 创建 w_system_configs, w_templates, w_schedules, w_task_executions + ├── 创建 w_uploads, w_upload_stats + └── 创建所有 w_ 前缀表 + │ + ├─ auth/00002 (access_tokens is_admin) + ├─ auth/00003 (drop last_used_at) + ├─ auth/00004 (login_session_ttl seed) + │ + ├─ user/00002 (system user seed) + │ + ├─ admin/00001 (w_system_configs, w_templates) [IF NOT EXISTS] + ├─ admin/00002 (29 config seeds + 2 template seeds) + ├─ admin/00003–00008 (拆分后的种子迁移) + │ + ├─ upload/00001–00005 (access_mode → indexes → stats → backfill → drop) + │ + ├─ message_gateway/00001 (w_message_* tables) + ├─ message_gateway/00002–00006 (push tables → channels → task_type → cleanup) + │ + ├─ driver_asynq_cron/00001–00003 (schedules → identity → cleanup) + │ + ├─ driver_asynq_worker/00001 (task_executions — 如果有追加操作) + │ + └─ risk_control/logstore/00001 (w_user_access_logs) +``` + +所有步骤执行的迁移顺序由 Goose 的文件名前缀控制。Bootstrap 使用 `00001_`,每个插件的迁移从 `00002_` 开始编号(`00001` 留给插件自身表 CREATE,若插件 bootstrap 已创建则从 `00002` 开始)。 \ No newline at end of file diff --git a/pkg/migrator/clickhouse.go b/pkg/migrator/clickhouse.go deleted file mode 100644 index 145df1c6..00000000 --- a/pkg/migrator/clickhouse.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2025 linux.do -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - -package migrator - -import ( - "context" - "database/sql" - "embed" - "io/fs" - "log" - "time" - - "github.com/ClickHouse/clickhouse-go/v2" - "github.com/Rain-kl/Wavelet/pkg/config" - "github.com/pressly/goose/v3" -) - -const ( - clickhouseMigrationDir = "goose/clickhouse" - clickhouseGooseVersionTable = "goose_clickhouse_version" - clickhouseMaxExecTime = 60 - clickhouseReadTimeoutFactor = 2 -) - -// clickhouseMigrationFS contains SQL migrations under goose/clickhouse. -// -//go:embed goose/clickhouse/*.sql -var clickhouseMigrationFS embed.FS - -// MigrateClickHouse runs goose migrations against ClickHouse when enabled. -func MigrateClickHouse() Report { - if !config.Config.ClickHouse.Enabled { - return Report{Backend: "ClickHouse"} - } - - cfg := config.Config.ClickHouse - sqlDB := clickhouse.OpenDB(&clickhouse.Options{ - Addr: cfg.Hosts, - Auth: clickhouse.Auth{ - Database: cfg.Database, - Username: cfg.Username, - Password: cfg.Password, - }, - Settings: clickhouse.Settings{ - "max_execution_time": clickhouseMaxExecTime, - }, - Compression: &clickhouse.Compression{ - Method: clickhouse.CompressionLZ4, - }, - DialTimeout: time.Duration(cfg.DialTimeout) * time.Second, - MaxOpenConns: cfg.MaxOpenConn, - MaxIdleConns: cfg.MaxIdleConn, - ConnMaxLifetime: time.Duration(cfg.ConnMaxLifetime) * time.Second, - ReadTimeout: time.Duration(cfg.DialTimeout*clickhouseReadTimeoutFactor) * time.Second, - BlockBufferSize: cfg.BlockBufferSize, - }) - - subFS, err := fs.Sub(clickhouseMigrationFS, "goose/clickhouse") - if err != nil { - closeClickHouseDB(sqlDB) - log.Fatalf("[ClickHouse] get sub fs failed: %v\n", err) - } - - provider, err := goose.NewProvider( - "clickhouse", - sqlDB, - subFS, - goose.WithTableName(clickhouseGooseVersionTable), - goose.WithDisableGlobalRegistry(true), - ) - if err != nil { - closeClickHouseDB(sqlDB) - log.Fatalf("[ClickHouse] create goose provider failed: %v\n", err) - } - previousVersion, err := provider.GetDBVersion(context.Background()) - if err != nil { - closeClickHouseDB(sqlDB) - log.Fatalf("[ClickHouse] get goose version failed: %v\n", err) - } - - if _, err := provider.Up(context.Background()); err != nil { - closeClickHouseDB(sqlDB) - log.Fatalf("[ClickHouse] goose migrate failed: %v\n", err) - } - currentVersion, err := provider.GetDBVersion(context.Background()) - if err != nil { - closeClickHouseDB(sqlDB) - log.Fatalf("[ClickHouse] get migrated goose version failed: %v\n", err) - } - closeClickHouseDB(sqlDB) - - log.Println("[ClickHouse] goose migrate success") - return Report{ - Backend: "ClickHouse", - Enabled: true, - Version: currentVersion, - Applied: currentVersion != previousVersion, - } -} - -func closeClickHouseDB(sqlDB *sql.DB) { - if err := sqlDB.Close(); err != nil { - log.Printf("[ClickHouse] close sql db failed: %v\n", err) - } -} diff --git a/pkg/migrator/clickhouse_test.go b/pkg/migrator/clickhouse_test.go deleted file mode 100644 index 1b892d0c..00000000 --- a/pkg/migrator/clickhouse_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - -package migrator - -import ( - "testing" - - "github.com/Rain-kl/Wavelet/pkg/config" - "github.com/pressly/goose/v3" -) - -func TestClickHouseMigrationFilesEmbedded(t *testing.T) { - entries, err := clickhouseMigrationFS.ReadDir(clickhouseMigrationDir) - if err != nil { - t.Fatalf("ReadDir(%q) error = %v", clickhouseMigrationDir, err) - } - if len(entries) == 0 { - t.Fatal("expected embedded ClickHouse migrations, got none") - } - - found := false - for _, entry := range entries { - if entry.IsDir() { - continue - } - if entry.Name() == "202606190001_create_user_access_logs.sql" { - found = true - break - } - } - if !found { - t.Fatal("expected 202606190001_create_user_access_logs.sql in embedded migrations") - } -} - -func TestClickHouseGooseDialect(t *testing.T) { - if err := goose.SetDialect("clickhouse"); err != nil { - t.Fatalf("SetDialect(clickhouse) error = %v", err) - } -} - -func TestMigrateClickHouseSkipsWhenDisabled(t *testing.T) { - previousEnabled := config.Config.ClickHouse.Enabled - config.Config.ClickHouse.Enabled = false - t.Cleanup(func() { - config.Config.ClickHouse.Enabled = previousEnabled - }) - - MigrateClickHouse() -} diff --git a/pkg/migrator/goose/postgres/202606090001_initial_schema.sql b/pkg/migrator/goose/postgres/202606090001_initial_schema.sql deleted file mode 100644 index 5628860d..00000000 --- a/pkg/migrator/goose/postgres/202606090001_initial_schema.sql +++ /dev/null @@ -1,184 +0,0 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS users ( - id BIGINT PRIMARY KEY, - username VARCHAR(64) UNIQUE, - password VARCHAR(255), - nickname VARCHAR(255), - email VARCHAR(255), - avatar_url VARCHAR(255), - is_active BOOLEAN DEFAULT TRUE, - is_admin BOOLEAN DEFAULT FALSE, - bio VARCHAR(500), - phone VARCHAR(32), - gender VARCHAR(16), - website VARCHAR(255), - location VARCHAR(255), - last_login_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); -CREATE INDEX IF NOT EXISTS idx_users_is_active ON users (is_active); -CREATE INDEX IF NOT EXISTS idx_users_last_login_at ON users (last_login_at); -CREATE INDEX IF NOT EXISTS idx_users_created_at ON users (created_at); - -CREATE TABLE IF NOT EXISTS auth_sources ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(80) NOT NULL UNIQUE, - type VARCHAR(20) NOT NULL, - display_name VARCHAR(100), - is_active BOOLEAN NOT NULL DEFAULT FALSE, - client_id VARCHAR(255), - client_secret VARCHAR(1024), - openid_discovery_url VARCHAR(1024), - scopes VARCHAR(255), - icon_url VARCHAR(1024), - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_auth_sources_is_active ON auth_sources (is_active); - -CREATE TABLE IF NOT EXISTS external_accounts ( - id BIGSERIAL PRIMARY KEY, - auth_source_id BIGINT, - user_id BIGINT NOT NULL, - external_id VARCHAR(255) NOT NULL, - external_username VARCHAR(255), - email VARCHAR(255), - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_external_accounts_auth_source_id ON external_accounts (auth_source_id); -CREATE INDEX IF NOT EXISTS idx_external_accounts_user_id ON external_accounts (user_id); -CREATE UNIQUE INDEX IF NOT EXISTS idx_external_accounts_source_external ON external_accounts (auth_source_id, external_id); - -CREATE TABLE IF NOT EXISTS system_configs ( - key VARCHAR(64) PRIMARY KEY, - value TEXT NOT NULL, - type VARCHAR(32) NOT NULL DEFAULT 'system', - visibility INTEGER NOT NULL DEFAULT 0, - description VARCHAR(255), - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS uploads ( - id BIGINT PRIMARY KEY, - user_id BIGINT NOT NULL, - file_name VARCHAR(255) NOT NULL, - file_path VARCHAR(500) NOT NULL, - file_size BIGINT NOT NULL, - mime_type VARCHAR(100) NOT NULL, - extension VARCHAR(50) NOT NULL, - hash VARCHAR(64), - storage_driver VARCHAR(50) NOT NULL, - type VARCHAR(50) NOT NULL, - status VARCHAR(20) NOT NULL, - metadata JSONB, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_uploads_user_id ON uploads (user_id); -CREATE INDEX IF NOT EXISTS idx_uploads_file_path ON uploads (file_path); -CREATE INDEX IF NOT EXISTS idx_uploads_hash ON uploads (hash); -CREATE INDEX IF NOT EXISTS idx_uploads_type ON uploads (type); - -CREATE TABLE IF NOT EXISTS access_tokens ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL, - name VARCHAR(128) NOT NULL, - token_hash VARCHAR(64) NOT NULL UNIQUE, - masked_token VARCHAR(64) NOT NULL, - last_used_at TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_access_tokens_user_id ON access_tokens (user_id); - -CREATE TABLE IF NOT EXISTS task_executions ( - id BIGINT PRIMARY KEY, - task_id VARCHAR(128) NOT NULL UNIQUE, - task_type VARCHAR(64) NOT NULL, - task_name VARCHAR(128), - status VARCHAR(32) NOT NULL, - retryable BOOLEAN NOT NULL DEFAULT FALSE, - max_retry INTEGER NOT NULL DEFAULT 0, - retry_count INTEGER NOT NULL DEFAULT 0, - log TEXT, - error_message TEXT, - result TEXT, - started_at TIMESTAMPTZ, - finished_at TIMESTAMPTZ, - duration BIGINT, - payload TEXT, - triggered_by VARCHAR(32) NOT NULL DEFAULT 'system', - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_task_executions_task_type ON task_executions (task_type); -CREATE INDEX IF NOT EXISTS idx_task_executions_status ON task_executions (status); -CREATE INDEX IF NOT EXISTS idx_task_executions_started_at ON task_executions (started_at); -CREATE INDEX IF NOT EXISTS idx_task_executions_created_at ON task_executions (created_at); - -CREATE TABLE IF NOT EXISTS templates ( - id BIGSERIAL PRIMARY KEY, - key VARCHAR(80) NOT NULL UNIQUE, - name VARCHAR(100) NOT NULL, - type VARCHAR(20) NOT NULL DEFAULT 'email', - subject VARCHAR(255), - content TEXT NOT NULL, - description VARCHAR(255), - is_system BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_templates_is_system ON templates (is_system); -CREATE INDEX IF NOT EXISTS idx_templates_created_at ON templates (created_at); -CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates (updated_at); - -INSERT INTO system_configs (key, value, type, visibility, description, created_at, updated_at) VALUES - ('cap_login_enabled', 'false', 'system', 1, '是否启用登录人机验证(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_auto_solve', 'true', 'system', 1, '打开页面后是否自动开始计算,关闭则需用户手动点击触发', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_count', '1', 'system', 0, '客户端需求解的 PoW 难题总数,默认 1,推荐 1~5', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_size', '32', 'system', 0, '人机验证盐值长度', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_difficulty', '4', 'system', 0, '人机验证 PoW 难度(目标前缀长度)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_ttl_seconds', '600', 'system', 0, '人机验证难题有效时间(秒)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_token_ttl_seconds', '1200', 'system', 0, '人机验证兑换凭证有效时间(秒)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('server_address', '', 'system', 0, '服务器地址(用于跨域源控制,不设定则允许任意源)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_host', '', 'system', 0, 'SMTP 服务器地址(例如 smtp.example.com)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_port', '587', 'system', 0, 'SMTP 端口(例如 587 或 465)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_username', '', 'system', 0, 'SMTP 账户(如 sender@example.com)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_password', '', 'system', 0, 'SMTP 访问凭证(授权码/密码)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('upload_allowed_extensions', 'jpg,png,webp', 'system', 1, '允许上传的图片扩展名(逗号分隔)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('site_name', 'Wavelet', 'system', 1, '系统平台的展示名称', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('password_login_enabled', 'true', 'system', 1, '是否允许使用账号密码登录', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('registration_enabled', 'true', 'system', 1, '控制普通用户是否可以自主注册(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('password_register_enabled', 'true', 'system', 1, '是否允许通过密码创建本地账号', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('oidc_login_enabled', 'true', 'system', 1, '是否允许使用第三方 OIDC 认证源登录', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('max_api_keys_per_user', '5', 'business', 1, '限制每个普通用户可以创建的 API Key 最大数量', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('email_login_verification_enabled', 'false', 'system', 1, '是否开启邮箱登录验证(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('email_register_verification_enabled', 'false', 'system', 1, '是否开启邮箱注册验证(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('menu_display_config', '{}', 'system', 1, '目录显示配置(JSON 字符串,格式为 {url: enabled})', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('search_engine_indexing_enabled', 'false', 'system', 1, '是否允许搜索引擎爬取/检索该站点(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('update_upstream_repository', 'Rain-kl/Wavelet', 'system', 0, 'GitHub Actions Release 上游仓库(owner/repo 或 GitHub 仓库地址)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('storage_config', '{"driver":"local","local":{"root":"."},"s3":{"region":"us-east-1"},"r2":{"region":"auto"},"minio":{"region":"us-east-1","path_style":true},"oss":{},"webdav":{}}', 'system', 0, '文件存储驱动及连接配置(JSON)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - -INSERT INTO users (id, username, password, nickname, avatar_url, is_active, is_admin, last_login_at, created_at, updated_at) -VALUES (1, 'admin', '12345678', 'Administrator', '', TRUE, TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (username) DO NOTHING; - -INSERT INTO templates (key, name, type, subject, content, description, is_system, created_at, updated_at) VALUES - ('login_email', '登录验证码邮件', 'email', 'Wavelet 登录验证码', '
您的登录验证码为:{{.Code}},5分钟内有效,请勿将验证码泄露给他人。
', '用户密码登录时发送的验证码邮件模板,支持变量:{{.Code}}', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('register_email', '注册验证码邮件', 'email', 'Wavelet 注册验证码', '您的注册验证码为:{{.Code}},5分钟内有效,请勿泄露给他人。
', '用户注册时发送的验证码邮件模板,支持变量:{{.Code}}', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DROP TABLE IF EXISTS templates; -DROP TABLE IF EXISTS task_executions; -DROP TABLE IF EXISTS access_tokens; -DROP TABLE IF EXISTS uploads; -DROP TABLE IF EXISTS system_configs; -DROP TABLE IF EXISTS external_accounts; -DROP TABLE IF EXISTS auth_sources; -DROP TABLE IF EXISTS users; diff --git a/pkg/migrator/goose/postgres/202606100002_access_token_is_admin.sql b/pkg/migrator/goose/postgres/202606100002_access_token_is_admin.sql deleted file mode 100644 index 3163d6cf..00000000 --- a/pkg/migrator/goose/postgres/202606100002_access_token_is_admin.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -ALTER TABLE access_tokens ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT FALSE; - --- +goose Down -ALTER TABLE access_tokens DROP COLUMN IF EXISTS is_admin; diff --git a/pkg/migrator/goose/postgres/202606110001_remove_access_token_last_used_at.sql b/pkg/migrator/goose/postgres/202606110001_remove_access_token_last_used_at.sql deleted file mode 100644 index 4a0fa676..00000000 --- a/pkg/migrator/goose/postgres/202606110001_remove_access_token_last_used_at.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +goose Up --- +goose StatementBegin -ALTER TABLE access_tokens DROP COLUMN IF EXISTS last_used_at; --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -ALTER TABLE access_tokens ADD COLUMN last_used_at TIMESTAMPTZ; --- +goose StatementEnd diff --git a/pkg/migrator/goose/postgres/202606110002_alter_schedules_id_auto_increment.sql b/pkg/migrator/goose/postgres/202606110002_alter_schedules_id_auto_increment.sql deleted file mode 100644 index 0797bd64..00000000 --- a/pkg/migrator/goose/postgres/202606110002_alter_schedules_id_auto_increment.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +goose Up --- +goose StatementBegin -ALTER TABLE schedules ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH 100); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -ALTER TABLE schedules ALTER COLUMN id DROP IDENTITY IF EXISTS; --- +goose StatementEnd diff --git a/pkg/migrator/goose/postgres/202606110003_rename_tables_to_w_prefix.sql b/pkg/migrator/goose/postgres/202606110003_rename_tables_to_w_prefix.sql deleted file mode 100644 index b635e677..00000000 --- a/pkg/migrator/goose/postgres/202606110003_rename_tables_to_w_prefix.sql +++ /dev/null @@ -1,66 +0,0 @@ --- +goose Up -ALTER TABLE users RENAME TO w_users; -ALTER TABLE auth_sources RENAME TO w_auth_sources; -ALTER TABLE external_accounts RENAME TO w_external_accounts; -ALTER TABLE system_configs RENAME TO w_system_configs; -ALTER TABLE uploads RENAME TO w_uploads; -ALTER TABLE access_tokens RENAME TO w_access_tokens; -ALTER TABLE task_executions RENAME TO w_task_executions; -ALTER TABLE templates RENAME TO w_templates; -ALTER TABLE schedules RENAME TO w_schedules; - --- Rename indexes for consistency -ALTER INDEX IF EXISTS idx_users_email RENAME TO idx_w_users_email; -ALTER INDEX IF EXISTS idx_users_is_active RENAME TO idx_w_users_is_active; -ALTER INDEX IF EXISTS idx_users_last_login_at RENAME TO idx_w_users_last_login_at; -ALTER INDEX IF EXISTS idx_users_created_at RENAME TO idx_w_users_created_at; -ALTER INDEX IF EXISTS idx_auth_sources_is_active RENAME TO idx_w_auth_sources_is_active; -ALTER INDEX IF EXISTS idx_external_accounts_auth_source_id RENAME TO idx_w_external_accounts_auth_source_id; -ALTER INDEX IF EXISTS idx_external_accounts_user_id RENAME TO idx_w_external_accounts_user_id; -ALTER INDEX IF EXISTS idx_external_accounts_source_external RENAME TO idx_w_external_accounts_source_external; -ALTER INDEX IF EXISTS idx_uploads_user_id RENAME TO idx_w_uploads_user_id; -ALTER INDEX IF EXISTS idx_uploads_file_path RENAME TO idx_w_uploads_file_path; -ALTER INDEX IF EXISTS idx_uploads_hash RENAME TO idx_w_uploads_hash; -ALTER INDEX IF EXISTS idx_uploads_type RENAME TO idx_w_uploads_type; -ALTER INDEX IF EXISTS idx_access_tokens_user_id RENAME TO idx_w_access_tokens_user_id; -ALTER INDEX IF EXISTS idx_task_executions_task_type RENAME TO idx_w_task_executions_task_type; -ALTER INDEX IF EXISTS idx_task_executions_status RENAME TO idx_w_task_executions_status; -ALTER INDEX IF EXISTS idx_task_executions_started_at RENAME TO idx_w_task_executions_started_at; -ALTER INDEX IF EXISTS idx_task_executions_created_at RENAME TO idx_w_task_executions_created_at; -ALTER INDEX IF EXISTS idx_templates_is_system RENAME TO idx_w_templates_is_system; -ALTER INDEX IF EXISTS idx_templates_created_at RENAME TO idx_w_templates_created_at; -ALTER INDEX IF EXISTS idx_templates_updated_at RENAME TO idx_w_templates_updated_at; -ALTER INDEX IF EXISTS idx_schedules_is_active RENAME TO idx_w_schedules_is_active; - --- +goose Down -ALTER INDEX IF EXISTS idx_w_schedules_is_active RENAME TO idx_schedules_is_active; -ALTER INDEX IF EXISTS idx_w_templates_updated_at RENAME TO idx_templates_updated_at; -ALTER INDEX IF EXISTS idx_w_templates_created_at RENAME TO idx_templates_created_at; -ALTER INDEX IF EXISTS idx_w_templates_is_system RENAME TO idx_templates_is_system; -ALTER INDEX IF EXISTS idx_w_task_executions_created_at RENAME TO idx_task_executions_created_at; -ALTER INDEX IF EXISTS idx_w_task_executions_started_at RENAME TO idx_task_executions_started_at; -ALTER INDEX IF EXISTS idx_w_task_executions_status RENAME TO idx_task_executions_status; -ALTER INDEX IF EXISTS idx_w_task_executions_task_type RENAME TO idx_task_executions_task_type; -ALTER INDEX IF EXISTS idx_w_access_tokens_user_id RENAME TO idx_access_tokens_user_id; -ALTER INDEX IF EXISTS idx_w_uploads_type RENAME TO idx_uploads_type; -ALTER INDEX IF EXISTS idx_w_uploads_hash RENAME TO idx_uploads_hash; -ALTER INDEX IF EXISTS idx_w_uploads_file_path RENAME TO idx_uploads_file_path; -ALTER INDEX IF EXISTS idx_w_uploads_user_id RENAME TO idx_uploads_user_id; -ALTER INDEX IF EXISTS idx_w_external_accounts_source_external RENAME TO idx_external_accounts_source_external; -ALTER INDEX IF EXISTS idx_w_external_accounts_user_id RENAME TO idx_external_accounts_user_id; -ALTER INDEX IF EXISTS idx_w_external_accounts_auth_source_id RENAME TO idx_external_accounts_auth_source_id; -ALTER INDEX IF EXISTS idx_w_auth_sources_is_active RENAME TO idx_auth_sources_is_active; -ALTER INDEX IF EXISTS idx_w_users_created_at RENAME TO idx_users_created_at; -ALTER INDEX IF EXISTS idx_w_users_last_login_at RENAME TO idx_users_last_login_at; -ALTER INDEX IF EXISTS idx_w_users_is_active RENAME TO idx_users_is_active; -ALTER INDEX IF EXISTS idx_w_users_email RENAME TO idx_users_email; - -ALTER TABLE w_schedules RENAME TO schedules; -ALTER TABLE w_templates RENAME TO templates; -ALTER TABLE w_task_executions RENAME TO task_executions; -ALTER TABLE w_access_tokens RENAME TO access_tokens; -ALTER TABLE w_uploads RENAME TO uploads; -ALTER TABLE w_system_configs RENAME TO system_configs; -ALTER TABLE w_external_accounts RENAME TO external_accounts; -ALTER TABLE w_auth_sources RENAME TO auth_sources; -ALTER TABLE w_users RENAME TO users; diff --git a/pkg/migrator/goose/postgres/202606110005_add_disk_cache_configs.sql b/pkg/migrator/goose/postgres/202606110005_add_disk_cache_configs.sql deleted file mode 100644 index 8a27b6a3..00000000 --- a/pkg/migrator/goose/postgres/202606110005_add_disk_cache_configs.sql +++ /dev/null @@ -1,10 +0,0 @@ --- +goose Up -INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -VALUES - ('disk_cache_max_size_mb', '100', 'system', 0, '磁盘缓存最大空间大小 (MB)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('disk_cache_ttl_minutes', '60', 'system', 0, '磁盘缓存默认有效期 (分钟)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('disk_cache_lru_enabled', 'true', 'system', 0, '是否启用 LRU 淘汰机制', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DELETE FROM w_system_configs WHERE key IN ('disk_cache_max_size_mb', 'disk_cache_ttl_minutes', 'disk_cache_lru_enabled'); diff --git a/pkg/migrator/goose/postgres/202606120002_add_update_upstream_repository_config.sql b/pkg/migrator/goose/postgres/202606120002_add_update_upstream_repository_config.sql deleted file mode 100644 index 19c4f73d..00000000 --- a/pkg/migrator/goose/postgres/202606120002_add_update_upstream_repository_config.sql +++ /dev/null @@ -1,7 +0,0 @@ --- +goose Up -INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -VALUES ('update_upstream_repository', 'Rain-kl/Wavelet', 'system', 0, 'GitHub Actions Release 上游仓库(owner/repo 或 GitHub 仓库地址)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DELETE FROM w_system_configs WHERE key = 'update_upstream_repository'; diff --git a/pkg/migrator/goose/postgres/202606130001_add_upload_access_mode.sql b/pkg/migrator/goose/postgres/202606130001_add_upload_access_mode.sql deleted file mode 100644 index f5f4120a..00000000 --- a/pkg/migrator/goose/postgres/202606130001_add_upload_access_mode.sql +++ /dev/null @@ -1,6 +0,0 @@ --- +goose Up -ALTER TABLE w_uploads ADD COLUMN access_mode INTEGER NOT NULL DEFAULT 0; -UPDATE w_uploads SET access_mode = 1 WHERE type = 'avatar'; - --- +goose Down -ALTER TABLE w_uploads DROP COLUMN access_mode; diff --git a/pkg/migrator/goose/postgres/202606130003_add_storage_config.sql b/pkg/migrator/goose/postgres/202606130003_add_storage_config.sql deleted file mode 100644 index 4d49aa64..00000000 --- a/pkg/migrator/goose/postgres/202606130003_add_storage_config.sql +++ /dev/null @@ -1,15 +0,0 @@ --- +goose Up -INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -VALUES ( - 'storage_config', - '{"driver":"local","local":{"root":"."},"s3":{"region":"us-east-1"},"r2":{"region":"auto"},"minio":{"region":"us-east-1","path_style":true},"oss":{},"webdav":{}}', - 'system', - 0, - '文件存储驱动及连接配置(JSON)', - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP -) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DELETE FROM w_system_configs WHERE key = 'storage_config'; diff --git a/pkg/migrator/goose/postgres/202606140003_add_system_user.sql b/pkg/migrator/goose/postgres/202606140003_add_system_user.sql deleted file mode 100644 index a6b3f981..00000000 --- a/pkg/migrator/goose/postgres/202606140003_add_system_user.sql +++ /dev/null @@ -1,8 +0,0 @@ --- +goose Up -INSERT INTO w_users (id, username, password, nickname, avatar_url, is_active, is_admin, last_login_at, created_at, updated_at) -VALUES (999, 'system', '*', '系统', '', TRUE, FALSE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (username) DO NOTHING; -SELECT setval(pg_get_serial_sequence('w_users', 'id'), COALESCE((SELECT MAX(id) FROM w_users), 1)); - --- +goose Down -DELETE FROM w_users WHERE username = 'system'; diff --git a/pkg/migrator/goose/postgres/202606140005_update_system_cleanup_schedule.sql b/pkg/migrator/goose/postgres/202606140005_update_system_cleanup_schedule.sql deleted file mode 100644 index f7965f22..00000000 --- a/pkg/migrator/goose/postgres/202606140005_update_system_cleanup_schedule.sql +++ /dev/null @@ -1,11 +0,0 @@ --- +goose Up -UPDATE w_schedules -SET name = '系统定期垃圾清理', - task_type = 'system_cleanup' -WHERE id = 1; - --- +goose Down -UPDATE w_schedules -SET name = '清理未使用上传', - task_type = 'cleanup_unused_uploads' -WHERE id = 1; diff --git a/pkg/migrator/goose/postgres/202606150001_add_task_type_to_push_events.sql b/pkg/migrator/goose/postgres/202606150001_add_task_type_to_push_events.sql deleted file mode 100644 index eb27dae3..00000000 --- a/pkg/migrator/goose/postgres/202606150001_add_task_type_to_push_events.sql +++ /dev/null @@ -1,7 +0,0 @@ --- +goose Up -ALTER TABLE w_push_events ADD COLUMN task_type VARCHAR(100) NOT NULL DEFAULT ''; -CREATE INDEX idx_w_push_events_task_type ON w_push_events(task_type); - --- +goose Down -DROP INDEX IF EXISTS idx_w_push_events_task_type; -ALTER TABLE w_push_events DROP COLUMN task_type; diff --git a/pkg/migrator/goose/postgres/202606160001_remove_push_config.sql b/pkg/migrator/goose/postgres/202606160001_remove_push_config.sql deleted file mode 100644 index d3214a00..00000000 --- a/pkg/migrator/goose/postgres/202606160001_remove_push_config.sql +++ /dev/null @@ -1,6 +0,0 @@ --- +goose Up -DELETE FROM w_system_configs WHERE key = 'push_config'; -DELETE FROM w_push_events WHERE event_key = 'admin_login'; -DELETE FROM w_system_configs WHERE key = 'push_global_token'; - --- +goose Down diff --git a/pkg/migrator/goose/postgres/202606170003_backfill_upload_stats.sql b/pkg/migrator/goose/postgres/202606170003_backfill_upload_stats.sql deleted file mode 100644 index 6e5bf473..00000000 --- a/pkg/migrator/goose/postgres/202606170003_backfill_upload_stats.sql +++ /dev/null @@ -1,67 +0,0 @@ --- +goose Up -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT 'total', '', COUNT(*), COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = EXCLUDED.file_count, - file_size = EXCLUDED.file_size, - updated_at = CURRENT_TIMESTAMP; - -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT - 'type', - COALESCE(NULLIF(type, ''), 'generic'), - COUNT(*), - COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -GROUP BY COALESCE(NULLIF(type, ''), 'generic') -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = EXCLUDED.file_count, - file_size = EXCLUDED.file_size, - updated_at = CURRENT_TIMESTAMP; - -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT - 'category', - CASE - WHEN LOWER(mime_type) LIKE 'image/%' - OR LOWER(extension) IN ('jpg', 'jpeg', 'png', 'webp', 'gif') THEN '图片' - WHEN LOWER(mime_type) LIKE 'video/%' THEN '视频' - WHEN LOWER(mime_type) LIKE 'audio/%' THEN '音频' - WHEN LOWER(extension) IN ('zip', 'rar', '7z', 'tar', 'gz', 'tgz', 'bz2', 'xz') - OR LOWER(mime_type) LIKE '%zip%' - OR LOWER(mime_type) LIKE '%tar%' - OR LOWER(mime_type) LIKE '%gzip%' THEN '压缩包' - WHEN LOWER(extension) IN ('pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv', 'json', 'yaml', 'yml', 'xml') - OR LOWER(mime_type) LIKE 'text/%' - OR LOWER(mime_type) = 'application/pdf' THEN '文档' - ELSE '其他' - END, - COUNT(*), - COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -GROUP BY 2 -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = EXCLUDED.file_count, - file_size = EXCLUDED.file_size, - updated_at = CURRENT_TIMESTAMP; - -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT - 'trend', - TO_CHAR(created_at, 'YYYY-MM-DD'), - COUNT(*), - COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -GROUP BY TO_CHAR(created_at, 'YYYY-MM-DD') -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = EXCLUDED.file_count, - file_size = EXCLUDED.file_size, - updated_at = CURRENT_TIMESTAMP; - --- +goose Down -DELETE FROM w_upload_stats; \ No newline at end of file diff --git a/pkg/migrator/goose/postgres/202606180001_drop_upload_storage_driver.sql b/pkg/migrator/goose/postgres/202606180001_drop_upload_storage_driver.sql deleted file mode 100644 index 3ba6aa08..00000000 --- a/pkg/migrator/goose/postgres/202606180001_drop_upload_storage_driver.sql +++ /dev/null @@ -1,7 +0,0 @@ --- +goose Up -DROP INDEX IF EXISTS idx_w_uploads_storage_driver_status; -ALTER TABLE w_uploads DROP COLUMN IF EXISTS storage_driver; - --- +goose Down -ALTER TABLE w_uploads ADD COLUMN IF NOT EXISTS storage_driver VARCHAR(50) NOT NULL DEFAULT 'local'; -CREATE INDEX IF NOT EXISTS idx_w_uploads_storage_driver_status ON w_uploads (storage_driver, status); \ No newline at end of file diff --git a/pkg/migrator/goose/postgres/202608160002_log_database_configs.sql b/pkg/migrator/goose/postgres/202608160002_log_database_configs.sql deleted file mode 100644 index 81643145..00000000 --- a/pkg/migrator/goose/postgres/202608160002_log_database_configs.sql +++ /dev/null @@ -1,24 +0,0 @@ --- +goose Up -INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -VALUES - ('log_database', '', 'system', 0, '当前日志主库(postgres/sqlite/clickhouse),由切换任务写入', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('log_db_migration', '', 'system', 0, '日志库迁移冻结标记(空或 migrating)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - -INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -SELECT k, '30', 'business', 0, descr, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP -FROM (VALUES - ('log_retention_days_postgres', 'PostgreSQL 用户访问日志保留天数'), - ('log_retention_days_sqlite', 'SQLite 用户访问日志保留天数'), - ('log_retention_days_clickhouse', 'ClickHouse 用户访问日志保留天数') -) AS v(k, descr) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DELETE FROM w_system_configs WHERE key IN ( - 'log_database', - 'log_db_migration', - 'log_retention_days_postgres', - 'log_retention_days_sqlite', - 'log_retention_days_clickhouse' -); diff --git a/pkg/migrator/goose/postgres/202608160003_create_message_gateway.sql b/pkg/migrator/goose/postgres/202608160003_create_message_gateway.sql deleted file mode 100644 index dab497d5..00000000 --- a/pkg/migrator/goose/postgres/202608160003_create_message_gateway.sql +++ /dev/null @@ -1,40 +0,0 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS w_message_channels ( - id BIGINT PRIMARY KEY, - name VARCHAR(128) NOT NULL, - type VARCHAR(32) NOT NULL, - owner_scope VARCHAR(16) NOT NULL DEFAULT 'system', - owner_id BIGINT NULL, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - credentials TEXT NOT NULL DEFAULT '', - extra TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_w_message_channels_type ON w_message_channels (type); - -CREATE TABLE IF NOT EXISTS w_message_bindings ( - id BIGINT PRIMARY KEY, - user_id BIGINT NOT NULL, - channel_id BIGINT NOT NULL, - platform_user_id VARCHAR(128) NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); -CREATE UNIQUE INDEX IF NOT EXISTS uniq_w_message_bindings_channel_platform - ON w_message_bindings (channel_id, platform_user_id); -CREATE INDEX IF NOT EXISTS idx_w_message_bindings_user ON w_message_bindings (user_id); - -CREATE TABLE IF NOT EXISTS w_message_pairing_codes ( - code VARCHAR(16) PRIMARY KEY, - channel_id BIGINT NOT NULL, - platform_user_id VARCHAR(128) NOT NULL, - expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_w_message_pairing_lookup - ON w_message_pairing_codes (channel_id, platform_user_id); - --- +goose Down -DROP TABLE IF EXISTS w_message_pairing_codes; -DROP TABLE IF EXISTS w_message_bindings; -DROP TABLE IF EXISTS w_message_channels; diff --git a/pkg/migrator/goose/sqlite/202606090001_initial_schema.sql b/pkg/migrator/goose/sqlite/202606090001_initial_schema.sql deleted file mode 100644 index c54b5caa..00000000 --- a/pkg/migrator/goose/sqlite/202606090001_initial_schema.sql +++ /dev/null @@ -1,184 +0,0 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS users ( - id BIGINT PRIMARY KEY, - username VARCHAR(64) UNIQUE, - password VARCHAR(255), - nickname VARCHAR(255), - email VARCHAR(255), - avatar_url VARCHAR(255), - is_active BOOLEAN DEFAULT TRUE, - is_admin BOOLEAN DEFAULT FALSE, - bio VARCHAR(500), - phone VARCHAR(32), - gender VARCHAR(16), - website VARCHAR(255), - location VARCHAR(255), - last_login_at DATETIME, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); -CREATE INDEX IF NOT EXISTS idx_users_is_active ON users (is_active); -CREATE INDEX IF NOT EXISTS idx_users_last_login_at ON users (last_login_at); -CREATE INDEX IF NOT EXISTS idx_users_created_at ON users (created_at); - -CREATE TABLE IF NOT EXISTS auth_sources ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(80) NOT NULL UNIQUE, - type VARCHAR(20) NOT NULL, - display_name VARCHAR(100), - is_active BOOLEAN NOT NULL DEFAULT FALSE, - client_id VARCHAR(255), - client_secret VARCHAR(1024), - openid_discovery_url VARCHAR(1024), - scopes VARCHAR(255), - icon_url VARCHAR(1024), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_auth_sources_is_active ON auth_sources (is_active); - -CREATE TABLE IF NOT EXISTS external_accounts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - auth_source_id BIGINT, - user_id BIGINT NOT NULL, - external_id VARCHAR(255) NOT NULL, - external_username VARCHAR(255), - email VARCHAR(255), - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_external_accounts_auth_source_id ON external_accounts (auth_source_id); -CREATE INDEX IF NOT EXISTS idx_external_accounts_user_id ON external_accounts (user_id); -CREATE UNIQUE INDEX IF NOT EXISTS idx_external_accounts_source_external ON external_accounts (auth_source_id, external_id); - -CREATE TABLE IF NOT EXISTS system_configs ( - key VARCHAR(64) PRIMARY KEY, - value TEXT NOT NULL, - type VARCHAR(32) NOT NULL DEFAULT 'system', - visibility INTEGER NOT NULL DEFAULT 0, - description VARCHAR(255), - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS uploads ( - id BIGINT PRIMARY KEY, - user_id BIGINT NOT NULL, - file_name VARCHAR(255) NOT NULL, - file_path VARCHAR(500) NOT NULL, - file_size BIGINT NOT NULL, - mime_type VARCHAR(100) NOT NULL, - extension VARCHAR(50) NOT NULL, - hash VARCHAR(64), - storage_driver VARCHAR(50) NOT NULL, - type VARCHAR(50) NOT NULL, - status VARCHAR(20) NOT NULL, - metadata JSON, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_uploads_user_id ON uploads (user_id); -CREATE INDEX IF NOT EXISTS idx_uploads_file_path ON uploads (file_path); -CREATE INDEX IF NOT EXISTS idx_uploads_hash ON uploads (hash); -CREATE INDEX IF NOT EXISTS idx_uploads_type ON uploads (type); - -CREATE TABLE IF NOT EXISTS access_tokens ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id BIGINT NOT NULL, - name VARCHAR(128) NOT NULL, - token_hash VARCHAR(64) NOT NULL UNIQUE, - masked_token VARCHAR(64) NOT NULL, - last_used_at DATETIME, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_access_tokens_user_id ON access_tokens (user_id); - -CREATE TABLE IF NOT EXISTS task_executions ( - id BIGINT PRIMARY KEY, - task_id VARCHAR(128) NOT NULL UNIQUE, - task_type VARCHAR(64) NOT NULL, - task_name VARCHAR(128), - status VARCHAR(32) NOT NULL, - retryable BOOLEAN NOT NULL DEFAULT FALSE, - max_retry INTEGER NOT NULL DEFAULT 0, - retry_count INTEGER NOT NULL DEFAULT 0, - log TEXT, - error_message TEXT, - result TEXT, - started_at DATETIME, - finished_at DATETIME, - duration BIGINT, - payload TEXT, - triggered_by VARCHAR(32) NOT NULL DEFAULT 'system', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_task_executions_task_type ON task_executions (task_type); -CREATE INDEX IF NOT EXISTS idx_task_executions_status ON task_executions (status); -CREATE INDEX IF NOT EXISTS idx_task_executions_started_at ON task_executions (started_at); -CREATE INDEX IF NOT EXISTS idx_task_executions_created_at ON task_executions (created_at); - -CREATE TABLE IF NOT EXISTS templates ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key VARCHAR(80) NOT NULL UNIQUE, - name VARCHAR(100) NOT NULL, - type VARCHAR(20) NOT NULL DEFAULT 'email', - subject VARCHAR(255), - content TEXT NOT NULL, - description VARCHAR(255), - is_system BOOLEAN NOT NULL DEFAULT FALSE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_templates_is_system ON templates (is_system); -CREATE INDEX IF NOT EXISTS idx_templates_created_at ON templates (created_at); -CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates (updated_at); - -INSERT INTO system_configs (key, value, type, visibility, description, created_at, updated_at) VALUES - ('cap_login_enabled', 'false', 'system', 1, '是否启用登录人机验证(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_auto_solve', 'true', 'system', 1, '打开页面后是否自动开始计算,关闭则需用户手动点击触发', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_count', '1', 'system', 0, '客户端需求解的 PoW 难题总数,默认 1,推荐 1~5', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_size', '32', 'system', 0, '人机验证盐值长度', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_difficulty', '4', 'system', 0, '人机验证 PoW 难度(目标前缀长度)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_challenge_ttl_seconds', '600', 'system', 0, '人机验证难题有效时间(秒)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('cap_token_ttl_seconds', '1200', 'system', 0, '人机验证兑换凭证有效时间(秒)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('server_address', '', 'system', 0, '服务器地址(用于跨域源控制,不设定则允许任意源)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_host', '', 'system', 0, 'SMTP 服务器地址(例如 smtp.example.com)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_port', '587', 'system', 0, 'SMTP 端口(例如 587 或 465)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_username', '', 'system', 0, 'SMTP 账户(如 sender@example.com)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('smtp_password', '', 'system', 0, 'SMTP 访问凭证(授权码/密码)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('upload_allowed_extensions', 'jpg,png,webp', 'system', 1, '允许上传的图片扩展名(逗号分隔)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('site_name', 'Wavelet', 'system', 1, '系统平台的展示名称', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('password_login_enabled', 'true', 'system', 1, '是否允许使用账号密码登录', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('registration_enabled', 'true', 'system', 1, '控制普通用户是否可以自主注册(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('password_register_enabled', 'true', 'system', 1, '是否允许通过密码创建本地账号', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('oidc_login_enabled', 'true', 'system', 1, '是否允许使用第三方 OIDC 认证源登录', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('max_api_keys_per_user', '5', 'business', 1, '限制每个普通用户可以创建的 API Key 最大数量', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('email_login_verification_enabled', 'false', 'system', 1, '是否开启邮箱登录验证(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('email_register_verification_enabled', 'false', 'system', 1, '是否开启邮箱注册验证(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('menu_display_config', '{}', 'system', 1, '目录显示配置(JSON 字符串,格式为 {url: enabled})', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('search_engine_indexing_enabled', 'false', 'system', 1, '是否允许搜索引擎爬取/检索该站点(true/false)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('update_upstream_repository', 'Rain-kl/Wavelet', 'system', 0, 'GitHub Actions Release 上游仓库(owner/repo 或 GitHub 仓库地址)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('storage_config', '{"driver":"local","local":{"root":"."},"s3":{"region":"us-east-1"},"r2":{"region":"auto"},"minio":{"region":"us-east-1","path_style":true},"oss":{},"webdav":{}}', 'system', 0, '文件存储驱动及连接配置(JSON)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - -INSERT INTO users (id, username, password, nickname, avatar_url, is_active, is_admin, last_login_at, created_at, updated_at) -VALUES (1, 'admin', '12345678', 'Administrator', '', TRUE, TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (username) DO NOTHING; - -INSERT INTO templates (key, name, type, subject, content, description, is_system, created_at, updated_at) VALUES - ('login_email', '登录验证码邮件', 'email', 'Wavelet 登录验证码', '您的登录验证码为:{{.Code}},5分钟内有效,请勿将验证码泄露给他人。
', '用户密码登录时发送的验证码邮件模板,支持变量:{{.Code}}', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('register_email', '注册验证码邮件', 'email', 'Wavelet 注册验证码', '您的注册验证码为:{{.Code}},5分钟内有效,请勿泄露给他人。
', '用户注册时发送的验证码邮件模板,支持变量:{{.Code}}', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DROP TABLE IF EXISTS templates; -DROP TABLE IF EXISTS task_executions; -DROP TABLE IF EXISTS access_tokens; -DROP TABLE IF EXISTS uploads; -DROP TABLE IF EXISTS system_configs; -DROP TABLE IF EXISTS external_accounts; -DROP TABLE IF EXISTS auth_sources; -DROP TABLE IF EXISTS users; diff --git a/pkg/migrator/goose/sqlite/202606100001_create_schedules.sql b/pkg/migrator/goose/sqlite/202606100001_create_schedules.sql deleted file mode 100644 index 0ee0df13..00000000 --- a/pkg/migrator/goose/sqlite/202606100001_create_schedules.sql +++ /dev/null @@ -1,20 +0,0 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS schedules ( - id BIGINT PRIMARY KEY, - name VARCHAR(128) NOT NULL, - task_type VARCHAR(64) NOT NULL, - cron VARCHAR(64) NOT NULL, - payload TEXT, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_schedules_is_active ON schedules (is_active); - --- Seed initial cleanup task -INSERT INTO schedules (id, name, task_type, cron, payload, is_active, created_at, updated_at) -VALUES (1, '清理未使用上传', 'cleanup_unused_uploads', '0 */2 * * *', '{}', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (id) DO NOTHING; - --- +goose Down -DROP TABLE IF EXISTS schedules; diff --git a/pkg/migrator/goose/sqlite/202606100002_access_token_is_admin.sql b/pkg/migrator/goose/sqlite/202606100002_access_token_is_admin.sql deleted file mode 100644 index 73f5d7ce..00000000 --- a/pkg/migrator/goose/sqlite/202606100002_access_token_is_admin.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -ALTER TABLE access_tokens ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT 0; - --- +goose Down -ALTER TABLE access_tokens DROP COLUMN is_admin; diff --git a/pkg/migrator/goose/sqlite/202606110001_remove_access_token_last_used_at.sql b/pkg/migrator/goose/sqlite/202606110001_remove_access_token_last_used_at.sql deleted file mode 100644 index 1b2c38c7..00000000 --- a/pkg/migrator/goose/sqlite/202606110001_remove_access_token_last_used_at.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +goose Up --- +goose StatementBegin -ALTER TABLE access_tokens DROP COLUMN last_used_at; --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -ALTER TABLE access_tokens ADD COLUMN last_used_at DATETIME; --- +goose StatementEnd diff --git a/pkg/migrator/goose/sqlite/202606110002_alter_schedules_id_auto_increment.sql b/pkg/migrator/goose/sqlite/202606110002_alter_schedules_id_auto_increment.sql deleted file mode 100644 index 4f338526..00000000 --- a/pkg/migrator/goose/sqlite/202606110002_alter_schedules_id_auto_increment.sql +++ /dev/null @@ -1,32 +0,0 @@ --- +goose Up --- +goose StatementBegin --- 1. Rename existing schedules table -ALTER TABLE schedules RENAME TO schedules_old; - --- 2. Create new schedules table with AUTOINCREMENT -CREATE TABLE schedules ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(128) NOT NULL, - task_type VARCHAR(64) NOT NULL, - cron VARCHAR(64) NOT NULL, - payload TEXT, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP -); - --- 3. Copy existing data -INSERT INTO schedules (id, name, task_type, cron, payload, is_active, created_at, updated_at) -SELECT id, name, task_type, cron, payload, is_active, created_at, updated_at FROM schedules_old; - --- 4. Drop the old table -DROP TABLE schedules_old; - --- 5. Recreate index -CREATE INDEX IF NOT EXISTS idx_schedules_is_active ON schedules (is_active); --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin --- Re-creating table with AUTOINCREMENT cannot be undone simply without recreating table again. --- +goose StatementEnd diff --git a/pkg/migrator/goose/sqlite/202606110003_rename_tables_to_w_prefix.sql b/pkg/migrator/goose/sqlite/202606110003_rename_tables_to_w_prefix.sql deleted file mode 100644 index 184e17e9..00000000 --- a/pkg/migrator/goose/sqlite/202606110003_rename_tables_to_w_prefix.sql +++ /dev/null @@ -1,123 +0,0 @@ --- +goose Up -ALTER TABLE users RENAME TO w_users; -ALTER TABLE auth_sources RENAME TO w_auth_sources; -ALTER TABLE external_accounts RENAME TO w_external_accounts; -ALTER TABLE system_configs RENAME TO w_system_configs; -ALTER TABLE uploads RENAME TO w_uploads; -ALTER TABLE access_tokens RENAME TO w_access_tokens; -ALTER TABLE task_executions RENAME TO w_task_executions; -ALTER TABLE templates RENAME TO w_templates; -ALTER TABLE schedules RENAME TO w_schedules; - --- Drop and Recreate SQLite indexes -DROP INDEX IF EXISTS idx_users_email; -CREATE INDEX IF NOT EXISTS idx_w_users_email ON w_users (email); -DROP INDEX IF EXISTS idx_users_is_active; -CREATE INDEX IF NOT EXISTS idx_w_users_is_active ON w_users (is_active); -DROP INDEX IF EXISTS idx_users_last_login_at; -CREATE INDEX IF NOT EXISTS idx_w_users_last_login_at ON w_users (last_login_at); -DROP INDEX IF EXISTS idx_users_created_at; -CREATE INDEX IF NOT EXISTS idx_w_users_created_at ON w_users (created_at); - -DROP INDEX IF EXISTS idx_auth_sources_is_active; -CREATE INDEX IF NOT EXISTS idx_w_auth_sources_is_active ON w_auth_sources (is_active); - -DROP INDEX IF EXISTS idx_external_accounts_auth_source_id; -CREATE INDEX IF NOT EXISTS idx_w_external_accounts_auth_source_id ON w_external_accounts (auth_source_id); -DROP INDEX IF EXISTS idx_external_accounts_user_id; -CREATE INDEX IF NOT EXISTS idx_w_external_accounts_user_id ON w_external_accounts (user_id); -DROP INDEX IF EXISTS idx_external_accounts_source_external; -CREATE UNIQUE INDEX IF NOT EXISTS idx_w_external_accounts_source_external ON w_external_accounts (auth_source_id, external_id); - -DROP INDEX IF EXISTS idx_uploads_user_id; -CREATE INDEX IF NOT EXISTS idx_w_uploads_user_id ON w_uploads (user_id); -DROP INDEX IF EXISTS idx_uploads_file_path; -CREATE INDEX IF NOT EXISTS idx_w_uploads_file_path ON w_uploads (file_path); -DROP INDEX IF EXISTS idx_uploads_hash; -CREATE INDEX IF NOT EXISTS idx_w_uploads_hash ON w_uploads (hash); -DROP INDEX IF EXISTS idx_uploads_type; -CREATE INDEX IF NOT EXISTS idx_w_uploads_type ON w_uploads (type); - -DROP INDEX IF EXISTS idx_access_tokens_user_id; -CREATE INDEX IF NOT EXISTS idx_w_access_tokens_user_id ON w_access_tokens (user_id); - -DROP INDEX IF EXISTS idx_task_executions_task_type; -CREATE INDEX IF NOT EXISTS idx_w_task_executions_task_type ON w_task_executions (task_type); -DROP INDEX IF EXISTS idx_task_executions_status; -CREATE INDEX IF NOT EXISTS idx_w_task_executions_status ON w_task_executions (status); -DROP INDEX IF EXISTS idx_task_executions_started_at; -CREATE INDEX IF NOT EXISTS idx_w_task_executions_started_at ON w_task_executions (started_at); -DROP INDEX IF EXISTS idx_task_executions_created_at; -CREATE INDEX IF NOT EXISTS idx_w_task_executions_created_at ON w_task_executions (created_at); - -DROP INDEX IF EXISTS idx_templates_is_system; -CREATE INDEX IF NOT EXISTS idx_w_templates_is_system ON w_templates (is_system); -DROP INDEX IF EXISTS idx_templates_created_at; -CREATE INDEX IF NOT EXISTS idx_w_templates_created_at ON w_templates (created_at); -DROP INDEX IF EXISTS idx_templates_updated_at; -CREATE INDEX IF NOT EXISTS idx_w_templates_updated_at ON w_templates (updated_at); - -DROP INDEX IF EXISTS idx_schedules_is_active; -CREATE INDEX IF NOT EXISTS idx_w_schedules_is_active ON w_schedules (is_active); - --- +goose Down -ALTER TABLE w_schedules RENAME TO schedules; -ALTER TABLE w_templates RENAME TO templates; -ALTER TABLE w_task_executions RENAME TO task_executions; -ALTER TABLE w_access_tokens RENAME TO access_tokens; -ALTER TABLE w_uploads RENAME TO uploads; -ALTER TABLE w_system_configs RENAME TO system_configs; -ALTER TABLE w_external_accounts RENAME TO external_accounts; -ALTER TABLE w_auth_sources RENAME TO auth_sources; -ALTER TABLE w_users RENAME TO users; - --- Revert indexes -DROP INDEX IF EXISTS idx_w_users_email; -CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); -DROP INDEX IF EXISTS idx_w_users_is_active; -CREATE INDEX IF NOT EXISTS idx_users_is_active ON users (is_active); -DROP INDEX IF EXISTS idx_w_users_last_login_at; -CREATE INDEX IF NOT EXISTS idx_users_last_login_at ON users (last_login_at); -DROP INDEX IF EXISTS idx_w_users_created_at; -CREATE INDEX IF NOT EXISTS idx_users_created_at ON users (created_at); - -DROP INDEX IF EXISTS idx_w_auth_sources_is_active; -CREATE INDEX IF NOT EXISTS idx_auth_sources_is_active ON auth_sources (is_active); - -DROP INDEX IF EXISTS idx_w_external_accounts_auth_source_id; -CREATE INDEX IF NOT EXISTS idx_external_accounts_auth_source_id ON external_accounts (auth_source_id); -DROP INDEX IF EXISTS idx_w_external_accounts_user_id; -CREATE INDEX IF NOT EXISTS idx_external_accounts_user_id ON external_accounts (user_id); -DROP INDEX IF EXISTS idx_w_external_accounts_source_external; -CREATE UNIQUE INDEX IF NOT EXISTS idx_external_accounts_source_external ON external_accounts (auth_source_id, external_id); - -DROP INDEX IF EXISTS idx_w_uploads_user_id; -CREATE INDEX IF NOT EXISTS idx_uploads_user_id ON uploads (user_id); -DROP INDEX IF EXISTS idx_w_uploads_file_path; -CREATE INDEX IF NOT EXISTS idx_uploads_file_path ON uploads (file_path); -DROP INDEX IF EXISTS idx_w_uploads_hash; -CREATE INDEX IF NOT EXISTS idx_uploads_hash ON uploads (hash); -DROP INDEX IF EXISTS idx_w_uploads_type; -CREATE INDEX IF NOT EXISTS idx_uploads_type ON uploads (type); - -DROP INDEX IF EXISTS idx_w_access_tokens_user_id; -CREATE INDEX IF NOT EXISTS idx_access_tokens_user_id ON access_tokens (user_id); - -DROP INDEX IF EXISTS idx_w_task_executions_task_type; -CREATE INDEX IF NOT EXISTS idx_task_executions_task_type ON task_executions (task_type); -DROP INDEX IF EXISTS idx_w_task_executions_status; -CREATE INDEX IF NOT EXISTS idx_task_executions_status ON task_executions (status); -DROP INDEX IF EXISTS idx_w_task_executions_started_at; -CREATE INDEX IF NOT EXISTS idx_task_executions_started_at ON task_executions (started_at); -DROP INDEX IF EXISTS idx_w_task_executions_created_at; -CREATE INDEX IF NOT EXISTS idx_task_executions_created_at ON task_executions (created_at); - -DROP INDEX IF EXISTS idx_w_templates_is_system; -CREATE INDEX IF NOT EXISTS idx_templates_is_system ON templates (is_system); -DROP INDEX IF EXISTS idx_w_templates_created_at; -CREATE INDEX IF NOT EXISTS idx_templates_created_at ON templates (created_at); -DROP INDEX IF EXISTS idx_w_templates_updated_at; -CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates (updated_at); - -DROP INDEX IF EXISTS idx_w_schedules_is_active; -CREATE INDEX IF NOT EXISTS idx_schedules_is_active ON schedules (is_active); diff --git a/pkg/migrator/goose/sqlite/202606110004_add_file_access_whitelist_config.sql b/pkg/migrator/goose/sqlite/202606110004_add_file_access_whitelist_config.sql deleted file mode 100644 index 20963f50..00000000 --- a/pkg/migrator/goose/sqlite/202606110004_add_file_access_whitelist_config.sql +++ /dev/null @@ -1,7 +0,0 @@ --- +goose Up -INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -VALUES ('file_access_whitelist', '["avatar"]', 'system', 1, '免登录访问的文件业务类型白名单 (JSON 数组格式)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DELETE FROM w_system_configs WHERE key = 'file_access_whitelist'; diff --git a/pkg/migrator/goose/sqlite/202606120001_add_login_session_ttl_config.sql b/pkg/migrator/goose/sqlite/202606120001_add_login_session_ttl_config.sql deleted file mode 100644 index 4300c4c4..00000000 --- a/pkg/migrator/goose/sqlite/202606120001_add_login_session_ttl_config.sql +++ /dev/null @@ -1,8 +0,0 @@ --- +goose Up -INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -VALUES - ('login_session_ttl_hours', '0', 'system', 0, '登录会话过期时间 (小时,0表示浏览器关闭后自动退出,-1表示永不过期)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON CONFLICT (key) DO NOTHING; - --- +goose Down -DELETE FROM w_system_configs WHERE key = 'login_session_ttl_hours'; diff --git a/pkg/migrator/goose/sqlite/202606130001_add_upload_access_mode.sql b/pkg/migrator/goose/sqlite/202606130001_add_upload_access_mode.sql deleted file mode 100644 index f5f4120a..00000000 --- a/pkg/migrator/goose/sqlite/202606130001_add_upload_access_mode.sql +++ /dev/null @@ -1,6 +0,0 @@ --- +goose Up -ALTER TABLE w_uploads ADD COLUMN access_mode INTEGER NOT NULL DEFAULT 0; -UPDATE w_uploads SET access_mode = 1 WHERE type = 'avatar'; - --- +goose Down -ALTER TABLE w_uploads DROP COLUMN access_mode; diff --git a/pkg/migrator/goose/sqlite/202606130002_expand_system_config_value.sql b/pkg/migrator/goose/sqlite/202606130002_expand_system_config_value.sql deleted file mode 100644 index ac50e2a4..00000000 --- a/pkg/migrator/goose/sqlite/202606130002_expand_system_config_value.sql +++ /dev/null @@ -1,6 +0,0 @@ --- +goose Up --- SQLite stores VARCHAR and TEXT with the same TEXT affinity. -SELECT 1; - --- +goose Down -SELECT 1; diff --git a/pkg/migrator/goose/sqlite/202606140001_create_push_tables.sql b/pkg/migrator/goose/sqlite/202606140001_create_push_tables.sql deleted file mode 100644 index 128e581f..00000000 --- a/pkg/migrator/goose/sqlite/202606140001_create_push_tables.sql +++ /dev/null @@ -1,34 +0,0 @@ --- +goose Up -CREATE TABLE w_push_events ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - event_key TEXT NOT NULL UNIQUE, - name TEXT NOT NULL, - channels TEXT NOT NULL, - targets TEXT NOT NULL, - template TEXT NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT FALSE, - created_at DATETIME NOT NULL, - updated_at DATETIME NOT NULL -); - -CREATE INDEX idx_w_push_events_enabled ON w_push_events(enabled); - -CREATE TABLE w_push_histories ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - event_key TEXT NOT NULL, - channel TEXT NOT NULL, - target TEXT NOT NULL, - title TEXT NOT NULL, - content TEXT NOT NULL, - level TEXT NOT NULL, - status TEXT NOT NULL, - error_msg TEXT, - created_at DATETIME NOT NULL -); - -CREATE INDEX idx_w_push_histories_event ON w_push_histories(event_key); -CREATE INDEX idx_w_push_histories_created ON w_push_histories(created_at); - --- +goose Down -DROP TABLE IF EXISTS w_push_histories; -DROP TABLE IF EXISTS w_push_events; diff --git a/pkg/migrator/goose/sqlite/202606140004_create_push_channels.sql b/pkg/migrator/goose/sqlite/202606140004_create_push_channels.sql deleted file mode 100644 index b58ccb50..00000000 --- a/pkg/migrator/goose/sqlite/202606140004_create_push_channels.sql +++ /dev/null @@ -1,19 +0,0 @@ --- +goose Up -CREATE TABLE w_push_channels ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE, - description TEXT, - type TEXT NOT NULL DEFAULT 'custom', - token TEXT, - url TEXT NOT NULL, - other TEXT NOT NULL, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - created_at DATETIME NOT NULL, - updated_at DATETIME NOT NULL -); - -CREATE INDEX idx_w_push_channels_name ON w_push_channels(name); -CREATE INDEX idx_w_push_channels_enabled ON w_push_channels(enabled); - --- +goose Down -DROP TABLE IF EXISTS w_push_channels; diff --git a/pkg/migrator/goose/sqlite/202606150001_add_task_type_to_push_events.sql b/pkg/migrator/goose/sqlite/202606150001_add_task_type_to_push_events.sql deleted file mode 100644 index 77f5dbd9..00000000 --- a/pkg/migrator/goose/sqlite/202606150001_add_task_type_to_push_events.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +goose Up -ALTER TABLE w_push_events ADD COLUMN task_type VARCHAR(100) NOT NULL DEFAULT ''; -CREATE INDEX idx_w_push_events_task_type ON w_push_events(task_type); - --- +goose Down -DROP INDEX IF EXISTS idx_w_push_events_task_type; --- SQLite does not support DROP COLUMN in older versions easily, but standard ALTER TABLE DROP COLUMN works in SQLite 3.35.0+. --- We can write standard DROP COLUMN. -ALTER TABLE w_push_events DROP COLUMN task_type; diff --git a/pkg/migrator/goose/sqlite/202606170001_add_upload_composite_indexes.sql b/pkg/migrator/goose/sqlite/202606170001_add_upload_composite_indexes.sql deleted file mode 100644 index 7d2f0e7d..00000000 --- a/pkg/migrator/goose/sqlite/202606170001_add_upload_composite_indexes.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +goose Up -CREATE INDEX IF NOT EXISTS idx_w_uploads_status_created_at ON w_uploads (status, created_at); -CREATE INDEX IF NOT EXISTS idx_w_uploads_storage_driver_status ON w_uploads (storage_driver, status); -CREATE INDEX IF NOT EXISTS idx_w_uploads_hash_file_size_status ON w_uploads (hash, file_size, status); - --- +goose Down -DROP INDEX IF EXISTS idx_w_uploads_hash_file_size_status; -DROP INDEX IF EXISTS idx_w_uploads_storage_driver_status; -DROP INDEX IF EXISTS idx_w_uploads_status_created_at; \ No newline at end of file diff --git a/pkg/migrator/goose/sqlite/202606170002_create_upload_stats_table.sql b/pkg/migrator/goose/sqlite/202606170002_create_upload_stats_table.sql deleted file mode 100644 index ab6b7b6c..00000000 --- a/pkg/migrator/goose/sqlite/202606170002_create_upload_stats_table.sql +++ /dev/null @@ -1,12 +0,0 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS w_upload_stats ( - dimension VARCHAR(32) NOT NULL, - stat_key VARCHAR(64) NOT NULL DEFAULT '', - file_count BIGINT NOT NULL DEFAULT 0, - file_size BIGINT NOT NULL DEFAULT 0, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (dimension, stat_key) -); - --- +goose Down -DROP TABLE IF EXISTS w_upload_stats; \ No newline at end of file diff --git a/pkg/migrator/goose/sqlite/202606170003_backfill_upload_stats.sql b/pkg/migrator/goose/sqlite/202606170003_backfill_upload_stats.sql deleted file mode 100644 index 2ebbd8c4..00000000 --- a/pkg/migrator/goose/sqlite/202606170003_backfill_upload_stats.sql +++ /dev/null @@ -1,67 +0,0 @@ --- +goose Up -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT 'total', '', COUNT(*), COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = excluded.file_count, - file_size = excluded.file_size, - updated_at = CURRENT_TIMESTAMP; - -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT - 'type', - COALESCE(NULLIF(type, ''), 'generic'), - COUNT(*), - COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -GROUP BY COALESCE(NULLIF(type, ''), 'generic') -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = excluded.file_count, - file_size = excluded.file_size, - updated_at = CURRENT_TIMESTAMP; - -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT - 'category', - CASE - WHEN LOWER(mime_type) LIKE 'image/%' - OR LOWER(extension) IN ('jpg', 'jpeg', 'png', 'webp', 'gif') THEN '图片' - WHEN LOWER(mime_type) LIKE 'video/%' THEN '视频' - WHEN LOWER(mime_type) LIKE 'audio/%' THEN '音频' - WHEN LOWER(extension) IN ('zip', 'rar', '7z', 'tar', 'gz', 'tgz', 'bz2', 'xz') - OR LOWER(mime_type) LIKE '%zip%' - OR LOWER(mime_type) LIKE '%tar%' - OR LOWER(mime_type) LIKE '%gzip%' THEN '压缩包' - WHEN LOWER(extension) IN ('pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'csv', 'json', 'yaml', 'yml', 'xml') - OR LOWER(mime_type) LIKE 'text/%' - OR LOWER(mime_type) = 'application/pdf' THEN '文档' - ELSE '其他' - END, - COUNT(*), - COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -GROUP BY 2 -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = excluded.file_count, - file_size = excluded.file_size, - updated_at = CURRENT_TIMESTAMP; - -INSERT INTO w_upload_stats (dimension, stat_key, file_count, file_size) -SELECT - 'trend', - STRFTIME('%Y-%m-%d', created_at), - COUNT(*), - COALESCE(SUM(file_size), 0) -FROM w_uploads -WHERE status != 'deleted' -GROUP BY STRFTIME('%Y-%m-%d', created_at) -ON CONFLICT (dimension, stat_key) DO UPDATE SET - file_count = excluded.file_count, - file_size = excluded.file_size, - updated_at = CURRENT_TIMESTAMP; - --- +goose Down -DELETE FROM w_upload_stats; \ No newline at end of file diff --git a/pkg/migrator/goose/sqlite/202606180001_drop_upload_storage_driver.sql b/pkg/migrator/goose/sqlite/202606180001_drop_upload_storage_driver.sql deleted file mode 100644 index aa66dcdc..00000000 --- a/pkg/migrator/goose/sqlite/202606180001_drop_upload_storage_driver.sql +++ /dev/null @@ -1,8 +0,0 @@ --- +goose Up -DROP INDEX IF EXISTS idx_w_uploads_storage_driver_status; --- SQLite lacks DROP COLUMN IF EXISTS; goose runs this only after initial schema created the column. -ALTER TABLE w_uploads DROP COLUMN storage_driver; - --- +goose Down -ALTER TABLE w_uploads ADD COLUMN storage_driver VARCHAR(50) NOT NULL DEFAULT 'local'; -CREATE INDEX IF NOT EXISTS idx_w_uploads_storage_driver_status ON w_uploads (storage_driver, status); \ No newline at end of file diff --git a/pkg/migrator/goose/sqlite/202608160001_create_user_access_logs.sql b/pkg/migrator/goose/sqlite/202608160001_create_user_access_logs.sql deleted file mode 100644 index e8abea95..00000000 --- a/pkg/migrator/goose/sqlite/202608160001_create_user_access_logs.sql +++ /dev/null @@ -1,22 +0,0 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS w_user_access_logs ( - id INTEGER NOT NULL, - user_id INTEGER NOT NULL DEFAULT 0, - path TEXT NOT NULL DEFAULT '', - method TEXT NOT NULL DEFAULT '', - ip TEXT NOT NULL DEFAULT '', - user_agent TEXT NOT NULL DEFAULT '', - headers TEXT NOT NULL DEFAULT '', - status INTEGER NOT NULL DEFAULT 0, - latency INTEGER NOT NULL DEFAULT 0, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id, created_at) -); - -CREATE INDEX IF NOT EXISTS idx_w_user_access_logs_user_id ON w_user_access_logs (user_id, created_at DESC); -CREATE INDEX IF NOT EXISTS idx_w_user_access_logs_created_at ON w_user_access_logs (created_at DESC); - --- +goose Down -DROP TABLE IF EXISTS w_user_access_logs; -DROP INDEX IF EXISTS idx_w_user_access_logs_user_id; -DROP INDEX IF EXISTS idx_w_user_access_logs_created_at; diff --git a/pkg/migrator/goose/sqlite/202608160002_log_database_configs.sql b/pkg/migrator/goose/sqlite/202608160002_log_database_configs.sql deleted file mode 100644 index 0a6918c6..00000000 --- a/pkg/migrator/goose/sqlite/202608160002_log_database_configs.sql +++ /dev/null @@ -1,17 +0,0 @@ --- +goose Up -INSERT OR IGNORE INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at) -VALUES - ('log_database', '', 'system', 0, '当前日志主库(postgres/sqlite/clickhouse),由切换任务写入', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('log_db_migration', '', 'system', 0, '日志库迁移冻结标记(空或 migrating)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('log_retention_days_postgres', '30', 'business', 0, 'PostgreSQL 用户访问日志保留天数', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('log_retention_days_sqlite', '30', 'business', 0, 'SQLite 用户访问日志保留天数', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('log_retention_days_clickhouse', '30', 'business', 0, 'ClickHouse 用户访问日志保留天数', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); - --- +goose Down -DELETE FROM w_system_configs WHERE key IN ( - 'log_database', - 'log_db_migration', - 'log_retention_days_postgres', - 'log_retention_days_sqlite', - 'log_retention_days_clickhouse' -); diff --git a/pkg/migrator/goose/sqlite/202608160003_create_message_gateway.sql b/pkg/migrator/goose/sqlite/202608160003_create_message_gateway.sql deleted file mode 100644 index aae7221e..00000000 --- a/pkg/migrator/goose/sqlite/202608160003_create_message_gateway.sql +++ /dev/null @@ -1,40 +0,0 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS w_message_channels ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - type TEXT NOT NULL, - owner_scope TEXT NOT NULL DEFAULT 'system', - owner_id INTEGER NULL, - enabled INTEGER NOT NULL DEFAULT 1, - credentials TEXT NOT NULL DEFAULT '', - extra TEXT NOT NULL DEFAULT '', - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_w_message_channels_type ON w_message_channels (type); - -CREATE TABLE IF NOT EXISTS w_message_bindings ( - id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - channel_id INTEGER NOT NULL, - platform_user_id TEXT NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); -CREATE UNIQUE INDEX IF NOT EXISTS uniq_w_message_bindings_channel_platform - ON w_message_bindings (channel_id, platform_user_id); -CREATE INDEX IF NOT EXISTS idx_w_message_bindings_user ON w_message_bindings (user_id); - -CREATE TABLE IF NOT EXISTS w_message_pairing_codes ( - code TEXT PRIMARY KEY, - channel_id INTEGER NOT NULL, - platform_user_id TEXT NOT NULL, - expires_at DATETIME NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); -CREATE INDEX IF NOT EXISTS idx_w_message_pairing_lookup - ON w_message_pairing_codes (channel_id, platform_user_id); - --- +goose Down -DROP TABLE IF EXISTS w_message_pairing_codes; -DROP TABLE IF EXISTS w_message_bindings; -DROP TABLE IF EXISTS w_message_channels; diff --git a/pkg/migrator/migrator.go b/pkg/migrator/migrator.go deleted file mode 100644 index 25b2d5db..00000000 --- a/pkg/migrator/migrator.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2025 linux.do -// Copyright 2026 Arctel.net -// SPDX-License-Identifier: Apache-2.0 - -// Package migrator 提供数据库迁移功能 -package migrator - -import ( - "context" - "embed" - "log" - - "github.com/Rain-kl/Wavelet/pkg/config" - "github.com/Rain-kl/Wavelet/plugins/infra/database" - "github.com/pressly/goose/v3" -) - -// migrationFS contains SQL migrations under goose/