feat(19-04): add users.handle, HandleGenerator and user.Created
Generate a unique mention handle on API register and admin create, and fire user.Created from both paths so listeners run in the same write. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -649,6 +649,7 @@ func Register(app *backpack.App) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
_ = FireRegisterEvent(r.Context(), app, user, fields)
|
||||
_ = FireCreatedEvent(r.Context(), app, user)
|
||||
ctx := r.Context()
|
||||
if mode == "user" {
|
||||
vars := mailVars(displayName(user), "", "")
|
||||
|
||||
@@ -77,6 +77,9 @@ func RegisterUser(ctx context.Context, app *backpack.App, db *gorm.DB, fields ma
|
||||
if err := db.WithContext(ctx).Create(user).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := classes.AssignHandle(ctx, db, user); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return user, nil, nil
|
||||
}
|
||||
|
||||
@@ -97,6 +100,16 @@ func FireRegisterEvent(ctx context.Context, app *backpack.App, user *models.User
|
||||
return app.Events.Fire(ctx, &classes.RegisterEvent{User: user, Payload: payload})
|
||||
}
|
||||
|
||||
// FireCreatedEvent fires classes.CreatedEvent (user.Created) for user and
|
||||
// returns the listeners' error. /register ignores that error, as it does
|
||||
// for FireRegisterEvent.
|
||||
func FireCreatedEvent(ctx context.Context, app *backpack.App, user *models.User) error {
|
||||
if app == nil || app.Events == nil {
|
||||
return nil
|
||||
}
|
||||
return app.Events.Fire(ctx, &classes.CreatedEvent{User: user})
|
||||
}
|
||||
|
||||
// IssueToken mints the session JWT for user exactly as /register and
|
||||
// /login do: the plugin's JWT secret, golem15.user.jwt.ttl and the request
|
||||
// URL as the issuer.
|
||||
|
||||
@@ -302,23 +302,33 @@ func (c usersAdminController) storePassword(ctx context.Context, user *models.Us
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// FormAfterCreate sends the invitation when the send_invite checkbox was
|
||||
// set: an activation code is issued inside the create's transaction, and
|
||||
// exactly one golem15.user::mail.invite message with the activation link
|
||||
// leaves after the commit. A create that rolls back sends nothing.
|
||||
// FormAfterCreate assigns a unique handle, fires user.Created
|
||||
// (CreatedEventName) inside the create transaction, and sends the
|
||||
// invitation when the send_invite checkbox was set: an activation code is
|
||||
// issued inside the same transaction, and exactly one
|
||||
// golem15.user::mail.invite message with the activation link leaves after
|
||||
// the commit. A create that rolls back sends nothing.
|
||||
func (c usersAdminController) FormAfterCreate(ctx context.Context, model any) error {
|
||||
user, ok := model.(*models.User)
|
||||
if !ok || user == nil {
|
||||
return errors.New("users admin: form model is not a user")
|
||||
}
|
||||
values, _ := cabana.VirtualFieldsFromContext(ctx)
|
||||
if invite, _ := values["send_invite"].(bool); !invite {
|
||||
return nil
|
||||
}
|
||||
tx, ok := cabana.TxFromContext(ctx)
|
||||
if !ok {
|
||||
return errors.New("users admin: a create needs its transaction")
|
||||
}
|
||||
if err := classes.AssignHandle(ctx, tx.Session(&gorm.Session{NewDB: true}), user); err != nil {
|
||||
return err
|
||||
}
|
||||
if app := c.application(); app != nil && app.Events != nil {
|
||||
if err := app.Events.Fire(ctx, &classes.CreatedEvent{User: user, Tx: tx}); err != nil {
|
||||
return fmt.Errorf("%s: %w", classes.CreatedEventName, err)
|
||||
}
|
||||
}
|
||||
values, _ := cabana.VirtualFieldsFromContext(ctx)
|
||||
if invite, _ := values["send_invite"].(bool); !invite {
|
||||
return nil
|
||||
}
|
||||
code, err := classes.IssueActivationCode(ctx, tx.Session(&gorm.Session{NewDB: true}), user)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user