This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/notifications/sources.go
Jeffrey Smith 680ec3b897
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m34s
CI/CD / test-sqlite (push) Successful in 2m46s
CI/CD / build-and-deploy (push) Successful in 1m55s
Feat rebrand armature (#43)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-31 23:25:37 +00:00

186 lines
6.1 KiB
Go

package notifications
import (
"context"
"encoding/json"
"fmt"
"log"
"armature/auth"
"armature/events"
"armature/models"
"armature/store"
)
// ── Role Fallback ───────────────────────────
// Subscribes to the existing "role.fallback" EventBus event (emitted by
// capabilities/resolver.go) and creates notifications for
// all admin users.
// roleFallbackPayload matches the payload emitted by the role resolver.
type roleFallbackPayload struct {
Role string `json:"role"`
Primary string `json:"primary_model"`
Fallback string `json:"fallback_model"`
Error string `json:"error"`
}
// RoleFallbackHandler returns an events.Handler that creates admin
// notifications when a model role falls back.
func RoleFallbackHandler(svc *Service, stores store.Stores) events.Handler {
return func(e events.Event) {
var p roleFallbackPayload
if err := json.Unmarshal(e.Payload, &p); err != nil {
log.Printf("[notifications] failed to parse role.fallback payload: %v", err)
return
}
title := fmt.Sprintf("Model fallback: %s → %s", p.Primary, p.Fallback)
if p.Primary == "" {
title = fmt.Sprintf("Role '%s' fell back to %s", p.Role, p.Fallback)
}
body := ""
if p.Error != "" {
body = fmt.Sprintf("Primary model error: %s", p.Error)
}
// Notify all Admins group members
ctx := context.Background()
admins, err := stores.Groups.ListMembers(ctx, auth.AdminsGroupID)
if err != nil {
log.Printf("[notifications] failed to list admins for role.fallback: %v", err)
return
}
for _, u := range admins {
n := models.Notification{
UserID: u.UserID,
Type: models.NotifTypeRoleFallback,
Title: title,
Body: body,
}
if err := svc.Notify(ctx, &n); err != nil {
log.Printf("[notifications] failed to create role.fallback notification for %s: %v", u.UserID, err)
}
}
}
}
// ── Knowledge Base ──────────────────────────
// Called directly from the KB handler after indexing completes or fails.
// Not a bus subscriber — the KB handler calls these functions.
// NotifyKBReady creates a notification when a knowledge base finishes indexing.
func NotifyKBReady(svc *Service, userID, kbID, kbName string, chunkCount int) {
if svc == nil {
return
}
n := models.Notification{
UserID: userID,
Type: models.NotifTypeKBReady,
Title: fmt.Sprintf("Knowledge base \"%s\" ready (%d chunks)", kbName, chunkCount),
ResourceType: models.ResourceTypeKnowledgeBase,
ResourceID: kbID,
}
if err := svc.Notify(context.Background(), &n); err != nil {
log.Printf("[notifications] kb.ready failed for user %s: %v", userID, err)
}
}
// NotifyKBError creates a notification when KB indexing fails.
func NotifyKBError(svc *Service, userID, kbID, kbName, errMsg string) {
if svc == nil {
return
}
n := models.Notification{
UserID: userID,
Type: models.NotifTypeKBError,
Title: fmt.Sprintf("Knowledge base \"%s\" indexing failed", kbName),
Body: errMsg,
ResourceType: models.ResourceTypeKnowledgeBase,
ResourceID: kbID,
}
if err := svc.Notify(context.Background(), &n); err != nil {
log.Printf("[notifications] kb.error failed for user %s: %v", userID, err)
}
}
// ── Group Membership ────────────────────────
// Called directly from the group handler on add/remove.
// NotifyGroupMemberAdded creates a notification when a user is added to a group.
func NotifyGroupMemberAdded(svc *Service, userID, groupID, groupName string) {
if svc == nil {
return
}
n := models.Notification{
UserID: userID,
Type: models.NotifTypeGrantChanged,
Title: fmt.Sprintf("You were added to group \"%s\"", groupName),
ResourceType: models.ResourceTypeGroup,
ResourceID: groupID,
}
if err := svc.Notify(context.Background(), &n); err != nil {
log.Printf("[notifications] grant.changed (add) failed for user %s: %v", userID, err)
}
}
// NotifyGroupMemberRemoved creates a notification when a user is removed from a group.
func NotifyGroupMemberRemoved(svc *Service, userID, groupID, groupName string) {
if svc == nil {
return
}
n := models.Notification{
UserID: userID,
Type: models.NotifTypeGrantChanged,
Title: fmt.Sprintf("You were removed from group \"%s\"", groupName),
ResourceType: models.ResourceTypeGroup,
ResourceID: groupID,
}
if err := svc.Notify(context.Background(), &n); err != nil {
log.Printf("[notifications] grant.changed (remove) failed for user %s: %v", userID, err)
}
}
// ── User Mention ───────────────────────────
// Called from handlers/completion.go when an @mention targets a human user.
// NotifyUserMentioned creates a persisted notification for an @mention.
func NotifyUserMentioned(svc *Service, mentionedUserID, channelID, fromUserID, preview string) {
if svc == nil {
return
}
n := models.Notification{
UserID: mentionedUserID,
Type: models.NotifTypeUserMentioned,
Title: "You were mentioned in a conversation",
Body: preview,
ResourceType: models.ResourceTypeChannel,
ResourceID: channelID,
}
if err := svc.Notify(context.Background(), &n); err != nil {
log.Printf("[notifications] user.mentioned failed for user %s: %v", mentionedUserID, err)
}
}
// ── Workflow Claimed ───────────────────────
// Called from handlers/workflow_assignments.go when a user claims an assignment.
// NotifyWorkflowClaimed creates a persisted notification when an assignment is claimed.
func NotifyWorkflowClaimed(svc *Service, claimerID, assignmentID, channelID string) {
if svc == nil {
return
}
n := models.Notification{
UserID: claimerID,
Type: models.NotifTypeWorkflowClaimed,
Title: "You claimed a workflow assignment",
ResourceType: models.ResourceTypeChannel,
ResourceID: channelID,
}
if err := svc.Notify(context.Background(), &n); err != nil {
log.Printf("[notifications] workflow.claimed failed for user %s: %v", claimerID, err)
}
}