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,32 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {faker} = require('@faker-js/faker');
3
+ const {luck} = require('../utils/random');
4
+
5
+ class RecommendationSubscribeEventsImporter extends TableImporter {
6
+ static table = 'recommendation_subscribe_events';
7
+ static dependencies = ['recommendations', 'members'];
8
+
9
+ constructor(knex, transaction) {
10
+ super(RecommendationSubscribeEventsImporter.table, knex, transaction);
11
+ }
12
+
13
+ async import(quantity) {
14
+ const recommendations = await this.transaction.select('id', 'created_at').from('recommendations').where('one_click_subscribe', true);
15
+ this.members = await this.transaction.select('id').from('members').limit(500);
16
+
17
+ await this.importForEach(recommendations, quantity ? quantity / recommendations.length : () => faker.datatype.number({min: 0, max: 50}));
18
+ }
19
+
20
+ generate() {
21
+ // Not unique
22
+ const member = luck(1) ? null : faker.helpers.arrayElement(this.members);
23
+ return {
24
+ id: this.fastFakeObjectId(),
25
+ recommendation_id: this.model.id,
26
+ member_id: member?.id ?? null,
27
+ created_at: faker.date.past()
28
+ };
29
+ }
30
+ }
31
+
32
+ module.exports = RecommendationSubscribeEventsImporter;
@@ -0,0 +1,34 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {faker} = require('@faker-js/faker');
3
+
4
+ function capitalize(str) {
5
+ return str.charAt(0).toUpperCase() + str.slice(1);
6
+ }
7
+
8
+ class RecommendationsImporter extends TableImporter {
9
+ static table = 'recommendations';
10
+ static dependencies = [];
11
+ defaultQuantity = 15;
12
+
13
+ constructor(knex, transaction) {
14
+ super(RecommendationsImporter.table, knex, transaction);
15
+ }
16
+
17
+ generate() {
18
+ const id = this.fastFakeObjectId();
19
+ return {
20
+ id,
21
+ url: faker.internet.url(),
22
+ title: capitalize(`${faker.word.adjective()} ${faker.word.noun()}`),
23
+ excerpt: faker.lorem.sentence(),
24
+ featured_image: `https://api.dicebear.com/5.x/shapes/png?size=256&seed=${id}`,
25
+ favicon: `https://api.dicebear.com/5.x/shapes/png?size=32&seed=${id}`,
26
+ description: faker.lorem.sentence(),
27
+ one_click_subscribe: faker.datatype.boolean(),
28
+ created_at: faker.date.past(),
29
+ updated_at: faker.date.past()
30
+ };
31
+ }
32
+ }
33
+
34
+ module.exports = RecommendationsImporter;
@@ -0,0 +1,49 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {faker} = require('@faker-js/faker');
3
+
4
+ class RedirectsImporter extends TableImporter {
5
+ static table = 'redirects';
6
+ static dependencies = ['posts'];
7
+
8
+ constructor(knex, transaction) {
9
+ super(RedirectsImporter.table, knex, transaction);
10
+ }
11
+
12
+ async import(quantity) {
13
+ const posts = await this.transaction
14
+ .select('id', 'published_at')
15
+ .from('posts')
16
+ .where('type', 'post')
17
+ .andWhere('status', 'published');
18
+
19
+ this.quantity = quantity ? quantity / posts.length : 10;
20
+ await this.importForEach(posts, this.quantity);
21
+ }
22
+
23
+ setReferencedModel(model) {
24
+ this.model = model;
25
+
26
+ // Reset the amount for each model
27
+ this.amount = faker.datatype.number({
28
+ min: 0,
29
+ max: this.quantity
30
+ });
31
+ }
32
+
33
+ generate() {
34
+ if (this.amount <= 0) {
35
+ return;
36
+ }
37
+ this.amount -= 1;
38
+ return {
39
+ id: this.fastFakeObjectId(),
40
+ from: `/r/${faker.datatype.hexadecimal({length: 8, prefix: '', case: 'lower'})}`,
41
+ to: `${faker.internet.url()}/${faker.helpers.slugify(`${faker.word.adjective()} ${faker.word.noun()}`).toLowerCase()}`,
42
+ post_id: this.model.id,
43
+ created_at: this.model.published_at,
44
+ updated_at: this.model.published_at
45
+ };
46
+ }
47
+ }
48
+
49
+ module.exports = RedirectsImporter;
@@ -0,0 +1,42 @@
1
+ const {faker} = require('@faker-js/faker');
2
+ const TableImporter = require('./TableImporter');
3
+
4
+ class RolesUsersImporter extends TableImporter {
5
+ static table = 'roles_users';
6
+ // No roles imorter, since roles are statically defined in database
7
+ static dependencies = ['users'];
8
+
9
+ constructor(knex, transaction) {
10
+ super(RolesUsersImporter.table, knex, transaction);
11
+ }
12
+
13
+ /**
14
+ * Ignore overriden quantity for 1:1 relationship
15
+ */
16
+ async import() {
17
+ const users = await this.transaction.select('id').from('users').whereNot('id', 1);
18
+ this.roles = await this.transaction.select('id', 'name').from('roles');
19
+
20
+ await this.importForEach(users, 1);
21
+ }
22
+
23
+ generate() {
24
+ const userRoles = ['Editor', 'Contributor', 'Author'];
25
+ const userRole = userRoles[faker.datatype.number({
26
+ min: 0,
27
+ max: userRoles.length - 1
28
+ })];
29
+ const actualRole = this.roles.find(role => role.name === userRole);
30
+ if (!actualRole) {
31
+ // No roles defined in database, don't bother creating user role
32
+ return;
33
+ }
34
+ return {
35
+ id: this.fastFakeObjectId(),
36
+ role_id: actualRole.id,
37
+ user_id: this.model.id
38
+ };
39
+ }
40
+ }
41
+
42
+ module.exports = RolesUsersImporter;
@@ -0,0 +1,69 @@
1
+ const {faker} = require('@faker-js/faker');
2
+ const TableImporter = require('./TableImporter');
3
+ const {blogStartDate} = require('../utils/blog-info');
4
+
5
+ const sixWeeksLater = new Date(blogStartDate);
6
+ sixWeeksLater.setDate(sixWeeksLater.getDate() + (7 * 6));
7
+
8
+ class StripePricesImporter extends TableImporter {
9
+ static table = 'stripe_prices';
10
+ static dependencies = ['products', 'stripe_products'];
11
+
12
+ constructor(knex, transaction) {
13
+ super(StripePricesImporter.table, knex, transaction);
14
+ }
15
+
16
+ async import() {
17
+ const stripeProducts = await this.transaction.select('id', 'stripe_product_id', 'product_id').from('stripe_products');
18
+ this.products = await this.transaction.select('id', 'monthly_price', 'yearly_price').from('products');
19
+
20
+ await this.importForEach(stripeProducts, 2);
21
+ }
22
+
23
+ setReferencedModel(model) {
24
+ this.model = model;
25
+ this.count = 0;
26
+ }
27
+
28
+ generate() {
29
+ const count = this.count;
30
+ this.count = this.count + 1;
31
+
32
+ const relatedProduct = this.products.find(product => product.id === this.model.product_id);
33
+
34
+ if (count === 1 && relatedProduct.monthly_price === null) {
35
+ // Only single complimentary price (yearly)
36
+ return null;
37
+ }
38
+
39
+ const billingCycle = {
40
+ nickname: 'Monthly',
41
+ interval: 'month',
42
+ type: 'recurring',
43
+ currency: 'usd',
44
+ amount: relatedProduct.monthly_price
45
+ };
46
+ if (count === 1) {
47
+ billingCycle.nickname = 'Yearly';
48
+ billingCycle.interval = 'year';
49
+ billingCycle.amount = relatedProduct.yearly_price;
50
+ } else if (relatedProduct.monthly_price === null) {
51
+ billingCycle.nickname = 'Complimentary';
52
+ billingCycle.interval = 'year';
53
+ billingCycle.amount = 0;
54
+ }
55
+
56
+ return Object.assign({}, {
57
+ id: this.fastFakeObjectId(),
58
+ stripe_price_id: faker.datatype.hexadecimal({
59
+ length: 64,
60
+ prefix: ''
61
+ }),
62
+ stripe_product_id: this.model.stripe_product_id,
63
+ active: true,
64
+ created_at: faker.date.between(blogStartDate, sixWeeksLater)
65
+ }, billingCycle);
66
+ }
67
+ }
68
+
69
+ module.exports = StripePricesImporter;
@@ -0,0 +1,34 @@
1
+ const {faker} = require('@faker-js/faker');
2
+ const TableImporter = require('./TableImporter');
3
+ const {blogStartDate} = require('../utils/blog-info');
4
+
5
+ const sixWeeksLater = new Date(blogStartDate);
6
+ sixWeeksLater.setDate(sixWeeksLater.getDate() + (7 * 6));
7
+
8
+ class StripeProductsImporter extends TableImporter {
9
+ static table = 'stripe_products';
10
+ static dependencies = ['products'];
11
+
12
+ constructor(knex, transaction) {
13
+ super(StripeProductsImporter.table, knex, transaction);
14
+ }
15
+
16
+ async import() {
17
+ const products = await this.transaction.select('id').from('products');
18
+ await this.importForEach(products, 1);
19
+ }
20
+
21
+ generate() {
22
+ return {
23
+ id: this.fastFakeObjectId(),
24
+ product_id: this.model.id,
25
+ stripe_product_id: faker.datatype.hexadecimal({
26
+ length: 64,
27
+ prefix: ''
28
+ }),
29
+ created_at: faker.date.between(blogStartDate, sixWeeksLater)
30
+ };
31
+ }
32
+ }
33
+
34
+ module.exports = StripeProductsImporter;
@@ -0,0 +1,187 @@
1
+ const debug = require('@tryghost/debug')('TableImporter');
2
+ const dateToDatabaseString = require('../utils/database-date');
3
+ const path = require('path');
4
+ const createCsvWriter = require('csv-writer').createObjectCsvWriter;
5
+ const fs = require('fs');
6
+ const {luck} = require('../utils/random');
7
+ const os = require('os');
8
+ const crypto = require('crypto');
9
+ const logging = require('@tryghost/logging');
10
+ const errors = require('@tryghost/errors');
11
+
12
+ class TableImporter {
13
+ /**
14
+ * @type {object|undefined} model Referenced model when generating data
15
+ */
16
+ model;
17
+
18
+ /**
19
+ * @type {number|undefined} defaultQuantity Default number of records to import
20
+ */
21
+ defaultQuantity;
22
+
23
+ /**
24
+ * Transaction and knex need to be separate since we're using the batchInsert helper
25
+ * @param {string} name Name of the table to be generated
26
+ * @param {import('knex/types').Knex} knex Database connection
27
+ * @param {import('knex/types').Knex.Transaction} transaction Transaction to be used for import
28
+ */
29
+ constructor(name, knex, transaction) {
30
+ this.name = name;
31
+ this.knex = knex;
32
+ this.transaction = transaction;
33
+ }
34
+
35
+ fastFakeObjectId() {
36
+ // It is important that IDs are generated for a timestamp < NOW (for email batch sending) and that
37
+ // generating the ids is fast.
38
+ return `00000000` + crypto.randomBytes(8).toString('hex');
39
+ }
40
+
41
+ async #generateData(amount = this.defaultQuantity) {
42
+ let data = [];
43
+
44
+ for (let i = 0; i < amount; i++) {
45
+ const model = await this.generate();
46
+ if (model) {
47
+ data.push(model);
48
+ }
49
+ }
50
+
51
+ return data;
52
+ }
53
+
54
+ async import(amount = this.defaultQuantity) {
55
+ const generateNow = Date.now();
56
+ const data = await this.#generateData(amount);
57
+ debug(`${this.name} generated ${data.length} records in ${Date.now() - generateNow}ms`);
58
+
59
+ if (data.length > 0) {
60
+ await this.batchInsert(data);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @param {Array<Object>} models List of models to reference
66
+ * @param {Number|function} amount Number of records to import per model
67
+ */
68
+ async importForEach(models = [], amount) {
69
+ const data = [];
70
+
71
+ debug (`Generating data for ${models.length} models x ${amount} for ${this.name}`);
72
+ const now = Date.now();
73
+ let settingReferenceModel = 0;
74
+
75
+ for (const model of models) {
76
+ let s = Date.now();
77
+ this.setReferencedModel(model);
78
+ settingReferenceModel += Date.now() - s;
79
+
80
+ let currentAmount = (typeof amount === 'function') ? amount() : amount;
81
+ if (!Number.isInteger(currentAmount)) {
82
+ currentAmount = Math.floor(currentAmount) + luck((currentAmount % 1) * 100);
83
+ }
84
+
85
+ const generatedData = await this.#generateData(currentAmount);
86
+ if (generatedData.length > 0) {
87
+ data.push(...generatedData);
88
+ }
89
+ }
90
+
91
+ debug(`${this.name} generated ${data.length} records in ${Date.now() - now}ms (${settingReferenceModel}ms setting reference model)`);
92
+
93
+ if (data.length > 0) {
94
+ await this.batchInsert(data);
95
+ }
96
+ }
97
+
98
+ async batchInsert(data) {
99
+ // Write to CSV file
100
+ const rootFolder = os.tmpdir();
101
+ const filePath = path.join(rootFolder, `${this.name}.csv`);
102
+ let now = Date.now();
103
+
104
+ if (data.length > 5000 && !process.env.DISABLE_FAST_IMPORT) {
105
+ try {
106
+ await fs.promises.unlink(filePath);
107
+ } catch (e) {
108
+ // Ignore: file doesn't exist
109
+ }
110
+
111
+ const csvWriter = createCsvWriter({
112
+ path: filePath,
113
+ header: Object.keys(data[0]).map((key) => {
114
+ return {id: key, title: key};
115
+ })
116
+ });
117
+
118
+ // Loop the data in chunks of 50.000 items
119
+ const batchSize = 50000;
120
+
121
+ // Otherwise we get a out of range error because csvWriter tries to create a string that is too long
122
+ for (let i = 0; i < data.length; i += batchSize) {
123
+ const slicedData = data.slice(i, i + batchSize);
124
+
125
+ // Map data to what MySQL expects in the CSV for values like booleans, null and dates
126
+ for (let j = 0; j < slicedData.length; j++) {
127
+ const obj = slicedData[j];
128
+
129
+ for (const [key, value] of Object.entries(obj)) {
130
+ if (typeof value === 'boolean') {
131
+ obj[key] = value ? 1 : 0;
132
+ } else if (value instanceof Date) {
133
+ obj[key] = dateToDatabaseString(value);
134
+ } else if (value === null) {
135
+ obj[key] = '\\N';
136
+ }
137
+ }
138
+ }
139
+ await csvWriter.writeRecords(slicedData);
140
+ }
141
+
142
+ debug(`${this.name} saved CSV import file in ${Date.now() - now}ms`);
143
+ now = Date.now();
144
+
145
+ // Import from CSV file
146
+ const [result] = await this.transaction.raw(`LOAD DATA LOCAL INFILE '${filePath}' INTO TABLE \`${this.name}\` FIELDS TERMINATED BY ',' ENCLOSED BY '"' IGNORE 1 LINES (${Object.keys(data[0]).map(d => '`' + d + '`').join(',')});`);
147
+ if (result.affectedRows !== data.length) {
148
+ if (Math.abs(result.affectedRows - data.length) > 0.01 * data.length) {
149
+ throw new errors.InternalServerError({
150
+ message: `CSV import failed: expected ${data.length} imported rows, got ${result.affectedRows}`
151
+ });
152
+ }
153
+ logging.warn(`CSV import warning: expected ${data.length} imported rows, got ${result.affectedRows}.`);
154
+ }
155
+ } else {
156
+ await this.knex.batchInsert(this.name, data).transacting(this.transaction);
157
+ }
158
+
159
+ debug(`${this.name} imported ${data.length} records in ${Date.now() - now}ms`);
160
+ }
161
+
162
+ /**
163
+ * Finalise the imported data, e.g. adding summary records based on a table's dependents
164
+ */
165
+ async finalise() {
166
+ // No-op by default
167
+ }
168
+
169
+ /**
170
+ * Sets the model which newly generated data will reference
171
+ * @param {Object} model Model to reference when generating data
172
+ */
173
+ setReferencedModel(model) {
174
+ this.model = model;
175
+ }
176
+
177
+ /**
178
+ * Generates the data for a single model to be imported
179
+ * @returns {Object|null} Data to import, optional
180
+ */
181
+ generate() {
182
+ // Should never be called
183
+ return false;
184
+ }
185
+ }
186
+
187
+ module.exports = TableImporter;
@@ -0,0 +1,41 @@
1
+ const {faker} = require('@faker-js/faker');
2
+ const {slugify} = require('@tryghost/string');
3
+ const TableImporter = require('./TableImporter');
4
+ const dateToDatabaseString = require('../utils/database-date');
5
+
6
+ class TagsImporter extends TableImporter {
7
+ static table = 'tags';
8
+ static dependencies = ['users'];
9
+ defaultQuantity = faker.datatype.number({
10
+ min: 16,
11
+ max: 24
12
+ });
13
+
14
+ constructor(knex, transaction) {
15
+ super(TagsImporter.table, knex, transaction);
16
+ }
17
+
18
+ async import(quantity = this.defaultQuantity) {
19
+ this.users = await this.transaction.select('id').from('users');
20
+ await super.import(quantity);
21
+ }
22
+
23
+ generate() {
24
+ let name = `${faker.color.human()} ${faker.name.jobType()}`;
25
+ name = `${name[0].toUpperCase()}${name.slice(1)}`;
26
+ const threeYearsAgo = new Date();
27
+ threeYearsAgo.setFullYear(threeYearsAgo.getFullYear() - 3);
28
+ const twoYearsAgo = new Date();
29
+ twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2);
30
+ return {
31
+ id: this.fastFakeObjectId(),
32
+ name: name,
33
+ slug: `${slugify(name)}-${faker.random.numeric(3)}`,
34
+ description: faker.lorem.sentence(),
35
+ created_at: dateToDatabaseString(faker.date.between(threeYearsAgo, twoYearsAgo)),
36
+ created_by: this.users[faker.datatype.number(this.users.length - 1)].id
37
+ };
38
+ }
39
+ }
40
+
41
+ module.exports = TagsImporter;
@@ -0,0 +1,31 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {faker} = require('@faker-js/faker');
3
+ const {slugify} = require('@tryghost/string');
4
+ const security = require('@tryghost/security');
5
+ const dateToDatabaseString = require('../utils/database-date');
6
+
7
+ class UsersImporter extends TableImporter {
8
+ static table = 'users';
9
+ static dependencies = [];
10
+ defaultQuantity = 8;
11
+
12
+ constructor(knex, transaction) {
13
+ super(UsersImporter.table, knex, transaction);
14
+ }
15
+
16
+ async generate() {
17
+ const name = `${faker.name.firstName()} ${faker.name.lastName()}`;
18
+ return {
19
+ id: this.fastFakeObjectId(),
20
+ name: name,
21
+ slug: slugify(name),
22
+ password: await security.password.hash(faker.color.human()),
23
+ email: faker.internet.email(name),
24
+ profile_image: faker.internet.avatar(),
25
+ created_at: dateToDatabaseString(faker.date.between(new Date(2016, 0), new Date())),
26
+ created_by: 'unused'
27
+ };
28
+ }
29
+ }
30
+
31
+ module.exports = UsersImporter;
@@ -0,0 +1,42 @@
1
+ const TableImporter = require('./TableImporter');
2
+ const {faker} = require('@faker-js/faker');
3
+ const dateToDatabaseString = require('../utils/database-date');
4
+
5
+ class WebMentionsImporter extends TableImporter {
6
+ static table = 'mentions';
7
+ static dependencies = [];
8
+ defaultQuantity = 23;
9
+
10
+ constructor(knex, transaction, {baseUrl}) {
11
+ super(WebMentionsImporter.table, knex, transaction);
12
+
13
+ this.baseUrl = baseUrl;
14
+ }
15
+
16
+ generate() {
17
+ const id = this.fastFakeObjectId();
18
+
19
+ const author = `${faker.name.fullName()}`;
20
+
21
+ // Generating only incoming recommendations for now, since we don't use webmentions for other things atm
22
+ return {
23
+ id,
24
+ source: `${faker.internet.url()}/.well-known/recommendations.json`,
25
+ source_title: faker.lorem.sentence(5),
26
+ source_site_title: `${author}'s ${faker.word.noun()}`,
27
+ source_excerpt: faker.lorem.paragraph(),
28
+ source_author: author,
29
+ source_featured_image: `https://api.dicebear.com/5.x/shapes/png?size=256&seed=${id}`,
30
+ source_favicon: `https://api.dicebear.com/5.x/bottts/png?size=32&seed=${id}`,
31
+ target: `${this.baseUrl}`,
32
+ resource_id: null,
33
+ resource_type: null,
34
+ created_at: dateToDatabaseString(faker.date.past()),
35
+ payload: JSON.stringify({}),
36
+ deleted: 0,
37
+ verified: 1
38
+ };
39
+ }
40
+ }
41
+
42
+ module.exports = WebMentionsImporter;
@@ -0,0 +1,41 @@
1
+ module.exports = [
2
+ require('./NewslettersImporter'),
3
+ require('./PostsImporter'),
4
+ require('./UsersImporter'),
5
+ require('./TagsImporter'),
6
+ require('./ProductsImporter'),
7
+ require('./MembersImporter'),
8
+ require('./BenefitsImporter'),
9
+ require('./WebMentionsImporter'),
10
+ require('./PostsAuthorsImporter'),
11
+ require('./PostsTagsImporter'),
12
+ require('./ProductsBenefitsImporter'),
13
+ require('./MembersProductsImporter'),
14
+ require('./PostsProductsImporter'),
15
+ require('./MembersNewslettersImporter'),
16
+ require('./StripeProductsImporter'),
17
+ require('./StripePricesImporter'),
18
+ require('./EmailsImporter'),
19
+ require('./EmailBatchesImporter'),
20
+ require('./EmailRecipientsImporter'),
21
+ require('./EmailRecipientFailuresImporter'),
22
+ require('./RedirectsImporter'),
23
+ require('./MembersClickEventsImporter'),
24
+ require('./OffersImporter'),
25
+ require('./MembersCreatedEventsImporter'),
26
+ require('./MembersLoginEventsImporter'),
27
+ require('./MembersStatusEventsImporter'),
28
+ require('./MembersStripeCustomersImporter'),
29
+ require('./MembersStripeCustomersSubscriptionsImporter'),
30
+ require('./MembersPaidSubscriptionEventsImporter'),
31
+ require('./MembersSubscriptionCreatedEventsImporter'),
32
+ require('./MembersSubscribeEventsImporter'),
33
+ require('./LabelsImporter'),
34
+ require('./MembersLabelsImporter'),
35
+ require('./RolesUsersImporter'),
36
+ require('./MembersFeedbackImporter'),
37
+ require('./RecommendationsImporter'),
38
+ require('./RecommendationClickEventsImporter'),
39
+ require('./RecommendationSubscribeEventsImporter'),
40
+ require('./CommentsImporter')
41
+ ];
@@ -0,0 +1,39 @@
1
+ const TableImporter = require('../importers/TableImporter');
2
+
3
+ class JsonImporter extends TableImporter {
4
+ constructor(knex, transaction) {
5
+ super();
6
+ this.knex = knex;
7
+ this.transaction = transaction;
8
+ }
9
+
10
+ /**
11
+ * @typedef {Object} JsonImportOptions
12
+ * @property {string} name Name of the table to import
13
+ * @property {Object} data Models without ids to be imported
14
+ * @property {Array<string>} [rows] Set of rows to be returned
15
+ */
16
+
17
+ /**
18
+ * Import a dataset to the database
19
+ * @param {JsonImportOptions} options
20
+ * @returns {Promise}
21
+ */
22
+ async import({
23
+ name,
24
+ data,
25
+ rows = []
26
+ }) {
27
+ for (const obj of data) {
28
+ if (!('id' in obj)) {
29
+ obj.id = this.fastFakeObjectId();
30
+ }
31
+ }
32
+ if (rows.findIndex(row => row === 'id') === -1) {
33
+ rows.unshift('id');
34
+ }
35
+ await this.knex.batchInsert(name, data, 500).transacting(this.transaction);
36
+ }
37
+ }
38
+
39
+ module.exports = JsonImporter;
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ blogStartDate: new Date(2018, 5, 4)
3
+ };
@@ -0,0 +1,7 @@
1
+ module.exports = function dateToDatabaseString(date) {
2
+ if (typeof date === 'string') {
3
+ // SQLite fix when reusing other dates from the db
4
+ return date;
5
+ }
6
+ return date.toISOString().replace('Z','').replace('T', ' ');
7
+ };