@@ -19,6 +19,7 @@ router.use(requireAuth);
1919/**
2020 * GET /api/servers
2121 * Get all servers with pagination and filtering
22+ * Supports: pagination, search, filtering by plan/status, sorting
2223 */
2324router . get ( '/' , async ( req : Request , res : Response ) => {
2425 try {
@@ -32,22 +33,45 @@ router.get('/', async (req: Request, res: Response) => {
3233 order = 'desc'
3334 } = req . query ;
3435
35- const pageNum = parseInt ( page as string ) ;
36- const limitNum = parseInt ( limit as string ) ;
36+ // Validate and sanitize pagination parameters
37+ const pageNum = Math . max ( 1 , parseInt ( page as string ) || 1 ) ;
38+ const limitNum = Math . min ( 100 , Math . max ( 1 , parseInt ( limit as string ) || 20 ) ) ;
3739 const skip = ( pageNum - 1 ) * limitNum ;
3840
41+ // Validate sort field - only allow specific fields for security
42+ const allowedSortFields = [
43+ 'serverName' ,
44+ 'customDomain' ,
45+ 'adminEmail' ,
46+ 'plan' ,
47+ 'createdAt' ,
48+ 'updatedAt' ,
49+ 'userCount' ,
50+ 'ticketCount' ,
51+ 'provisioningStatus' ,
52+ 'lastActivityAt'
53+ ] ;
54+ const sortField = allowedSortFields . includes ( sort as string ) ? sort as string : 'createdAt' ;
55+ const sortOrder = order === 'asc' ? 1 : - 1 ;
56+
3957 // Build filter object
4058 const filter : any = { } ;
4159
42- if ( search ) {
43- filter . $text = { $search : search as string } ;
60+ if ( search && ( search as string ) . trim ( ) ) {
61+ const searchTerm = ( search as string ) . trim ( ) ;
62+ // Use regex for more flexible search across multiple fields
63+ filter . $or = [
64+ { serverName : { $regex : searchTerm , $options : 'i' } } ,
65+ { customDomain : { $regex : searchTerm , $options : 'i' } } ,
66+ { adminEmail : { $regex : searchTerm , $options : 'i' } }
67+ ] ;
4468 }
4569
4670 if ( plan && plan !== 'all' ) {
4771 filter . plan = plan ;
4872 }
4973
50- if ( status ) {
74+ if ( status && status !== 'all' ) {
5175 switch ( status ) {
5276 case 'active' :
5377 filter . provisioningStatus = 'completed' ;
@@ -67,42 +91,52 @@ router.get('/', async (req: Request, res: Response) => {
6791
6892 // Build sort object
6993 const sortObj : any = { } ;
70- sortObj [ sort as string ] = order === 'desc' ? - 1 : 1 ;
94+ sortObj [ sortField ] = sortOrder ;
7195
72- // Execute queries
96+ // Execute queries with better error handling
7397 const ModlServerModel = getModlServerModel ( ) ;
74- const [ servers , total ] = await Promise . all ( [
75- ModlServerModel
76- . find ( filter )
77- . sort ( sortObj )
78- . skip ( skip )
79- . limit ( limitNum )
80- . lean ( ) ,
81- ModlServerModel . countDocuments ( filter )
82- ] ) ;
83-
84- const response : ApiResponse < {
85- servers : IModlServer [ ] ;
86- pagination : {
87- page : number ;
88- limit : number ;
89- total : number ;
90- pages : number ;
91- } ;
92- } > = {
93- success : true ,
94- data : {
95- servers : servers as IModlServer [ ] ,
98+
99+ try {
100+ const [ servers , total ] = await Promise . all ( [
101+ ModlServerModel
102+ . find ( filter )
103+ . sort ( sortObj )
104+ . skip ( skip )
105+ . limit ( limitNum )
106+ . select ( '-__v -emailVerificationToken -provisioningSignInToken' ) // Exclude sensitive fields
107+ . lean ( ) ,
108+ ModlServerModel . countDocuments ( filter )
109+ ] ) ;
110+
111+ const response : ApiResponse < {
112+ servers : IModlServer [ ] ;
96113 pagination : {
97- page : pageNum ,
98- limit : limitNum ,
99- total,
100- pages : Math . ceil ( total / limitNum )
114+ page : number ;
115+ limit : number ;
116+ total : number ;
117+ pages : number ;
118+ } ;
119+ } > = {
120+ success : true ,
121+ data : {
122+ servers : servers as IModlServer [ ] ,
123+ pagination : {
124+ page : pageNum ,
125+ limit : limitNum ,
126+ total,
127+ pages : Math . ceil ( total / limitNum )
128+ }
101129 }
102- }
103- } ;
130+ } ;
104131
105- return res . json ( response ) ;
132+ return res . json ( response ) ;
133+ } catch ( queryError ) {
134+ console . error ( 'Database query error:' , queryError ) ;
135+ return res . status ( 500 ) . json ( {
136+ success : false ,
137+ error : 'Database query failed'
138+ } ) ;
139+ }
106140 } catch ( error ) {
107141 console . error ( 'Get servers error:' , error ) ;
108142 return res . status ( 500 ) . json ( {
@@ -247,6 +281,64 @@ router.put('/:id', async (req: Request, res: Response) => {
247281 }
248282} ) ;
249283
284+ /**
285+ * PUT /api/servers/:id/stats
286+ * Update server statistics (userCount, ticketCount, lastActivityAt)
287+ */
288+ router . put ( '/:id/stats' , async ( req : Request , res : Response ) => {
289+ try {
290+ const { id } = req . params ;
291+ const { userCount, ticketCount, lastActivityAt } = req . body ;
292+
293+ // Validate input
294+ const updateData : any = { } ;
295+ if ( typeof userCount === 'number' && userCount >= 0 ) {
296+ updateData . userCount = userCount ;
297+ }
298+ if ( typeof ticketCount === 'number' && ticketCount >= 0 ) {
299+ updateData . ticketCount = ticketCount ;
300+ }
301+ if ( lastActivityAt ) {
302+ updateData . lastActivityAt = new Date ( lastActivityAt ) ;
303+ }
304+
305+ if ( Object . keys ( updateData ) . length === 0 ) {
306+ return res . status ( 400 ) . json ( {
307+ success : false ,
308+ error : 'No valid stats provided to update'
309+ } ) ;
310+ }
311+
312+ updateData . updatedAt = new Date ( ) ;
313+
314+ const ModlServerModel = getModlServerModel ( ) ;
315+ const server = await ModlServerModel . findByIdAndUpdate (
316+ id ,
317+ updateData ,
318+ { new : true , runValidators : true }
319+ ) ;
320+
321+ if ( ! server ) {
322+ return res . status ( 404 ) . json ( {
323+ success : false ,
324+ error : 'Server not found'
325+ } ) ;
326+ }
327+
328+ return res . json ( {
329+ success : true ,
330+ data : server ,
331+ message : 'Server statistics updated successfully'
332+ } ) ;
333+ } catch ( error ) {
334+ console . error ( 'Update server stats error:' , error ) ;
335+ return res . status ( 500 ) . json ( {
336+ success : false ,
337+ error : 'Failed to update server statistics'
338+ } ) ;
339+ }
340+ } ) ;
341+
250342/**
251343 * DELETE /api/servers/:id
252344 * Delete a server
0 commit comments