Add recent subscribers to admin dashboard

This commit is contained in:
Matt Lott 2015-04-07 15:00:50 -07:00
parent 603f9f9a37
commit 3c755d39e6
4 changed files with 139 additions and 18 deletions

View file

@ -26,9 +26,6 @@
height: 500px height: 500px
width: 100% width: 100%
// TODO: figure out why this is necessary
margin-bottom: 100px
.x.axis .x.axis
font-size: 9pt font-size: 9pt
path path

View file

@ -1,12 +1,13 @@
extends /templates/base extends /templates/base
block content block content
if !me.isAdmin() if !me.isAdmin()
div You must be logged in as an admin to view this page. div You must be logged in as an admin to view this page.
else else
if total === 0 if total === 0
h1 Fetching subscriptions data... h4 Fetching dashboard data...
else else
.container-fluid .container-fluid
.row .row
@ -37,15 +38,56 @@ block content
div *Stripe APIs do not return information about inactive subs. div *Stripe APIs do not return information about inactive subs.
br h2 Recent Subscribers
if !subscribers || subscribers.length < 1
table.table.table-condensed.concepts-table h4 Fetching recent subscribers...
else
table.table.table-condensed
thead
tr
th User Start
th Sub Start
th
th
//- th Name
th Email
th Hero
th Level
th Last Level
th Age
th Spoken
tbody
each subscriber in subscribers
tr
td= subscriber.user.dateCreated.substring(0, 10)
td= subscriber.start.substring(0, 10)
td
if subscriber.cancel
span= subscriber.cancel.substring(0, 10)
td
if subscriber.user.stripe.sponsorID
span Sponsored
//- td
//- a(href="/user/#{subscriber.user._id}")= subscriber.user.name || 'Anoner'
td= subscriber.user.emailLower
td= subscriber.hero
td= subscriber.level
td= subscriber.user.lastLevel
td= subscriber.user.ageRange
td= subscriber.user.preferredLanguage
h2 Subscriptions
if !subs || subs.length < 1
h4 Fetching subscriptions...
else
table.table.table-condensed
thead thead
tr tr
th Day th Day
th Total th Total
th Started th Started
th Cancelled th Cancelled
th Net
tbody tbody
each sub in subs each sub in subs
tr tr
@ -53,3 +95,4 @@ block content
td= sub.total td= sub.total
td= sub.started td= sub.started
td= sub.cancelled td= sub.cancelled
td= sub.started - sub.cancelled

View file

@ -1,6 +1,7 @@
RootView = require 'views/core/RootView' RootView = require 'views/core/RootView'
template = require 'templates/admin/analytics-subscriptions' template = require 'templates/admin/analytics-subscriptions'
RealTimeCollection = require 'collections/RealTimeCollection' ThangType = require 'models/ThangType'
User = require 'models/User'
# TODO: Add last N subscribers table # TODO: Add last N subscribers table
# TODO: Add revenue line # TODO: Add revenue line
@ -23,6 +24,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
context = super() context = super()
context.analytics = @analytics ? graphs: [] context.analytics = @analytics ? graphs: []
context.subs = _.cloneDeep(@subs ? []).reverse() context.subs = _.cloneDeep(@subs ? []).reverse()
context.subscribers = @subscribers ? []
context.total = @total ? 0 context.total = @total ? 0
context.cancelled = @cancelled ? 0 context.cancelled = @cancelled ? 0
context.monthlyChurn = @monthlyChurn ? 0.0 context.monthlyChurn = @monthlyChurn ? 0.0
@ -43,7 +45,28 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
refreshData: -> refreshData: ->
return unless me.isAdmin() return unless me.isAdmin()
@resetData() @resetData()
@getSubscribers()
@getSubscriptions()
getSubscribers: ->
options =
url: '/db/subscription/-/subscribers'
method: 'POST'
data: {maxCount: 30}
options.error = (model, response, options) =>
return if @destroyed
console.error 'Failed to get subscribers', response
options.success = (subscribers, response, options) =>
return if @destroyed
@subscribers = subscribers
for subscriber in @subscribers
subscriber.level = User.levelFromExp subscriber.user.points
if hero = subscriber.user.heroConfig?.thangType
subscriber.hero = slug for slug, original of ThangType.heroes when original is hero
@render?()
@supermodel.addRequestResource('get_subscribers', options, 0).load()
getSubscriptions: ->
options = options =
url: '/db/subscription/-/subscriptions' url: '/db/subscription/-/subscriptions'
method: 'GET' method: 'GET'
@ -66,7 +89,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
for day of subDayMap for day of subDayMap
@subs.push @subs.push
day: day day: day
started: subDayMap[day]['start'] started: subDayMap[day]['start'] or 0
cancelled: subDayMap[day]['cancel'] or 0 cancelled: subDayMap[day]['cancel'] or 0
@subs.sort (a, b) -> a.day.localeCompare(b.day) @subs.sort (a, b) -> a.day.localeCompare(b.day)
@ -78,9 +101,9 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
startedLastMonth += sub.started if @subs.length - i < 31 startedLastMonth += sub.started if @subs.length - i < 31
@monthlyChurn = @cancelled / startedLastMonth * 100.0 if startedLastMonth > 0 @monthlyChurn = @cancelled / startedLastMonth * 100.0 if startedLastMonth > 0
if @subs.length > 30 and @subs[@subs.length - 31].total > 0 if @subs.length > 30 and @subs[@subs.length - 31].total > 0
lastMonthTotal = @subs[@subs.length - 31].total startMonthTotal = @subs[@subs.length - 31].total
thisMonthTotal = @subs[@subs.length - 1].total endMonthTotal = @subs[@subs.length - 1].total
@monthlyGrowth = (thisMonthTotal - lastMonthTotal) / lastMonthTotal * 100 @monthlyGrowth = (endMonthTotal / startMonthTotal - 1) * 100
@updateAnalyticsGraphData() @updateAnalyticsGraphData()
@render?() @render?()
@supermodel.addRequestResource('get_subscriptions', options, 0).load() @supermodel.addRequestResource('get_subscriptions', options, 0).load()

