ghost 4.42.1 → 4.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/content/themes/casper/package.json +2 -3
  2. package/content/themes/casper/partials/post-card.hbs +1 -1
  3. package/core/built/assets/{ghost-dark-a93afb20027060d760ac6d78f115a76f.css → ghost-dark-1933079797e24ccb8839657020830be5.css} +1 -1
  4. package/core/built/assets/{ghost.min-20096eef632760c3a2906e243adbd24b.js → ghost.min-2a278873d60d6a13a4c05a396e5bed5e.js} +336 -278
  5. package/core/built/assets/{ghost.min-ce35ef1b76d9a943ab912c076773b132.css → ghost.min-38f3c38c0c6a1864f57079b068a0b0ce.css} +1 -1
  6. package/core/frontend/apps/amp/lib/helpers/amp_analytics.js +1 -1
  7. package/core/frontend/apps/amp/lib/helpers/amp_components.js +1 -1
  8. package/core/frontend/apps/amp/lib/helpers/amp_content.js +1 -1
  9. package/core/frontend/apps/amp/lib/helpers/amp_style.js +1 -1
  10. package/core/frontend/apps/amp/lib/router.js +6 -5
  11. package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +1 -1
  12. package/core/frontend/apps/private-blogging/lib/router.js +2 -2
  13. package/core/frontend/helpers/asset.js +1 -1
  14. package/core/frontend/helpers/author.js +1 -1
  15. package/core/frontend/helpers/authors.js +1 -1
  16. package/core/frontend/helpers/body_class.js +1 -1
  17. package/core/frontend/helpers/cancel_link.js +1 -1
  18. package/core/frontend/helpers/concat.js +1 -1
  19. package/core/frontend/helpers/content.js +1 -1
  20. package/core/frontend/helpers/date.js +1 -1
  21. package/core/frontend/helpers/encode.js +1 -1
  22. package/core/frontend/helpers/excerpt.js +1 -1
  23. package/core/frontend/helpers/facebook_url.js +1 -1
  24. package/core/frontend/helpers/foreach.js +2 -2
  25. package/core/frontend/helpers/get.js +1 -1
  26. package/core/frontend/helpers/ghost_foot.js +1 -1
  27. package/core/frontend/helpers/ghost_head.js +1 -1
  28. package/core/frontend/helpers/lang.js +1 -1
  29. package/core/frontend/helpers/link.js +1 -1
  30. package/core/frontend/helpers/link_class.js +1 -1
  31. package/core/frontend/helpers/match.js +1 -1
  32. package/core/frontend/helpers/navigation.js +1 -1
  33. package/core/frontend/helpers/pagination.js +1 -1
  34. package/core/frontend/helpers/plural.js +1 -1
  35. package/core/frontend/helpers/post_class.js +1 -1
  36. package/core/frontend/helpers/prev_post.js +6 -5
  37. package/core/frontend/helpers/products.js +1 -1
  38. package/core/frontend/helpers/reading_time.js +2 -2
  39. package/core/frontend/helpers/t.js +1 -1
  40. package/core/frontend/helpers/tags.js +1 -1
  41. package/core/frontend/helpers/tiers.js +1 -1
  42. package/core/frontend/helpers/title.js +1 -1
  43. package/core/frontend/helpers/twitter_url.js +1 -1
  44. package/core/frontend/helpers/url.js +1 -1
  45. package/core/frontend/meta/url.js +4 -4
  46. package/core/{server/data/schema → frontend/services/data}/checks.js +4 -4
  47. package/core/frontend/services/{routing/helpers → data}/entry-lookup.js +3 -3
  48. package/core/frontend/services/{routing/helpers → data}/fetch-data.js +3 -3
  49. package/core/frontend/services/data/index.js +5 -0
  50. package/core/frontend/services/{rendering.js → handlebars.js} +2 -1
  51. package/core/frontend/services/helpers/handlebars.js +1 -1
  52. package/core/frontend/services/proxy.js +2 -4
  53. package/core/frontend/services/{routing/helpers → rendering}/context.js +0 -0
  54. package/core/frontend/services/{routing/helpers → rendering}/error.js +0 -0
  55. package/core/frontend/services/{routing/helpers → rendering}/format-response.js +1 -1
  56. package/core/frontend/services/{routing/helpers → rendering}/index.js +0 -8
  57. package/core/frontend/services/{routing/helpers → rendering}/render-entries.js +1 -1
  58. package/core/frontend/services/{routing/helpers → rendering}/render-entry.js +1 -1
  59. package/core/frontend/services/{routing/helpers → rendering}/renderer.js +1 -1
  60. package/core/frontend/services/{routing/helpers → rendering}/secure.js +0 -0
  61. package/core/frontend/services/{routing/helpers → rendering}/templates.js +2 -2
  62. package/core/frontend/services/routing/CollectionRouter.js +1 -1
  63. package/core/frontend/services/routing/controllers/channel.js +9 -9
  64. package/core/frontend/services/routing/controllers/collection.js +9 -9
  65. package/core/frontend/services/routing/controllers/email-post.js +5 -6
  66. package/core/frontend/services/routing/controllers/entry.js +6 -6
  67. package/core/frontend/services/routing/controllers/preview.js +5 -6
  68. package/core/frontend/services/routing/controllers/rss.js +4 -3
  69. package/core/frontend/services/routing/controllers/static.js +5 -5
  70. package/core/frontend/services/routing/controllers/unsubscribe.js +2 -2
  71. package/core/frontend/services/routing/index.js +0 -4
  72. package/core/frontend/web/middleware/error-handler.js +2 -2
  73. package/core/server/api/canary/stats.js +9 -0
  74. package/core/server/api/canary/utils/serializers/output/members.js +5 -0
  75. package/core/server/api/canary/utils/validators/input/index.js +6 -0
  76. package/core/server/api/shared/http.js +52 -51
  77. package/core/server/data/exporter/table-lists.js +1 -0
  78. package/core/server/data/migrations/utils.js +33 -1
  79. package/core/server/data/migrations/versions/4.42/2022-03-21-17-17-add.js +5 -0
  80. package/core/server/data/migrations/versions/4.43/2022-03-28-19-26-recreate-newsletter-table.js +29 -0
  81. package/core/server/data/migrations/versions/4.43/2022-03-29-14-45-add-members-newsletters-table.js +7 -0
  82. package/core/server/data/migrations/versions/4.43/2022-04-01-10-13-add-post-newsletter-relation.js +108 -0
  83. package/core/server/data/migrations/versions/4.43/2022-04-06-09-47-add-type-column-to-paid-subscription-events.js +7 -0
  84. package/core/server/data/migrations/versions/4.43/2022-04-06-14-56-add-email-newsletter-relation.js +8 -0
  85. package/core/server/data/migrations/versions/4.43/2022-04-08-10-45-add-subscription-id-to-mrr-events.js +7 -0
  86. package/core/server/data/schema/commands.js +6 -1
  87. package/core/server/data/schema/index.js +0 -1
  88. package/core/server/data/schema/schema.js +34 -16
  89. package/core/server/models/base/bookshelf.js +1 -1
  90. package/core/server/models/base/plugins/crud.js +8 -0
  91. package/core/server/models/member.js +18 -1
  92. package/core/server/models/newsletter.js +35 -1
  93. package/core/server/models/post.js +4 -1
  94. package/core/server/services/auth/setup.js +4 -1
  95. package/core/server/services/members/api.js +3 -1
  96. package/core/server/services/members/middleware.js +13 -3
  97. package/core/server/services/members/utils.js +13 -1
  98. package/core/server/services/newsletters/index.js +10 -0
  99. package/core/server/services/newsletters/service.js +24 -0
  100. package/core/server/services/slack.js +11 -3
  101. package/core/server/services/stats/lib/members-stats-service.js +30 -34
  102. package/core/server/services/stats/lib/mrr-stats-service.js +154 -0
  103. package/core/server/services/stats/service.js +3 -1
  104. package/core/server/services/stripe/service.js +1 -0
  105. package/core/server/web/admin/views/default-prod.html +3 -3
  106. package/core/server/web/admin/views/default.html +3 -3
  107. package/core/server/web/api/canary/admin/routes.js +1 -0
  108. package/core/shared/config/defaults.json +2 -2
  109. package/package.json +33 -33
  110. package/yarn.lock +352 -277
  111. package/content/themes/casper/assets/css/csscomb.json +0 -240
