ghost 5.115.1 → 5.116.1

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 (243) hide show
  1. package/components/{tryghost-api-framework-5.115.1.tgz → tryghost-api-framework-5.116.1.tgz} +0 -0
  2. package/components/tryghost-constants-5.116.1.tgz +0 -0
  3. package/components/tryghost-custom-fonts-5.116.1.tgz +0 -0
  4. package/components/{tryghost-custom-theme-settings-service-5.115.1.tgz → tryghost-custom-theme-settings-service-5.116.1.tgz} +0 -0
  5. package/components/{tryghost-domain-events-5.115.1.tgz → tryghost-domain-events-5.116.1.tgz} +0 -0
  6. package/components/{tryghost-donations-5.115.1.tgz → tryghost-donations-5.116.1.tgz} +0 -0
  7. package/components/tryghost-email-addresses-5.116.1.tgz +0 -0
  8. package/components/tryghost-email-service-5.116.1.tgz +0 -0
  9. package/components/tryghost-email-suppression-list-5.116.1.tgz +0 -0
  10. package/components/tryghost-html-to-plaintext-5.116.1.tgz +0 -0
  11. package/components/tryghost-i18n-5.116.1.tgz +0 -0
  12. package/components/tryghost-job-manager-5.116.1.tgz +0 -0
  13. package/components/tryghost-link-replacer-5.116.1.tgz +0 -0
  14. package/components/tryghost-magic-link-5.116.1.tgz +0 -0
  15. package/components/{tryghost-member-attribution-5.115.1.tgz → tryghost-member-attribution-5.116.1.tgz} +0 -0
  16. package/components/tryghost-member-events-5.116.1.tgz +0 -0
  17. package/components/tryghost-members-api-5.116.1.tgz +0 -0
  18. package/components/tryghost-members-csv-5.116.1.tgz +0 -0
  19. package/components/{tryghost-members-offers-5.115.1.tgz → tryghost-members-offers-5.116.1.tgz} +0 -0
  20. package/components/{tryghost-milestones-5.115.1.tgz → tryghost-milestones-5.116.1.tgz} +0 -0
  21. package/components/{tryghost-mw-error-handler-5.115.1.tgz → tryghost-mw-error-handler-5.116.1.tgz} +0 -0
  22. package/components/tryghost-mw-vhost-5.116.1.tgz +0 -0
  23. package/components/{tryghost-post-events-5.115.1.tgz → tryghost-post-events-5.116.1.tgz} +0 -0
  24. package/components/{tryghost-post-revisions-5.115.1.tgz → tryghost-post-revisions-5.116.1.tgz} +0 -0
  25. package/components/tryghost-posts-service-5.116.1.tgz +0 -0
  26. package/components/{tryghost-prometheus-metrics-5.115.1.tgz → tryghost-prometheus-metrics-5.116.1.tgz} +0 -0
  27. package/components/tryghost-security-5.116.1.tgz +0 -0
  28. package/components/{tryghost-tiers-5.115.1.tgz → tryghost-tiers-5.116.1.tgz} +0 -0
  29. package/components/tryghost-webmentions-5.116.1.tgz +0 -0
  30. package/content/themes/casper/LICENSE +1 -1
  31. package/content/themes/casper/README.md +1 -1
  32. package/content/themes/source/LICENSE +1 -1
  33. package/content/themes/source/README.md +1 -1
  34. package/content/themes/source/assets/built/screen.css +1 -1
  35. package/content/themes/source/assets/built/screen.css.map +1 -1
  36. package/content/themes/source/assets/css/screen.css +6 -11
  37. package/content/themes/source/partials/feature-image.hbs +2 -2
  38. package/core/boot.js +0 -42
  39. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +24764 -24129
  40. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  41. package/core/built/admin/assets/admin-x-demo/{index-15df2af5.mjs → index-a9601514.mjs} +3 -3
  42. package/core/built/admin/assets/admin-x-demo/{modals-8ca61d78.mjs → modals-c1789d04.mjs} +2 -2
  43. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-d2e6872f.mjs → CodeEditorView-e9c9deb8.mjs} +2 -2
  44. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  45. package/core/built/admin/assets/admin-x-settings/{index-8e8821e5.mjs → index-84580c3a.mjs} +2 -2
  46. package/core/built/admin/assets/admin-x-settings/{index-f5cb3db3.mjs → index-f744cab7.mjs} +49 -35
  47. package/core/built/admin/assets/admin-x-settings/{modals-e8ae4d46.mjs → modals-d9ca60c5.mjs} +1198 -1192
  48. package/core/built/admin/assets/chunk.524.cb72a86e19c9ffd6172e.js +35 -0
  49. package/core/built/admin/assets/chunk.582.4f4d38ffe79fbdbd26f7.js +37 -0
  50. package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js → chunk.713.e9027c0cc3c56110f5da.js} +125 -98
  51. package/core/built/admin/assets/{ghost-df7b9558260aa27d18b195ee895b487d.js → ghost-03b64c086f3c60cabc85fe7a7e2b640a.js} +144 -145
  52. package/core/built/admin/assets/ghost-ba58e9822f7384461e926c7e23f04a75.css +1 -0
  53. package/core/built/admin/assets/ghost-dark-f1f29683b14ffa11615b3bba8b6ab92c.css +1 -0
  54. package/core/built/admin/assets/koenig-lexical/index.css +1 -1
  55. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +20563 -20891
  56. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +139 -139
  57. package/core/built/admin/assets/posts/posts.js +5732 -5667
  58. package/core/built/admin/assets/stats/stats.js +71082 -7533
  59. package/core/built/admin/assets/{vendor-68a4aa424a179a90f5bbc2b750def576.js → vendor-72026232b36d97babc6320917c16c321.js} +36 -34
  60. package/core/built/admin/index.html +6 -6
  61. package/core/cli/generate-data.js +1 -1
  62. package/core/frontend/helpers/ghost_head.js +6 -1
  63. package/core/frontend/public/ghost-stats.js +55 -2
  64. package/core/frontend/services/assets-minification/AdminAuthAssets.js +2 -1
  65. package/core/frontend/services/assets-minification/CardAssets.js +1 -1
  66. package/core/frontend/services/assets-minification/CommentCountsAssets.js +1 -1
  67. package/core/frontend/services/assets-minification/MemberAttributionAssets.js +1 -1
  68. package/core/frontend/services/assets-minification/Minifier.js +191 -0
  69. package/core/frontend/services/routing/controllers/previews.js +2 -1
  70. package/core/server/adapters/cache/Redis.js +1 -1
  71. package/core/server/adapters/lib/redis/AdapterCacheRedis.js +287 -0
  72. package/core/server/adapters/lib/redis/redis-store-factory.js +22 -0
  73. package/core/server/api/endpoints/posts.js +9 -3
  74. package/core/server/api/endpoints/previews.js +35 -1
  75. package/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js +6 -9
  76. package/core/server/api/endpoints/utils/validators/input/settings.js +1 -1
  77. package/core/server/data/db/connection.js +2 -0
  78. package/core/server/data/db/index.js +1 -0
  79. package/core/server/data/importer/handlers/ImporterContentFileHandler.js +90 -0
  80. package/core/server/data/importer/import-manager.js +1 -1
  81. package/core/server/data/seeders/DataGenerator.js +288 -0
  82. package/core/server/data/seeders/importers/BenefitsImporter.js +28 -0
  83. package/core/server/data/seeders/importers/CommentsImporter.js +73 -0
  84. package/core/server/data/seeders/importers/EmailBatchesImporter.js +38 -0
  85. package/core/server/data/seeders/importers/EmailRecipientFailuresImporter.js +67 -0
  86. package/core/server/data/seeders/importers/EmailRecipientsImporter.js +212 -0
  87. package/core/server/data/seeders/importers/EmailsImporter.js +99 -0
  88. package/core/server/data/seeders/importers/LabelsImporter.js +41 -0
  89. package/core/server/data/seeders/importers/MembersClickEventsImporter.js +69 -0
  90. package/core/server/data/seeders/importers/MembersCreatedEventsImporter.js +103 -0
  91. package/core/server/data/seeders/importers/MembersFeedbackImporter.js +45 -0
  92. package/core/server/data/seeders/importers/MembersImporter.js +111 -0
  93. package/core/server/data/seeders/importers/MembersLabelsImporter.js +39 -0
  94. package/core/server/data/seeders/importers/MembersLoginEventsImporter.js +69 -0
  95. package/core/server/data/seeders/importers/MembersNewslettersImporter.js +38 -0
  96. package/core/server/data/seeders/importers/MembersPaidSubscriptionEventsImporter.js +99 -0
  97. package/core/server/data/seeders/importers/MembersProductsImporter.js +42 -0
  98. package/core/server/data/seeders/importers/MembersStatusEventsImporter.js +58 -0
  99. package/core/server/data/seeders/importers/MembersStripeCustomersImporter.js +60 -0
  100. package/core/server/data/seeders/importers/MembersStripeCustomersSubscriptionsImporter.js +259 -0
  101. package/core/server/data/seeders/importers/MembersSubscribeEventsImporter.js +69 -0
  102. package/core/server/data/seeders/importers/MembersSubscriptionCreatedEventsImporter.js +95 -0
  103. package/core/server/data/seeders/importers/NewslettersImporter.js +40 -0
  104. package/core/server/data/seeders/importers/OffersImporter.js +70 -0
  105. package/core/server/data/seeders/importers/PostsAuthorsImporter.js +32 -0
  106. package/core/server/data/seeders/importers/PostsImporter.js +102 -0
  107. package/core/server/data/seeders/importers/PostsProductsImporter.js +35 -0
  108. package/core/server/data/seeders/importers/PostsTagsImporter.js +46 -0
  109. package/core/server/data/seeders/importers/ProductsBenefitsImporter.js +54 -0
  110. package/core/server/data/seeders/importers/ProductsImporter.js +90 -0
  111. package/core/server/data/seeders/importers/RecommendationClickEventsImporter.js +32 -0
  112. package/core/server/data/seeders/importers/RecommendationSubscribeEventsImporter.js +32 -0
  113. package/core/server/data/seeders/importers/RecommendationsImporter.js +34 -0
  114. package/core/server/data/seeders/importers/RedirectsImporter.js +49 -0
  115. package/core/server/data/seeders/importers/RolesUsersImporter.js +42 -0
  116. package/core/server/data/seeders/importers/StripePricesImporter.js +69 -0
  117. package/core/server/data/seeders/importers/StripeProductsImporter.js +34 -0
  118. package/core/server/data/seeders/importers/TableImporter.js +187 -0
  119. package/core/server/data/seeders/importers/TagsImporter.js +41 -0
  120. package/core/server/data/seeders/importers/UsersImporter.js +31 -0
  121. package/core/server/data/seeders/importers/WebMentionsImporter.js +42 -0
  122. package/core/server/data/seeders/importers/index.js +41 -0
  123. package/core/server/data/seeders/utils/JsonImporter.js +39 -0
  124. package/core/server/data/seeders/utils/blog-info.js +3 -0
  125. package/core/server/data/seeders/utils/database-date.js +7 -0
  126. package/core/server/data/seeders/utils/event-generator.js +48 -0
  127. package/core/server/data/seeders/utils/random.js +13 -0
  128. package/core/server/data/seeders/utils/topological-sort.js +33 -0
  129. package/core/server/services/adapter-manager/AdapterManager.js +161 -0
  130. package/core/server/services/adapter-manager/index.js +1 -1
  131. package/core/server/services/announcement-bar-service/AnnouncementBarSettings.js +54 -0
  132. package/core/server/services/announcement-bar-service/AnnouncementVisibilityValues.js +11 -0
  133. package/core/server/services/announcement-bar-service/index.js +1 -1
  134. package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +1 -1
  135. package/core/server/services/auth/session/session-service.js +15 -5
  136. package/core/server/services/custom-redirects/index.js +1 -1
  137. package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +1 -1
  138. package/core/server/services/email-service/EmailServiceWrapper.js +4 -4
  139. package/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js +1 -1
  140. package/core/server/services/email-suppression-list/service.js +1 -1
  141. package/core/server/services/lib/DynamicRedirectManager.js +156 -0
  142. package/core/server/services/lib/EmailContentGenerator.js +54 -0
  143. package/core/server/services/lib/InMemoryRepository.js +62 -0
  144. package/core/server/services/lib/InMemoryRepository.ts +80 -0
  145. package/core/server/services/lib/MailgunClient.js +364 -0
  146. package/core/server/services/link-redirection/LinkRedirect.js +26 -0
  147. package/core/server/services/link-redirection/LinkRedirectRepository.js +7 -7
  148. package/core/server/services/link-redirection/LinkRedirectsService.js +123 -0
  149. package/core/server/services/link-redirection/README.md +151 -0
  150. package/core/server/services/link-redirection/RedirectEvent.js +24 -0
  151. package/core/server/services/link-redirection/index.js +1 -1
  152. package/core/server/services/link-tracking/LinkClickTrackingService.js +1 -1
  153. package/core/server/services/mail/index.js +1 -1
  154. package/core/server/services/mail-events/InMemoryMailEventRepository.js +2 -2
  155. package/core/server/services/mail-events/InMemoryMailEventRepository.ts +1 -1
  156. package/core/server/services/members-events/LastSeenAtUpdater.js +1 -1
  157. package/core/server/services/offers/service.js +1 -1
  158. package/core/server/services/recommendations/RecommendationServiceWrapper.js +8 -8
  159. package/core/server/services/recommendations/service/BookshelfClickEventRepository.js +48 -0
  160. package/core/server/services/recommendations/service/BookshelfClickEventRepository.ts +49 -0
  161. package/core/server/services/recommendations/service/BookshelfRecommendationRepository.js +98 -0
  162. package/core/server/services/recommendations/service/BookshelfRecommendationRepository.ts +117 -0
  163. package/core/server/services/recommendations/service/BookshelfRepository.js +134 -0
  164. package/core/server/services/recommendations/service/BookshelfRepository.ts +196 -0
  165. package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.js +48 -0
  166. package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.ts +49 -0
  167. package/core/server/services/recommendations/service/ClickEvent.js +33 -0
  168. package/core/server/services/recommendations/service/ClickEvent.ts +32 -0
  169. package/core/server/services/recommendations/service/InMemoryRecommendationRepository.js +19 -0
  170. package/core/server/services/recommendations/service/InMemoryRecommendationRepository.ts +20 -0
  171. package/core/server/services/recommendations/service/IncomingRecommendationController.js +34 -0
  172. package/core/server/services/recommendations/service/IncomingRecommendationController.ts +51 -0
  173. package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.js +25 -0
  174. package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.ts +37 -0
  175. package/core/server/services/recommendations/service/IncomingRecommendationService.js +93 -0
  176. package/core/server/services/recommendations/service/IncomingRecommendationService.ts +160 -0
  177. package/core/server/services/recommendations/service/Recommendation.js +140 -0
  178. package/core/server/services/recommendations/service/Recommendation.ts +201 -0
  179. package/core/server/services/recommendations/service/RecommendationController.js +208 -0
  180. package/core/server/services/recommendations/service/RecommendationController.ts +258 -0
  181. package/core/server/services/recommendations/service/RecommendationMetadataService.js +86 -0
  182. package/core/server/services/recommendations/service/RecommendationMetadataService.ts +128 -0
  183. package/core/server/services/recommendations/service/RecommendationRepository.js +2 -0
  184. package/core/server/services/recommendations/service/RecommendationRepository.ts +13 -0
  185. package/core/server/services/recommendations/service/RecommendationService.js +228 -0
  186. package/core/server/services/recommendations/service/RecommendationService.ts +281 -0
  187. package/core/server/services/recommendations/service/SubscribeEvent.js +33 -0
  188. package/core/server/services/recommendations/service/SubscribeEvent.ts +32 -0
  189. package/core/server/services/recommendations/service/UnsafeData.js +183 -0
  190. package/core/server/services/recommendations/service/UnsafeData.ts +217 -0
  191. package/core/server/services/recommendations/service/WellknownService.js +36 -0
  192. package/core/server/services/recommendations/service/WellknownService.ts +47 -0
  193. package/core/server/services/recommendations/service/index.js +31 -0
  194. package/core/server/services/recommendations/service/index.ts +15 -0
  195. package/core/server/services/recommendations/service/libraries.d.ts +5 -0
  196. package/core/server/services/slack-notifications/SlackNotifications.js +211 -0
  197. package/core/server/services/slack-notifications/SlackNotificationsService.js +90 -0
  198. package/core/server/services/slack-notifications/service.js +4 -6
  199. package/core/server/web/api/endpoints/admin/app.js +1 -21
  200. package/core/server/web/api/middleware/version-match.js +41 -0
  201. package/core/shared/labs.js +2 -2
  202. package/package.json +87 -104
  203. package/tsconfig.tsbuildinfo +1 -1
  204. package/yarn.lock +1470 -1540
  205. package/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
  206. package/components/tryghost-adapter-manager-5.115.1.tgz +0 -0
  207. package/components/tryghost-announcement-bar-settings-5.115.1.tgz +0 -0
  208. package/components/tryghost-constants-5.115.1.tgz +0 -0
  209. package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
  210. package/components/tryghost-data-generator-5.115.1.tgz +0 -0
  211. package/components/tryghost-email-addresses-5.115.1.tgz +0 -0
  212. package/components/tryghost-email-content-generator-5.115.1.tgz +0 -0
  213. package/components/tryghost-email-events-5.115.1.tgz +0 -0
  214. package/components/tryghost-email-service-5.115.1.tgz +0 -0
  215. package/components/tryghost-email-suppression-list-5.115.1.tgz +0 -0
  216. package/components/tryghost-express-dynamic-redirects-5.115.1.tgz +0 -0
  217. package/components/tryghost-ghost-5.115.1.tgz +0 -0
  218. package/components/tryghost-html-to-plaintext-5.115.1.tgz +0 -0
  219. package/components/tryghost-i18n-5.115.1.tgz +0 -0
  220. package/components/tryghost-importer-handler-content-files-5.115.1.tgz +0 -0
  221. package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
  222. package/components/tryghost-job-manager-5.115.1.tgz +0 -0
  223. package/components/tryghost-link-redirects-5.115.1.tgz +0 -0
  224. package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
  225. package/components/tryghost-magic-link-5.115.1.tgz +0 -0
  226. package/components/tryghost-mailgun-client-5.115.1.tgz +0 -0
  227. package/components/tryghost-member-events-5.115.1.tgz +0 -0
  228. package/components/tryghost-members-api-5.115.1.tgz +0 -0
  229. package/components/tryghost-members-csv-5.115.1.tgz +0 -0
  230. package/components/tryghost-members-payments-5.115.1.tgz +0 -0
  231. package/components/tryghost-minifier-5.115.1.tgz +0 -0
  232. package/components/tryghost-mw-version-match-5.115.1.tgz +0 -0
  233. package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
  234. package/components/tryghost-posts-service-5.115.1.tgz +0 -0
  235. package/components/tryghost-recommendations-5.115.1.tgz +0 -0
  236. package/components/tryghost-security-5.115.1.tgz +0 -0
  237. package/components/tryghost-slack-notifications-5.115.1.tgz +0 -0
  238. package/components/tryghost-webmentions-5.115.1.tgz +0 -0
  239. package/core/built/admin/assets/chunk.524.2439684964c164c598ab.js +0 -35
  240. package/core/built/admin/assets/chunk.582.bf5a2bbb2c4eb69ef1e7.js +0 -37
  241. package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +0 -1
  242. package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +0 -1
  243. /package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js.LICENSE.txt → chunk.713.e9027c0cc3c56110f5da.js.LICENSE.txt} +0 -0
