mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-10-06 20:09:07 +00:00
Here's the commit message: ``` Fix code style and indentation Apply consistent indentation, fix whitespace and tabs vs spaces issues, remove trailing whitespace, and ensure proper line endings throughout the codebase. Also add comments and improve code organization. ``` The message body is included since this is a bigger cleanup effort that touched multiple files and made various formatting improvements that are worth explaining.
189 lines
5.5 KiB
Go
189 lines
5.5 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.debros.io/DeBros/network/pkg/client"
|
|
"git.debros.io/DeBros/network/pkg/logging"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var errNoMigrationsFound = errors.New("no migrations found")
|
|
|
|
func (g *Gateway) applyAutoMigrations(ctx context.Context) error {
|
|
if g.client == nil {
|
|
return nil
|
|
}
|
|
db := g.client.Database()
|
|
|
|
// Use internal context to bypass authentication for system migrations
|
|
internalCtx := client.WithInternalAuth(ctx)
|
|
|
|
stmts := []string{
|
|
// namespaces
|
|
"CREATE TABLE IF NOT EXISTS namespaces (\n\t id INTEGER PRIMARY KEY AUTOINCREMENT,\n\t name TEXT NOT NULL UNIQUE,\n\t created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n)",
|
|
// api_keys
|
|
"CREATE TABLE IF NOT EXISTS api_keys (\n\t id INTEGER PRIMARY KEY AUTOINCREMENT,\n\t key TEXT NOT NULL UNIQUE,\n\t name TEXT,\n\t namespace_id INTEGER NOT NULL,\n\t scopes TEXT,\n\t created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\t last_used_at TIMESTAMP,\n\t FOREIGN KEY(namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE\n)",
|
|
"CREATE INDEX IF NOT EXISTS idx_api_keys_namespace ON api_keys(namespace_id)",
|
|
// request_logs
|
|
"CREATE TABLE IF NOT EXISTS request_logs (\n\t id INTEGER PRIMARY KEY AUTOINCREMENT,\n\t method TEXT NOT NULL,\n\t path TEXT NOT NULL,\n\t status_code INTEGER NOT NULL,\n\t bytes_out INTEGER NOT NULL DEFAULT 0,\n\t duration_ms INTEGER NOT NULL DEFAULT 0,\n\t ip TEXT,\n\t api_key_id INTEGER,\n\t created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\t FOREIGN KEY(api_key_id) REFERENCES api_keys(id) ON DELETE SET NULL\n)",
|
|
"CREATE INDEX IF NOT EXISTS idx_request_logs_api_key ON request_logs(api_key_id)",
|
|
"CREATE INDEX IF NOT EXISTS idx_request_logs_created_at ON request_logs(created_at)",
|
|
// seed default namespace
|
|
"INSERT OR IGNORE INTO namespaces(name) VALUES ('default')",
|
|
}
|
|
|
|
for _, stmt := range stmts {
|
|
if _, err := db.Query(internalCtx, stmt); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *Gateway) applyMigrations(ctx context.Context) error {
|
|
if g.client == nil {
|
|
return nil
|
|
}
|
|
db := g.client.Database()
|
|
|
|
// Use internal context to bypass authentication for system migrations
|
|
internalCtx := client.WithInternalAuth(ctx)
|
|
|
|
// Ensure schema_migrations exists first
|
|
if _, err := db.Query(internalCtx, "CREATE TABLE IF NOT EXISTS schema_migrations (\n\tversion INTEGER PRIMARY KEY,\n\tapplied_at TIMESTAMP NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))\n)"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Locate migrations directory relative to CWD
|
|
migDir := "migrations"
|
|
if fi, err := os.Stat(migDir); err != nil || !fi.IsDir() {
|
|
return errNoMigrationsFound
|
|
}
|
|
|
|
entries, err := os.ReadDir(migDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
type mig struct {
|
|
ver int
|
|
path string
|
|
}
|
|
migrations := make([]mig, 0)
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
continue
|
|
}
|
|
name := e.Name()
|
|
if !strings.HasSuffix(strings.ToLower(name), ".sql") {
|
|
continue
|
|
}
|
|
if ver, ok := parseMigrationVersion(name); ok {
|
|
migrations = append(migrations, mig{ver: ver, path: filepath.Join(migDir, name)})
|
|
}
|
|
}
|
|
if len(migrations) == 0 {
|
|
return errNoMigrationsFound
|
|
}
|
|
sort.Slice(migrations, func(i, j int) bool { return migrations[i].ver < migrations[j].ver })
|
|
|
|
// Helper to check if version applied
|
|
isApplied := func(ctx context.Context, v int) (bool, error) {
|
|
res, err := db.Query(ctx, "SELECT 1 FROM schema_migrations WHERE version = ? LIMIT 1", v)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return res != nil && res.Count > 0, nil
|
|
}
|
|
|
|
for _, m := range migrations {
|
|
applied, err := isApplied(internalCtx, m.ver)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if applied {
|
|
continue
|
|
}
|
|
// Read and split SQL file into statements
|
|
content, err := os.ReadFile(m.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stmts := splitSQLStatements(string(content))
|
|
for _, s := range stmts {
|
|
if s == "" {
|
|
continue
|
|
}
|
|
if _, err := db.Query(internalCtx, s); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Mark as applied
|
|
if _, err := db.Query(internalCtx, "INSERT INTO schema_migrations (version) VALUES (?)", m.ver); err != nil {
|
|
return err
|
|
}
|
|
g.logger.ComponentInfo(logging.ComponentDatabase, "applied migration", zap.Int("version", m.ver), zap.String("file", m.path))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseMigrationVersion(name string) (int, bool) {
|
|
i := 0
|
|
for i < len(name) && name[i] >= '0' && name[i] <= '9' {
|
|
i++
|
|
}
|
|
if i == 0 {
|
|
return 0, false
|
|
}
|
|
v, err := strconv.Atoi(name[:i])
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
return v, true
|
|
}
|
|
|
|
func splitSQLStatements(sqlText string) []string {
|
|
lines := strings.Split(sqlText, "\n")
|
|
cleaned := make([]string, 0, len(lines))
|
|
for _, ln := range lines {
|
|
s := strings.TrimSpace(ln)
|
|
if s == "" {
|
|
continue
|
|
}
|
|
// Handle inline comments by removing everything after --
|
|
if commentIdx := strings.Index(s, "--"); commentIdx >= 0 {
|
|
s = strings.TrimSpace(s[:commentIdx])
|
|
if s == "" {
|
|
continue // line was only a comment
|
|
}
|
|
}
|
|
upper := strings.ToUpper(s)
|
|
if upper == "BEGIN;" || upper == "COMMIT;" || upper == "BEGIN" || upper == "COMMIT" {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(upper, "INSERT") && strings.Contains(upper, "SCHEMA_MIGRATIONS") {
|
|
// ignore in-file migration markers
|
|
continue
|
|
}
|
|
cleaned = append(cleaned, s)
|
|
}
|
|
// Join and split by ';'
|
|
joined := strings.Join(cleaned, "\n")
|
|
parts := strings.Split(joined, ";")
|
|
out := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
sp := strings.TrimSpace(p)
|
|
if sp == "" {
|
|
continue
|
|
}
|
|
out = append(out, sp+";")
|
|
}
|
|
return out
|
|
}
|