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 { conn.RegisterModel((*db.RolePermission)(nil), (*db.UserRole)(nil)) // Create permissions table _, err := conn.NewCreateTable(). Model((*db.Role)(nil)). Exec(ctx) if err != nil { return err } // Create permissions table _, err = conn.NewCreateTable(). Model((*db.Permission)(nil)). Exec(ctx) if err != nil { return err } // Create indexes for permissions _, err = conn.NewCreateIndex(). Model((*db.Permission)(nil)). Index("idx_permissions_resource"). Column("resource"). Exec(ctx) if err != nil { return err } _, err = conn.NewCreateIndex(). Model((*db.Permission)(nil)). Index("idx_permissions_action"). Column("action"). Exec(ctx) if err != nil { return err } _, err = conn.NewCreateTable(). Model((*db.RolePermission)(nil)). Exec(ctx) if err != nil { return err } _, err = conn.ExecContext(ctx, ` CREATE INDEX idx_role_permissions_role ON role_permissions(role_id) `) if err != nil { return err } _, err = conn.ExecContext(ctx, ` CREATE INDEX idx_role_permissions_permission ON role_permissions(permission_id) `) if err != nil { return err } // Create user_roles table _, err = conn.NewCreateTable(). Model((*db.UserRole)(nil)). Exec(ctx) if err != nil { return err } // Create indexes for user_roles _, err = conn.NewCreateIndex(). Model((*db.UserRole)(nil)). Index("idx_user_roles_user"). Column("user_id"). Exec(ctx) if err != nil { return err } _, err = conn.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 = conn.NewCreateTable(). Model((*db.AuditLog)(nil)). Exec(ctx) if err != nil { return err } // Create indexes for audit_log _, err = conn.NewCreateIndex(). Model((*db.AuditLog)(nil)). Index("idx_audit_log_user"). Column("user_id"). Exec(ctx) if err != nil { return err } _, err = conn.NewCreateIndex(). Model((*db.AuditLog)(nil)). Index("idx_audit_log_action"). Column("action"). Exec(ctx) if err != nil { return err } _, err = conn.NewCreateIndex(). Model((*db.AuditLog)(nil)). Index("idx_audit_log_resource"). Column("resource_type", "resource_id"). Exec(ctx) if err != nil { return err } _, err = conn.NewCreateIndex(). Model((*db.AuditLog)(nil)). Index("idx_audit_log_created"). Column("created_at"). Exec(ctx) if err != nil { return err } err = seedSystemRBAC(ctx, conn) 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, conn *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 := conn.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 = conn.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 = conn.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 = conn.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 = conn.NewInsert(). Model(adminRolePerms). On("CONFLICT (role_id, permission_id) DO NOTHING"). Exec(ctx) if err != nil { return errors.Wrap(err, "dbConn.NewInsert") } return nil }