tenseleyflow/shithub / 334b0c7

Browse files

search: wire ratelimit.Limiter into /search Mount (SR2 H4 wiring)

Threads a separate ratelimit.New(pool) into buildSearchHandlers so
SR2 H4's middleware actually fires on the live route. Shares the
DB-backed counter table with auth's RateLimiter; segregated by
Policy.Scope='search'.

Tests covering H4 use a constructed limiter directly (no DB needed
beyond a stub pool) — see search.go's documented Mount path.

Together with the previous commit (H5 tab-count cache), /search
goes from worst-case 6 FTS queries per request unrate-limited to
1 query per request on cache hit + 60/min/viewer hard ceiling.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
334b0c741555f0fc5e2917db6a75aa88147057af
Parents
fe289bb
Tree
25f0286

2 changed files

StatusFile+-
M internal/web/search_wiring.go 8 1
M internal/web/server.go 6 1
internal/web/search_wiring.gomodified
@@ -8,22 +8,29 @@ import (
8
 
8
 
9
 	"github.com/jackc/pgx/v5/pgxpool"
9
 	"github.com/jackc/pgx/v5/pgxpool"
10
 
10
 
11
+	"github.com/tenseleyFlow/shithub/internal/ratelimit"
11
 	searchhandlers "github.com/tenseleyFlow/shithub/internal/web/handlers/search"
12
 	searchhandlers "github.com/tenseleyFlow/shithub/internal/web/handlers/search"
12
 	"github.com/tenseleyFlow/shithub/internal/web/render"
13
 	"github.com/tenseleyFlow/shithub/internal/web/render"
13
 )
14
 )
14
 
15
 
15
 // buildSearchHandlers wires the S28 search handler set. Owns its own
16
 // buildSearchHandlers wires the S28 search handler set. Owns its own
16
 // renderer (same pattern as the other handler builders).
17
 // renderer (same pattern as the other handler builders).
18
+//
19
+// Limiter is the per-request rate-limit entrypoint applied to /search
20
+// (audit 2026-05-10 H4). Pass nil from tests when you don't care
21
+// about throttling; production wiring constructs one shared limiter
22
+// per shithubd-web process and threads it everywhere.
17
 func buildSearchHandlers(
23
 func buildSearchHandlers(
18
 	pool *pgxpool.Pool,
24
 	pool *pgxpool.Pool,
19
 	tmplFS fs.FS,
25
 	tmplFS fs.FS,
20
 	logger *slog.Logger,
26
 	logger *slog.Logger,
27
+	limiter *ratelimit.Limiter,
21
 ) (*searchhandlers.Handlers, error) {
28
 ) (*searchhandlers.Handlers, error) {
22
 	rr, err := render.New(tmplFS, render.Options{Octicons: render.BuiltinOcticons()})
29
 	rr, err := render.New(tmplFS, render.Options{Octicons: render.BuiltinOcticons()})
23
 	if err != nil {
30
 	if err != nil {
24
 		return nil, err
31
 		return nil, err
25
 	}
32
 	}
26
 	return searchhandlers.New(searchhandlers.Deps{
33
 	return searchhandlers.New(searchhandlers.Deps{
27
-		Logger: logger, Render: rr, Pool: pool,
34
+		Logger: logger, Render: rr, Pool: pool, Limiter: limiter,
28
 	})
35
 	})
29
 }
36
 }
internal/web/server.gomodified
@@ -30,6 +30,7 @@ import (
30
 	infralog "github.com/tenseleyFlow/shithub/internal/infra/log"
30
 	infralog "github.com/tenseleyFlow/shithub/internal/infra/log"
31
 	"github.com/tenseleyFlow/shithub/internal/infra/metrics"
31
 	"github.com/tenseleyFlow/shithub/internal/infra/metrics"
32
 	"github.com/tenseleyFlow/shithub/internal/infra/tracing"
32
 	"github.com/tenseleyFlow/shithub/internal/infra/tracing"
33
+	"github.com/tenseleyFlow/shithub/internal/ratelimit"
33
 	"github.com/tenseleyFlow/shithub/internal/version"
34
 	"github.com/tenseleyFlow/shithub/internal/version"
34
 	"github.com/tenseleyFlow/shithub/internal/web/handlers"
35
 	"github.com/tenseleyFlow/shithub/internal/web/handlers"
35
 	"github.com/tenseleyFlow/shithub/internal/web/middleware"
36
 	"github.com/tenseleyFlow/shithub/internal/web/middleware"
@@ -221,7 +222,11 @@ func Run(ctx context.Context, opts Options) error {
221
 		deps.RepoSocialMounter = repoH.MountSocial
222
 		deps.RepoSocialMounter = repoH.MountSocial
222
 		deps.RepoForkMounter = repoH.MountFork
223
 		deps.RepoForkMounter = repoH.MountFork
223
 
224
 
224
-		searchH, err := buildSearchHandlers(pool, deps.TemplatesFS, logger)
225
+		// Search gets its own Limiter wired around /search +
226
+		// /search/quick (audit 2026-05-10 H4). Independent instance
227
+		// from auth's RateLimiter; both share DB-backed counter
228
+		// state, segregated by Policy.Scope.
229
+		searchH, err := buildSearchHandlers(pool, deps.TemplatesFS, logger, ratelimit.New(pool))
225
 		if err != nil {
230
 		if err != nil {
226
 			return fmt.Errorf("search handlers: %w", err)
231
 			return fmt.Errorf("search handlers: %w", err)
227
 		}
232
 		}