2021-01-13 06:30:24 +03:00
const moment = require ( 'moment-timezone' ) ;
const Promise = require ( 'bluebird' ) ;
2021-01-13 12:42:16 +03:00
const dateFormat = 'YYYY-MM-DD HH:mm:ss' ;
class MembersStats {
2021-01-14 02:59:10 +03:00
/ * *
* @ param { Object } config
* @ param { Object } config . db - an instance holding knex connection to the database
* @ param { Object } config . settingsCache - an instance of the Ghost Settings Cache
* @ param { Boolean } config . isSQLite - flag identifying if storage is connected to SQLite
* /
2021-01-13 12:42:16 +03:00
constructor ( { db , settingsCache , isSQLite } ) {
this . _db = db ;
this . _settingsCache = settingsCache ;
this . _isSQLite = isSQLite ;
}
2021-01-13 06:30:24 +03:00
2021-01-13 12:42:16 +03:00
/ * *
* Fetches count of all members
* /
async getTotalMembers ( ) {
const result = await this . _db . knex . raw ( 'SELECT COUNT(id) AS total FROM members' ) ;
return this . _isSQLite ? result [ 0 ] . total : result [ 0 ] [ 0 ] . total ;
2021-01-13 06:30:24 +03:00
}
2021-01-13 12:42:16 +03:00
/ * *
*
* @ param { Number | String } days - number of days to fetch of 'all-time' to get for all existing records
* @ param { Number } totalMembers - number of registered members
* @ param { String } siteTimezone - site ' s current timezone
* /
async getTotalMembersInRange ( { days , totalMembers , siteTimezone } ) {
2021-01-13 06:30:24 +03:00
if ( days === 'all-time' ) {
return totalMembers ;
}
const startOfRange = moment . tz ( siteTimezone ) . subtract ( days - 1 , 'days' ) . startOf ( 'day' ) . utc ( ) . format ( dateFormat ) ;
2021-01-13 12:42:16 +03:00
const result = await this . _db . knex . raw ( 'SELECT COUNT(id) AS total FROM members WHERE created_at >= ?' , [ startOfRange ] ) ;
return this . _isSQLite ? result [ 0 ] . total : result [ 0 ] [ 0 ] . total ;
2021-01-13 06:30:24 +03:00
}
2021-01-13 12:42:16 +03:00
/ * *
* Fetches member signups for current day
*
* @ param { String } siteTimezone - site ' s current timezone
* /
async getNewMembersToday ( { siteTimezone } ) {
const startOfToday = moment . tz ( siteTimezone ) . startOf ( 'day' ) . utc ( ) . format ( dateFormat ) ;
const result = await this . _db . knex . raw ( 'SELECT count(id) AS total FROM members WHERE created_at >= ?' , [ startOfToday ] ) ;
return this . _isSQLite ? result [ 0 ] . total : result [ 0 ] [ 0 ] . total ;
}
/ * *
*
* @ param { Number | String } days - number of days to fetch of 'all-time' to get for all existing records
* @ param { Number } totalMembers - number of registered members
* @ param { String } siteTimezone - site ' s current timezone
* /
async getTotalMembersOnDatesInRange ( { days , totalMembers , siteTimezone } ) {
2021-01-13 06:30:24 +03:00
const startOfRange = moment . tz ( siteTimezone ) . subtract ( days - 1 , 'days' ) . startOf ( 'day' ) . utc ( ) . format ( dateFormat ) ;
2021-01-13 12:42:16 +03:00
const tzOffsetMins = moment . tz ( siteTimezone ) . utcOffset ( ) ;
2021-01-13 06:30:24 +03:00
let result ;
2021-01-13 12:42:16 +03:00
if ( this . _isSQLite ) {
2021-01-13 06:30:24 +03:00
const dateModifier = ` ${ Math . sign ( tzOffsetMins ) === - 1 ? '' : '+' } ${ tzOffsetMins } minutes ` ;
2021-01-13 12:42:16 +03:00
result = await this . _db . knex ( 'members' )
. select ( this . _db . knex . raw ( 'DATE(created_at, ?) AS created_at, COUNT(DATE(created_at, ?)) AS count' , [ dateModifier , dateModifier ] ) )
2021-01-13 06:30:24 +03:00
. where ( ( builder ) => {
if ( days !== 'all-time' ) {
builder . whereRaw ( 'created_at >= ?' , [ startOfRange ] ) ;
}
} ) . groupByRaw ( 'DATE(created_at, ?)' , [ dateModifier ] ) ;
} else {
const mins = Math . abs ( tzOffsetMins ) % 60 ;
const hours = ( Math . abs ( tzOffsetMins ) - mins ) / 60 ;
const utcOffset = ` ${ Math . sign ( tzOffsetMins ) === - 1 ? '-' : '+' } ${ hours } : ${ mins < 10 ? '0' : '' } ${ mins } ` ;
2021-01-13 12:42:16 +03:00
result = await this . _db . knex ( 'members' )
. select ( this . _db . knex . raw ( 'DATE(CONVERT_TZ(created_at, \'+00:00\', ?)) AS created_at, COUNT(CONVERT_TZ(created_at, \'+00:00\', ?)) AS count' , [ utcOffset , utcOffset ] ) )
2021-01-13 06:30:24 +03:00
. where ( ( builder ) => {
if ( days !== 'all-time' ) {
builder . whereRaw ( 'created_at >= ?' , [ startOfRange ] ) ;
}
} )
. groupByRaw ( 'DATE(CONVERT_TZ(created_at, \'+00:00\', ?))' , [ utcOffset ] ) ;
}
// sql doesn't return rows with a 0 count so we build an object
// with sparse results to reference by date rather than performing
// multiple finds across an array
const resultObject = { } ;
result . forEach ( ( row ) => {
resultObject [ moment ( row . created _at ) . format ( 'YYYY-MM-DD' ) ] = row . count ;
} ) ;
// loop over every date in the range so we can return a contiguous range object
const totalInRange = Object . values ( resultObject ) . reduce ( ( acc , value ) => acc + value , 0 ) ;
let runningTotal = totalMembers - totalInRange ;
let currentRangeDate ;
if ( days === 'all-time' ) {
// start from the date of first created member
currentRangeDate = moment ( moment ( result [ 0 ] . created _at ) . format ( 'YYYY-MM-DD' ) ) . tz ( siteTimezone ) ;
} else {
currentRangeDate = moment . tz ( siteTimezone ) . subtract ( days - 1 , 'days' ) ;
}
let endDate = moment . tz ( siteTimezone ) . add ( 1 , 'hour' ) ;
const output = { } ;
while ( currentRangeDate . isBefore ( endDate ) ) {
let dateStr = currentRangeDate . format ( 'YYYY-MM-DD' ) ;
runningTotal += resultObject [ dateStr ] || 0 ;
output [ dateStr ] = runningTotal ;
currentRangeDate = currentRangeDate . add ( 1 , 'day' ) ;
}
return output ;
}
2021-01-13 12:42:16 +03:00
/ * *
* Fetches member ' s signup statistics
*
* @ param { Number | String } days - number of days to fetch of 'all-time' to get for all existing records
* /
async fetch ( days ) {
const siteTimezone = this . _settingsCache . get ( 'timezone' ) ;
const totalMembers = await this . getTotalMembers ( ) ;
// perform final calculations in parallel
const results = await Promise . props ( {
total : totalMembers ,
total _in _range : this . getTotalMembersInRange ( { days , totalMembers , siteTimezone } ) ,
total _on _date : this . getTotalMembersOnDatesInRange ( { days , totalMembers , siteTimezone } ) ,
new _today : this . getNewMembersToday ( { siteTimezone } )
} ) ;
2021-01-13 06:30:24 +03:00
2021-01-13 12:42:16 +03:00
return results ;
}
}
2021-01-13 06:30:24 +03:00
2021-01-13 12:42:16 +03:00
module . exports = MembersStats ;