@@ -2,6 +2,7 @@ const _ = require('lodash')
22const AV = require ( 'leanengine' )
33const { Router } = require ( 'express' )
44const { check, query } = require ( 'express-validator' )
5+ const Redis = require ( 'ioredis' )
56
67const { captureException } = require ( '../errorHandler' )
78const { checkPermission } = require ( '../../oauth/lc' )
@@ -17,6 +18,27 @@ const config = require('../../config')
1718const Ticket = require ( './model' )
1819const { getRoles } = require ( '../common' )
1920
21+ // Initialize Redis client if URL is configured
22+ let redisClient = null
23+ if ( process . env . REDIS_URL_CACHE ) {
24+ try {
25+ redisClient = new Redis ( process . env . REDIS_URL_CACHE )
26+ redisClient . on ( 'error' , ( err ) => {
27+ console . error ( 'Redis connection error:' , err )
28+ // Optionally disable Redis features if connection fails persistently
29+ redisClient = null
30+ captureException ( new Error ( 'Redis connection failed' ) , { extra : { component : 'TicketAPI' } } )
31+ } )
32+ console . log ( 'Redis client initialized for rate limiting.' )
33+ } catch ( error ) {
34+ console . error ( 'Failed to initialize Redis client:' , error )
35+ captureException ( error , { extra : { component : 'TicketAPI' , msg : 'Redis init failed' } } )
36+ redisClient = null
37+ }
38+ } else {
39+ console . warn ( 'REDIS_URL_CACHE not set. Rate limiting is disabled.' )
40+ }
41+
2042const TICKET_SORT_KEY_MAP = {
2143 created_at : 'createdAt' ,
2244 updated_at : 'updatedAt' ,
@@ -73,6 +95,38 @@ router.post(
7395 res . throw ( 403 , 'Your account is not qualified to create ticket.' )
7496 }
7597
98+ // === Rate Limiting Start ===
99+ const currentUser = req . user
100+ const isCS = await isCSInTicket ( currentUser )
101+
102+ if ( ! isCS && redisClient ) {
103+ try {
104+ const today = new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) . replace ( / - / g, '' ) // YYYYMMDD
105+ const redisKey = `rate_limit:ticket:create:${ currentUser . id } :${ today } `
106+ const currentCount = await redisClient . incr ( redisKey )
107+
108+ if ( currentCount === 1 ) {
109+ // Set expiry to 24 hours when the key is first created today
110+ await redisClient . expire ( redisKey , 86400 ) // 86400 seconds = 24 hours
111+ }
112+
113+ if ( currentCount > 20 ) {
114+ console . warn ( `Rate limit exceeded for user ${ currentUser . id } . Count: ${ currentCount } ` )
115+ // Optionally log the violation details
116+ // LogRateLimitViolation({ userId: currentUser.id, limit: 20, resource: 'ticket_create' })
117+ return res . throw ( 429 , 'Rate limit exceeded. You can create up to 20 tickets per day.' )
118+ }
119+ } catch ( error ) {
120+ console . error ( `Redis rate limiting check failed for user ${ currentUser . id } :` , error )
121+ captureException ( error , {
122+ extra : { component : 'TicketAPI' , msg : 'Rate limit check failed' , userId : currentUser . id } ,
123+ } )
124+ // Fail open: If Redis fails, allow the request to proceed.
125+ // Alternatively, you could fail closed: return res.throw(500, 'Internal server error during rate check.')
126+ }
127+ }
128+ // === Rate Limiting End ===
129+
76130 const {
77131 title,
78132 category_id,
0 commit comments