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:
@@ -99,15 +99,32 @@ type AdminRoleSummary struct {
|
||||
|
||||
// AdminProfile is the GET /auth/me payload.
|
||||
type AdminProfile struct {
|
||||
ID uint `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
ID uint `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
// Subtitle is backend_users.metadata.subtitle; "" when not set.
|
||||
Subtitle string `json:"subtitle"`
|
||||
IsSuperuser bool `json:"is_superuser"`
|
||||
Role *AdminRoleSummary `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
// AdminAccountRequest is the body of PUT /account. It documents the only keys
|
||||
// the route accepts: any other key is a 422 on that key and nothing is
|
||||
// written. PUT replaces the profile, so an absent first_name, last_name or
|
||||
// subtitle is saved empty. The password keys change the password only when
|
||||
// password is not empty, and then current_password must match.
|
||||
type AdminAccountRequest struct {
|
||||
Email string `json:"email"`
|
||||
FirstName *string `json:"first_name,omitempty"`
|
||||
LastName *string `json:"last_name,omitempty"`
|
||||
Subtitle *string `json:"subtitle,omitempty"`
|
||||
CurrentPassword string `json:"current_password,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
PasswordConfirmation string `json:"password_confirmation,omitempty"`
|
||||
}
|
||||
|
||||
// AdminLogin documents POST /auth/login.
|
||||
//
|
||||
// @Summary Admin login
|
||||
@@ -164,6 +181,7 @@ func AdminLogout() {}
|
||||
// AdminMe documents GET /auth/me.
|
||||
//
|
||||
// @Summary Current admin
|
||||
// @Description The signed-in admin's profile: login, email, names, the subtitle (backend_users.metadata.subtitle, "" when not set), the superuser flag and the role.
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security BackendBearer
|
||||
@@ -174,6 +192,23 @@ func AdminLogout() {}
|
||||
// @Router /auth/me [get]
|
||||
func AdminMe() {}
|
||||
|
||||
// AdminAccountUpdate documents PUT /account.
|
||||
//
|
||||
// @Summary Update my account
|
||||
// @Description Saves the signed-in admin's own first name, last name, email and subtitle; the row is the token subject, never a request value. Only the AdminAccountRequest keys are accepted: any other key (login, role_id, is_superuser, permissions, is_activated, id) is a 422 on that key and nothing is written. The email must be valid, 6 to 255 characters, and not equal (ignoring case) the email or login of another admin. A non-empty password needs current_password, at least 4 characters, at most 72 bytes and an equal password_confirmation; it ends every other session, and a cookie session receives a fresh cookie.
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BackendBearer
|
||||
// @Param body body AdminAccountRequest true "Profile and optional password change"
|
||||
// @Success 200 {object} Envelope[AdminProfile]
|
||||
// @Failure 401 {object} ErrorEnvelope
|
||||
// @Failure 403 {object} ErrorEnvelope
|
||||
// @Failure 413 {object} ErrorEnvelope
|
||||
// @Failure 422 {object} ErrorEnvelope
|
||||
// @Router /account [put]
|
||||
func AdminAccountUpdate() {}
|
||||
|
||||
// AdminNavigation documents GET /navigation.
|
||||
//
|
||||
// @Summary Admin navigation
|
||||
|
||||
Reference in New Issue
Block a user