@@ -310,7 +310,7 @@ export async function getAllUsers(
310310 try {
311311 let cursor = '0' ;
312312 const cachedUserIds : string [ ] = [ ] ;
313-
313+
314314 do {
315315 const [ newCursor , keys ] = await redisConnection . scan (
316316 cursor ,
@@ -320,11 +320,11 @@ export async function getAllUsers(
320320 1000
321321 ) ;
322322 cursor = newCursor ;
323-
323+
324324 const userIds = keys
325325 . filter ( key => key . startsWith ( 'user:' ) && ! key . includes ( ':username:' ) )
326326 . map ( key => key . replace ( 'user:' , '' ) ) ;
327-
327+
328328 cachedUserIds . push ( ...userIds ) ;
329329 } while ( cursor !== '0' ) ;
330330
@@ -624,6 +624,164 @@ export async function syncUserSessionCounts() {
624624 }
625625}
626626
627+ export async function getControllerRatingStats ( ) {
628+ const cacheKey = 'admin:controller_rating_stats' ;
629+
630+ try {
631+ const cached = await redisConnection . get ( cacheKey ) ;
632+ if ( cached ) {
633+ return JSON . parse ( cached ) ;
634+ }
635+ } catch ( error ) {
636+ if ( error instanceof Error ) {
637+ console . warn (
638+ '[Redis] Failed to read cache for controller rating stats:' ,
639+ error . message
640+ ) ;
641+ }
642+ }
643+
644+ try {
645+ const topRatedControllers = await mainDb
646+ . selectFrom ( 'controller_ratings' )
647+ . select ( [
648+ 'controller_id' ,
649+ ( eb ) => eb . fn . avg < number > ( 'rating' ) . as ( 'avg_rating' ) ,
650+ ( eb ) => eb . fn . count < number > ( 'id' ) . as ( 'rating_count' ) ,
651+ ] )
652+ . groupBy ( 'controller_id' )
653+ . having ( ( eb ) => eb . fn . count < number > ( 'id' ) , '>=' , 3 )
654+ . orderBy ( 'avg_rating' , 'desc' )
655+ . limit ( 10 )
656+ . execute ( ) ;
657+
658+ const mostRatedControllers = await mainDb
659+ . selectFrom ( 'controller_ratings' )
660+ . select ( [
661+ 'controller_id' ,
662+ ( eb ) => eb . fn . count < number > ( 'id' ) . as ( 'rating_count' ) ,
663+ ( eb ) => eb . fn . avg < number > ( 'rating' ) . as ( 'avg_rating' ) ,
664+ ] )
665+ . groupBy ( 'controller_id' )
666+ . orderBy ( 'rating_count' , 'desc' )
667+ . limit ( 10 )
668+ . execute ( ) ;
669+
670+ const topRatingPilots = await mainDb
671+ . selectFrom ( 'controller_ratings' )
672+ . select ( [
673+ 'pilot_id' ,
674+ ( eb ) => eb . fn . count < number > ( 'id' ) . as ( 'rating_count' ) ,
675+ ] )
676+ . groupBy ( 'pilot_id' )
677+ . orderBy ( 'rating_count' , 'desc' )
678+ . limit ( 9 )
679+ . execute ( ) ;
680+
681+ const controllerIds = [
682+ ...new Set ( [
683+ ...topRatedControllers . map ( ( c ) => c . controller_id ) ,
684+ ...mostRatedControllers . map ( ( c ) => c . controller_id ) ,
685+ ] ) ,
686+ ] ;
687+ const pilotIds = topRatingPilots . map ( ( p ) => p . pilot_id ) ;
688+
689+ const users = await mainDb
690+ . selectFrom ( 'users' )
691+ . select ( [ 'id' , 'username' , 'avatar' ] )
692+ . where ( 'id' , 'in' , [ ...controllerIds , ...pilotIds ] )
693+ . execute ( ) ;
694+
695+ const userMap = new Map ( users . map ( ( u ) => [ u . id , { username : u . username , avatar : u . avatar } ] ) ) ;
696+
697+ const result = {
698+ topRated : topRatedControllers . map ( ( c ) => ( {
699+ ...c ,
700+ username : userMap . get ( c . controller_id ) ?. username || 'Unknown' ,
701+ avatar : userMap . get ( c . controller_id ) ?. avatar || null ,
702+ } ) ) ,
703+ mostRated : mostRatedControllers . map ( ( c ) => ( {
704+ ...c ,
705+ username : userMap . get ( c . controller_id ) ?. username || 'Unknown' ,
706+ avatar : userMap . get ( c . controller_id ) ?. avatar || null ,
707+ } ) ) ,
708+ topPilots : topRatingPilots . map ( ( p ) => ( {
709+ ...p ,
710+ username : userMap . get ( p . pilot_id ) ?. username || 'Unknown' ,
711+ avatar : userMap . get ( p . pilot_id ) ?. avatar || null ,
712+ } ) ) ,
713+ } ;
714+
715+ try {
716+ await redisConnection . set ( cacheKey , JSON . stringify ( result ) , 'EX' , 300 ) ;
717+ } catch ( error ) {
718+ if ( error instanceof Error ) {
719+ console . warn (
720+ '[Redis] Failed to set cache for controller rating stats:' ,
721+ error . message
722+ ) ;
723+ }
724+ }
725+
726+ return result ;
727+ } catch ( error ) {
728+ console . error ( 'Error fetching controller rating stats:' , error ) ;
729+ throw error ;
730+ }
731+ }
732+
733+ export async function getControllerRatingsDailyStats ( days : number = 30 ) {
734+ const cacheKey = `admin:controller_rating_daily_stats:${ days } ` ;
735+
736+ try {
737+ const cached = await redisConnection . get ( cacheKey ) ;
738+ if ( cached ) {
739+ return JSON . parse ( cached ) ;
740+ }
741+ } catch ( error ) {
742+ if ( error instanceof Error ) {
743+ console . warn (
744+ `[Redis] Failed to read cache for controller rating daily stats (${ days } days):` ,
745+ error . message
746+ ) ;
747+ }
748+ }
749+
750+ try {
751+ const dailyStats = await mainDb
752+ . selectFrom ( 'controller_ratings' )
753+ . select ( [
754+ sql < string > `DATE(created_at)` . as ( 'date' ) ,
755+ ( eb ) => eb . fn . count < number > ( 'id' ) . as ( 'count' ) ,
756+ ( eb ) => eb . fn . avg < number > ( 'rating' ) . as ( 'avg_rating' ) ,
757+ ] )
758+ . where (
759+ 'created_at' ,
760+ '>=' ,
761+ sql < Date > `NOW() - INTERVAL '${ sql . raw ( days . toString ( ) ) } days'`
762+ )
763+ . groupBy ( sql `DATE(created_at)` )
764+ . orderBy ( sql `DATE(created_at)` , 'asc' )
765+ . execute ( ) ;
766+
767+ try {
768+ await redisConnection . set ( cacheKey , JSON . stringify ( dailyStats ) , 'EX' , 300 ) ;
769+ } catch ( error ) {
770+ if ( error instanceof Error ) {
771+ console . warn (
772+ `[Redis] Failed to set cache for controller rating daily stats (${ days } days):` ,
773+ error . message
774+ ) ;
775+ }
776+ }
777+
778+ return dailyStats ;
779+ } catch ( error ) {
780+ console . error ( 'Error fetching daily controller rating stats:' , error ) ;
781+ throw error ;
782+ }
783+ }
784+
627785export async function invalidateAllUsersCache ( ) {
628786 try {
629787 let cursor = '0' ;
0 commit comments