| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | package notif |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | |
| 8 | "github.com/jackc/pgx/v5/pgxpool" |
| 9 | |
| 10 | "github.com/tenseleyFlow/shithub/internal/markdown" |
| 11 | usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc" |
| 12 | ) |
| 13 | |
| 14 | // ResolveMentionUserIDs maps a slice of (possibly duplicate) |
| 15 | // `markdown.Mention` records to deduplicated user IDs. Unknown, |
| 16 | // suspended, and soft-deleted accounts are silently dropped — the |
| 17 | // fan-out worker should never produce notifications for accounts |
| 18 | // that wouldn't have read access. Result is stable-ordered by |
| 19 | // first-occurrence in the input. |
| 20 | // |
| 21 | // The lookup is one-by-one rather than batched: typical mention |
| 22 | // counts are in the single digits and the per-username GetUserByUsername |
| 23 | // is index-backed. Batching becomes worth it only above ~50 distinct |
| 24 | // mentions per body. |
| 25 | func ResolveMentionUserIDs(ctx context.Context, pool *pgxpool.Pool, ms []markdown.Mention) []int64 { |
| 26 | if len(ms) == 0 { |
| 27 | return nil |
| 28 | } |
| 29 | q := usersdb.New() |
| 30 | seen := map[int64]struct{}{} |
| 31 | out := make([]int64, 0, len(ms)) |
| 32 | for _, m := range ms { |
| 33 | u, err := q.GetUserByUsername(ctx, pool, m.Username) |
| 34 | if err != nil { |
| 35 | continue |
| 36 | } |
| 37 | if u.SuspendedAt.Valid || u.DeletedAt.Valid { |
| 38 | continue |
| 39 | } |
| 40 | if _, dup := seen[u.ID]; dup { |
| 41 | continue |
| 42 | } |
| 43 | seen[u.ID] = struct{}{} |
| 44 | out = append(out, u.ID) |
| 45 | } |
| 46 | if len(out) == 0 { |
| 47 | return nil |
| 48 | } |
| 49 | return out |
| 50 | } |
| 51 |