second commit

This commit is contained in:
Rogee
2026-06-04 15:44:48 +08:00
parent 4db6efb3a7
commit 8ac150bc7b
1275 changed files with 286124 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
package main
import (
"fmt"
"os"
"sort"
"strings"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/config"
"github.com/gochat/gochat/internal/middleware"
"github.com/gochat/gochat/internal/router"
)
func main() {
gin.SetMode(gin.TestMode)
engine := gin.New()
cfg, err := config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "config load: %v\n", err)
os.Exit(1)
}
// Create a zero-value Handlers struct — we only need route paths, not working handlers
handlers := &router.Handlers{}
corsCfg := middleware.CORSConfigFromAppConfig(cfg)
router.RegisterRoutes(
engine,
nil, // jwtService
nil, // refreshStore
nil, // webhookRegistry
handlers,
nil, // hub
nil, // wsAuthenticator
&cfg.JWT,
corsCfg,
nil, // db
)
routes := engine.Routes()
sort.Slice(routes, func(i, j int) bool {
if routes[i].Method != routes[j].Method {
return routes[i].Method < routes[j].Method
}
return routes[i].Path < routes[j].Path
})
count := 0
for _, r := range routes {
if strings.HasPrefix(r.Path, "/swagger") {
continue
}
fmt.Printf("%s %s\n", r.Method, r.Path)
count++
}
fmt.Printf("TOTAL: %d\n", count)
}
+109
View File
@@ -0,0 +1,109 @@
package main
import (
"fmt"
"os"
"strconv"
"github.com/gochat/gochat/internal/config"
"github.com/gochat/gochat/internal/database"
)
func main() {
if len(os.Args) < 2 {
printUsage()
os.Exit(1)
}
command := os.Args[1]
// Load config to get database connection info
cfg, err := config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
os.Exit(1)
}
dbURL := cfg.Database.MigrateDSN()
migrationsPath := cfg.Database.GetMigrationsPath()
switch command {
case "up":
if err := database.RunMigrations(dbURL, migrationsPath); err != nil {
fmt.Fprintf(os.Stderr, "Error running migrations up: %v\n", err)
os.Exit(1)
}
fmt.Println("Migrations applied successfully (up)")
case "down":
if err := database.RollbackMigrations(dbURL, migrationsPath); err != nil {
fmt.Fprintf(os.Stderr, "Error running migrations down: %v\n", err)
os.Exit(1)
}
fmt.Println("Migrations rolled back successfully (down)")
case "force":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "Error: 'force' requires a version argument\n")
printUsage()
os.Exit(1)
}
version, err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "Error: invalid version number '%s': %v\n", os.Args[2], err)
os.Exit(1)
}
if err := database.ForceVersion(dbURL, migrationsPath, version); err != nil {
fmt.Fprintf(os.Stderr, "Error forcing version %d: %v\n", version, err)
os.Exit(1)
}
fmt.Printf("Migration version forced to %d\n", version)
case "version":
version, dirty, err := database.CurrentVersion(dbURL, migrationsPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting current version: %v\n", err)
os.Exit(1)
}
if dirty {
fmt.Printf("Current version: %d (DIRTY — a migration partially failed)\n", version)
} else {
fmt.Printf("Current version: %d\n", version)
}
case "steps":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "Error: 'steps' requires a step count argument\n")
printUsage()
os.Exit(1)
}
steps, err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "Error: invalid step count '%s': %v\n", os.Args[2], err)
os.Exit(1)
}
if err := database.MigrateSteps(dbURL, migrationsPath, steps); err != nil {
fmt.Fprintf(os.Stderr, "Error running %d migration steps: %v\n", steps, err)
os.Exit(1)
}
fmt.Printf("Applied %d migration steps successfully\n", steps)
default:
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
printUsage()
os.Exit(1)
}
}
func printUsage() {
fmt.Println("Usage: migrate <command> [args]")
fmt.Println("")
fmt.Println("Commands:")
fmt.Println(" up Apply all pending migrations")
fmt.Println(" down Rollback all migrations")
fmt.Println(" force <v> Force version to v (fix dirty state)")
fmt.Println(" version Show current migration version")
fmt.Println(" steps <n> Apply n steps (positive=up, negative=down)")
fmt.Println("")
fmt.Println("Environment: Uses database config from Viper (config.yaml)")
}
+220
View File
@@ -0,0 +1,220 @@
package main
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/gochat/gochat/internal/database"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// setupTestMigrations creates a temporary directory with test migration files
// that are compatible with SQLite (no PostgreSQL-specific features).
func setupTestMigrations(t *testing.T) string {
t.Helper()
dir := t.TempDir()
// Migration 1: Create a simple users table (SQLite-compatible)
up1 := `
CREATE TABLE IF NOT EXISTS test_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`
down1 := `DROP TABLE IF EXISTS test_users;`
require.NoError(t, os.WriteFile(filepath.Join(dir, "000001_create_test_users.up.sql"), []byte(up1), 0644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "000001_create_test_users.down.sql"), []byte(down1), 0644))
// Migration 2: Add a phone column
up2 := `
ALTER TABLE test_users ADD COLUMN phone TEXT;
`
down2 := `
-- SQLite doesn't support DROP COLUMN before 3.35.0, so we recreate the table
CREATE TABLE test_users_backup AS SELECT id, name, email, created_at, updated_at FROM test_users;
DROP TABLE test_users;
CREATE TABLE test_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO test_users SELECT id, name, email, created_at, updated_at FROM test_users_backup;
DROP TABLE test_users_backup;
`
require.NoError(t, os.WriteFile(filepath.Join(dir, "000002_add_test_user_phone.up.sql"), []byte(up2), 0644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "000002_add_test_user_phone.down.sql"), []byte(down2), 0644))
// Migration 3: Create a posts table
up3 := `
CREATE TABLE IF NOT EXISTS test_posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES test_users(id),
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`
down3 := `DROP TABLE IF EXISTS test_posts;`
require.NoError(t, os.WriteFile(filepath.Join(dir, "000003_create_test_posts.up.sql"), []byte(up3), 0644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "000003_create_test_posts.down.sql"), []byte(down3), 0644))
return dir
}
// sqliteDBURL creates a SQLite database URL for golang-migrate.
func sqliteDBURL(t *testing.T) string {
t.Helper()
dbPath := filepath.Join(t.TempDir(), "test.db")
return fmt.Sprintf("sqlite3://%s", dbPath)
}
func TestRunMigrationsUp(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
err := database.RunMigrations(dbURL, migrationsDir)
assert.NoError(t, err, "RunMigrations should apply all pending migrations")
// Verify current version is 3
version, dirty, err := database.CurrentVersion(dbURL, migrationsDir)
assert.NoError(t, err)
assert.Equal(t, uint(3), version)
assert.False(t, dirty)
}
func TestRunMigrationsNoChange(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
// First run: apply all migrations
err := database.RunMigrations(dbURL, migrationsDir)
require.NoError(t, err)
// Second run: should return nil (ErrNoChange is handled)
err = database.RunMigrations(dbURL, migrationsDir)
assert.NoError(t, err, "Running migrations with no pending changes should succeed")
}
func TestMigrateStepsUp(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
// Apply 1 step at a time
err := database.MigrateSteps(dbURL, migrationsDir, 1)
require.NoError(t, err)
version, dirty, err := database.CurrentVersion(dbURL, migrationsDir)
require.NoError(t, err)
assert.Equal(t, uint(1), version)
assert.False(t, dirty)
// Apply 2 more steps
err = database.MigrateSteps(dbURL, migrationsDir, 2)
require.NoError(t, err)
version, dirty, err = database.CurrentVersion(dbURL, migrationsDir)
require.NoError(t, err)
assert.Equal(t, uint(3), version)
assert.False(t, dirty)
}
func TestMigrateStepsDown(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
// First, apply all migrations
err := database.RunMigrations(dbURL, migrationsDir)
require.NoError(t, err)
// Rollback 1 step
err = database.MigrateSteps(dbURL, migrationsDir, -1)
require.NoError(t, err)
version, _, err := database.CurrentVersion(dbURL, migrationsDir)
require.NoError(t, err)
assert.Equal(t, uint(2), version)
// Rollback 2 more steps
err = database.MigrateSteps(dbURL, migrationsDir, -2)
require.NoError(t, err)
version, _, err = database.CurrentVersion(dbURL, migrationsDir)
require.NoError(t, err)
assert.Equal(t, uint(0), version)
}
func TestRollbackMigrations(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
// Apply all migrations first
err := database.RunMigrations(dbURL, migrationsDir)
require.NoError(t, err)
// Rollback all
err = database.RollbackMigrations(dbURL, migrationsDir)
assert.NoError(t, err, "RollbackMigrations should succeed")
version, _, err := database.CurrentVersion(dbURL, migrationsDir)
require.NoError(t, err)
assert.Equal(t, uint(0), version, "All migrations should be rolled back")
}
func TestRollbackNoChange(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
// Try to rollback when no migrations have been applied
err := database.RollbackMigrations(dbURL, migrationsDir)
assert.NoError(t, err, "Rollback with no applied migrations should succeed (ErrNoChange)")
}
func TestForceVersion(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
// Apply all migrations first
err := database.RunMigrations(dbURL, migrationsDir)
require.NoError(t, err)
// Force version to 1
err = database.ForceVersion(dbURL, migrationsDir, 1)
require.NoError(t, err)
version, dirty, err := database.CurrentVersion(dbURL, migrationsDir)
require.NoError(t, err)
assert.Equal(t, uint(1), version)
// After Force, dirty state should be cleared
assert.False(t, dirty)
}
func TestCurrentVersion(t *testing.T) {
migrationsDir := setupTestMigrations(t)
dbURL := sqliteDBURL(t)
// Before any migrations, version should be 0 (with ErrNoChange handled internally)
version, dirty, err := database.CurrentVersion(dbURL, migrationsDir)
// When no migrations have been applied, Version returns an error
// This is expected — golang-migrate returns ErrNoChange for version 0
if err != nil {
t.Logf("Expected: version 0 with no migrations applied may return error: %v", err)
}
// Apply migrations
err = database.RunMigrations(dbURL, migrationsDir)
require.NoError(t, err)
version, dirty, err = database.CurrentVersion(dbURL, migrationsDir)
require.NoError(t, err)
assert.Equal(t, uint(3), version)
assert.False(t, dirty)
}
+38
View File
@@ -0,0 +1,38 @@
package main
import (
"encoding/json"
"fmt"
"github.com/gochat/gochat/internal/model"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func main() {
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic(err)
}
db.AutoMigrate(&model.DashboardApp{})
// Create an app with Active=false
app := &model.DashboardApp{
AccountID: 1,
Title: "InactiveApp",
Kind: "frame",
Active: model.BoolPtr(false),
Content: json.RawMessage(`[]`),
}
result := db.Select("AccountID", "UserID", "Title", "Description",
"Icon", "URL", "Kind", "Content", "Active").Create(app)
fmt.Printf("Create error: %v\n", result.Error)
fmt.Printf("App Active after create: %v\n", app.Active)
// Read it back
var found model.DashboardApp
db.First(&found, app.ID)
fmt.Printf("Found Active: %v\n", found.Active)
}