View file

@ -1,6 +1,7 @@
# Not paired with a document in the DB, just handles coordinating between # Not paired with a document in the DB, just handles coordinating between
# the stripe property in the user with what's being stored in Stripe. # the stripe property in the user with what's being stored in Stripe.
mongoose = require 'mongoose'
async = require 'async' async = require 'async'
config = require '../../server_config' config = require '../../server_config'
Handler = require '../commons/Handler' Handler = require '../commons/Handler'
@ -23,9 +24,70 @@ class SubscriptionHandler extends Handler
console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'" console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'"
getByRelationship: (req, res, args...) -> getByRelationship: (req, res, args...) ->
return @getSubscribers(req, res) if args[1] is 'subscribers'
return @getSubscriptions(req, res) if args[1] is 'subscriptions' return @getSubscriptions(req, res) if args[1] is 'subscriptions'
super(arguments...) super(arguments...)
getSubscribers: (req, res) ->
return @sendForbiddenError(res) unless req.user and req.user.isAdmin()
maxReturnCount = req.body.maxCount or 20
# @subscribers ?= []
# return @sendSuccess(res, @subscribers) unless _.isEmpty(@subscribers)
@subscribers = []
subscriberIDs = []
customersProcessed = 0
nextBatch = (starting_after, done) =>
options = limit: 100
options.starting_after = starting_after if starting_after
stripe.customers.list options, (err, customers) =>
return done(err) if err
customersProcessed += customers.data.length
for customer in customers.data
break unless @subscribers.length < maxReturnCount
continue unless customer?.subscriptions?.data?.length > 0
for subscription in customer.subscriptions.data
continue unless subscription.plan.id is 'basic'
amount = subscription.plan.amount
if subscription?.discount?.coupon?
if subscription.discount.coupon.percent_off
amount = amount * (100 - subscription.discount.coupon.percent_off) / 100;
else if subscription.discount.coupon.amount_off
amount -= subscription.discount.coupon.amount_off
else if customer.discount?.coupon?
if customer.discount.coupon.percent_off
amount = amount * (100 - customer.discount.coupon.percent_off) / 100
else if customer.discount.coupon.amount_off
amount -= customer.discount.coupon.amount_off
continue unless amount > 0
subscriber = start: new Date(subscription.start * 1000)
if subscription.metadata?.id?
subscriber.userID = subscription.metadata.id
subscriberIDs.push subscription.metadata.id
if subscription.cancel_at_period_end
subscriber.cancel = new Date(subscription.canceled_at * 1000)
subscriber.end = new Date(subscription.current_period_end * 1000)
@subscribers.push(subscriber)
if customers.has_more and @subscribers.length < maxReturnCount
return nextBatch(customers.data[customers.data.length - 1].id, done)
else
return done()
nextBatch null, (err) =>
return @sendDatabaseError(res, err) if err
User.find {_id: {$in: subscriberIDs}}, (err, users) =>
return @sendDatabaseError(res, err) if err
for user in users
subscriber.user = user for subscriber in @subscribers when subscriber.userID is user.id
@sendSuccess(res, @subscribers)
getSubscriptions: (req, res) -> getSubscriptions: (req, res) ->
# Returns a list of active subscriptions # Returns a list of active subscriptions
# TODO: does not handle customers with 11+ active subscriptions # TODO: does not handle customers with 11+ active subscriptions
@ -54,7 +116,6 @@ class SubscriptionHandler extends Handler
for subscription in customer.subscriptions.data for subscription in customer.subscriptions.data
continue unless subscription.plan.id is 'basic' continue unless subscription.plan.id is 'basic'
amount = subscription.plan.amount amount = subscription.plan.amount
if subscription?.discount?.coupon? if subscription?.discount?.coupon?
if subscription.discount.coupon.percent_off if subscription.discount.coupon.percent_off
@ -72,7 +133,7 @@ class SubscriptionHandler extends Handler
sub = start: new Date(subscription.start * 1000) sub = start: new Date(subscription.start * 1000)
if subscription.cancel_at_period_end if subscription.cancel_at_period_end
sub.cancel = new Date(subscription.canceled_at * 1000) sub.cancel = new Date(subscription.canceled_at * 1000)
sub.end = new Date(sub.current_period_end * 1000) sub.end = new Date(subscription.current_period_end * 1000)
@subs.push(sub) @subs.push(sub)
# Can't fetch all the test Stripe data # Can't fetch all the test Stripe data
@ -84,9 +145,6 @@ class SubscriptionHandler extends Handler
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, @subs) @sendSuccess(res, @subs)
subscribeUser: (req, user, done) -> subscribeUser: (req, user, done) ->
if (not req.user) or req.user.isAnonymous() or user.isAnonymous() if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
return done({res: 'You must be signed in to subscribe.', code: 403}) return done({res: 'You must be signed in to subscribe.', code: 403})