66 sanitizeMeterId ,
77 sanitizeString ,
88 sanitizePositiveNumber ,
9+ sanitizeInteger ,
910 validationError ,
1011 type ValidationError ,
1112} from "../utils/sanitize" ;
@@ -50,16 +51,46 @@ router.get("/user/:userId", async (req, res) => {
5051/**
5152 * GET /api/analytics/system
5253 * Get system-wide analytics (admin only)
54+ * Query params: page (default 1), limit (default 12)
5355 */
5456router . get ( "/system" , async ( req , res ) => {
5557 try {
58+ const errors : ValidationError [ ] = [ ] ;
59+
60+ const rawPage = sanitizeInteger ( req . query . page ?? '1' , 1 , 10_000 ) ;
61+ const rawLimit = sanitizeInteger ( req . query . limit ?? '12' , 1 , 100 ) ;
62+
63+ if ( req . query . page !== undefined && Number . isNaN ( rawPage ) ) errors . push ( validationError ( 'page' , 'page must be a positive integer (1–10000)' ) ) ;
64+ if ( req . query . limit !== undefined && Number . isNaN ( rawLimit ) ) errors . push ( validationError ( 'limit' , 'limit must be an integer between 1 and 100' ) ) ;
65+ if ( errors . length > 0 ) return res . status ( 400 ) . json ( { success : false , errors } ) ;
66+
67+ const page = Number . isNaN ( rawPage ) ? 1 : rawPage ;
68+ const limit = Number . isNaN ( rawLimit ) ? 12 : rawLimit ;
69+
5670 // TODO: Add admin authentication check
5771 const analytics = await analyticsService . generateSystemAnalytics ( ) ;
5872
59- logger . info ( "System analytics retrieved" ) ;
73+ // Paginate monthlyGrowth
74+ const all = analytics . monthlyGrowth ;
75+ const total = all . length ;
76+ const offset = ( page - 1 ) * limit ;
77+ const pagedGrowth = all . slice ( offset , offset + limit ) ;
78+
79+ logger . info ( "System analytics retrieved" , { page, limit } ) ;
6080 return res . status ( 200 ) . json ( {
6181 success : true ,
62- data : analytics ,
82+ data : {
83+ ...analytics ,
84+ monthlyGrowth : pagedGrowth ,
85+ } ,
86+ pagination : {
87+ page,
88+ limit,
89+ total,
90+ totalPages : Math . max ( 1 , Math . ceil ( total / limit ) ) ,
91+ hasNextPage : offset + limit < total ,
92+ hasPreviousPage : page > 1 ,
93+ } ,
6394 timestamp : new Date ( ) . toISOString ( ) ,
6495 } ) ;
6596 } catch ( error ) {
@@ -71,6 +102,43 @@ router.get("/system", async (req, res) => {
71102 }
72103} ) ;
73104
105+ /**
106+ * GET /api/analytics/monthly-growth
107+ * Get paginated monthly growth data
108+ * Query params: page (default 1), limit (default 12)
109+ */
110+ router . get ( "/monthly-growth" , async ( req , res ) => {
111+ try {
112+ const errors : ValidationError [ ] = [ ] ;
113+
114+ const rawPage = sanitizeInteger ( req . query . page ?? '1' , 1 , 10_000 ) ;
115+ const rawLimit = sanitizeInteger ( req . query . limit ?? '12' , 1 , 100 ) ;
116+
117+ if ( req . query . page !== undefined && Number . isNaN ( rawPage ) ) errors . push ( validationError ( 'page' , 'page must be a positive integer (1–10000)' ) ) ;
118+ if ( req . query . limit !== undefined && Number . isNaN ( rawLimit ) ) errors . push ( validationError ( 'limit' , 'limit must be an integer between 1 and 100' ) ) ;
119+ if ( errors . length > 0 ) return res . status ( 400 ) . json ( { success : false , errors } ) ;
120+
121+ const page = Number . isNaN ( rawPage ) ? 1 : rawPage ;
122+ const limit = Number . isNaN ( rawLimit ) ? 12 : rawLimit ;
123+
124+ const result = await analyticsService . getPaginatedMonthlyGrowth ( { page, limit } ) ;
125+
126+ logger . info ( "Monthly growth analytics retrieved" , { page, limit } ) ;
127+ return res . status ( 200 ) . json ( {
128+ success : true ,
129+ data : result . data ,
130+ pagination : result . pagination ,
131+ timestamp : new Date ( ) . toISOString ( ) ,
132+ } ) ;
133+ } catch ( error ) {
134+ logger . error ( "Failed to retrieve monthly growth analytics" , { error } ) ;
135+ return res . status ( 500 ) . json ( {
136+ success : false ,
137+ error : "Failed to retrieve monthly growth analytics" ,
138+ } ) ;
139+ }
140+ } ) ;
141+
74142/**
75143 * GET /api/analytics/payments/dashboard
76144 * Get payment analytics dashboard insights
0 commit comments