@@ -10,6 +10,7 @@ import (
1010 "net/http"
1111 "strconv"
1212 "strings"
13+ "sync"
1314 "time"
1415
1516 "report-listener/database"
@@ -25,8 +26,17 @@ import (
2526const (
2627 // MaxReportsLimit is the maximum number of reports that can be requested in a single query
2728 MaxReportsLimit = 100000
29+ // brandCountsCacheTTL controls how long by-brand aggregate counts are cached in memory.
30+ brandCountsCacheTTL = 10 * time .Minute
2831)
2932
33+ type brandCountsCacheEntry struct {
34+ Total int
35+ High int
36+ Medium int
37+ ExpiresAt time.Time
38+ }
39+
3040// WebSocket upgrader with default configuration
3141var upgrader = gorilla.Upgrader {
3242 CheckOrigin : func (r * http.Request ) bool {
@@ -40,6 +50,9 @@ type Handlers struct {
4050 db * database.Database
4151 rabbitmqPublisher * rabbitmq.Publisher
4252 rabbitmqReplier * rabbitmq.Publisher
53+
54+ brandCountsMu sync.RWMutex
55+ brandCountsCache map [string ]brandCountsCacheEntry
4356}
4457
4558// NewHandlers creates a new handlers instance
@@ -49,7 +62,36 @@ func NewHandlers(hub *ws.Hub, db *database.Database, pub *rabbitmq.Publisher, re
4962 db : db ,
5063 rabbitmqPublisher : pub ,
5164 rabbitmqReplier : replyPub ,
65+ brandCountsCache : make (map [string ]brandCountsCacheEntry ),
66+ }
67+ }
68+
69+ func brandCountsCacheKey (brandName string ) string {
70+ return strings .ToLower (strings .TrimSpace (brandName ))
71+ }
72+
73+ func (h * Handlers ) getBrandCountsCached (brandName string ) (brandCountsCacheEntry , bool , bool ) {
74+ key := brandCountsCacheKey (brandName )
75+ h .brandCountsMu .RLock ()
76+ entry , ok := h .brandCountsCache [key ]
77+ h .brandCountsMu .RUnlock ()
78+ if ! ok {
79+ return brandCountsCacheEntry {}, false , false
5280 }
81+ fresh := time .Now ().Before (entry .ExpiresAt )
82+ return entry , true , fresh
83+ }
84+
85+ func (h * Handlers ) setBrandCountsCached (brandName string , total , high , medium int ) {
86+ key := brandCountsCacheKey (brandName )
87+ h .brandCountsMu .Lock ()
88+ h .brandCountsCache [key ] = brandCountsCacheEntry {
89+ Total : total ,
90+ High : high ,
91+ Medium : medium ,
92+ ExpiresAt : time .Now ().Add (brandCountsCacheTTL ),
93+ }
94+ h .brandCountsMu .Unlock ()
5395}
5496
5597// DB exposes the underlying database handle for wiring
@@ -981,20 +1023,36 @@ func (h *Handlers) GetReportsByBrand(c *gin.Context) {
9811023 }
9821024
9831025 // Get counts with a bounded timeout to avoid slow/full-table scans blocking UI.
984- // For n=1 (digital search flow), skip aggregate counts entirely.
9851026 totalCount := len (reports )
9861027 highPriorityCount := 0
9871028 mediumPriorityCount := 0
988- if limit > 1 {
989- countCtx , cancelCounts := context .WithTimeout (c .Request .Context (), 1200 * time .Millisecond )
990- defer cancelCounts ()
1029+
1030+ if cached , ok , fresh := h .getBrandCountsCached (brandName ); ok && fresh {
1031+ totalCount = cached .Total
1032+ highPriorityCount = cached .High
1033+ mediumPriorityCount = cached .Medium
1034+ } else {
1035+ countTimeout := 1200 * time .Millisecond
1036+ if limit <= 1 {
1037+ // Keep search dropdown (n=1) snappy while still attempting accurate totals.
1038+ countTimeout = 250 * time .Millisecond
1039+ }
1040+
1041+ countCtx , cancelCounts := context .WithTimeout (c .Request .Context (), countTimeout )
9911042 t , hpc , mpc , err := h .db .GetBrandPriorityCountsByBrandName (countCtx , brandName )
1043+ cancelCounts ()
9921044 if err != nil {
993- log .Printf ("Failed to get aggregated counts for brand '%s' (fallbacking to partial): %v" , brandName , err )
1045+ log .Printf ("Failed to get aggregated counts for brand '%s' (fallbacking to cache/partial): %v" , brandName , err )
1046+ if cached , ok , _ := h .getBrandCountsCached (brandName ); ok {
1047+ totalCount = cached .Total
1048+ highPriorityCount = cached .High
1049+ mediumPriorityCount = cached .Medium
1050+ }
9941051 } else {
9951052 totalCount = t
9961053 highPriorityCount = hpc
9971054 mediumPriorityCount = mpc
1055+ h .setBrandCountsCached (brandName , t , hpc , mpc )
9981056 }
9991057 }
10001058
0 commit comments