feat(cabana): add the My account page with subtitle and password change
- lagoon: 202610090001_backend_users_metadata adds WinterCMS's nullable jsonable backend_users.metadata column (TEXT); rollback drops only it - cabana.BackendUser.Metadata and Subtitle read metadata.subtitle leniently (NULL, blank, null, [], invalid JSON and non-strings read as empty) - GET /auth/me returns subtitle; PUT /account edits the signed-in admin's own names, email and subtitle, and the password with current_password; any other key is a 422 and nothing is written - email: required, valid, 6-255 chars, unique against other admins' email and login ignoring case (soft-deleted included); a unique-index race is 422 - a password change sets tokens_valid_after, revokes the presented token and gives a cookie session a fresh cookie - swag annotation, regenerated admin.json and schema.d.ts; route inventories, CSRF walk (27) and OpenAPI conformance learn the route - SPA: My account item in the user menu opens /account (AccountView), which saves through PUT /account and updates the top bar via setCurrentUser - backend::lang.myaccount strings (en, pl), rebuilt boardwalk dist - cabana and lagoon READMEs, users-and-permissions and admin-spa docs
This commit is contained in:
@@ -124,7 +124,7 @@ func (p *Plugin) Migrations() []*gormigrate.Migration {
|
||||
| `lagoon.RollbackLast` | Rolls back the last migration of one plugin. |
|
||||
| `lagoon.Status` | Lists applied migration IDs per plugin as `lagoon.StatusRow` values. |
|
||||
| `lagoon.HistoryTableName` | Returns the gormigrate history table for a plugin ID. |
|
||||
| `lagoon.BackendAdminMigrations` | Creates the backend user, role and admin token blacklist tables, seeds the system roles and adds a case-insensitive unique index on `backend_users.email`. The index migration refuses to run while emails differ only in case and names the clashing logins. |
|
||||
| `lagoon.BackendAdminMigrations` | Creates the backend user, role and admin token blacklist tables, seeds the system roles, adds a case-insensitive unique index on `backend_users.email` and adds WinterCMS's nullable jsonable `backend_users.metadata` column (TEXT; the admin subtitle lives under its `subtitle` key). The index migration refuses to run while emails differ only in case and names the clashing logins. Rolling back the metadata migration drops only that column. |
|
||||
| `lagoon.QueueMigrations` | Migrates River's schema to `lagoon.RiverSchemaVersion` and creates the `summer_jobs` job record table. |
|
||||
| `lagoon.QueueHistoryID` | History id of the job-queue set, `summercms.conga`. |
|
||||
| `lagoon.JobsTable` | Name of the job record table, `summer_jobs`. |
|
||||
|
||||
@@ -13,8 +13,9 @@ import (
|
||||
const BackendJWTBlacklistTable = "backend_jwt_blacklist"
|
||||
|
||||
// BackendAdminMigrations creates Winter-shaped backend identity tables, seeds
|
||||
// the developer and publisher system roles and makes backend user emails
|
||||
// unique case-insensitively. History is isolated under the summercms.cabana
|
||||
// the developer and publisher system roles, makes backend user emails
|
||||
// unique case-insensitively and adds WinterCMS's nullable jsonable metadata
|
||||
// column to backend_users (the admin subtitle lives there). History is isolated under the summercms.cabana
|
||||
// plugin id. DDL is re-runnable so the system-role seed stays idempotent if
|
||||
// the history row is removed.
|
||||
var BackendAdminMigrations = []*gormigrate.Migration{
|
||||
@@ -117,4 +118,16 @@ FROM backend_users GROUP BY lower(email) HAVING count(*) > 1 ORDER BY 1`).Scan(&
|
||||
return tx.Exec(`DROP INDEX IF EXISTS backend_users_email_lower_unique`).Error
|
||||
},
|
||||
},
|
||||
{
|
||||
// WinterCMS 2023_02_16_000012_Db_Backend_Add_User_Metadata: a
|
||||
// nullable jsonable column. It is TEXT, not JSONB, so a copied value
|
||||
// such as [] or a blank string loads unchanged.
|
||||
ID: "202610090001_backend_users_metadata",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.Exec(`ALTER TABLE backend_users ADD COLUMN IF NOT EXISTS metadata TEXT`).Error
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return tx.Exec(`ALTER TABLE backend_users DROP COLUMN IF EXISTS metadata`).Error
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestBackendAdminMigration(t *testing.T) {
|
||||
"password": false, "activation_code": true, "persist_code": true, "reset_password_code": true,
|
||||
"permissions": true, "is_activated": false, "is_superuser": false, "role_id": true,
|
||||
"activated_at": true, "last_login": true, "created_at": false, "updated_at": false,
|
||||
"deleted_at": true, "tokens_valid_after": true,
|
||||
"deleted_at": true, "tokens_valid_after": true, "metadata": true,
|
||||
}
|
||||
assertColumns(t, "backend_users", users, wantUsers)
|
||||
roles := columnNullability(t, gdb, "backend_user_roles")
|
||||
@@ -481,7 +481,9 @@ func TestBackendAdminEmailIndexRefusesCaseDuplicates(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m.RollbackLast(); err != nil {
|
||||
// Roll back to the identity tables, which drops the email index and the
|
||||
// later metadata column.
|
||||
if err := m.RollbackTo("202609240001_backend_admin_identity"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A Winter copy can hold emails that differ only in case.
|
||||
|
||||
Reference in New Issue
Block a user