package classes import ( "context" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/lagoon" "git.golem15.com/golem15/summercms/modules/pact" "gorm.io/gorm" ) // Guard names the plugin's pact.MCPSubjectChanged events carry: the frontend // user guard this plugin provides and the admin's guard (for the backend // side of an account link). const ( SubjectGuardUser = "user" SubjectGuardBackend = "backend" ) // FireSubjectEvent fires ev through the application's event bus at once, so // the MCP endpoint re-lists the subject's tools or ends its sessions. A // revoked event may carry TokenID (only the sessions one personal token // opened end) or ClientID with UserID (only that OAuth client's sessions // end). A missing application or bus, a zero UserID or an empty Kind is a // no-op; a listener's error is logged and never returned, so the write that // caused the event never fails because of it (every MCP request re-checks // anyway). func FireSubjectEvent(ctx context.Context, app *backpack.App, ev pact.MCPSubjectChanged) { if app == nil || app.Events == nil || ev.UserID == 0 || ev.Kind == "" { return } if ctx == nil { ctx = context.Background() } event := ev if err := app.Events.Fire(ctx, &event); err != nil { pluginLog.WarnContext(ctx, "golem15.user: subject changed listener failed", "guard", ev.Guard, "user_id", ev.UserID, "kind", ev.Kind, "token_id", ev.TokenID, "client_id", ev.ClientID, "err", err) } } // FireSubjectEventAfterCommit is FireSubjectEvent once the write // transaction tx commits (lagoon.AfterCommit), so a listener that reads the // database sees the change; nothing is fired when the transaction rolls // back. A nil tx fires at once. ctx must be the context the transaction // runs under (the one cabana hands a hook or an action). func FireSubjectEventAfterCommit(ctx context.Context, app *backpack.App, tx *gorm.DB, ev pact.MCPSubjectChanged) { if app == nil || app.Events == nil || ev.UserID == 0 || ev.Kind == "" { return } if tx == nil { FireSubjectEvent(ctx, app, ev) return } lagoon.AfterCommit(ctx, tx, func(ctx context.Context, _ *gorm.DB) { FireSubjectEvent(ctx, app, ev) }) } // FireSubjectChanged fires one *pact.MCPSubjectChanged (guard, user id, // kind) at once: FireSubjectEvent for the whole subject. func FireSubjectChanged(ctx context.Context, app *backpack.App, guard string, userID uint, kind string) { FireSubjectEvent(ctx, app, pact.MCPSubjectChanged{Guard: guard, UserID: userID, Kind: kind}) } // FireSubjectChangedAfterCommit is FireSubjectChanged once the write // transaction tx commits: FireSubjectEventAfterCommit for the whole subject. func FireSubjectChangedAfterCommit(ctx context.Context, app *backpack.App, tx *gorm.DB, guard string, userID uint, kind string) { FireSubjectEventAfterCommit(ctx, app, tx, pact.MCPSubjectChanged{Guard: guard, UserID: userID, Kind: kind}) }