mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 23:26:38 +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:
@@ -0,0 +1,7 @@
|
||||
-- +goose Up
|
||||
INSERT INTO w_system_configs (key, value, type, visibility, description, created_at, updated_at)
|
||||
VALUES ('file_access_whitelist', '["avatar"]', 'system', 0, '免登录访问的文件业务类型白名单 (JSON 数组格式)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- +goose Down
|
||||
DELETE FROM w_system_configs WHERE key = 'file_access_whitelist';
|
||||
@@ -0,0 +1,10 @@
|
||||
-- +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');
|
||||
@@ -0,0 +1,7 @@
|
||||
-- +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';
|
||||
@@ -0,0 +1,5 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE w_system_configs ALTER COLUMN value TYPE TEXT;
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE w_system_configs ALTER COLUMN value TYPE VARCHAR(255);
|
||||
@@ -0,0 +1,10 @@
|
||||
-- +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';
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +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;
|
||||
|
||||
-- +goose Down
|
||||
DELETE FROM w_system_configs WHERE key IN ('log_database', 'log_db_migration');
|
||||
@@ -6,6 +6,7 @@ package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
@@ -14,6 +15,9 @@ import (
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var adminMigrations embed.FS
|
||||
|
||||
// Option configures the admin plugin.
|
||||
type Option func(*Plugin)
|
||||
|
||||
@@ -56,6 +60,9 @@ func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
loginMW := authSvc.RequireAuthMiddleware().(gin.HandlerFunc)
|
||||
adminMW := authSvc.RequireAdminMiddleware().(gin.HandlerFunc)
|
||||
|
||||
// 0a. Register migrations
|
||||
ctx.Migrations().Register("admin", adminMigrations)
|
||||
|
||||
// 1. Register Admin HTTP Routes
|
||||
adminRouter := ctx.Router().Group("/api/v1/admin", loginMW, adminMW)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE w_access_tokens ADD COLUMN IF NOT EXISTS is_admin BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE w_access_tokens DROP COLUMN IF EXISTS is_admin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE w_access_tokens DROP COLUMN IF EXISTS last_used_at;
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE w_access_tokens ADD COLUMN last_used_at TIMESTAMPTZ;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- +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';
|
||||
@@ -0,0 +1,34 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS w_push_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
event_key VARCHAR(80) NOT NULL UNIQUE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
channels TEXT NOT NULL,
|
||||
targets TEXT NOT NULL,
|
||||
template TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_w_push_events_enabled ON w_push_events(enabled);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS w_push_histories (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
event_key VARCHAR(80) NOT NULL,
|
||||
channel VARCHAR(50) NOT NULL,
|
||||
target VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
level VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
error_msg TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_w_push_histories_event ON w_push_histories(event_key);
|
||||
CREATE INDEX IF NOT EXISTS 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;
|
||||
@@ -0,0 +1,19 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS w_push_channels (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(80) NOT NULL UNIQUE,
|
||||
description VARCHAR(255),
|
||||
type VARCHAR(50) NOT NULL DEFAULT 'custom',
|
||||
token VARCHAR(100),
|
||||
url TEXT NOT NULL,
|
||||
other TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_w_push_channels_name ON w_push_channels(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_w_push_channels_enabled ON w_push_channels(enabled);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS w_push_channels;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE w_push_events ADD COLUMN IF NOT EXISTS task_type VARCHAR(100) NOT NULL DEFAULT '';
|
||||
CREATE INDEX IF NOT EXISTS 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 IF EXISTS task_type;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- +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
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS w_user_access_logs
|
||||
(
|
||||
id UInt64,
|
||||
user_id UInt64,
|
||||
path String,
|
||||
method String,
|
||||
ip String,
|
||||
user_agent String,
|
||||
headers String,
|
||||
status Int32,
|
||||
latency Int64,
|
||||
created_at DateTime
|
||||
)
|
||||
ENGINE = MergeTree()
|
||||
PARTITION BY toYYYYMM(created_at)
|
||||
ORDER BY (created_at, ip, user_id)
|
||||
SETTINGS index_granularity = 8192;
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS w_user_access_logs;
|
||||
@@ -0,0 +1,20 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS w_user_access_logs (
|
||||
id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL DEFAULT 0,
|
||||
path VARCHAR(2048) NOT NULL DEFAULT '',
|
||||
method VARCHAR(16) NOT NULL DEFAULT '',
|
||||
ip VARCHAR(128) NOT NULL DEFAULT '',
|
||||
user_agent TEXT NOT NULL DEFAULT '',
|
||||
headers TEXT NOT NULL DEFAULT '',
|
||||
status INTEGER NOT NULL DEFAULT 0,
|
||||
latency BIGINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id, created_at)
|
||||
) PARTITION BY RANGE (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;
|
||||
@@ -6,12 +6,16 @@ package risk_control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"github.com/Rain-kl/Wavelet/core/extpoints"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
//go:embed logstore/migrations/*.sql
|
||||
var riskControlMigrations embed.FS
|
||||
|
||||
// Option configures the risk_control plugin.
|
||||
type Option func(*Plugin)
|
||||
|
||||
@@ -55,6 +59,9 @@ func (p *Plugin) Manifest() core.Manifest {
|
||||
|
||||
// Apply registers risk control middlewares, settings, and cleanup hooks into the Context.
|
||||
func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
// 0. Register user access log table migrations
|
||||
ctx.Migrations().Register("risk_control/logstore", riskControlMigrations)
|
||||
|
||||
// 1. Initialize LogWriter if needed
|
||||
InitLogWriter(ctx.GoContext())
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE w_uploads ADD COLUMN IF NOT EXISTS 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 IF EXISTS access_mode;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- +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_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_status_created_at;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- +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 TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (dimension, stat_key)
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS w_upload_stats;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- +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;
|
||||
|
||||
-- +goose Down
|
||||
DELETE FROM w_upload_stats;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- +goose Up
|
||||
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';
|
||||
@@ -6,6 +6,7 @@ package upload
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
@@ -17,6 +18,9 @@ import (
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var uploadMigrations embed.FS
|
||||
|
||||
// Plugin implements core.Plugin to provide file upload and media serving domain services.
|
||||
type Plugin struct{}
|
||||
|
||||
@@ -49,6 +53,9 @@ func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
}
|
||||
loginMW := authSvc.RequireAuthMiddleware().(gin.HandlerFunc)
|
||||
|
||||
// 0a. Register migrations
|
||||
ctx.Migrations().Register("upload", uploadMigrations)
|
||||
|
||||
// 1. Register File Server Routes
|
||||
ctx.Router().GET("/f/:id", filesrv.ServeFileByID)
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- +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;
|
||||
|
||||
-- +goose Down
|
||||
DELETE FROM w_users WHERE username = 'system';
|
||||
Reference in New Issue
Block a user