@@ -7,7 +7,8 @@ import { type NextRequest, NextResponse } from 'next/server'
77import { templateIdParamsSchema , updateTemplateContract } from '@/lib/api/contracts/templates'
88import { parseRequest } from '@/lib/api/server'
99import { getSession } from '@/lib/auth'
10- import { generateRequestId } from '@/lib/core/utils/request'
10+ import { RateLimiter } from '@/lib/core/rate-limiter'
11+ import { generateRequestId , getClientIp } from '@/lib/core/utils/request'
1112import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1213import { canAccessTemplate } from '@/lib/templates/permissions'
1314import {
@@ -18,6 +19,18 @@ import type { WorkflowState } from '@/stores/workflows/workflow/types'
1819
1920const logger = createLogger ( 'TemplateByIdAPI' )
2021
22+ const viewRateLimiter = new RateLimiter ( )
23+
24+ /**
25+ * Per-IP, per-template view-counter dedup bucket: one increment per 10 minutes.
26+ * Prevents scripted inflation of `templates.views` from the public GET handler.
27+ */
28+ const TEMPLATE_VIEW_DEDUP = {
29+ maxTokens : 1 ,
30+ refillRate : 1 ,
31+ refillIntervalMs : 10 * 60_000 ,
32+ }
33+
2134export const revalidate = 0
2235
2336export const GET = withRouteHandler (
@@ -63,21 +76,31 @@ export const GET = withRouteHandler(
6376 isStarred = starResult . length > 0
6477 }
6578
66- const shouldIncrementView = template . status === 'approved'
79+ let shouldIncrementView = template . status === 'approved'
6780
6881 if ( shouldIncrementView ) {
69- try {
70- await db
71- . update ( templates )
72- . set ( {
73- views : sql `${ templates . views } + 1` ,
74- } )
75- . where ( eq ( templates . id , id ) )
76- } catch ( viewError ) {
77- logger . warn (
78- `[${ requestId } ] Failed to increment view count for template: ${ id } ` ,
79- viewError
80- )
82+ const viewer = session ?. user ?. id ?? `ip:${ getClientIp ( request ) } `
83+ const dedupKey = `template-view:${ id } :${ viewer } `
84+ const { allowed } = await viewRateLimiter . checkRateLimitDirect (
85+ dedupKey ,
86+ TEMPLATE_VIEW_DEDUP
87+ )
88+ if ( ! allowed ) {
89+ shouldIncrementView = false
90+ } else {
91+ try {
92+ await db
93+ . update ( templates )
94+ . set ( {
95+ views : sql `${ templates . views } + 1` ,
96+ } )
97+ . where ( eq ( templates . id , id ) )
98+ } catch ( viewError ) {
99+ logger . warn (
100+ `[${ requestId } ] Failed to increment view count for template: ${ id } ` ,
101+ viewError
102+ )
103+ }
81104 }
82105 }
83106
0 commit comments