@@ -0,0 +1,39 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {faker} = require('@faker-js/faker');
3
+ const {luck} = require('../utils/random');
4
+
5
+ class MembersLabelsImporter extends TableImporter {
6
+ static table = 'members_labels';
7
+ static dependencies = ['labels', 'members'];
8
+
9
+ constructor(knex, transaction, {labels}) {
10
+ super(MembersLabelsImporter.table, knex, transaction);
11
+ this.labels = labels;
12
+ }
13
+
14
+ async import(quantity) {
15
+ const members = await this.transaction.select('id').from('members');
16
+ this.labels = await this.transaction.select('id').from('labels');
17
+
18
+ await this.importForEach(members, quantity ? quantity / members.length : 1);
19
+ }
20
+
21
+ generate() {
22
+ if (luck(90)) {
23
+ // 90% of members don't have labels
24
+ return;
25
+ }
26
+ // TODO: Ensure we don't generate the same member label twice
27
+ return {
28
+ id: this.fastFakeObjectId(),
29
+ member_id: this.model.id,
30
+ label_id: this.labels[faker.datatype.number({
31
+ min: 0,
32
+ max: this.labels.length - 1
33
+ })].id,
34
+ sort_order: 0
35
+ };
36
+ }
37
+ }
38
+
39
+ module.exports = MembersLabelsImporter;
@@ -0,0 +1,69 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {luck} = require('../utils/random');
3
+ const generateEvents = require('../utils/event-generator');
4
+ const dateToDatabaseString = require('../utils/database-date');
5
+
6
+ class MembersLoginEventsImporter extends TableImporter {
7
+ static table = 'members_login_events';
8
+ static dependencies = ['members'];
9
+
10
+ constructor(knex, transaction) {
11
+ super(MembersLoginEventsImporter.table, knex, transaction);
12
+ }
13
+
14
+ async import(quantity) {
15
+ if (quantity === 0) {
16
+ return;
17
+ }
18
+
19
+ let offset = 0;
20
+ let limit = 100000;
21
+
22
+ // eslint-disable-next-line no-constant-condition
23
+ while (true) {
24
+ const members = await this.transaction.select('id', 'created_at').from('members').limit(limit).offset(offset);
25
+
26
+ if (members.length === 0) {
27
+ break;
28
+ }
29
+
30
+ await this.importForEach(members, quantity ? quantity / members.length : 5);
31
+
32
+ offset += limit;
33
+ }
34
+ }
35
+
36
+ setReferencedModel(model) {
37
+ this.model = model;
38
+
39
+ const endDate = new Date();
40
+ const daysBetween = Math.ceil((endDate.valueOf() - new Date(model.created_at).valueOf()) / (1000 * 60 * 60 * 24));
41
+
42
+ // Assuming most people either subscribe and lose interest, or maintain steady readership
43
+ const shape = luck(40) ? 'ease-out' : 'flat';
44
+ this.timestamps = generateEvents({
45
+ shape,
46
+ trend: 'negative',
47
+ // Steady readers login more, readers who lose interest read less overall.
48
+ // ceil because members will all have logged in at least once
49
+ total: Math.min(5, shape === 'flat' ? Math.ceil(daysBetween / 3) : Math.ceil(daysBetween / 7)),
50
+ startTime: new Date(model.created_at),
51
+ endTime: endDate
52
+ });
53
+ }
54
+
55
+ generate() {
56
+ const timestamp = this.timestamps.pop();
57
+ if (!timestamp) {
58
+ // Out of events for this user
59
+ return null;
60
+ }
61
+ return {
62
+ id: this.fastFakeObjectId(),
63
+ created_at: dateToDatabaseString(timestamp),
64
+ member_id: this.model.id
65
+ };
66
+ }
67
+ }
68
+
69
+ module.exports = MembersLoginEventsImporter;
@@ -0,0 +1,38 @@
1
+ const TableImporter = require('./TableImporter');
2
+
3
+ class MembersNewslettersImporter extends TableImporter {
4
+ static table = 'members_newsletters';
5
+ static dependencies = ['members_subscribe_events'];
6
+
7
+ constructor(knex, transaction) {
8
+ super(MembersNewslettersImporter.table, knex, transaction);
9
+ }
10
+
11
+ async import(quantity) {
12
+ let offset = 0;
13
+ let limit = 100000;
14
+
15
+ // eslint-disable-next-line no-constant-condition
16
+ while (true) {
17
+ const membersSubscribeEvents = await this.transaction.select('member_id', 'newsletter_id').from('members_subscribe_events').limit(limit).offset(offset);
18
+
19
+ if (membersSubscribeEvents.length === 0) {
20
+ break;
21
+ }
22
+
23
+ await this.importForEach(membersSubscribeEvents, quantity ? quantity / membersSubscribeEvents.length : 1);
24
+
25
+ offset += limit;
26
+ }
27
+ }
28
+
29
+ generate() {
30
+ return {
31
+ id: this.fastFakeObjectId(),
32
+ member_id: this.model.member_id,
33
+ newsletter_id: this.model.newsletter_id
34
+ };
35
+ }
36
+ }
37
+
38
+ module.exports = MembersNewslettersImporter;
@@ -0,0 +1,99 @@
1
+ const TableImporter = require('./TableImporter');
2
+
3
+ class MembersPaidSubscriptionEventsImporter extends TableImporter {
4
+ static table = 'members_paid_subscription_events';
5
+ static dependencies = ['members_stripe_customers_subscriptions'];
6
+
7
+ constructor(knex, transaction) {
8
+ super(MembersPaidSubscriptionEventsImporter.table, knex, transaction);
9
+ }
10
+
11
+ async import() {
12
+ let offset = 0;
13
+ let limit = 1000;
14
+
15
+ // eslint-disable-next-line no-constant-condition
16
+ while (true) {
17
+ const subscriptions = await this.transaction.select('id', 'customer_id', 'plan_currency', 'plan_amount', 'created_at', 'plan_id', 'status', 'cancel_at_period_end', 'current_period_end').from('members_stripe_customers_subscriptions').limit(limit).offset(offset);
18
+
19
+ if (subscriptions.length === 0) {
20
+ break;
21
+ }
22
+ const membersStripeCustomers = await this.transaction.select('id', 'member_id', 'customer_id').from('members_stripe_customers').whereIn('customer_id', subscriptions.map(subscription => subscription.customer_id));
23
+
24
+ this.membersStripeCustomers = new Map();
25
+ for (const customer of membersStripeCustomers) {
26
+ this.membersStripeCustomers.set(customer.customer_id, customer);
27
+ }
28
+ await this.importForEach(subscriptions, 2);
29
+
30
+ offset += limit;
31
+ }
32
+ }
33
+
34
+ setReferencedModel(model) {
35
+ this.model = model;
36
+ this.count = 0;
37
+ }
38
+
39
+ isActiveSubscriptionStatus(status) {
40
+ return ['active', 'trialing', 'unpaid', 'past_due'].includes(status);
41
+ }
42
+
43
+ getStatus(modelToCheck) {
44
+ const status = modelToCheck.status;
45
+ const canceled = modelToCheck.cancel_at_period_end;
46
+
47
+ if (status === 'canceled') {
48
+ return 'expired';
49
+ }
50
+
51
+ if (canceled) {
52
+ return 'canceled';
53
+ }
54
+
55
+ if (this.isActiveSubscriptionStatus(status)) {
56
+ return 'active';
57
+ }
58
+
59
+ return 'inactive';
60
+ }
61
+
62
+ generate() {
63
+ this.count += 1;
64
+
65
+ const isActive = this.isActiveSubscriptionStatus(this.model.status);
66
+ if (this.count > 1 && isActive) {
67
+ // We only need one event, because the MRR is still here
68
+ return;
69
+ }
70
+
71
+ if (this.model.status === 'incomplete' || this.model.status === 'incomplete_expired') {
72
+ // Not a paid subscription
73
+ return;
74
+ }
75
+
76
+ const memberCustomer = this.membersStripeCustomers.get(this.model.customer_id);
77
+ const isMonthly = this.model.plan_interval === 'month';
78
+
79
+ // Note that we need to recalculate the MRR, because it will be zero for inactive subscrptions
80
+ const mrr = isMonthly ? this.model.plan_amount : Math.floor(this.model.plan_amount / 12);
81
+
82
+ // todo: implement + MRR and -MRR in case of inactive subscriptions
83
+ return {
84
+ id: this.fastFakeObjectId(),
85
+ // TODO: Support expired / updated / cancelled events too
86
+ type: this.count === 1 ? 'created' : this.getStatus(this.model),
87
+ member_id: memberCustomer.member_id,
88
+ subscription_id: this.model.id,
89
+ from_plan: this.count === 1 ? null : this.model.plan_id,
90
+ to_plan: this.count === 1 ? this.model.plan_id : null,
91
+ currency: this.model.plan_currency,
92
+ source: 'stripe',
93
+ mrr_delta: this.count === 1 ? mrr : -mrr,
94
+ created_at: this.count === 1 ? this.model.created_at : this.model.current_period_end
95
+ };
96
+ }
97
+ }
98
+
99
+ module.exports = MembersPaidSubscriptionEventsImporter;
@@ -0,0 +1,42 @@
1
+ const {faker} = require('@faker-js/faker');
2
+ const TableImporter = require('./TableImporter');
3
+ const {luck} = require('../utils/random');
4
+ const dateToDatabaseString = require('../utils/database-date');
5
+
6
+ class MembersProductsImporter extends TableImporter {
7
+ static table = 'members_products';
8
+ static dependencies = ['products', 'members'];
9
+
10
+ constructor(knex, transaction) {
11
+ super(MembersProductsImporter.table, knex, transaction);
12
+ }
13
+
14
+ async import(quantity) {
15
+ const members = await this.transaction.select('id').from('members').whereNot('status', 'free');
16
+ this.products = await this.transaction.select('id').from('products').whereNot('type', 'fee');
17
+
18
+ await this.importForEach(members, quantity ? quantity / members.length : 1);
19
+ }
20
+
21
+ getProduct() {
22
+ if (this.products.length > 1) {
23
+ return luck(10) ? this.products[2]
24
+ : luck(50) ? this.products[1]
25
+ : this.products[0];
26
+ } else {
27
+ return this.products[0];
28
+ }
29
+ }
30
+
31
+ generate() {
32
+ return {
33
+ id: this.fastFakeObjectId(),
34
+ member_id: this.model.id,
35
+ product_id: this.getProduct().id,
36
+ sort_order: 0,
37
+ expiry_at: this.model.status === 'paid' ? null : (luck(50) ? null : dateToDatabaseString(faker.date.future()))
38
+ };
39
+ }
40
+ }
41
+
42
+ module.exports = MembersProductsImporter;
@@ -0,0 +1,58 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {faker} = require('@faker-js/faker');
3
+ const dateToDatabaseString = require('../utils/database-date');
4
+
5
+ class MembersStatusEventsImporter extends TableImporter {
6
+ static table = 'members_status_events';
7
+ static dependencies = ['members'];
8
+
9
+ constructor(knex, transaction) {
10
+ super(MembersStatusEventsImporter.table, knex, transaction);
11
+ }
12
+
13
+ async import(quantity) {
14
+ let offset = 0;
15
+ let limit = 100000;
16
+
17
+ // eslint-disable-next-line no-constant-condition
18
+ while (true) {
19
+ const members = await this.transaction.select('id', 'created_at', 'status').from('members').limit(limit).offset(offset);
20
+
21
+ if (members.length === 0) {
22
+ break;
23
+ }
24
+
25
+ await this.importForEach(members, quantity ? quantity / members.length : 2);
26
+ offset += limit;
27
+ }
28
+ }
29
+
30
+ setReferencedModel(model) {
31
+ this.events = [{
32
+ id: this.fastFakeObjectId(),
33
+ member_id: model.id,
34
+ from_status: null,
35
+ to_status: 'free',
36
+ created_at: dateToDatabaseString(model.created_at)
37
+ }];
38
+ if (model.status !== 'free') {
39
+ this.events.push({
40
+ id: this.fastFakeObjectId(),
41
+ member_id: model.id,
42
+ from_status: 'free',
43
+ to_status: model.status,
44
+ created_at: dateToDatabaseString(faker.date.between(new Date(model.created_at), new Date()))
45
+ });
46
+ }
47
+ }
48
+
49
+ generate() {
50
+ const event = this.events.pop();
51
+ if (!event) {
52
+ return null;
53
+ }
54
+ return event;
55
+ }
56
+ }
57
+
58
+ module.exports = MembersStatusEventsImporter;
@@ -0,0 +1,60 @@
1
+ const {faker} = require('@faker-js/faker');
2
+ const TableImporter = require('./TableImporter');
3
+
4
+ class MembersStripeCustomersImporter extends TableImporter {
5
+ static table = 'members_stripe_customers';
6
+ static dependencies = ['members'];
7
+
8
+ constructor(knex, transaction) {
9
+ super(MembersStripeCustomersImporter.table, knex, transaction);
10
+ }
11
+
12
+ async import(quantity) {
13
+ if (quantity === 0) {
14
+ return;
15
+ }
16
+
17
+ let offset = 0;
18
+ let limit = 100000;
19
+
20
+ // eslint-disable-next-line no-constant-condition
21
+ while (true) {
22
+ const members = await this.transaction.select('id', 'name', 'email', 'created_at', 'status').from('members').limit(limit).offset(offset);
23
+
24
+ if (members.length === 0) {
25
+ break;
26
+ }
27
+
28
+ await this.importForEach(members, quantity ? quantity / members.length : 1);
29
+ offset += limit;
30
+ }
31
+ }
32
+
33
+ generate() {
34
+ if (this.model.status !== 'paid') {
35
+ // Only 30% of free members should have a stripe customer = have had a subscription in the past or tried to subscribe
36
+ // The number should increase the older the member is
37
+
38
+ const daysSinceMemberCreated = Math.floor((new Date() - new Date(this.model.created_at)) / (1000 * 60 * 60 * 24));
39
+ const shouldHaveStripeCustomer = faker.datatype.number({min: 0, max: 100}) < Math.max(Math.min(daysSinceMemberCreated / 60, 15), 2);
40
+
41
+ if (!shouldHaveStripeCustomer) {
42
+ return;
43
+ }
44
+ }
45
+
46
+ return {
47
+ id: this.fastFakeObjectId(),
48
+ member_id: this.model.id,
49
+ customer_id: `cus_${faker.random.alphaNumeric(14, {
50
+ casing: 'mixed'
51
+ })}`,
52
+ name: this.model.name,
53
+ email: this.model.email,
54
+ created_at: this.model.created_at,
55
+ created_by: 'unused'
56
+ };
57
+ }
58
+ }
59
+
60
+ module.exports = MembersStripeCustomersImporter;
@@ -0,0 +1,259 @@
1
+ const {faker} = require('@faker-js/faker');
2
+ const TableImporter = require('./TableImporter');
3
+ const dateToDatabaseString = require('../utils/database-date');
4
+ const generateEvents = require('../utils/event-generator');
5
+ const {luck} = require('../utils/random');
6
+
7
+ class MembersStripeCustomersSubscriptionsImporter extends TableImporter {
8
+ static table = 'members_stripe_customers_subscriptions';
9
+ static dependencies = ['members', 'members_products', 'members_stripe_customers', 'products', 'stripe_products', 'stripe_prices'];
10
+
11
+ constructor(knex, transaction) {
12
+ super(MembersStripeCustomersSubscriptionsImporter.table, knex, transaction);
13
+ }
14
+
15
+ async import() {
16
+ let offset = 0;
17
+ let limit = 5000;
18
+ this.products = await this.transaction.select('id', 'name').from('products').whereNot('type', 'free');
19
+ this.stripeProducts = await this.transaction.select('id', 'product_id', 'stripe_product_id').from('stripe_products');
20
+ this.stripePrices = await this.transaction.select('id', 'nickname', 'stripe_product_id', 'stripe_price_id', 'amount', 'interval', 'currency').from('stripe_prices');
21
+
22
+ // eslint-disable-next-line no-constant-condition
23
+ while (true) {
24
+ const membersStripeCustomers = await this.transaction.select('id', 'member_id', 'customer_id').from('members_stripe_customers').limit(limit).offset(offset);
25
+
26
+ if (membersStripeCustomers.length === 0) {
27
+ break;
28
+ }
29
+
30
+ this.members = await this.transaction.select('id', 'status', 'created_at').from('members').whereIn('id', membersStripeCustomers.map(m => m.member_id));
31
+
32
+ if (this.members.length === 0) {
33
+ continue;
34
+ }
35
+
36
+ const membersProducts = await this.transaction.select('member_id', 'product_id').from('members_products').whereIn('member_id', this.members.map(member => member.id));
37
+ //const membersStripeCustomers = await this.transaction.select('id', 'member_id', 'customer_id').from('members_stripe_customers').whereIn('member_id', this.members.map(member => member.id));
38
+
39
+ this.membersStripeCustomers = new Map();
40
+ for (const customer of membersStripeCustomers) {
41
+ this.membersStripeCustomers.set(customer.member_id, customer);
42
+ }
43
+
44
+ this.membersProducts = new Map();
45
+
46
+ for (const product of membersProducts) {
47
+ this.membersProducts.set(product.member_id, product);
48
+ }
49
+
50
+ await this.importForEach(this.members, 1.2);
51
+
52
+ offset += limit;
53
+ }
54
+ }
55
+
56
+ setReferencedModel(model) {
57
+ this.model = model;
58
+ this.count = 0;
59
+ this.lastSubscriptionStart = null;
60
+ }
61
+
62
+ generate() {
63
+ this.count += 1;
64
+
65
+ const member = this.model;
66
+ const customer = this.membersStripeCustomers.get(this.model.id);
67
+
68
+ if (!customer) {
69
+ // This is a requirement, so skip if we don't have a customer
70
+ return;
71
+ }
72
+
73
+ if (this.count > 1 && member.status !== 'paid') {
74
+ return;
75
+ }
76
+
77
+ const memberProduct = this.membersProducts.get(this.model.id);
78
+ let ghostProduct = memberProduct ? this.products.find(product => product.id === memberProduct.product_id) : null;
79
+
80
+ // Whether we should create a valid subscription or not
81
+ // We'll only create one valid subscription for each member if they are currently paid
82
+ let createValid = this.count === 1 && member.status === 'paid';
83
+
84
+ if (!ghostProduct) {
85
+ // Generate canceled, incomplete, incomplete_expired or unpaid subscriptions
86
+ // Choose a random paid product
87
+ ghostProduct = faker.helpers.arrayElement(this.products);
88
+ createValid = false;
89
+ }
90
+
91
+ const isMonthly = luck(70);
92
+ const stripeProduct = this.stripeProducts.find(product => product.product_id === ghostProduct.id);
93
+ const stripePrice = this.stripePrices.find((price) => {
94
+ return price.stripe_product_id === stripeProduct.stripe_product_id &&
95
+ (isMonthly ? price.interval === 'month' : price.interval === 'year');
96
+ });
97
+ const mrr = createValid ? (isMonthly ? stripePrice.amount : Math.floor(stripePrice.amount / 12)) : 0;
98
+
99
+ const referenceEndDate = this.lastSubscriptionStart ?? new Date();
100
+
101
+ if (!createValid) {
102
+ if (isMonthly) {
103
+ referenceEndDate.setMonth(referenceEndDate.getMonth() - 1);
104
+ } else {
105
+ referenceEndDate.setFullYear(referenceEndDate.getFullYear() - 1);
106
+ }
107
+ }
108
+
109
+ if (referenceEndDate < member.created_at) {
110
+ // Not possible to create an invalid subscription here
111
+ return;
112
+ }
113
+
114
+ const [startDate] = generateEvents({
115
+ total: 1,
116
+ trend: 'negative',
117
+ startTime: new Date(member.created_at),
118
+ endTime: referenceEndDate,
119
+ shape: 'ease-out'
120
+ });
121
+ this.lastSubscriptionStart = startDate;
122
+ const endDate = new Date(startDate);
123
+
124
+ if (createValid) {
125
+ // End date should be in the future
126
+
127
+ if (isMonthly) {
128
+ endDate.setFullYear(new Date().getFullYear());
129
+ endDate.setMonth(new Date().getMonth());
130
+ if (endDate < new Date()) {
131
+ endDate.setMonth(endDate.getMonth() + 1);
132
+ }
133
+ } else {
134
+ endDate.setFullYear(new Date().getFullYear());
135
+ if (endDate < new Date()) {
136
+ endDate.setFullYear(endDate.getFullYear() + 1);
137
+ }
138
+ }
139
+ } else {
140
+ // End date should be in the past
141
+ if (isMonthly) {
142
+ // What is the month difference between startDate and now? Pick a random number in between
143
+ const monthDiff = (new Date().getFullYear() - startDate.getFullYear()) * 12 + (new Date().getMonth() - startDate.getMonth());
144
+ if (monthDiff === 0) {
145
+ // Not possible to create an invalid subscription here
146
+ return;
147
+ }
148
+
149
+ const randomMonthDiff = faker.datatype.number({min: 1, max: monthDiff});
150
+ endDate.setMonth(startDate.getMonth() + randomMonthDiff);
151
+ } else {
152
+ // What is the year difference between startDate and now? Pick a random number in between
153
+ const yearDiff = new Date().getFullYear() - startDate.getFullYear();
154
+
155
+ if (yearDiff === 0) {
156
+ // Not possible to create an invalid subscription here
157
+ return;
158
+ }
159
+ const randomYearDiff = faker.datatype.number({min: 1, max: yearDiff});
160
+
161
+ endDate.setFullYear(startDate.getFullYear() + randomYearDiff);
162
+ }
163
+ }
164
+
165
+ // Simulate some different statusses here:
166
+ // - active, not ending (cancel_at_period_end = false)
167
+ // - active, ending (cancel_at_period_end = true)
168
+ // - canceled -> current_period_end can be in both past or present, cancel_at_period_end can be both true or false
169
+ // - incomplete_expired -> user tried to pay but 3D secure expired
170
+ // - incomplete -> waiting on 3D secure
171
+ // - trialing -> need to set trial_end_at to a date in the future
172
+ // - past_due -> last paymet failed, but subscription still active until tried a couple of times
173
+ // - unpaid -> all payment attempts failed - but keep the subscription active (special setting in Stripe)
174
+
175
+ const validStatusses = new Array(10).fill({
176
+ status: 'active',
177
+ cancel_at_period_end: false
178
+ });
179
+
180
+ // Trialing only possible when the startDate > 1 month ago
181
+ const monthAgo = new Date();
182
+
183
+ if (!isMonthly) {
184
+ // Year ago
185
+ monthAgo.setFullYear(monthAgo.getFullYear() - 1);
186
+ } else {
187
+ // Month ago
188
+ monthAgo.setMonth(monthAgo.getMonth() - 1);
189
+ }
190
+
191
+ if (startDate > monthAgo) {
192
+ validStatusses.push({
193
+ status: 'trialing',
194
+ cancel_at_period_end: false,
195
+ trial_end_at: dateToDatabaseString(endDate),
196
+ trial_start_at: dateToDatabaseString(startDate)
197
+ });
198
+ }
199
+
200
+ // Past due only possible if startDate < 1 month ago
201
+ if (startDate < monthAgo) {
202
+ validStatusses.push({
203
+ status: 'past_due',
204
+ cancel_at_period_end: false
205
+ });
206
+ validStatusses.push({
207
+ status: 'unpaid',
208
+ cancel_at_period_end: false
209
+ });
210
+ }
211
+
212
+ const invalidStatusses = [
213
+ {
214
+ status: 'canceled',
215
+ cancel_at_period_end: true
216
+ },
217
+ {
218
+ status: 'canceled',
219
+ cancel_at_period_end: false
220
+ },
221
+ {
222
+ status: 'incomplete_expired',
223
+ cancel_at_period_end: false
224
+ },
225
+ {
226
+ status: 'incomplete',
227
+ cancel_at_period_end: false
228
+ }
229
+ ];
230
+
231
+ const status = createValid ? faker.helpers.arrayElement(validStatusses) : faker.helpers.arrayElement(invalidStatusses);
232
+
233
+ return {
234
+ id: this.fastFakeObjectId(),
235
+ customer_id: customer.customer_id,
236
+ subscription_id: `sub_${faker.random.alphaNumeric(14)}`,
237
+ stripe_price_id: stripePrice.stripe_price_id,
238
+ start_date: dateToDatabaseString(startDate),
239
+ created_at: dateToDatabaseString(startDate),
240
+ created_by: 'unused',
241
+ mrr,
242
+ plan_id: stripeProduct.stripe_product_id,
243
+ plan_nickname: `${ghostProduct.name} - ${stripePrice.nickname}`,
244
+ plan_interval: stripePrice.interval,
245
+ plan_amount: stripePrice.amount,
246
+ plan_currency: stripePrice.currency,
247
+
248
+ // Defaults
249
+ status: 'active',
250
+ cancel_at_period_end: false,
251
+ current_period_end: dateToDatabaseString(endDate),
252
+
253
+ // Override
254
+ ...status
255
+ };
256
+ }
257
+ }
258
+
259
+ module.exports = MembersStripeCustomersSubscriptionsImporter;