ghost 4.44.0 → 4.45.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 (49) hide show
  1. package/core/boot.js +2 -0
  2. package/core/built/assets/ghost-dark-887882218a8f9a4a367de52212d27917.css +1 -0
  3. package/core/built/assets/ghost.min-0b3ecc9dd9e8b3b380d93f1839213af5.css +1 -0
  4. package/core/built/assets/{ghost.min-1e7dce606e92a03207d15ae7eb3d3c23.js → ghost.min-aafce1ab3f2ab6b4a385e8b888548e15.js} +243 -172
  5. package/core/built/assets/img/abstract-2-2937e2902b64360d0cbe4cec8bd8479b.jpg +0 -0
  6. package/core/built/assets/img/abstract-c52b2f4208e7fd2e7b8abd8b1eec4f7b.jpg +0 -0
  7. package/core/built/assets/img/community-be8c1dcecfb157f2bfba5cababc8e686.jpg +0 -0
  8. package/core/built/assets/{vendor.min-fe2c9b1235b4119b5406b788db2db434.js → vendor.min-eaf9e7b39e2ba76722eabc7a814e0ff1.js} +96 -93
  9. package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -3
  10. package/core/frontend/web/middleware/cors.js +56 -0
  11. package/core/frontend/web/middleware/index.js +1 -0
  12. package/core/frontend/web/middleware/static-theme.js +8 -8
  13. package/core/frontend/web/site.js +1 -48
  14. package/core/server/api/canary/newsletters.js +32 -0
  15. package/core/server/api/canary/stats.js +2 -2
  16. package/core/server/data/importer/importers/data/settings.js +0 -3
  17. package/core/server/data/migrations/versions/4.45/2022-04-19-12-23-backfill-subscriptions-offers.js +60 -0
  18. package/core/server/data/migrations/versions/4.45/2022-04-20-11-25-add-newsletter-read-permission.js +9 -0
  19. package/core/server/data/migrations/versions/4.45/2022-04-21-02-55-add-notifications-key-entry-to-settings-table.js +8 -0
  20. package/core/server/data/schema/default-settings/default-settings.json +4 -0
  21. package/core/server/data/schema/fixtures/fixtures.json +5 -0
  22. package/core/server/models/label.js +1 -1
  23. package/core/server/models/post.js +1 -1
  24. package/core/server/models/role.js +1 -1
  25. package/core/server/models/tag.js +1 -1
  26. package/core/server/models/user.js +1 -1
  27. package/core/server/services/api-version-compatibility/index.js +26 -0
  28. package/core/server/services/members/middleware.js +10 -2
  29. package/core/server/services/stats/service.js +2 -6
  30. package/core/server/web/admin/views/default-prod.html +4 -4
  31. package/core/server/web/admin/views/default.html +4 -4
  32. package/core/server/web/api/app.js +3 -0
  33. package/core/server/web/api/canary/admin/app.js +3 -0
  34. package/core/server/web/api/canary/admin/routes.js +1 -0
  35. package/core/server/web/api/canary/content/app.js +3 -0
  36. package/core/server/web/api/middleware/cors.js +1 -1
  37. package/core/server/web/api/v2/admin/app.js +3 -0
  38. package/core/server/web/api/v2/content/app.js +3 -0
  39. package/core/server/web/api/v3/admin/app.js +3 -0
  40. package/core/server/web/api/v3/content/app.js +3 -0
  41. package/core/shared/config/defaults.json +1 -1
  42. package/core/shared/labs.js +3 -1
  43. package/core/shared/settings-cache/public.js +1 -1
  44. package/package.json +20 -16
  45. package/yarn.lock +240 -150
  46. package/core/built/assets/ghost-dark-470c1ef06b10e5c40ad05f3a642eaaea.css +0 -1
  47. package/core/built/assets/ghost.min-d0c17e8314b5583c0df5d05fab3c051c.css +0 -1
  48. package/core/server/services/stats/lib/members-stats-service.js +0 -161
  49. package/core/server/services/stats/lib/mrr-stats-service.js +0 -154
