big ole refactor
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"git.haelnorr.com/h/oslstats/internal/handlers"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func setupAuth(
|
||||
cfg *hwsauth.Config,
|
||||
logger *hlog.Logger,
|
||||
conn *bun.DB,
|
||||
server *hws.Server,
|
||||
ignoredPaths []string,
|
||||
) (*hwsauth.Authenticator[*db.User, bun.Tx], error) {
|
||||
beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
return tx, err
|
||||
}
|
||||
auth, err := hwsauth.NewAuthenticator(
|
||||
cfg,
|
||||
db.GetUserByID,
|
||||
server,
|
||||
beginTx,
|
||||
logger,
|
||||
handlers.ErrorPage,
|
||||
conn.DB,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hwsauth.NewAuthenticator")
|
||||
}
|
||||
|
||||
err = auth.IgnorePaths(ignoredPaths...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "auth.IgnorePaths")
|
||||
}
|
||||
|
||||
db.CurrentUser = auth.CurrentModel
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/config"
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
)
|
||||
|
||||
func setupBun(cfg *config.Config) (conn *bun.DB, close func() error) {
|
||||
dsn := fmt.Sprintf("postgres://%s:%s@%s:%v/%s?sslmode=%s",
|
||||
cfg.DB.User, cfg.DB.Password, cfg.DB.Host, cfg.DB.Port, cfg.DB.DB, cfg.DB.SSL)
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
|
||||
sqldb.SetMaxOpenConns(25)
|
||||
sqldb.SetMaxIdleConns(10)
|
||||
sqldb.SetConnMaxLifetime(5 * time.Minute)
|
||||
sqldb.SetConnMaxIdleTime(5 * time.Minute)
|
||||
|
||||
conn = bun.NewDB(sqldb, pgdialect.New())
|
||||
registerDBModels(conn)
|
||||
close = sqldb.Close
|
||||
return conn, close
|
||||
}
|
||||
|
||||
func registerDBModels(conn *bun.DB) []any {
|
||||
models := []any{
|
||||
(*db.RolePermission)(nil),
|
||||
(*db.UserRole)(nil),
|
||||
(*db.SeasonLeague)(nil),
|
||||
(*db.TeamParticipation)(nil),
|
||||
(*db.User)(nil),
|
||||
(*db.DiscordToken)(nil),
|
||||
(*db.Season)(nil),
|
||||
(*db.League)(nil),
|
||||
(*db.Team)(nil),
|
||||
(*db.Role)(nil),
|
||||
(*db.Permission)(nil),
|
||||
(*db.AuditLog)(nil),
|
||||
}
|
||||
conn.RegisterModel(models...)
|
||||
return models
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/auditlog"
|
||||
"git.haelnorr.com/h/oslstats/internal/config"
|
||||
"git.haelnorr.com/h/oslstats/internal/discord"
|
||||
"git.haelnorr.com/h/oslstats/internal/handlers"
|
||||
"git.haelnorr.com/h/oslstats/internal/rbac"
|
||||
"git.haelnorr.com/h/oslstats/internal/store"
|
||||
)
|
||||
|
||||
func setupHTTPServer(
|
||||
staticFS *fs.FS,
|
||||
cfg *config.Config,
|
||||
logger *hlog.Logger,
|
||||
bun *bun.DB,
|
||||
store *store.Store,
|
||||
discordAPI *discord.APIClient,
|
||||
) (server *hws.Server, err error) {
|
||||
if staticFS == nil {
|
||||
return nil, errors.New("No filesystem provided")
|
||||
}
|
||||
fs := http.FS(*staticFS)
|
||||
httpServer, err := hws.NewServer(cfg.HWS)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hws.NewServer")
|
||||
}
|
||||
|
||||
ignoredPaths := []string{
|
||||
"/static/*",
|
||||
"/.well-known/*",
|
||||
"/ws/notifications",
|
||||
}
|
||||
|
||||
auth, err := setupAuth(
|
||||
cfg.HWSAuth, logger, bun, httpServer, ignoredPaths)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "setupAuth")
|
||||
}
|
||||
|
||||
err = httpServer.AddErrorPage(handlers.ErrorPage)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "httpServer.AddErrorPage")
|
||||
}
|
||||
|
||||
err = httpServer.AddLogger(logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "httpServer.AddLogger")
|
||||
}
|
||||
|
||||
err = httpServer.LoggerIgnorePaths(ignoredPaths...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "httpServer.LoggerIgnorePaths")
|
||||
}
|
||||
|
||||
// Initialize permissions checker
|
||||
perms, err := rbac.NewChecker(bun, httpServer)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "rbac.NewChecker")
|
||||
}
|
||||
|
||||
// Initialize audit logger
|
||||
audit := auditlog.NewLogger(bun)
|
||||
|
||||
err = addRoutes(httpServer, &fs, cfg, bun, auth, store, discordAPI, perms, audit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "addRoutes")
|
||||
}
|
||||
|
||||
err = addMiddleware(httpServer, auth, cfg, perms, discordAPI, store, bun)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "addMiddleware")
|
||||
}
|
||||
|
||||
return httpServer, nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"git.haelnorr.com/h/oslstats/internal/config"
|
||||
"git.haelnorr.com/h/oslstats/internal/db/migrate"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -48,7 +49,7 @@ func main() {
|
||||
|
||||
// Handle migration file creation (doesn't need DB connection)
|
||||
if flags.MigrateCreate != "" {
|
||||
if err := createMigration(flags.MigrateCreate); err != nil {
|
||||
if err := migrate.CreateMigration(flags.MigrateCreate); err != nil {
|
||||
logger.Fatal().Err(err).Str("stacktrace", fmt.Sprintf("%+v", errors.Wrap(err, "createMigration"))).Msg("Error creating migration")
|
||||
}
|
||||
return
|
||||
@@ -59,17 +60,21 @@ func main() {
|
||||
flags.MigrateStatus || flags.MigrateDryRun ||
|
||||
flags.ResetDB {
|
||||
|
||||
var command, countStr string
|
||||
// Route to appropriate command
|
||||
if flags.MigrateUp != "" {
|
||||
err = runMigrations(ctx, cfg, "up", flags.MigrateUp)
|
||||
command = "up"
|
||||
countStr = flags.MigrateUp
|
||||
} else if flags.MigrateRollback != "" {
|
||||
err = runMigrations(ctx, cfg, "rollback", flags.MigrateRollback)
|
||||
command = "rollback"
|
||||
countStr = flags.MigrateRollback
|
||||
} else if flags.MigrateStatus {
|
||||
err = runMigrations(ctx, cfg, "status", "")
|
||||
} else if flags.MigrateDryRun {
|
||||
err = runMigrations(ctx, cfg, "dry-run", "")
|
||||
} else if flags.ResetDB {
|
||||
err = resetDatabase(ctx, cfg)
|
||||
command = "status"
|
||||
}
|
||||
if flags.ResetDB {
|
||||
err = migrate.ResetDatabase(ctx, cfg)
|
||||
} else {
|
||||
err = migrate.RunMigrations(ctx, cfg, command, countStr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"git.haelnorr.com/h/oslstats/internal/config"
|
||||
"git.haelnorr.com/h/oslstats/internal/contexts"
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"git.haelnorr.com/h/oslstats/internal/discord"
|
||||
"git.haelnorr.com/h/oslstats/internal/rbac"
|
||||
"git.haelnorr.com/h/oslstats/internal/store"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func addMiddleware(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*db.User, bun.Tx],
|
||||
cfg *config.Config,
|
||||
perms *rbac.Checker,
|
||||
discordAPI *discord.APIClient,
|
||||
store *store.Store,
|
||||
conn *bun.DB,
|
||||
) error {
|
||||
err := server.AddMiddleware(
|
||||
auth.Authenticate(tokenRefresh(auth, discordAPI, store)),
|
||||
rbac.LoadPreviewRoleMiddleware(server, conn),
|
||||
perms.LoadPermissionsMiddleware(),
|
||||
devMode(cfg),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "server.AddMiddleware")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func devMode(cfg *config.Config) hws.Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if cfg.Flags.DevMode {
|
||||
devInfo := contexts.DevInfo{
|
||||
WebsocketBase: "ws://" + cfg.HWS.Host + ":" + strconv.FormatUint(cfg.HWS.Port, 10),
|
||||
HTMXLog: true,
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), contexts.DevModeKey, devInfo)
|
||||
req := r.WithContext(ctx)
|
||||
next.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func tokenRefresh(
|
||||
auth *hwsauth.Authenticator[*db.User, bun.Tx],
|
||||
discordAPI *discord.APIClient,
|
||||
store *store.Store,
|
||||
) func(ctx context.Context, user *db.User, tx bun.Tx, w http.ResponseWriter, r *http.Request) (bool, *hws.HWSError) {
|
||||
return func(ctx context.Context, user *db.User, tx bun.Tx, w http.ResponseWriter, r *http.Request) (bool, *hws.HWSError) {
|
||||
success, err := refreshToken(ctx, store, discordAPI, user, tx)
|
||||
if err != nil {
|
||||
return false, &hws.HWSError{
|
||||
Error: errors.Wrap(err, "refreshToken"),
|
||||
Message: "Error refreshing discord token",
|
||||
Level: hws.ErrorERROR,
|
||||
RenderErrorPage: true,
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
err = auth.Logout(tx, w, r)
|
||||
if err != nil {
|
||||
return false, &hws.HWSError{
|
||||
Error: errors.Wrap(err, "auth.Logout"),
|
||||
Message: "Logout failed",
|
||||
Level: hws.ErrorERROR,
|
||||
RenderErrorPage: true,
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func refreshToken(
|
||||
ctx context.Context,
|
||||
store *store.Store,
|
||||
discordAPI *discord.APIClient,
|
||||
user *db.User,
|
||||
tx bun.Tx,
|
||||
) (bool, error) {
|
||||
token := store.CheckToken(user)
|
||||
if token != nil {
|
||||
return true, nil
|
||||
}
|
||||
// Get the token
|
||||
token, err := user.GetDiscordToken(ctx, tx)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "user.GetDiscordToken")
|
||||
}
|
||||
|
||||
tokenstatus, err := tokenStatus(token)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "tokenStatus")
|
||||
}
|
||||
switch tokenstatus {
|
||||
case "revoked":
|
||||
return false, nil
|
||||
case "expired", "expiring":
|
||||
newtoken, err := discordAPI.RefreshToken(token.Convert())
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "discordAPI.RefreshToken")
|
||||
}
|
||||
err = user.UpdateDiscordToken(ctx, tx, newtoken)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "user.UpdateDiscordToken")
|
||||
}
|
||||
err = store.NewTokenCheck(user, token)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "store.NewTokenCheck")
|
||||
}
|
||||
return true, nil
|
||||
case "valid":
|
||||
err = store.NewTokenCheck(user, token)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "store.NewTokenCheck")
|
||||
}
|
||||
return true, nil
|
||||
default:
|
||||
return false, errors.New("unexpected error occured validating discord token for user")
|
||||
}
|
||||
}
|
||||
|
||||
func tokenStatus(token *db.DiscordToken) (string, error) {
|
||||
now := time.Now().Unix()
|
||||
dayfromnow := now + int64(24*time.Hour/time.Second)
|
||||
oauthtoken := token.Convert()
|
||||
session, err := discord.NewOAuthSession(oauthtoken)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "discord.NewOAuthSession")
|
||||
}
|
||||
_, err = session.GetUser()
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "HTTP 401") {
|
||||
// Error not related to token status
|
||||
return "", errors.Wrap(err, "session.GetUser")
|
||||
}
|
||||
// Token not valid
|
||||
if token.ExpiresAt < now {
|
||||
return "expired", nil
|
||||
}
|
||||
return "revoked", nil
|
||||
}
|
||||
if token.ExpiresAt < dayfromnow {
|
||||
return "expiring", nil
|
||||
}
|
||||
return "valid", nil
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/cmd/oslstats/migrations"
|
||||
"git.haelnorr.com/h/oslstats/internal/backup"
|
||||
"git.haelnorr.com/h/oslstats/internal/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
// runMigrations executes database migrations
|
||||
func runMigrations(ctx context.Context, cfg *config.Config, command string, countStr string) error {
|
||||
conn, close := setupBun(cfg)
|
||||
defer func() { _ = close() }()
|
||||
|
||||
migrator := migrate.NewMigrator(conn, migrations.Migrations)
|
||||
|
||||
// Initialize migration tables
|
||||
if err := migrator.Init(ctx); err != nil {
|
||||
return errors.Wrap(err, "migrator.Init")
|
||||
}
|
||||
|
||||
switch command {
|
||||
case "up":
|
||||
err := migrateUp(ctx, migrator, conn, cfg, countStr)
|
||||
if err != nil {
|
||||
// On error, automatically rollback the migrations that were just applied
|
||||
fmt.Println("[WARN] Migration failed, attempting automatic rollback...")
|
||||
// We need to figure out how many migrations were applied in this batch
|
||||
// For now, we'll skip automatic rollback since it's complex with the new count system
|
||||
// The user can manually rollback if needed
|
||||
return err
|
||||
}
|
||||
return err
|
||||
case "rollback":
|
||||
return migrateRollback(ctx, migrator, conn, cfg, countStr)
|
||||
case "status":
|
||||
return migrateStatus(ctx, migrator)
|
||||
case "dry-run":
|
||||
return migrateDryRun(ctx, migrator)
|
||||
default:
|
||||
return fmt.Errorf("unknown migration command: %s", command)
|
||||
}
|
||||
}
|
||||
|
||||
// migrateUp runs pending migrations
|
||||
func migrateUp(ctx context.Context, migrator *migrate.Migrator, conn *bun.DB, cfg *config.Config, countStr string) error {
|
||||
// Parse count parameter
|
||||
count, all, err := parseMigrationCount(countStr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse migration count")
|
||||
}
|
||||
|
||||
fmt.Println("[INFO] Step 1/5: Validating migrations...")
|
||||
if err := validateMigrations(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("[INFO] Migration validation passed ✓")
|
||||
|
||||
fmt.Println("[INFO] Step 2/5: Checking for pending migrations...")
|
||||
// Check for pending migrations using MigrationsWithStatus (read-only)
|
||||
ms, err := migrator.MigrationsWithStatus(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get migration status")
|
||||
}
|
||||
|
||||
unapplied := ms.Unapplied()
|
||||
if len(unapplied) == 0 {
|
||||
fmt.Println("[INFO] No pending migrations")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select which migrations to apply
|
||||
toApply := selectMigrationsToApply(unapplied, count, all)
|
||||
if len(toApply) == 0 {
|
||||
fmt.Println("[INFO] No migrations to run")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print what we're about to do
|
||||
if all {
|
||||
fmt.Printf("[INFO] Running all %d pending migration(s):\n", len(toApply))
|
||||
} else {
|
||||
fmt.Printf("[INFO] Running %d migration(s):\n", len(toApply))
|
||||
}
|
||||
for _, m := range toApply {
|
||||
fmt.Printf(" 📋 %s\n", m.Name)
|
||||
}
|
||||
|
||||
// Create backup unless --no-backup flag is set
|
||||
if !cfg.Flags.MigrateNoBackup {
|
||||
fmt.Println("[INFO] Step 3/5: Creating backup...")
|
||||
_, err := backup.CreateBackup(ctx, cfg, "migration")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create backup")
|
||||
}
|
||||
|
||||
// Clean old backups
|
||||
if err := backup.CleanOldBackups(cfg, cfg.DB.BackupRetention); err != nil {
|
||||
fmt.Printf("[WARN] Failed to clean old backups: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[INFO] Step 3/5: Skipping backup (--no-backup flag set)")
|
||||
}
|
||||
|
||||
// Acquire migration lock
|
||||
fmt.Println("[INFO] Step 4/5: Acquiring migration lock...")
|
||||
if err := acquireMigrationLock(ctx, conn); err != nil {
|
||||
return errors.Wrap(err, "acquire migration lock")
|
||||
}
|
||||
defer releaseMigrationLock(ctx, conn)
|
||||
fmt.Println("[INFO] Migration lock acquired")
|
||||
|
||||
// Run migrations
|
||||
fmt.Println("[INFO] Step 5/5: Applying migrations...")
|
||||
group, err := executeUpMigrations(ctx, migrator, toApply)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute migrations")
|
||||
}
|
||||
|
||||
if group.IsZero() {
|
||||
fmt.Println("[INFO] No migrations to run")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("[INFO] Migrated to group %d\n", group.ID)
|
||||
for _, migration := range group.Migrations {
|
||||
fmt.Printf(" ✅ %s\n", migration.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateRollback rolls back migrations
|
||||
func migrateRollback(ctx context.Context, migrator *migrate.Migrator, conn *bun.DB, cfg *config.Config, countStr string) error {
|
||||
// Parse count parameter
|
||||
count, all, err := parseMigrationCount(countStr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse migration count")
|
||||
}
|
||||
|
||||
// Get all migrations with status
|
||||
ms, err := migrator.MigrationsWithStatus(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get migration status")
|
||||
}
|
||||
|
||||
applied := ms.Applied()
|
||||
if len(applied) == 0 {
|
||||
fmt.Println("[INFO] No migrations to rollback")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select which migrations to rollback
|
||||
toRollback := selectMigrationsToRollback(applied, count, all)
|
||||
if len(toRollback) == 0 {
|
||||
fmt.Println("[INFO] No migrations to rollback")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print what we're about to do
|
||||
if all {
|
||||
fmt.Printf("[INFO] Rolling back all %d migration(s):\n", len(toRollback))
|
||||
} else {
|
||||
fmt.Printf("[INFO] Rolling back %d migration(s):\n", len(toRollback))
|
||||
}
|
||||
for _, m := range toRollback {
|
||||
fmt.Printf(" 📋 %s (group %d)\n", m.Name, m.GroupID)
|
||||
}
|
||||
|
||||
// Create backup unless --no-backup flag is set
|
||||
if !cfg.Flags.MigrateNoBackup {
|
||||
fmt.Println("[INFO] Creating backup before rollback...")
|
||||
_, err := backup.CreateBackup(ctx, cfg, "rollback")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create backup")
|
||||
}
|
||||
|
||||
// Clean old backups
|
||||
if err := backup.CleanOldBackups(cfg, cfg.DB.BackupRetention); err != nil {
|
||||
fmt.Printf("[WARN] Failed to clean old backups: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[INFO] Skipping backup (--no-backup flag set)")
|
||||
}
|
||||
|
||||
// Acquire migration lock
|
||||
fmt.Println("[INFO] Acquiring migration lock...")
|
||||
if err := acquireMigrationLock(ctx, conn); err != nil {
|
||||
return errors.Wrap(err, "acquire migration lock")
|
||||
}
|
||||
defer releaseMigrationLock(ctx, conn)
|
||||
fmt.Println("[INFO] Migration lock acquired")
|
||||
|
||||
// Rollback
|
||||
fmt.Println("[INFO] Executing rollback...")
|
||||
rolledBack, err := executeDownMigrations(ctx, migrator, toRollback)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "execute rollback")
|
||||
}
|
||||
|
||||
fmt.Printf("[INFO] Successfully rolled back %d migration(s)\n", len(rolledBack))
|
||||
for _, migration := range rolledBack {
|
||||
fmt.Printf(" ↩️ %s\n", migration.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateStatus shows migration status
|
||||
func migrateStatus(ctx context.Context, migrator *migrate.Migrator) error {
|
||||
ms, err := migrator.MigrationsWithStatus(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get migration status")
|
||||
}
|
||||
|
||||
fmt.Println("╔══════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ DATABASE MIGRATION STATUS ║")
|
||||
fmt.Println("╚══════════════════════════════════════════════════════════╝")
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
_, _ = fmt.Fprintln(w, "STATUS\tMIGRATION\tGROUP\tCOMMENT")
|
||||
_, _ = fmt.Fprintln(w, "----------\t---------------\t-----\t---------------------------")
|
||||
|
||||
appliedCount := 0
|
||||
for _, m := range ms {
|
||||
status := "⏳ Pending"
|
||||
group := "-"
|
||||
|
||||
if m.GroupID > 0 {
|
||||
status = "✅ Applied"
|
||||
appliedCount++
|
||||
group = fmt.Sprint(m.GroupID)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", status, m.Name, group, m.Comment)
|
||||
}
|
||||
|
||||
_ = w.Flush()
|
||||
|
||||
fmt.Printf("\n📊 Summary: %d applied, %d pending\n\n",
|
||||
appliedCount, len(ms)-appliedCount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateDryRun shows what migrations would run without applying them
|
||||
func migrateDryRun(ctx context.Context, migrator *migrate.Migrator) error {
|
||||
group, err := migrator.Migrate(ctx, migrate.WithNopMigration())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dry-run")
|
||||
}
|
||||
|
||||
if group.IsZero() {
|
||||
fmt.Println("[INFO] No pending migrations")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("[INFO] Pending migrations (dry-run):")
|
||||
for _, migration := range group.Migrations {
|
||||
fmt.Printf(" 📋 %s\n", migration.Name)
|
||||
}
|
||||
fmt.Printf("[INFO] Would migrate to group %d\n", group.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMigrations ensures migrations compile before running
|
||||
func validateMigrations(ctx context.Context) error {
|
||||
cmd := exec.CommandContext(ctx, "go", "build",
|
||||
"-o", "/dev/null", "./cmd/oslstats/migrations")
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Migration validation failed!")
|
||||
fmt.Println(string(output))
|
||||
return errors.Wrap(err, "migration build failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// acquireMigrationLock prevents concurrent migrations using PostgreSQL advisory lock
|
||||
func acquireMigrationLock(ctx context.Context, conn *bun.DB) error {
|
||||
const lockID = 1234567890 // Arbitrary unique ID for migration lock
|
||||
const timeoutSeconds = 300 // 5 minutes
|
||||
|
||||
// Set statement timeout for this session
|
||||
_, err := conn.ExecContext(ctx,
|
||||
fmt.Sprintf("SET statement_timeout = '%ds'", timeoutSeconds))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "set timeout")
|
||||
}
|
||||
|
||||
var acquired bool
|
||||
err = conn.NewRaw("SELECT pg_try_advisory_lock(?)", lockID).
|
||||
Scan(ctx, &acquired)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "pg_try_advisory_lock")
|
||||
}
|
||||
|
||||
if !acquired {
|
||||
return errors.New("migration already in progress (could not acquire lock)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// releaseMigrationLock releases the migration lock
|
||||
func releaseMigrationLock(ctx context.Context, conn *bun.DB) {
|
||||
const lockID = 1234567890
|
||||
|
||||
_, err := conn.NewRaw("SELECT pg_advisory_unlock(?)", lockID).Exec(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("[WARN] Failed to release migration lock: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("[INFO] Migration lock released")
|
||||
}
|
||||
}
|
||||
|
||||
// createMigration generates a new migration file
|
||||
func createMigration(name string) error {
|
||||
if name == "" {
|
||||
return errors.New("migration name cannot be empty")
|
||||
}
|
||||
|
||||
// Sanitize name (replace spaces with underscores, lowercase)
|
||||
name = strings.ToLower(strings.ReplaceAll(name, " ", "_"))
|
||||
|
||||
// Generate timestamp
|
||||
timestamp := time.Now().Format("20060102150405")
|
||||
filename := fmt.Sprintf("cmd/oslstats/migrations/%s_%s.go", timestamp, name)
|
||||
|
||||
// Template
|
||||
template := `package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(
|
||||
// UP migration
|
||||
func(ctx context.Context, conn *bun.DB) error {
|
||||
// Add your migration code here
|
||||
return nil
|
||||
},
|
||||
// DOWN migration
|
||||
func(ctx context.Context, conn *bun.DB) error {
|
||||
// Add your rollback code here
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
// Write file
|
||||
if err := os.WriteFile(filename, []byte(template), 0o644); err != nil {
|
||||
return errors.Wrap(err, "write migration file")
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Created migration: %s\n", filename)
|
||||
fmt.Println("📝 Next steps:")
|
||||
fmt.Println(" 1. Edit the file and implement the UP and DOWN functions")
|
||||
fmt.Println(" 2. Run: just migrate up")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseMigrationCount parses a migration count string
|
||||
// Returns: (count, all, error)
|
||||
// - "" (empty) → (1, false, nil) - default to 1
|
||||
// - "all" → (0, true, nil) - special case for all
|
||||
// - "5" → (5, false, nil) - specific count
|
||||
// - "invalid" → (0, false, error)
|
||||
func parseMigrationCount(value string) (int, bool, error) {
|
||||
// Default to 1 if empty
|
||||
if value == "" {
|
||||
return 1, false, nil
|
||||
}
|
||||
|
||||
// Special case for "all"
|
||||
if value == "all" {
|
||||
return 0, true, nil
|
||||
}
|
||||
|
||||
// Parse as integer
|
||||
count, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return 0, false, errors.New("migration count must be a positive integer or 'all'")
|
||||
}
|
||||
if count < 1 {
|
||||
return 0, false, errors.New("migration count must be a positive integer (1 or greater)")
|
||||
}
|
||||
|
||||
return count, false, nil
|
||||
}
|
||||
|
||||
// selectMigrationsToApply returns the subset of unapplied migrations to run
|
||||
func selectMigrationsToApply(unapplied migrate.MigrationSlice, count int, all bool) migrate.MigrationSlice {
|
||||
if all {
|
||||
return unapplied
|
||||
}
|
||||
|
||||
count = min(count, len(unapplied))
|
||||
return unapplied[:count]
|
||||
}
|
||||
|
||||
// selectMigrationsToRollback returns the subset of applied migrations to rollback
|
||||
// Returns migrations in reverse chronological order (most recent first)
|
||||
func selectMigrationsToRollback(applied migrate.MigrationSlice, count int, all bool) migrate.MigrationSlice {
|
||||
if len(applied) == 0 || all {
|
||||
return applied
|
||||
}
|
||||
count = min(count, len(applied))
|
||||
return applied[:count]
|
||||
}
|
||||
|
||||
// executeUpMigrations executes a subset of UP migrations
|
||||
func executeUpMigrations(ctx context.Context, migrator *migrate.Migrator, migrations migrate.MigrationSlice) (*migrate.MigrationGroup, error) {
|
||||
if len(migrations) == 0 {
|
||||
return &migrate.MigrationGroup{}, nil
|
||||
}
|
||||
|
||||
// Get the next group ID
|
||||
ms, err := migrator.MigrationsWithStatus(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get migration status")
|
||||
}
|
||||
|
||||
lastGroup := ms.LastGroup()
|
||||
groupID := int64(1)
|
||||
if lastGroup.ID > 0 {
|
||||
groupID = lastGroup.ID + 1
|
||||
}
|
||||
|
||||
// Create the migration group
|
||||
group := &migrate.MigrationGroup{
|
||||
ID: groupID,
|
||||
Migrations: make(migrate.MigrationSlice, 0, len(migrations)),
|
||||
}
|
||||
|
||||
// Execute each migration
|
||||
for i := range migrations {
|
||||
migration := &migrations[i]
|
||||
migration.GroupID = groupID
|
||||
|
||||
// Mark as applied before execution (Bun's default behavior)
|
||||
if err := migrator.MarkApplied(ctx, migration); err != nil {
|
||||
return group, errors.Wrap(err, "mark applied")
|
||||
}
|
||||
|
||||
// Add to group
|
||||
group.Migrations = append(group.Migrations, *migration)
|
||||
|
||||
// Execute the UP function
|
||||
if migration.Up != nil {
|
||||
if err := migration.Up(ctx, migrator, migration); err != nil {
|
||||
return group, errors.Wrap(err, fmt.Sprintf("migration %s failed", migration.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return group, nil
|
||||
}
|
||||
|
||||
// executeDownMigrations executes a subset of DOWN migrations
|
||||
func executeDownMigrations(ctx context.Context, migrator *migrate.Migrator, migrations migrate.MigrationSlice) (migrate.MigrationSlice, error) {
|
||||
rolledBack := make(migrate.MigrationSlice, 0, len(migrations))
|
||||
|
||||
// Execute each migration in order (already reversed)
|
||||
for i := range migrations {
|
||||
migration := &migrations[i]
|
||||
|
||||
// Execute the DOWN function
|
||||
if migration.Down != nil {
|
||||
if err := migration.Down(ctx, migrator, migration); err != nil {
|
||||
return rolledBack, errors.Wrap(err, fmt.Sprintf("rollback %s failed", migration.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as unapplied after execution
|
||||
if err := migrator.MarkUnapplied(ctx, migration); err != nil {
|
||||
return rolledBack, errors.Wrap(err, "mark unapplied")
|
||||
}
|
||||
|
||||
rolledBack = append(rolledBack, *migration)
|
||||
}
|
||||
|
||||
return rolledBack, nil
|
||||
}
|
||||
|
||||
// resetDatabase drops and recreates all tables (destructive)
|
||||
func resetDatabase(ctx context.Context, cfg *config.Config) error {
|
||||
fmt.Println("⚠️ WARNING - This will DELETE ALL DATA in the database!")
|
||||
fmt.Print("Type 'yes' to continue: ")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read input")
|
||||
}
|
||||
|
||||
response = strings.TrimSpace(response)
|
||||
if response != "yes" {
|
||||
fmt.Println("❌ Reset cancelled")
|
||||
return nil
|
||||
}
|
||||
conn, close := setupBun(cfg)
|
||||
defer func() { _ = close() }()
|
||||
|
||||
models := registerDBModels(conn)
|
||||
|
||||
for _, model := range models {
|
||||
if err := conn.ResetModel(ctx, model); err != nil {
|
||||
return errors.Wrap(err, "reset model")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("✅ Database reset complete")
|
||||
return nil
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(
|
||||
// UP: Create initial tables (users, discord_tokens)
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Create users table
|
||||
_, err := dbConn.NewCreateTable().
|
||||
Model((*db.User)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create discord_tokens table
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.DiscordToken)(nil)).
|
||||
Exec(ctx)
|
||||
return err
|
||||
},
|
||||
// DOWN: Drop tables in reverse order
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Drop discord_tokens first (has foreign key to users)
|
||||
_, err := dbConn.NewDropTable().
|
||||
Model((*db.DiscordToken)(nil)).
|
||||
IfExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Drop users table
|
||||
_, err = dbConn.NewDropTable().
|
||||
Model((*db.User)(nil)).
|
||||
IfExists().
|
||||
Exec(ctx)
|
||||
return err
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(
|
||||
// UP migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
_, err := dbConn.NewCreateTable().
|
||||
Model((*db.Season)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// DOWN migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
_, err := dbConn.NewDropTable().
|
||||
Model((*db.Season)(nil)).
|
||||
IfExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(
|
||||
// UP migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
dbConn.RegisterModel((*db.RolePermission)(nil), (*db.UserRole)(nil))
|
||||
// Create permissions table
|
||||
_, err := dbConn.NewCreateTable().
|
||||
Model((*db.Role)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create permissions table
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.Permission)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes for permissions
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.Permission)(nil)).
|
||||
Index("idx_permissions_resource").
|
||||
Column("resource").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.Permission)(nil)).
|
||||
Index("idx_permissions_action").
|
||||
Column("action").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.RolePermission)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.ExecContext(ctx, `
|
||||
CREATE INDEX idx_role_permissions_role ON role_permissions(role_id)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.ExecContext(ctx, `
|
||||
CREATE INDEX idx_role_permissions_permission ON role_permissions(permission_id)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create user_roles table
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.UserRole)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes for user_roles
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.UserRole)(nil)).
|
||||
Index("idx_user_roles_user").
|
||||
Column("user_id").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.UserRole)(nil)).
|
||||
Index("idx_user_roles_role").
|
||||
Column("role_id").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create audit_log table
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.AuditLog)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes for audit_log
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.AuditLog)(nil)).
|
||||
Index("idx_audit_log_user").
|
||||
Column("user_id").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.AuditLog)(nil)).
|
||||
Index("idx_audit_log_action").
|
||||
Column("action").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.AuditLog)(nil)).
|
||||
Index("idx_audit_log_resource").
|
||||
Column("resource_type", "resource_id").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dbConn.NewCreateIndex().
|
||||
Model((*db.AuditLog)(nil)).
|
||||
Index("idx_audit_log_created").
|
||||
Column("created_at").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = seedSystemRBAC(ctx, dbConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
// DOWN migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Drop tables in reverse order
|
||||
// Use raw SQL to avoid relationship resolution issues
|
||||
tables := []string{
|
||||
"audit_log",
|
||||
"user_roles",
|
||||
"role_permissions",
|
||||
"permissions",
|
||||
"roles",
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
_, err := dbConn.ExecContext(ctx, "DROP TABLE IF EXISTS "+table+" CASCADE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func seedSystemRBAC(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Seed system roles
|
||||
now := time.Now().Unix()
|
||||
|
||||
adminRole := &db.Role{
|
||||
Name: "admin",
|
||||
DisplayName: "Administrator",
|
||||
Description: "Full system access with all permissions",
|
||||
IsSystem: true,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
_, err := dbConn.NewInsert().
|
||||
Model(adminRole).
|
||||
Returning("id").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dbConn.NewInsert")
|
||||
}
|
||||
|
||||
userRole := &db.Role{
|
||||
Name: "user",
|
||||
DisplayName: "User",
|
||||
Description: "Standard user with basic permissions",
|
||||
IsSystem: true,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
_, err = dbConn.NewInsert().
|
||||
Model(userRole).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dbConn.NewInsert")
|
||||
}
|
||||
|
||||
// Seed system permissions
|
||||
permissionsData := []*db.Permission{
|
||||
{Name: "*", DisplayName: "Wildcard (All Permissions)", Description: "Grants access to all permissions, past, present, and future", Resource: "*", Action: "*", IsSystem: true, CreatedAt: now},
|
||||
{Name: "seasons.create", DisplayName: "Create Seasons", Description: "Create new seasons", Resource: "seasons", Action: "create", IsSystem: true, CreatedAt: now},
|
||||
{Name: "seasons.update", DisplayName: "Update Seasons", Description: "Update existing seasons", Resource: "seasons", Action: "update", IsSystem: true, CreatedAt: now},
|
||||
{Name: "seasons.delete", DisplayName: "Delete Seasons", Description: "Delete seasons", Resource: "seasons", Action: "delete", IsSystem: true, CreatedAt: now},
|
||||
{Name: "users.update", DisplayName: "Update Users", Description: "Update user information", Resource: "users", Action: "update", IsSystem: true, CreatedAt: now},
|
||||
{Name: "users.ban", DisplayName: "Ban Users", Description: "Ban users from the system", Resource: "users", Action: "ban", IsSystem: true, CreatedAt: now},
|
||||
{Name: "users.manage_roles", DisplayName: "Manage User Roles", Description: "Assign and revoke user roles", Resource: "users", Action: "manage_roles", IsSystem: true, CreatedAt: now},
|
||||
}
|
||||
|
||||
_, err = dbConn.NewInsert().
|
||||
Model(&permissionsData).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dbConn.NewInsert")
|
||||
}
|
||||
|
||||
// Grant wildcard permission to admin role using Bun
|
||||
// First, get the IDs
|
||||
var wildcardPerm db.Permission
|
||||
err = dbConn.NewSelect().
|
||||
Model(&wildcardPerm).
|
||||
Where("name = ?", "*").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert role_permission mapping
|
||||
adminRolePerms := &db.RolePermission{
|
||||
RoleID: adminRole.ID,
|
||||
PermissionID: wildcardPerm.ID,
|
||||
}
|
||||
_, err = dbConn.NewInsert().
|
||||
Model(adminRolePerms).
|
||||
On("CONFLICT (role_id, permission_id) DO NOTHING").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dbConn.NewInsert")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(
|
||||
// UP migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Add slap_version column to seasons table
|
||||
_, err := dbConn.NewAddColumn().
|
||||
Model((*db.Season)(nil)).
|
||||
ColumnExpr("slap_version VARCHAR NOT NULL DEFAULT 'rebound'").
|
||||
IfNotExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create leagues table
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.League)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create season_leagues join table
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.SeasonLeague)(nil)).
|
||||
Exec(ctx)
|
||||
return err
|
||||
},
|
||||
// DOWN migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Drop season_leagues join table first
|
||||
_, err := dbConn.NewDropTable().
|
||||
Model((*db.SeasonLeague)(nil)).
|
||||
IfExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Drop leagues table
|
||||
_, err = dbConn.NewDropTable().
|
||||
Model((*db.League)(nil)).
|
||||
IfExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove slap_version column from seasons table
|
||||
_, err = dbConn.NewDropColumn().
|
||||
Model((*db.Season)(nil)).
|
||||
ColumnExpr("slap_version").
|
||||
Exec(ctx)
|
||||
return err
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(
|
||||
// UP migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Add your migration code here
|
||||
_, err := dbConn.NewCreateTable().
|
||||
Model((*db.Team)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = dbConn.NewCreateTable().
|
||||
Model((*db.TeamParticipation)(nil)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// DOWN migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Add your rollback code here
|
||||
_, err := dbConn.NewDropTable().
|
||||
Model((*db.TeamParticipation)(nil)).
|
||||
IfExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = dbConn.NewDropTable().
|
||||
Model((*db.Team)(nil)).
|
||||
IfExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(
|
||||
// UP migration
|
||||
func(ctx context.Context, conn *bun.DB) error {
|
||||
// Add your migration code here
|
||||
now := time.Now().Unix()
|
||||
permissionsData := []*db.Permission{
|
||||
{Name: "seasons.add_league", DisplayName: "Add Leagues to Season", Description: "Assign an existing league to Seasons", Resource: "seasons", Action: "add_league", IsSystem: true, CreatedAt: now},
|
||||
{Name: "seasons.remove_league", DisplayName: "Remove Leagues from a Season", Description: "Remove an assigned league league from Seasons", Resource: "seasons", Action: "remove_league", IsSystem: true, CreatedAt: now},
|
||||
{Name: "leagues.create", DisplayName: "Create Leagues", Description: "Create new leagues", Resource: "leagues", Action: "create", IsSystem: true, CreatedAt: now},
|
||||
{Name: "leagues.update", DisplayName: "Update Leagues", Description: "Update existing leagues", Resource: "leagues", Action: "update", IsSystem: true, CreatedAt: now},
|
||||
{Name: "leagues.delete", DisplayName: "Delete Leagues", Description: "Delete leagues", Resource: "leagues", Action: "delete", IsSystem: true, CreatedAt: now},
|
||||
{Name: "teams.create", DisplayName: "Create Teams", Description: "Create new teams", Resource: "teams", Action: "create", IsSystem: true, CreatedAt: now},
|
||||
{Name: "teams.update", DisplayName: "Update Teams", Description: "Update existing teams", Resource: "teams", Action: "update", IsSystem: true, CreatedAt: now},
|
||||
{Name: "teams.delete", DisplayName: "Delete Teams", Description: "Delete teams", Resource: "teams", Action: "delete", IsSystem: true, CreatedAt: now},
|
||||
{Name: "teams.add_to_league", DisplayName: "Add Teams to League", Description: "Add an existing team to a league/season", Resource: "teams", Action: "add_to_league", IsSystem: true, CreatedAt: now},
|
||||
}
|
||||
|
||||
_, err := conn.NewInsert().
|
||||
Model(&permissionsData).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dbConn.NewInsert")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
// DOWN migration
|
||||
func(ctx context.Context, dbConn *bun.DB) error {
|
||||
// Add your rollback code here
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Package migrations defines the database migrations to apply when using the migrate tags
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
// Migrations is the collection of all database migrations
|
||||
var Migrations = migrate.NewMigrations()
|
||||
@@ -1,329 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/auditlog"
|
||||
"git.haelnorr.com/h/oslstats/internal/config"
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"git.haelnorr.com/h/oslstats/internal/discord"
|
||||
"git.haelnorr.com/h/oslstats/internal/handlers"
|
||||
"git.haelnorr.com/h/oslstats/internal/permissions"
|
||||
"git.haelnorr.com/h/oslstats/internal/rbac"
|
||||
"git.haelnorr.com/h/oslstats/internal/store"
|
||||
)
|
||||
|
||||
func addRoutes(
|
||||
s *hws.Server,
|
||||
staticFS *http.FileSystem,
|
||||
cfg *config.Config,
|
||||
conn *bun.DB,
|
||||
auth *hwsauth.Authenticator[*db.User, bun.Tx],
|
||||
store *store.Store,
|
||||
discordAPI *discord.APIClient,
|
||||
perms *rbac.Checker,
|
||||
audit *auditlog.Logger,
|
||||
) error {
|
||||
// Create the routes
|
||||
baseRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/static/",
|
||||
Method: hws.MethodGET,
|
||||
Handler: http.StripPrefix("/static/", handlers.StaticFS(staticFS, s)),
|
||||
},
|
||||
{
|
||||
Path: "/",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handlers.Index(s),
|
||||
},
|
||||
}
|
||||
|
||||
authRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/login",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST},
|
||||
Handler: auth.LogoutReq(handlers.Login(s, conn, cfg, store, discordAPI)),
|
||||
},
|
||||
{
|
||||
Path: "/auth/callback",
|
||||
Method: hws.MethodGET,
|
||||
Handler: auth.LogoutReq(handlers.Callback(s, auth, conn, cfg, store, discordAPI)),
|
||||
},
|
||||
{
|
||||
Path: "/register",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST},
|
||||
Handler: auth.LogoutReq(handlers.Register(s, auth, conn, cfg, store)),
|
||||
},
|
||||
{
|
||||
Path: "/logout",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST},
|
||||
Handler: auth.LoginReq(handlers.Logout(s, auth, conn, discordAPI)),
|
||||
},
|
||||
}
|
||||
|
||||
seasonRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/seasons",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handlers.SeasonsPage(s, conn),
|
||||
},
|
||||
{
|
||||
Path: "/seasons",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.SeasonsList(s, conn),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/new",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequirePermission(s, permissions.SeasonsCreate)(handlers.NewSeason(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/new",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequirePermission(s, permissions.SeasonsCreate)(handlers.NewSeasonSubmit(s, conn, audit)),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/{season_short_name}",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handlers.SeasonPage(s, conn),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/{season_short_name}/edit",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequirePermission(s, permissions.SeasonsUpdate)(handlers.SeasonEditPage(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/{season_short_name}/edit",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequirePermission(s, permissions.SeasonsUpdate)(handlers.SeasonEditSubmit(s, conn, audit)),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/{season_short_name}/leagues/{league_short_name}",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handlers.SeasonLeaguePage(s, conn),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/{season_short_name}/leagues/add/{league_short_name}",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequirePermission(s, permissions.SeasonsAddLeague)(handlers.SeasonAddLeague(s, conn, audit)),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/{season_short_name}/leagues/{league_short_name}",
|
||||
Method: hws.MethodDELETE,
|
||||
Handler: perms.RequirePermission(s, permissions.SeasonsRemoveLeague)(handlers.SeasonRemoveLeague(s, conn, audit)),
|
||||
},
|
||||
{
|
||||
Path: "/seasons/{season_short_name}/leagues/{league_short_name}/teams/add",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequirePermission(s, permissions.TeamsAddToLeague)(handlers.SeasonLeagueAddTeam(s, conn, audit)),
|
||||
},
|
||||
}
|
||||
|
||||
leagueRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/leagues",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handlers.LeaguesList(s, conn),
|
||||
},
|
||||
{
|
||||
Path: "/leagues/new",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequirePermission(s, permissions.LeaguesCreate)(handlers.NewLeague(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/leagues/new",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequirePermission(s, permissions.LeaguesCreate)(handlers.NewLeagueSubmit(s, conn, audit)),
|
||||
},
|
||||
}
|
||||
|
||||
teamRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/teams",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handlers.TeamsPage(s, conn),
|
||||
},
|
||||
{
|
||||
Path: "/teams",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.TeamsList(s, conn),
|
||||
},
|
||||
{
|
||||
Path: "/teams/new",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequirePermission(s, permissions.TeamsCreate)(handlers.NewTeamPage(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/teams/new",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequirePermission(s, permissions.TeamsCreate)(handlers.NewTeamSubmit(s, conn, audit)),
|
||||
},
|
||||
}
|
||||
|
||||
htmxRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/htmx/isusernameunique",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.IsUnique(s, conn, (*db.User)(nil), "username"),
|
||||
},
|
||||
{
|
||||
Path: "/htmx/isseasonnameunique",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.IsUnique(s, conn, (*db.Season)(nil), "name"),
|
||||
},
|
||||
{
|
||||
Path: "/htmx/isseasonshortnameunique",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.IsUnique(s, conn, (*db.Season)(nil), "short_name"),
|
||||
},
|
||||
{
|
||||
Path: "/htmx/isleaguenameunique",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.IsUnique(s, conn, (*db.League)(nil), "name"),
|
||||
},
|
||||
{
|
||||
Path: "/htmx/isleagueshortnameunique",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.IsUnique(s, conn, (*db.League)(nil), "short_name"),
|
||||
},
|
||||
{
|
||||
Path: "/htmx/isteamnameunique",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.IsUnique(s, conn, (*db.Team)(nil), "name"),
|
||||
},
|
||||
{
|
||||
Path: "/htmx/isteamshortnamesunique",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handlers.IsTeamShortNamesUnique(s, conn),
|
||||
},
|
||||
}
|
||||
|
||||
wsRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/ws/notifications",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handlers.NotificationWS(s, cfg),
|
||||
},
|
||||
}
|
||||
|
||||
// Admin routes
|
||||
adminRoutes := []hws.Route{
|
||||
{
|
||||
Path: "/notification-tester",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST},
|
||||
Handler: perms.RequireAdmin(s)(handlers.NotifyTester(s)),
|
||||
},
|
||||
// Full page routes (for direct navigation and refreshes)
|
||||
{
|
||||
Path: "/admin",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminDashboard(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/users",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST},
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminUsersPage(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST},
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRoles(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/permissions",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminPermissionsPage(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/audit",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminAuditLogsPage(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/permissions",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminPermissionsList(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/audit",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminAuditLogsList(s, conn)),
|
||||
},
|
||||
// Role management routes
|
||||
{
|
||||
Path: "/admin/roles/create",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRoleCreateForm(s)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/create",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRoleCreate(s, conn, audit)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/{id}/manage",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRoleManage(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/{id}",
|
||||
Method: hws.MethodDELETE,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRoleDelete(s, conn, audit)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/{id}/delete-confirm",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRoleDeleteConfirm(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/{id}/permissions",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRolePermissionsModal(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/{id}/permissions",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminRolePermissionsUpdate(s, conn, audit)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/{id}/preview-start",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminPreviewRoleStart(s, conn, cfg)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/roles/preview-stop",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequireActualAdmin(s)(handlers.AdminPreviewRoleStop(s)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/audit/filter",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminAuditLogsFilter(s, conn)),
|
||||
},
|
||||
{
|
||||
Path: "/admin/audit/{id}",
|
||||
Method: hws.MethodGET,
|
||||
Handler: perms.RequireAdmin(s)(handlers.AdminAuditLogDetail(s, conn)),
|
||||
},
|
||||
}
|
||||
|
||||
routes := append(baseRoutes, htmxRoutes...)
|
||||
routes = append(routes, wsRoutes...)
|
||||
routes = append(routes, authRoutes...)
|
||||
routes = append(routes, adminRoutes...)
|
||||
routes = append(routes, seasonRoutes...)
|
||||
routes = append(routes, leagueRoutes...)
|
||||
routes = append(routes, teamRoutes...)
|
||||
|
||||
// Register the routes with the server
|
||||
err := s.AddRoutes(routes...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "server.AddRoutes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"git.haelnorr.com/h/oslstats/internal/config"
|
||||
"git.haelnorr.com/h/oslstats/internal/db"
|
||||
"git.haelnorr.com/h/oslstats/internal/discord"
|
||||
"git.haelnorr.com/h/oslstats/internal/embedfs"
|
||||
"git.haelnorr.com/h/oslstats/internal/server"
|
||||
"git.haelnorr.com/h/oslstats/internal/store"
|
||||
)
|
||||
|
||||
@@ -25,8 +27,7 @@ func run(ctx context.Context, logger *hlog.Logger, cfg *config.Config) error {
|
||||
// Setup the database connection
|
||||
logger.Debug().Msg("Config loaded and logger started")
|
||||
logger.Debug().Msg("Connecting to database")
|
||||
bun, closedb := setupBun(cfg)
|
||||
// registerDBModels(bun)
|
||||
conn := db.NewDB(cfg.DB)
|
||||
|
||||
// Setup embedded files
|
||||
logger.Debug().Msg("Getting embedded files")
|
||||
@@ -47,7 +48,7 @@ func run(ctx context.Context, logger *hlog.Logger, cfg *config.Config) error {
|
||||
}
|
||||
|
||||
logger.Debug().Msg("Setting up HTTP server")
|
||||
httpServer, err := setupHTTPServer(&staticFS, cfg, logger, bun, store, discordAPI)
|
||||
httpServer, err := server.Setup(staticFS, cfg, logger, conn, store, discordAPI)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setupHttpServer")
|
||||
}
|
||||
@@ -71,7 +72,7 @@ func run(ctx context.Context, logger *hlog.Logger, cfg *config.Config) error {
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Str("stacktrace", fmt.Sprintf("%+v", errors.Wrap(err, "httpServer.Shutdown"))).Msg("Error during HTTP server shutdown")
|
||||
}
|
||||
err = closedb()
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Str("stacktrace", fmt.Sprintf("%+v", errors.Wrap(err, "closedb"))).Msg("Error during database close")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user