feat(03-03): add typed path params and per-plugin rollback
Compile regex and enum constraints at route registration so malformed and unknown IDs share a 404, and named rollback errors isolate one plugin's history. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package lagoon
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/bonfire"
|
||||
@@ -61,7 +62,7 @@ func RuntimeCommands(app *backpack.App, plugins []party.Plugin) []bonfire.Comman
|
||||
for _, row := range rows {
|
||||
ids := "(none)"
|
||||
if len(row.IDs) > 0 {
|
||||
ids = fmt.Sprintf("%d: %s", len(row.IDs), row.IDs[len(row.IDs)-1])
|
||||
ids = strings.Join(row.IDs, ", ")
|
||||
}
|
||||
tableRows = append(tableRows, []string{row.Plugin, row.Table, ids})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package lagoon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -13,6 +14,13 @@ import (
|
||||
|
||||
const historyTablePrefix = "summer_migrations_"
|
||||
|
||||
var (
|
||||
// ErrUnknownPlugin is returned when migrate:rollback names a plugin that is not activated.
|
||||
ErrUnknownPlugin = errors.New("lagoon: plugin is not activated")
|
||||
// ErrNoMigrations is returned when there is no plugin migration set to roll back.
|
||||
ErrNoMigrations = errors.New("lagoon: no plugin migrations to roll back")
|
||||
)
|
||||
|
||||
// HistoryTableName returns the isolated gormigrate table for pluginID.
|
||||
func HistoryTableName(pluginID string) (string, error) {
|
||||
if err := validatePluginID(pluginID); err != nil {
|
||||
@@ -83,26 +91,30 @@ func RollbackLast(gdb *gorm.DB, plugins []party.Plugin, pluginID string) error {
|
||||
pluginID = lastMigrationPlugin(plugins)
|
||||
}
|
||||
if pluginID == "" {
|
||||
return fmt.Errorf("lagoon: no plugin migrations to roll back")
|
||||
return ErrNoMigrations
|
||||
}
|
||||
var target party.Plugin
|
||||
for _, p := range plugins {
|
||||
if p.ID() != pluginID {
|
||||
continue
|
||||
if p.ID() == pluginID {
|
||||
target = p
|
||||
break
|
||||
}
|
||||
hm, ok := p.(pact.HasMigrations)
|
||||
if !ok {
|
||||
return fmt.Errorf("lagoon: plugin %q has no migrations", pluginID)
|
||||
}
|
||||
m, err := migrator(gdb, p.ID(), hm.Migrations())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.RollbackLast(); err != nil {
|
||||
return fmt.Errorf("lagoon: rollback %s: %w", pluginID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("lagoon: plugin %q is not activated", pluginID)
|
||||
if target == nil {
|
||||
return fmt.Errorf("%w: %q", ErrUnknownPlugin, pluginID)
|
||||
}
|
||||
hm, ok := target.(pact.HasMigrations)
|
||||
if !ok || len(hm.Migrations()) == 0 {
|
||||
return fmt.Errorf("%w: %q", ErrNoMigrations, pluginID)
|
||||
}
|
||||
m, err := migrator(gdb, target.ID(), hm.Migrations())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.RollbackLast(); err != nil {
|
||||
return fmt.Errorf("lagoon: rollback %s: %w", pluginID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lastMigrationPlugin(plugins []party.Plugin) string {
|
||||
|
||||
@@ -2,12 +2,16 @@ package lagoon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/bonfire"
|
||||
"git.golem15.com/golem15/summercms/party"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestHistoryTableName(t *testing.T) {
|
||||
@@ -95,3 +99,27 @@ func TestOpenRequiresDSN(t *testing.T) {
|
||||
t.Fatalf("got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type stubPlugin struct{ id string }
|
||||
|
||||
func (s stubPlugin) ID() string { return s.id }
|
||||
func (s stubPlugin) Requires() []string { return nil }
|
||||
func (s stubPlugin) Register(*backpack.App) error { return nil }
|
||||
func (s stubPlugin) Boot(*backpack.App) error { return nil }
|
||||
|
||||
func TestRollbackUnknownPluginIsNamedError(t *testing.T) {
|
||||
err := RollbackLast(&gorm.DB{}, []party.Plugin{stubPlugin{id: "golem15.user"}}, "golem15.missing")
|
||||
if !errors.Is(err, ErrUnknownPlugin) {
|
||||
t.Fatalf("got %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "golem15.missing") {
|
||||
t.Fatalf("error should name plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollbackMissingPluginIsNamedError(t *testing.T) {
|
||||
err := RollbackLast(&gorm.DB{}, nil, "")
|
||||
if !errors.Is(err, ErrNoMigrations) {
|
||||
t.Fatalf("got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user