// Adds the two POS roles to app_roles. // // `app_roles` has no sequence on roleid — every id in it was assigned by hand — // so 7 and 8 are written explicitly and must match models.PosRoleSupervisor and // models.PosRoleCashier. // // configid is left NULL deliberately. Every other row is portal-specific, which // is why Admin appears twice (3 and 5) and Manager twice (4 and 6). A till is a // till whichever portal a tenant uses, and duplicating these per config would // be one more thing to remember on every onboarding. // // go run ./scratch/posroles plan // go run ./scratch/posroles apply package main import ( "fmt" "log" "os" "nearle/models" "github.com/joho/godotenv" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) func main() { mode := "plan" if len(os.Args) > 1 { mode = os.Args[1] } _ = godotenv.Load() dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME")) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) if err != nil { log.Fatal(err) } wanted := []struct { id int name string }{ {models.PosRoleSupervisor, "Supervisor"}, {models.PosRoleCashier, "Cashier"}, } write := mode == "apply" changes := 0 for _, w := range wanted { var existing string db.Raw(`SELECT COALESCE(rolename,'') FROM app_roles WHERE roleid = ?`, w.id).Scan(&existing) switch { case existing == w.name: fmt.Printf(" %-4d %-12s already present\n", w.id, w.name) case existing != "": // Refuses rather than overwrites. Renaming a role that something // else already points at would silently re-permission real accounts. fmt.Printf(" %-4d OCCUPIED by %q — refusing to overwrite\n", w.id, existing) default: fmt.Printf(" %-4d %-12s WOULD INSERT\n", w.id, w.name) changes++ if write { if err := db.Exec( `INSERT INTO app_roles (roleid, rolename, configid) VALUES (?, ?, NULL)`, w.id, w.name, ).Error; err != nil { log.Fatalf("inserting role %d: %v", w.id, err) } } } } fmt.Println() if write { fmt.Printf("APPLIED %d role(s).\n", changes) } else { fmt.Printf("%d role(s) would be added. Nothing written — run `apply`.\n", changes) } var rows []struct { Roleid int Rolename string } db.Raw(`SELECT roleid, COALESCE(rolename,'') AS rolename FROM app_roles ORDER BY roleid`).Scan(&rows) fmt.Println("\napp_roles now:") for _, r := range rows { fmt.Printf(" %-4d %s\n", r.Roleid, r.Rolename) } if len(rows) > 0 { fmt.Println("\n-- undo:") fmt.Printf("DELETE FROM app_roles WHERE roleid IN (%d, %d);\n", models.PosRoleSupervisor, models.PosRoleCashier) } }