@@ -1,4 +1,4 @@
1
- const {DateTime} = require('luxon');
1
+ const moment = require('moment');
2
2
 
3
3
  class MembersStatsService {
4
4
  constructor({db}) {
@@ -28,8 +28,8 @@ class MembersStatsService {
28
28
  }
29
29
 
30
30
  /**
31
- * Get the member deltas by status for all days (from new to old)
32
- * @returns {Promise<MemberStatusDelta[]>} The deltas of paid, free and comped users per day, sorted from new to old
31
+ * Get the member deltas by status for all days, sorted ascending
32
+ * @returns {Promise<MemberStatusDelta[]>} The deltas of paid, free and comped users per day, sorted ascending
33
33
  */
34
34
  async fetchAllStatusDeltas() {
35
35
  const knex = this.db.knex;
@@ -54,7 +54,7 @@ class MembersStatsService {
54
54
  ELSE 0 END
55
55
  ) as free_delta`))
56
56
  .groupByRaw('DATE(created_at)')
57
- .orderByRaw('DATE(created_at) DESC');
57
+ .orderByRaw('DATE(created_at)');
58
58
  return rows;
59
59
  }
60
60
 
@@ -69,22 +69,26 @@ class MembersStatsService {
69
69
  const totals = await this.getCount();
70
70
  let {paid, free, comped} = totals;
71
71
 
72
- // Get today in UTC (default timezone for Luxon)
73
- const today = DateTime.local().toISODate();
72
+ // Get today in UTC (default timezone)
73
+ const today = moment().format('YYYY-MM-DD');
74
74
 
75
75
  const cumulativeResults = [];
76
- for (const row of rows) {
76
+
77
+ // Loop in reverse order (needed to have correct sorted result)
78
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
79
+ const row = rows[i];
80
+
77
81
  // Convert JSDates to YYYY-MM-DD (in UTC)
78
- const date = DateTime.fromJSDate(row.date).toISODate();
82
+ const date = moment(row.date).format('YYYY-MM-DD');
79
83
  if (date > today) {
80
84
  // Skip results that are in the future (fix for invalid events)
81
85
  continue;
82
86
  }
83
87
  cumulativeResults.unshift({
84
88
  date,
85
- paid,
86
- free,
87
- comped,
89
+ paid: Math.max(0, paid),
90
+ free: Math.max(0, free),
91
+ comped: Math.max(0, comped),
88
92
 
89
93
  // Deltas
90
94
  paid_subscribed: row.paid_subscribed,
@@ -92,36 +96,28 @@ class MembersStatsService {
92
96
  });
93
97
 
94
98
  // Update current counts
95
- paid = Math.max(0, paid - row.paid_subscribed + row.paid_canceled);
96
- free = Math.max(0, free - row.free_delta);
97
- comped = Math.max(0, comped - row.comped_delta);
99
+ paid -= row.paid_subscribed - row.paid_canceled;
100
+ free -= row.free_delta;
101
+ comped -= row.comped_delta;
98
102
  }
99
103
 
100
- // Always make sure we have at least one result
101
- if (cumulativeResults.length === 0) {
102
- cumulativeResults.push({
103
- date: today,
104
- paid,
105
- free,
106
- comped,
104
+ // Now also add the oldest day we have left over (this one will be zero, which is also needed as a data point for graphs)
105
+ const oldestDate = rows.length > 0 ? moment(rows[0].date).add(-1, 'days').format('YYYY-MM-DD') : today;
107
106
 
108
- // Deltas
109
- paid_subscribed: 0,
110
- paid_canceled: 0
111
- });
112
- }
107
+ cumulativeResults.unshift({
108
+ date: oldestDate,
109
+ paid: Math.max(0, paid),
110
+ free: Math.max(0, free),
111
+ comped: Math.max(0, comped),
112
+
113
+ // Deltas
114
+ paid_subscribed: 0,
115
+ paid_canceled: 0
116
+ });
113
117
 
114
118
  return {
115
119
  data: cumulativeResults,
116
120
  meta: {
117
- pagination: {
118
- page: 1,
119
- limit: 'all',
120
- pages: 1,
121
- total: cumulativeResults.length,
122
- next: null,
123
- prev: null
124
- },
125
121
  totals
126
122
  }
127
123
  };
@@ -0,0 +1,154 @@
1
+ const moment = require('moment');
2
+
3
+ class MrrStatsService {
4
+ constructor({db}) {
5
+ this.db = db;
6
+ }
7
+
8
+ /**
9
+ * Get the current total MRR, grouped by currency (ascending order)
10
+ * @returns {Promise<MrrByCurrency[]>}
11
+ */
12
+ async getCurrentMrr() {
13
+ const knex = this.db.knex;
14
+ const rows = await knex('members_stripe_customers_subscriptions')
15
+ .select(knex.raw(`plan_currency as currency`))
16
+ .select(knex.raw(`SUM(
17
+ CASE WHEN plan_interval = 'year' THEN
18
+ FLOOR(plan_amount / 12)
19
+ ELSE
20
+ plan_amount
21
+ END
22
+ ) AS mrr`))
23
+ .whereIn('status', ['active', 'unpaid', 'past_due'])
24
+ .where('cancel_at_period_end', 0)
25
+ .groupBy('plan_currency')
26
+ .orderBy('currency');
27
+
28
+ if (rows.length === 0) {
29
+ // Add a USD placeholder to always have at least one currency
30
+ rows.push({
31
+ currency: 'usd',
32
+ mrr: 0
33
+ });
34
+ }
35
+
36
+ return rows;
37
+ }
38
+
39
+ /**
40
+ * Get the MRR deltas for all days (from old to new), grouped by currency (ascending alphabetically)
41
+ * @returns {Promise<MrrDelta[]>} The deltas sorted from new to old
42
+ */
43
+ async fetchAllDeltas() {
44
+ const knex = this.db.knex;
45
+ const rows = await knex('members_paid_subscription_events')
46
+ .select('currency')
47
+ // In SQLite, DATE(created_at) would map to a string value, while DATE(created_at) would map to a JSDate object in MySQL
48
+ // That is why we need the cast here (to have some consistency)
49
+ .select(knex.raw('CAST(DATE(created_at) as CHAR) as date'))
50
+ .select(knex.raw(`SUM(mrr_delta) as delta`))
51
+ .groupByRaw('CAST(DATE(created_at) as CHAR), currency')
52
+ .orderByRaw('CAST(DATE(created_at) as CHAR), currency');
53
+ return rows;
54
+ }
55
+
56
+ /**
57
+ * Returns a list of the MRR history for each day and currency, including the current MRR per currency as meta data.
58
+ * The respons is in ascending date order, and currencies for the same date are always in ascending order.
59
+ * @returns {Promise<MrrHistory>}
60
+ */
61
+ async getHistory() {
62
+ // Fetch current total amounts and start counting from there
63
+ const totals = await this.getCurrentMrr();
64
+
65
+ const rows = await this.fetchAllDeltas();
66
+
67
+ // Get today in UTC (default timezone)
68
+ const today = moment().format('YYYY-MM-DD');
69
+
70
+ const results = [];
71
+
72
+ // Create a map of the totals by currency for fast lookup and editing
73
+ const currentTotals = {};
74
+ for (const total of totals) {
75
+ currentTotals[total.currency] = total.mrr;
76
+ }
77
+
78
+ // Loop in reverse order (needed to have correct sorted result)
79
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
80
+ const row = rows[i];
81
+
82
+ if (currentTotals[row.currency] === undefined) {
83
+ // Skip unexpected currencies that are not in the totals
84
+ continue;
85
+ }
86
+
87
+ // Convert JSDates to YYYY-MM-DD (in UTC)
88
+ const date = moment(row.date).format('YYYY-MM-DD');
89
+
90
+ if (date > today) {
91
+ // Skip results that are in the future for some reason
92
+ continue;
93
+ }
94
+
95
+ results.unshift({
96
+ date,
97
+ mrr: Math.max(0, currentTotals[row.currency]),
98
+ currency: row.currency
99
+ });
100
+
101
+ currentTotals[row.currency] -= row.delta;
102
+ }
103
+
104
+ // Now also add the oldest days we have left over and do not have deltas
105
+ const oldestDate = rows.length > 0 ? moment(rows[0].date).add(-1, 'days').format('YYYY-MM-DD') : today;
106
+
107
+ // Note that we also need to loop the totals in reverse order because we need to unshift
108
+ for (let i = totals.length - 1; i >= 0; i -= 1) {
109
+ const total = totals[i];
110
+ results.unshift({
111
+ date: oldestDate,
112
+ mrr: Math.max(0, currentTotals[total.currency]),
113
+ currency: total.currency
114
+ });
115
+ }
116
+
117
+ return {
118
+ data: results,
119
+ meta: {
120
+ totals
121
+ }
122
+ };
123
+ }
124
+ }
125
+
126
+ module.exports = MrrStatsService;
127
+
128
+ /**
129
+ * @typedef MrrByCurrency
130
+ * @type {Object}
131
+ * @property {number} mrr
132
+ * @property {string} currency
133
+ */
134
+
135
+ /**
136
+ * @typedef MrrDelta
137
+ * @type {Object}
138
+ * @property {Date} date
139
+ * @property {string} currency
140
+ * @property {number} delta MRR change on this day
141
+ */
142
+
143
+ /**
144
+ * @typedef {Object} MrrRecord
145
+ * @property {string} date In YYYY-MM-DD format
146
+ * @property {string} currency
147
+ * @property {number} mrr MRR on this day
148
+ */
149
+
150
+ /**
151
+ * @typedef {Object} MrrHistory
152
+ * @property {MrrRecord[]} data List of the total members by status for each day, including the paid deltas paid_subscribed and paid_canceled
153
+ * @property {Object} meta
154
+ */
@@ -1,6 +1,8 @@
1
1
  const db = require('../../data/db');
2
2
  const MemberStatsService = require('./lib/members-stats-service');
3
+ const MrrStatsService = require('./lib/mrr-stats-service');
3
4
 
4
5
  module.exports = {
5
- members: new MemberStatsService({db})
6
+ members: new MemberStatsService({db}),
7
+ mrr: new MrrStatsService({db})
6
8
  };
@@ -12,6 +12,7 @@ const {getConfig} = require('./config');
12
12
  async function configureApi() {
13
13
  const cfg = getConfig(settings, config, urlUtils);
14
14
  if (cfg) {
15
+ cfg.testEnv = process.env.NODE_ENV.startsWith('test');
15
16
  await module.exports.configure(cfg);
16
17
  return true;
17
18
  }
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.42%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.43%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -38,7 +38,7 @@
38
38
 
39
39
 
40
40
  <link rel="stylesheet" href="assets/vendor.min-ba66b98f7c24fa40e061c7ffc94f4e23.css">
41
- <link rel="stylesheet" href="assets/ghost.min-ce35ef1b76d9a943ab912c076773b132.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-38f3c38c0c6a1864f57079b068a0b0ce.css" title="light">
42
42
 
43
43
 
44
44
 
@@ -57,7 +57,7 @@
57
57
 
58
58
 
59
59
  <script src="assets/vendor.min-21f79c68a284acb1b70039f3f63e5507.js"></script>
60
- <script src="assets/ghost.min-20096eef632760c3a2906e243adbd24b.js"></script>
60
+ <script src="assets/ghost.min-2a278873d60d6a13a4c05a396e5bed5e.js"></script>
61
61
 
62
62
  </body>
63
63
  </html>
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.42%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.43%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -38,7 +38,7 @@
38
38
 
39
39
 
40
40
  <link rel="stylesheet" href="assets/vendor.min-ba66b98f7c24fa40e061c7ffc94f4e23.css">
41
- <link rel="stylesheet" href="assets/ghost.min-ce35ef1b76d9a943ab912c076773b132.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-38f3c38c0c6a1864f57079b068a0b0ce.css" title="light">
42
42
 
43
43
 
44
44
 
@@ -57,7 +57,7 @@
57
57
 
58
58
 
59
59
  <script src="assets/vendor.min-21f79c68a284acb1b70039f3f63e5507.js"></script>
60
- <script src="assets/ghost.min-20096eef632760c3a2906e243adbd24b.js"></script>
60
+ <script src="assets/ghost.min-2a278873d60d6a13a4c05a396e5bed5e.js"></script>
61
61
 
62
62
  </body>
63
63
  </html>
@@ -139,6 +139,7 @@ module.exports = function apiRoutes() {
139
139
 
140
140
  // ## Stats
141
141
  router.get('/stats/member_count', mw.authAdminApi, http(api.stats.memberCountHistory));
142
+ router.get('/stats/mrr', mw.authAdminApi, http(api.stats.mrr));
142
143
 
143
144
  // ## Labels
144
145
  router.get('/labels', mw.authAdminApi, http(api.labels.browse));
@@ -128,8 +128,8 @@
128
128
  "emailAnalytics": true
129
129
  },
130
130
  "portal": {
131
- "url": "https://unpkg.com/@tryghost/portal@~1.18.0/umd/portal.min.js",
132
- "version": "1.18"
131
+ "url": "https://unpkg.com/@tryghost/portal@~1.19.0/umd/portal.min.js",
132
+ "version": "1.19"
133
133
  },
134
134
  "tenor": {
135
135
  "publicReadOnlyApiKey": null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost",
3
- "version": "4.42.1",
3
+ "version": "4.43.0",
4
4
  "description": "The professional publishing platform",
5
5
  "author": "Ghost Foundation",
6
6
  "homepage": "https://ghost.org",
@@ -55,22 +55,22 @@
55
55
  "cli": "^1.17.0"
56
56
  },
57
57
  "dependencies": {
58
- "@sentry/node": "6.19.3",
58
+ "@sentry/node": "6.19.6",
59
59
  "@tryghost/adapter-manager": "0.2.28",
60
- "@tryghost/admin-api-schema": "2.12.1",
61
- "@tryghost/bookshelf-plugins": "0.3.15",
60
+ "@tryghost/admin-api-schema": "2.13.0",
61
+ "@tryghost/bookshelf-plugins": "0.3.18",
62
62
  "@tryghost/bootstrap-socket": "0.2.17",
63
63
  "@tryghost/color-utils": "0.1.12",
64
64
  "@tryghost/config-url-helpers": "0.1.5",
65
65
  "@tryghost/constants": "1.0.2",
66
66
  "@tryghost/custom-theme-settings-service": "0.3.2",
67
- "@tryghost/database-info": "0.3.0",
67
+ "@tryghost/database-info": "0.3.1",
68
68
  "@tryghost/debug": "0.1.14",
69
69
  "@tryghost/domain-events": "0.1.9",
70
70
  "@tryghost/email-analytics-provider-mailgun": "1.0.8",
71
71
  "@tryghost/email-analytics-service": "1.0.6",
72
- "@tryghost/errors": "1.2.7",
73
- "@tryghost/express-dynamic-redirects": "0.2.7",
72
+ "@tryghost/errors": "1.2.10",
73
+ "@tryghost/express-dynamic-redirects": "0.2.8",
74
74
  "@tryghost/helpers": "1.1.62",
75
75
  "@tryghost/image-transform": "1.0.29",
76
76
  "@tryghost/job-manager": "0.8.21",
@@ -80,24 +80,24 @@
80
80
  "@tryghost/kg-markdown-html-renderer": "5.1.5",
81
81
  "@tryghost/kg-mobiledoc-html-renderer": "5.3.5",
82
82
  "@tryghost/limit-service": "1.0.10",
83
- "@tryghost/logging": "2.1.2",
83
+ "@tryghost/logging": "2.1.5",
84
84
  "@tryghost/magic-link": "1.0.21",
85
85
  "@tryghost/member-events": "0.4.1",
86
- "@tryghost/members-api": "5.4.1",
87
- "@tryghost/members-events-service": "0.3.2",
88
- "@tryghost/members-importer": "0.5.6",
86
+ "@tryghost/members-api": "5.6.1",
87
+ "@tryghost/members-events-service": "0.3.3",
88
+ "@tryghost/members-importer": "0.5.7",
89
89
  "@tryghost/members-offers": "0.10.9",
90
90
  "@tryghost/members-ssr": "1.0.23",
91
- "@tryghost/members-stripe-service": "0.9.2",
91
+ "@tryghost/members-stripe-service": "0.9.4",
92
92
  "@tryghost/metrics": "1.0.8",
93
93
  "@tryghost/minifier": "0.1.12",
94
- "@tryghost/mw-error-handler": "0.1.7",
94
+ "@tryghost/mw-error-handler": "0.2.0",
95
95
  "@tryghost/mw-session-from-token": "0.1.28",
96
- "@tryghost/nodemailer": "0.3.17",
96
+ "@tryghost/nodemailer": "0.3.20",
97
97
  "@tryghost/nql": "0.9.1",
98
98
  "@tryghost/package-json": "1.0.18",
99
99
  "@tryghost/promise": "0.1.15",
100
- "@tryghost/request": "0.1.21",
100
+ "@tryghost/request": "0.1.24",
101
101
  "@tryghost/root-utils": "0.3.12",
102
102
  "@tryghost/security": "0.2.15",
103
103
  "@tryghost/session-service": "0.1.38",
@@ -107,7 +107,7 @@
107
107
  "@tryghost/tpl": "0.1.14",
108
108
  "@tryghost/update-check-service": "0.3.2",
109
109
  "@tryghost/url-utils": "2.1.0",
110
- "@tryghost/validator": "0.1.19",
110
+ "@tryghost/validator": "0.1.22",
111
111
  "@tryghost/verification-trigger": "0.1.6",
112
112
  "@tryghost/version": "0.1.12",
113
113
  "@tryghost/vhost-middleware": "1.0.22",
@@ -115,7 +115,7 @@
115
115
  "amperize": "0.6.1",
116
116
  "analytics-node": "6.0.0",
117
117
  "bluebird": "3.7.2",
118
- "body-parser": "1.19.2",
118
+ "body-parser": "1.20.0",
119
119
  "bookshelf": "1.2.0",
120
120
  "bookshelf-relations": "2.4.0",
121
121
  "brute-knex": "4.0.1",
@@ -148,20 +148,20 @@
148
148
  "jsonwebtoken": "8.5.1",
149
149
  "juice": "8.0.0",
150
150
  "keypair": "1.0.4",
151
- "knex": "1.0.4",
152
- "knex-migrator": "4.2.5",
151
+ "knex": "1.0.5",
152
+ "knex-migrator": "4.2.6",
153
153
  "lodash": "4.17.21",
154
154
  "luxon": "2.3.1",
155
155
  "mailgun-js": "0.22.0",
156
- "metascraper": "5.28.2",
157
- "metascraper-author": "5.28.2",
158
- "metascraper-description": "5.28.2",
159
- "metascraper-image": "5.28.2",
160
- "metascraper-logo": "5.28.2",
161
- "metascraper-logo-favicon": "5.28.5",
162
- "metascraper-publisher": "5.28.2",
163
- "metascraper-title": "5.28.2",
164
- "metascraper-url": "5.28.2",
156
+ "metascraper": "5.29.0",
157
+ "metascraper-author": "5.29.0",
158
+ "metascraper-description": "5.29.0",
159
+ "metascraper-image": "5.29.0",
160
+ "metascraper-logo": "5.29.0",
161
+ "metascraper-logo-favicon": "5.29.0",
162
+ "metascraper-publisher": "5.29.0",
163
+ "metascraper-title": "5.29.0",
164
+ "metascraper-url": "5.29.0",
165
165
  "moment": "2.24.0",
166
166
  "moment-timezone": "0.5.23",
167
167
  "multer": "1.4.4",
@@ -175,7 +175,7 @@
175
175
  "probe-image-size": "7.2.3",
176
176
  "rss": "1.2.2",
177
177
  "sanitize-html": "2.7.0",
178
- "semver": "7.3.5",
178
+ "semver": "7.3.6",
179
179
  "stoppable": "1.1.0",
180
180
  "tough-cookie": "4.0.0",
181
181
  "uuid": "8.3.2",
@@ -187,11 +187,11 @@
187
187
  },
188
188
  "devDependencies": {
189
189
  "@lodder/grunt-postcss": "3.1.1",
190
- "@playwright/test": "1.20.1",
191
- "@tryghost/express-test": "0.8.0",
190
+ "@playwright/test": "1.20.2",
191
+ "@tryghost/express-test": "0.8.3",
192
192
  "c8": "7.11.0",
193
193
  "coffeescript": "2.6.1",
194
- "cssnano": "5.1.6",
194
+ "cssnano": "5.1.7",
195
195
  "eslint": "8.12.0",
196
196
  "eslint-plugin-ghost": "2.13.0",
197
197
  "grunt": "1.4.1",
@@ -219,7 +219,7 @@
219
219
  "tmp": "0.2.1"
220
220
  },
221
221
  "resolutions": {
222
- "@tryghost/logging": "2.1.2",
222
+ "@tryghost/logging": "2.1.5",
223
223
  "moment": "2.24.0",
224
224
  "moment-timezone": "0.5.23"
225
225
  }