package users import ( "context" "log/slog" "strings" "github.com/jackc/pgx/v5/pgxpool" "github.com/ultisuite/ulti-backend/internal/ai" "github.com/ultisuite/ulti-backend/internal/auth" "github.com/ultisuite/ulti-backend/internal/authentik" "github.com/ultisuite/ulti-backend/internal/config" ) // ImportAvatarFromAuthentik copies attributes.avatar into users.avatar_url when PG is empty. func ImportAvatarFromAuthentik(ctx context.Context, db *pgxpool.Pool, cfg *config.Config, externalID string) (string, error) { if db == nil || cfg == nil || strings.TrimSpace(externalID) == "" { return "", nil } current, err := GetAvatarURL(ctx, db, externalID) if err != nil || current != "" { return current, err } client := authentikClient(cfg) if client == nil { return "", nil } raw, err := client.GetUserAvatarAttribute(ctx, externalID) if err != nil || strings.TrimSpace(raw) == "" { return "", err } normalized, err := normalizeAvatarURL(raw) if err != nil { slog.Warn("skip authentik avatar import", "sub", externalID, "error", err) return "", nil } if err := SetAvatarURL(ctx, db, externalID, normalized); err != nil { return "", err } return normalized, nil } // PropagateAvatarOutbound syncs avatar to Authentik and UltiAI (OpenWebUI). func PropagateAvatarOutbound(ctx context.Context, db *pgxpool.Pool, cfg *config.Config, claims *auth.Claims, avatarURL string) { if claims == nil || strings.TrimSpace(claims.Sub) == "" { return } avatarURL = strings.TrimSpace(avatarURL) if client := authentikClient(cfg); client != nil { if err := client.SetUserAvatarAttribute(ctx, claims.Sub, avatarURL); err != nil { slog.Warn("sync avatar to authentik", "sub", claims.Sub, "error", err) } } if err := ai.SyncOpenWebUIProfile(ctx, cfg, claims, avatarURL); err != nil { slog.Warn("sync avatar to openwebui", "sub", claims.Sub, "error", err) } } func authentikClient(cfg *config.Config) *authentik.Client { if cfg == nil || strings.TrimSpace(cfg.AuthentikAPIToken) == "" { return nil } return authentik.NewClient(cfg.AuthentikAPIURL, cfg.AuthentikAPIToken) }