11import { redis } from './clientInstance'
2- import { getPaymentList } from 'redis/paymentCache'
3- import { ChartData , PeriodData , DashboardData , Payment , ButtonData , PaymentDataByButton , ChartColor } from './types'
2+ import { getCachedWeekKeysForUser , getPaymentsForWeekKey , getPaymentStream } from 'redis/paymentCache'
3+ import { ChartData , DashboardData , Payment , ButtonData , PaymentDataByButton , ChartColor , PeriodData , ButtonDisplayData } from './types'
44import { Prisma } from '@prisma/client'
55import moment , { DurationInputArg2 } from 'moment'
66import { XEC_NETWORK_ID , BCH_NETWORK_ID } from 'constants/index'
7- import { fetchPaybuttonArrayByUserId } from 'services/paybuttonService'
87import { QuoteValues } from 'services/priceService'
98
109// USERID:dashboard
@@ -16,30 +15,6 @@ const getChartLabels = function (n: number, periodString: string, formatString =
1615 return [ ...new Array ( n ) ] . map ( ( _ , idx ) => moment ( ) . startOf ( 'day' ) . subtract ( idx , periodString as DurationInputArg2 ) . format ( formatString ) ) . reverse ( )
1716}
1817
19- interface RevenuePaymentData {
20- revenue : QuoteValues [ ]
21- payments : number [ ]
22- }
23-
24- const getChartRevenuePaymentData = function ( n : number , periodString : string , paymentList : Payment [ ] ) : RevenuePaymentData {
25- const revenueArray : QuoteValues [ ] = [ ]
26- const paymentsArray : number [ ] = [ ]
27- const _ = [ ...new Array ( n ) ]
28- _ . forEach ( ( _ , idx ) => {
29- const lowerThreshold = moment ( ) . subtract ( idx , periodString as DurationInputArg2 ) . startOf ( periodString === 'months' ? 'month' : 'day' )
30- const upperThreshold = moment ( ) . subtract ( idx , periodString as DurationInputArg2 ) . endOf ( periodString === 'months' ? 'month' : 'day' )
31- const periodPaymentList = filterLastPayments ( lowerThreshold , upperThreshold , paymentList )
32- const revenue = sumPaymentsValue ( periodPaymentList )
33- const paymentCount = periodPaymentList . length
34- revenueArray . push ( revenue )
35- paymentsArray . push ( paymentCount )
36- } )
37- return {
38- revenue : revenueArray . reverse ( ) ,
39- payments : paymentsArray . reverse ( )
40- }
41- }
42-
4318const filterLastPayments = function ( lowerThreshold : moment . Moment , upperThreshold : moment . Moment , paymentList : Payment [ ] ) : Payment [ ] {
4419 return paymentList . filter ( ( p ) => {
4520 const tMoment = moment ( p . timestamp * 1000 )
@@ -64,27 +39,24 @@ const getChartData = function (n: number, periodString: string, dataArray: numbe
6439 }
6540}
6641
67- const getPeriodData = function ( n : number , periodString : string , paymentList : Payment [ ] , borderColor : ChartColor , formatString = 'M/D' ) : PeriodData {
68- const revenuePaymentData = getChartRevenuePaymentData ( n , periodString , paymentList )
69- const revenue = getChartData ( n , periodString , revenuePaymentData . revenue , borderColor . revenue , formatString )
70- const payments = getChartData ( n , periodString , revenuePaymentData . payments , borderColor . payments , formatString )
71- const buttons = getButtonPaymentData ( n , periodString , paymentList )
72- const totalRevenue = ( revenue . datasets [ 0 ] . data as QuoteValues [ ] ) . reduce ( sumQuoteValues , { usd : new Prisma . Decimal ( 0 ) , cad : new Prisma . Decimal ( 0 ) } )
73- const totalPayments = ( payments . datasets [ 0 ] . data as any ) . reduce ( ( a : number , b : number ) => a + b , 0 )
42+ function getOldestDateKey ( keys : string [ ] ) : string {
43+ const keyDatePairs = keys . map ( k => [ k , k . split ( ':' ) . slice ( - 2 ) . map ( Number ) ] as [ string , [ number , number ] ] )
44+ keyDatePairs . sort ( ( a , b ) => {
45+ const [ aYear , aWeek ] = a [ 1 ]
46+ const [ bYear , bWeek ] = b [ 1 ]
7447
75- return {
76- revenue,
77- payments,
78- totalRevenue,
79- totalPayments,
80- buttons
81- }
48+ // compare year first, then week
49+ return ( aYear !== bYear ? aYear - bYear : aWeek - bWeek )
50+ } )
51+ return keyDatePairs [ 0 ] [ 0 ]
8252}
8353
84- const getNumberOfMonths = function ( paymentList : Payment [ ] ) : number {
85- if ( paymentList . length === 0 ) return 0
86- const oldestTimestamp = Math . min ( ...paymentList . map ( p => p . timestamp )
87- )
54+ const getNumberOfMonths = async function ( userId : string ) : Promise < number > {
55+ const weekKeys = await getCachedWeekKeysForUser ( userId )
56+ if ( weekKeys . length === 0 ) return 0
57+ const oldestKey = getOldestDateKey ( weekKeys )
58+ const oldestPayments = await getPaymentsForWeekKey ( oldestKey )
59+ const oldestTimestamp = Math . min ( ...oldestPayments . map ( p => p . timestamp ) )
8860 const oldestDate = moment ( oldestTimestamp * 1000 )
8961 const today = moment ( )
9062 const floatDiff = today . diff ( oldestDate , 'months' , true )
@@ -147,32 +119,243 @@ export const sumPaymentsValue = function (paymentList: Payment[]): QuoteValues {
147119 return ret
148120}
149121
150- export const getUserDashboardData = async function ( userId : string ) : Promise < DashboardData > {
151- let dashboardData = await getCachedDashboardData ( userId )
152- if ( dashboardData === null ) {
153- const buttons = await fetchPaybuttonArrayByUserId ( userId )
154- const paymentList = await getPaymentList ( userId )
155-
156- const totalRevenue = sumPaymentsValue ( paymentList )
157- const nMonthsTotal = getNumberOfMonths ( paymentList )
158-
159- const thirtyDays : PeriodData = getPeriodData ( 30 , 'days' , paymentList , { revenue : '#66fe91' , payments : '#669cfe' } )
160- const sevenDays : PeriodData = getPeriodData ( 7 , 'days' , paymentList , { revenue : '#66fe91' , payments : '#669cfe' } )
161- const year : PeriodData = getPeriodData ( 12 , 'months' , paymentList , { revenue : '#66fe91' , payments : '#669cfe' } , 'MMM' )
162- const all : PeriodData = getPeriodData ( nMonthsTotal , 'months' , paymentList , { revenue : '#66fe91' , payments : '#669cfe' } , 'MMM YYYY' )
163-
164- dashboardData = {
165- thirtyDays,
166- sevenDays,
167- year,
168- all,
169- paymentList,
170- total : {
171- revenue : totalRevenue ,
172- payments : paymentList . length ,
173- buttons : buttons . length
122+ const generateDashboardDataFromStream = async function (
123+ paymentStream : AsyncGenerator < Payment > ,
124+ nMonthsTotal : number ,
125+ borderColor : ChartColor
126+ ) : Promise < DashboardData > {
127+ // Initialize accumulators for periods
128+ const revenueAccumulators = createRevenueAccumulators ( nMonthsTotal )
129+ const paymentCounters = createPaymentCounters ( nMonthsTotal )
130+ const buttonDataAccumulators = createButtonDataAccumulators ( )
131+
132+ const today = moment ( ) . startOf ( 'day' )
133+ const monthStart = moment ( ) . startOf ( 'month' )
134+ const thresholds = createThresholds ( today , monthStart , nMonthsTotal )
135+
136+ // Process all payments
137+ for await ( const payment of paymentStream ) {
138+ const paymentTime = moment ( payment . timestamp * 1000 )
139+
140+ // Process button data and assign to relevant periods
141+ payment . buttonDisplayDataList . forEach ( ( button ) => {
142+ processButtonData ( button , payment , paymentTime , buttonDataAccumulators , thresholds )
143+ } )
144+
145+ // Accumulate period data
146+ const periods = [ 'thirtyDays' , 'sevenDays' , 'year' , 'all' ] as const
147+ periods . forEach ( ( period ) => {
148+ if ( paymentTime . isSameOrAfter ( thresholds [ period ] ) ) {
149+ const index =
150+ period === 'thirtyDays' || period === 'sevenDays'
151+ ? today . diff ( paymentTime , 'days' )
152+ : monthStart . diff ( paymentTime , 'months' )
153+ if ( index < revenueAccumulators [ period ] . length ) {
154+ revenueAccumulators [ period ] [ index ] = sumQuoteValues ( revenueAccumulators [ period ] [ index ] , payment . values )
155+ paymentCounters [ period ] [ index ] += 1
156+ }
174157 }
158+ } )
159+ }
160+
161+ reverseAccumulators ( revenueAccumulators , paymentCounters )
162+
163+ // Generate PeriodData for each period
164+ const thirtyDays = createPeriodData (
165+ 30 ,
166+ 'days' ,
167+ revenueAccumulators . thirtyDays ,
168+ paymentCounters . thirtyDays ,
169+ buttonDataAccumulators . thirtyDays ,
170+ borderColor . revenue ,
171+ borderColor . payments
172+ )
173+
174+ const sevenDays = createPeriodData (
175+ 7 ,
176+ 'days' ,
177+ revenueAccumulators . sevenDays ,
178+ paymentCounters . sevenDays ,
179+ buttonDataAccumulators . sevenDays ,
180+ borderColor . revenue ,
181+ borderColor . payments
182+ )
183+
184+ const year = createPeriodData (
185+ 12 ,
186+ 'months' ,
187+ revenueAccumulators . year ,
188+ paymentCounters . year ,
189+ buttonDataAccumulators . year ,
190+ borderColor . revenue ,
191+ borderColor . payments ,
192+ 'MMM'
193+ )
194+
195+ const all = createPeriodData (
196+ nMonthsTotal ,
197+ 'months' ,
198+ revenueAccumulators . all ,
199+ paymentCounters . all ,
200+ buttonDataAccumulators . all ,
201+ borderColor . revenue ,
202+ borderColor . payments ,
203+ 'MMM YYYY'
204+ )
205+
206+ return {
207+ thirtyDays,
208+ sevenDays,
209+ year,
210+ all,
211+ total : {
212+ revenue : all . totalRevenue ,
213+ payments : all . totalPayments ,
214+ buttons : Object . keys ( buttonDataAccumulators . all ) . length
175215 }
216+ }
217+ }
218+
219+ interface PeriodRevenueAccumulators {
220+ thirtyDays : QuoteValues [ ]
221+ sevenDays : QuoteValues [ ]
222+ year : QuoteValues [ ]
223+ all : QuoteValues [ ]
224+ }
225+
226+ function createRevenueAccumulators ( nMonthsTotal : number ) : PeriodRevenueAccumulators {
227+ return {
228+ thirtyDays : Array ( 30 ) . fill ( { usd : new Prisma . Decimal ( 0 ) , cad : new Prisma . Decimal ( 0 ) } ) ,
229+ sevenDays : Array ( 7 ) . fill ( { usd : new Prisma . Decimal ( 0 ) , cad : new Prisma . Decimal ( 0 ) } ) ,
230+ year : Array ( 12 ) . fill ( { usd : new Prisma . Decimal ( 0 ) , cad : new Prisma . Decimal ( 0 ) } ) ,
231+ all : Array ( nMonthsTotal ) . fill ( { usd : new Prisma . Decimal ( 0 ) , cad : new Prisma . Decimal ( 0 ) } )
232+ }
233+ }
234+
235+ interface PeriodPaymentCounters {
236+ thirtyDays : number [ ]
237+ sevenDays : number [ ]
238+ year : number [ ]
239+ all : number [ ]
240+ }
241+
242+ function createPaymentCounters ( nMonthsTotal : number ) : PeriodPaymentCounters {
243+ return {
244+ thirtyDays : Array ( 30 ) . fill ( 0 ) ,
245+ sevenDays : Array ( 7 ) . fill ( 0 ) ,
246+ year : Array ( 12 ) . fill ( 0 ) ,
247+ all : Array ( nMonthsTotal ) . fill ( 0 )
248+ }
249+ }
250+
251+ interface PeriodButtonDataAccumulators {
252+ thirtyDays : PaymentDataByButton
253+ sevenDays : PaymentDataByButton
254+ year : PaymentDataByButton
255+ all : PaymentDataByButton
256+ }
257+
258+ function createButtonDataAccumulators ( ) : PeriodButtonDataAccumulators {
259+ return {
260+ thirtyDays : { } ,
261+ sevenDays : { } ,
262+ year : { } ,
263+ all : { }
264+ }
265+ }
266+
267+ interface PeriodThresholds {
268+ thirtyDays : moment . Moment
269+ sevenDays : moment . Moment
270+ year : moment . Moment
271+ all : moment . Moment
272+ }
273+
274+ function createThresholds ( today : moment . Moment , monthStart : moment . Moment , nMonthsTotal : number ) : PeriodThresholds {
275+ return {
276+ thirtyDays : today . clone ( ) . subtract ( 30 , 'days' ) ,
277+ sevenDays : today . clone ( ) . subtract ( 7 , 'days' ) ,
278+ year : monthStart . clone ( ) . subtract ( 12 , 'months' ) ,
279+ all : monthStart . clone ( ) . subtract ( nMonthsTotal , 'months' )
280+ }
281+ }
282+
283+ function processButtonData (
284+ button : ButtonDisplayData ,
285+ payment : Payment ,
286+ paymentTime : moment . Moment ,
287+ buttonDataAccumulators : ReturnType < typeof createButtonDataAccumulators > ,
288+ thresholds : ReturnType < typeof createThresholds >
289+ ) : void {
290+ const periods = [ 'thirtyDays' , 'sevenDays' , 'year' , 'all' ] as const
291+ periods . forEach ( ( period ) => {
292+ if ( paymentTime . isSameOrAfter ( thresholds [ period ] ) ) {
293+ if ( buttonDataAccumulators [ period ] [ button . id ] === undefined ) {
294+ buttonDataAccumulators [ period ] [ button . id ] = {
295+ displayData : {
296+ ...button ,
297+ isXec : payment . networkId === XEC_NETWORK_ID ,
298+ isBch : payment . networkId === BCH_NETWORK_ID ,
299+ lastPayment : payment . timestamp
300+ } ,
301+ total : {
302+ revenue : payment . values ,
303+ payments : 1
304+ }
305+ }
306+ } else {
307+ const buttonData = buttonDataAccumulators [ period ] [ button . id ]
308+ buttonData . total . revenue = sumQuoteValues ( buttonData . total . revenue , payment . values )
309+ buttonData . total . payments += 1
310+ buttonData . displayData . lastPayment = Math . max (
311+ buttonData . displayData . lastPayment ?? 0 ,
312+ payment . timestamp
313+ )
314+ }
315+ }
316+ } )
317+ }
318+
319+ function reverseAccumulators (
320+ revenueAccumulators : ReturnType < typeof createRevenueAccumulators > ,
321+ paymentCounters : ReturnType < typeof createPaymentCounters >
322+ ) : void {
323+ Object . keys ( revenueAccumulators ) . forEach ( ( key ) => {
324+ revenueAccumulators [ key as keyof typeof revenueAccumulators ] . reverse ( )
325+ paymentCounters [ key as keyof typeof paymentCounters ] . reverse ( )
326+ } )
327+ }
328+
329+ function createPeriodData (
330+ periodLength : number ,
331+ periodUnit : string ,
332+ revenueData : QuoteValues [ ] ,
333+ paymentData : number [ ] ,
334+ buttonData : PaymentDataByButton ,
335+ revenueColor : string ,
336+ paymentColor : string ,
337+ labelFormat = 'M/D'
338+ ) : PeriodData {
339+ return {
340+ revenue : getChartData ( periodLength , periodUnit , revenueData , revenueColor , labelFormat ) ,
341+ payments : getChartData ( periodLength , periodUnit , paymentData , paymentColor , labelFormat ) ,
342+ totalRevenue : revenueData . reduce ( sumQuoteValues , { usd : new Prisma . Decimal ( 0 ) , cad : new Prisma . Decimal ( 0 ) } ) ,
343+ totalPayments : paymentData . reduce ( ( a , b ) => a + b , 0 ) ,
344+ buttons : buttonData
345+ }
346+ }
347+
348+ export const getUserDashboardData = async function ( userId : string ) : Promise < DashboardData > {
349+ const dashboardData = await getCachedDashboardData ( userId )
350+ if ( dashboardData === null ) {
351+ const nMonthsTotal = await getNumberOfMonths ( userId )
352+ const paymentStream = getPaymentStream ( userId )
353+
354+ const dashboardData = await generateDashboardDataFromStream (
355+ paymentStream ,
356+ nMonthsTotal ,
357+ { revenue : '#66fe91' , payments : '#669cfe' }
358+ )
176359 await cacheDashboardData ( userId , dashboardData ) // WIP SET THIS NULL ON UPDATE BUTTONS & WS
177360 return dashboardData
178361 }
@@ -182,7 +365,6 @@ export const getUserDashboardData = async function (userId: string): Promise<Das
182365export const cacheDashboardData = async ( userId : string , dashboardData : DashboardData ) : Promise < void > => {
183366 const key = getDashboardSummaryKey ( userId )
184367 const {
185- paymentList,
186368 ...cachable
187369 } = dashboardData
188370 await redis . set ( key , JSON . stringify ( cachable ) )
0 commit comments