@@ -1,161 +0,0 @@
1
- const moment = require('moment');
2
-
3
- class MembersStatsService {
4
- constructor({db}) {
5
- this.db = db;
6
- }
7
-
8
- /**
9
- * Get the current total members grouped by status
10
- * @returns {Promise<TotalMembersByStatus>}
11
- */
12
- async getCount() {
13
- const knex = this.db.knex;
14
- const rows = await knex('members')
15
- .select('status')
16
- .select(knex.raw('COUNT(id) AS total'))
17
- .groupBy('status');
18
-
19
- const paidEvent = rows.find(c => c.status === 'paid');
20
- const freeEvent = rows.find(c => c.status === 'free');
21
- const compedEvent = rows.find(c => c.status === 'comped');
22
-
23
- return {
24
- paid: paidEvent ? paidEvent.total : 0,
25
- free: freeEvent ? freeEvent.total : 0,
26
- comped: compedEvent ? compedEvent.total : 0
27
- };
28
- }
29
-
30
- /**
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
- */
34
- async fetchAllStatusDeltas() {
35
- const knex = this.db.knex;
36
- const rows = await knex('members_status_events')
37
- .select(knex.raw('DATE(created_at) as date'))
38
- .select(knex.raw(`SUM(
39
- CASE WHEN to_status='paid' THEN 1
40
- ELSE 0 END
41
- ) as paid_subscribed`))
42
- .select(knex.raw(`SUM(
43
- CASE WHEN from_status='paid' THEN 1
44
- ELSE 0 END
45
- ) as paid_canceled`))
46
- .select(knex.raw(`SUM(
47
- CASE WHEN to_status='comped' THEN 1
48
- WHEN from_status='comped' THEN -1
49
- ELSE 0 END
50
- ) as comped_delta`))
51
- .select(knex.raw(`SUM(
52
- CASE WHEN to_status='free' THEN 1
53
- WHEN from_status='free' THEN -1
54
- ELSE 0 END
55
- ) as free_delta`))
56
- .groupByRaw('DATE(created_at)')
57
- .orderByRaw('DATE(created_at)');
58
- return rows;
59
- }
60
-
61
- /**
62
- * Returns a list of the total members by status for each day, including the paid deltas paid_subscribed and paid_canceled
63
- * @returns {Promise<CountHistory>}
64
- */
65
- async getCountHistory() {
66
- const rows = await this.fetchAllStatusDeltas();
67
-
68
- // Fetch current total amounts and start counting from there
69
- const totals = await this.getCount();
70
- let {paid, free, comped} = totals;
71
-
72
- // Get today in UTC (default timezone)
73
- const today = moment().format('YYYY-MM-DD');
74
-
75
- const cumulativeResults = [];
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
-
81
- // Convert JSDates to YYYY-MM-DD (in UTC)
82
- const date = moment(row.date).format('YYYY-MM-DD');
83
- if (date > today) {
84
- // Skip results that are in the future (fix for invalid events)
85
- continue;
86
- }
87
- cumulativeResults.unshift({
88
- date,
89
- paid: Math.max(0, paid),
90
- free: Math.max(0, free),
91
- comped: Math.max(0, comped),
92
-
93
- // Deltas
94
- paid_subscribed: row.paid_subscribed,
95
- paid_canceled: row.paid_canceled
96
- });
97
-
98
- // Update current counts
99
- paid -= row.paid_subscribed - row.paid_canceled;
100
- free -= row.free_delta;
101
- comped -= row.comped_delta;
102
- }
103
-
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;
106
-
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
- });
117
-
118
- return {
119
- data: cumulativeResults,
120
- meta: {
121
- totals
122
- }
123
- };
124
- }
125
- }
126
-
127
- module.exports = MembersStatsService;
128
-
129
- /**
130
- * @typedef MemberStatusDelta
131
- * @type {Object}
132
- * @property {Date} date
133
- * @property {number} paid_subscribed Paid members that subscribed on this day
134
- * @property {number} paid_canceled Paid members that canceled on this day
135
- * @property {number} comped_delta Total net comped members on this day
136
- * @property {number} free_delta Total net members on this day
137
- */
138
-
139
- /**
140
- * @typedef TotalMembersByStatus
141
- * @type {Object}
142
- * @property {number} paid Total paid members
143
- * @property {number} free Total free members
144
- * @property {number} comped Total comped members
145
- */
146
-
147
- /**
148
- * @typedef {Object} TotalMembersByStatusItem
149
- * @property {string} date In YYYY-MM-DD format
150
- * @property {number} paid Total paid members
151
- * @property {number} free Total free members
152
- * @property {number} comped Total comped members
153
- * @property {number} paid_subscribed Paid members that subscribed on this day
154
- * @property {number} paid_canceled Paid members that canceled on this day
155
- */
156
-
157
- /**
158
- * @typedef {Object} CountHistory
159
- * @property {TotalMembersByStatusItem[]} data List of the total members by status for each day, including the paid deltas paid_subscribed and paid_canceled
160
- * @property {Object} meta
161
- */
@@ -1,154 +0,0 @@
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
- */