ghost 5.115.1 → 5.116.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 (238) hide show
  1. package/components/{tryghost-api-framework-5.115.1.tgz → tryghost-api-framework-5.116.0.tgz} +0 -0
  2. package/components/tryghost-constants-5.116.0.tgz +0 -0
  3. package/components/tryghost-custom-fonts-5.116.0.tgz +0 -0
  4. package/components/{tryghost-custom-theme-settings-service-5.115.1.tgz → tryghost-custom-theme-settings-service-5.116.0.tgz} +0 -0
  5. package/components/tryghost-domain-events-5.116.0.tgz +0 -0
  6. package/components/tryghost-donations-5.116.0.tgz +0 -0
  7. package/components/tryghost-email-addresses-5.116.0.tgz +0 -0
  8. package/components/tryghost-email-service-5.116.0.tgz +0 -0
  9. package/components/tryghost-email-suppression-list-5.116.0.tgz +0 -0
  10. package/components/tryghost-html-to-plaintext-5.116.0.tgz +0 -0
  11. package/components/tryghost-i18n-5.116.0.tgz +0 -0
  12. package/components/tryghost-job-manager-5.116.0.tgz +0 -0
  13. package/components/tryghost-link-replacer-5.116.0.tgz +0 -0
  14. package/components/tryghost-magic-link-5.116.0.tgz +0 -0
  15. package/components/{tryghost-member-attribution-5.115.1.tgz → tryghost-member-attribution-5.116.0.tgz} +0 -0
  16. package/components/tryghost-member-events-5.116.0.tgz +0 -0
  17. package/components/tryghost-members-api-5.116.0.tgz +0 -0
  18. package/components/tryghost-members-csv-5.116.0.tgz +0 -0
  19. package/components/{tryghost-members-offers-5.115.1.tgz → tryghost-members-offers-5.116.0.tgz} +0 -0
  20. package/components/{tryghost-milestones-5.115.1.tgz → tryghost-milestones-5.116.0.tgz} +0 -0
  21. package/components/{tryghost-mw-error-handler-5.115.1.tgz → tryghost-mw-error-handler-5.116.0.tgz} +0 -0
  22. package/components/tryghost-mw-vhost-5.116.0.tgz +0 -0
  23. package/components/tryghost-post-events-5.116.0.tgz +0 -0
  24. package/components/{tryghost-post-revisions-5.115.1.tgz → tryghost-post-revisions-5.116.0.tgz} +0 -0
  25. package/components/tryghost-posts-service-5.116.0.tgz +0 -0
  26. package/components/{tryghost-prometheus-metrics-5.115.1.tgz → tryghost-prometheus-metrics-5.116.0.tgz} +0 -0
  27. package/components/tryghost-security-5.116.0.tgz +0 -0
  28. package/components/{tryghost-tiers-5.115.1.tgz → tryghost-tiers-5.116.0.tgz} +0 -0
  29. package/components/tryghost-webmentions-5.116.0.tgz +0 -0
  30. package/core/boot.js +0 -42
  31. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +24764 -24129
  32. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  33. package/core/built/admin/assets/admin-x-demo/{index-15df2af5.mjs → index-a9601514.mjs} +3 -3
  34. package/core/built/admin/assets/admin-x-demo/{modals-8ca61d78.mjs → modals-c1789d04.mjs} +2 -2
  35. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-d2e6872f.mjs → CodeEditorView-e9c9deb8.mjs} +2 -2
  36. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  37. package/core/built/admin/assets/admin-x-settings/{index-8e8821e5.mjs → index-84580c3a.mjs} +2 -2
  38. package/core/built/admin/assets/admin-x-settings/{index-f5cb3db3.mjs → index-f744cab7.mjs} +49 -35
  39. package/core/built/admin/assets/admin-x-settings/{modals-e8ae4d46.mjs → modals-d9ca60c5.mjs} +1198 -1192
  40. package/core/built/admin/assets/chunk.524.8371443ef8f60db429d0.js +35 -0
  41. package/core/built/admin/assets/chunk.582.f90151775f2e53dd21d9.js +37 -0
  42. package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js → chunk.713.e9027c0cc3c56110f5da.js} +125 -98
  43. package/core/built/admin/assets/{ghost-df7b9558260aa27d18b195ee895b487d.js → ghost-03b64c086f3c60cabc85fe7a7e2b640a.js} +144 -145
  44. package/core/built/admin/assets/ghost-ba58e9822f7384461e926c7e23f04a75.css +1 -0
  45. package/core/built/admin/assets/ghost-dark-f1f29683b14ffa11615b3bba8b6ab92c.css +1 -0
  46. package/core/built/admin/assets/koenig-lexical/index.css +1 -1
  47. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +20563 -20891
  48. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +139 -139
  49. package/core/built/admin/assets/posts/posts.js +5732 -5667
  50. package/core/built/admin/assets/stats/stats.js +71082 -7533
  51. package/core/built/admin/assets/{vendor-68a4aa424a179a90f5bbc2b750def576.js → vendor-72026232b36d97babc6320917c16c321.js} +36 -34
  52. package/core/built/admin/index.html +6 -6
  53. package/core/cli/generate-data.js +1 -1
  54. package/core/frontend/helpers/ghost_head.js +6 -1
  55. package/core/frontend/public/ghost-stats.js +55 -2
  56. package/core/frontend/services/assets-minification/AdminAuthAssets.js +2 -1
  57. package/core/frontend/services/assets-minification/CardAssets.js +1 -1
  58. package/core/frontend/services/assets-minification/CommentCountsAssets.js +1 -1
  59. package/core/frontend/services/assets-minification/MemberAttributionAssets.js +1 -1
  60. package/core/frontend/services/assets-minification/Minifier.js +191 -0
  61. package/core/frontend/services/routing/controllers/previews.js +2 -1
  62. package/core/server/adapters/cache/Redis.js +1 -1
  63. package/core/server/adapters/lib/redis/AdapterCacheRedis.js +287 -0
  64. package/core/server/adapters/lib/redis/redis-store-factory.js +22 -0
  65. package/core/server/api/endpoints/posts.js +9 -3
  66. package/core/server/api/endpoints/previews.js +35 -1
  67. package/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js +6 -9
  68. package/core/server/api/endpoints/utils/validators/input/settings.js +1 -1
  69. package/core/server/data/db/connection.js +2 -0
  70. package/core/server/data/db/index.js +1 -0
  71. package/core/server/data/importer/handlers/ImporterContentFileHandler.js +90 -0
  72. package/core/server/data/importer/import-manager.js +1 -1
  73. package/core/server/data/seeders/DataGenerator.js +288 -0
  74. package/core/server/data/seeders/importers/BenefitsImporter.js +28 -0
  75. package/core/server/data/seeders/importers/CommentsImporter.js +73 -0
  76. package/core/server/data/seeders/importers/EmailBatchesImporter.js +38 -0
  77. package/core/server/data/seeders/importers/EmailRecipientFailuresImporter.js +67 -0
  78. package/core/server/data/seeders/importers/EmailRecipientsImporter.js +212 -0
  79. package/core/server/data/seeders/importers/EmailsImporter.js +99 -0
  80. package/core/server/data/seeders/importers/LabelsImporter.js +41 -0
  81. package/core/server/data/seeders/importers/MembersClickEventsImporter.js +69 -0
  82. package/core/server/data/seeders/importers/MembersCreatedEventsImporter.js +103 -0
  83. package/core/server/data/seeders/importers/MembersFeedbackImporter.js +45 -0
  84. package/core/server/data/seeders/importers/MembersImporter.js +111 -0
  85. package/core/server/data/seeders/importers/MembersLabelsImporter.js +39 -0
  86. package/core/server/data/seeders/importers/MembersLoginEventsImporter.js +69 -0
  87. package/core/server/data/seeders/importers/MembersNewslettersImporter.js +38 -0
  88. package/core/server/data/seeders/importers/MembersPaidSubscriptionEventsImporter.js +99 -0
  89. package/core/server/data/seeders/importers/MembersProductsImporter.js +42 -0
  90. package/core/server/data/seeders/importers/MembersStatusEventsImporter.js +58 -0
  91. package/core/server/data/seeders/importers/MembersStripeCustomersImporter.js +60 -0
  92. package/core/server/data/seeders/importers/MembersStripeCustomersSubscriptionsImporter.js +259 -0
  93. package/core/server/data/seeders/importers/MembersSubscribeEventsImporter.js +69 -0
  94. package/core/server/data/seeders/importers/MembersSubscriptionCreatedEventsImporter.js +95 -0
  95. package/core/server/data/seeders/importers/NewslettersImporter.js +40 -0
  96. package/core/server/data/seeders/importers/OffersImporter.js +70 -0
  97. package/core/server/data/seeders/importers/PostsAuthorsImporter.js +32 -0
  98. package/core/server/data/seeders/importers/PostsImporter.js +102 -0
  99. package/core/server/data/seeders/importers/PostsProductsImporter.js +35 -0
  100. package/core/server/data/seeders/importers/PostsTagsImporter.js +46 -0
  101. package/core/server/data/seeders/importers/ProductsBenefitsImporter.js +54 -0
  102. package/core/server/data/seeders/importers/ProductsImporter.js +90 -0
  103. package/core/server/data/seeders/importers/RecommendationClickEventsImporter.js +32 -0
  104. package/core/server/data/seeders/importers/RecommendationSubscribeEventsImporter.js +32 -0
  105. package/core/server/data/seeders/importers/RecommendationsImporter.js +34 -0
  106. package/core/server/data/seeders/importers/RedirectsImporter.js +49 -0
  107. package/core/server/data/seeders/importers/RolesUsersImporter.js +42 -0
  108. package/core/server/data/seeders/importers/StripePricesImporter.js +69 -0
  109. package/core/server/data/seeders/importers/StripeProductsImporter.js +34 -0
  110. package/core/server/data/seeders/importers/TableImporter.js +187 -0
  111. package/core/server/data/seeders/importers/TagsImporter.js +41 -0
  112. package/core/server/data/seeders/importers/UsersImporter.js +31 -0
  113. package/core/server/data/seeders/importers/WebMentionsImporter.js +42 -0
  114. package/core/server/data/seeders/importers/index.js +41 -0
  115. package/core/server/data/seeders/utils/JsonImporter.js +39 -0
  116. package/core/server/data/seeders/utils/blog-info.js +3 -0
  117. package/core/server/data/seeders/utils/database-date.js +7 -0
  118. package/core/server/data/seeders/utils/event-generator.js +48 -0
  119. package/core/server/data/seeders/utils/random.js +13 -0
  120. package/core/server/data/seeders/utils/topological-sort.js +33 -0
  121. package/core/server/services/adapter-manager/AdapterManager.js +161 -0
  122. package/core/server/services/adapter-manager/index.js +1 -1
  123. package/core/server/services/announcement-bar-service/AnnouncementBarSettings.js +54 -0
  124. package/core/server/services/announcement-bar-service/AnnouncementVisibilityValues.js +11 -0
  125. package/core/server/services/announcement-bar-service/index.js +1 -1
  126. package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +1 -1
  127. package/core/server/services/auth/session/session-service.js +15 -5
  128. package/core/server/services/custom-redirects/index.js +1 -1
  129. package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +1 -1
  130. package/core/server/services/email-service/EmailServiceWrapper.js +4 -4
  131. package/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js +1 -1
  132. package/core/server/services/email-suppression-list/service.js +1 -1
  133. package/core/server/services/lib/DynamicRedirectManager.js +156 -0
  134. package/core/server/services/lib/EmailContentGenerator.js +54 -0
  135. package/core/server/services/lib/InMemoryRepository.js +62 -0
  136. package/core/server/services/lib/InMemoryRepository.ts +80 -0
  137. package/core/server/services/lib/MailgunClient.js +364 -0
  138. package/core/server/services/link-redirection/LinkRedirect.js +26 -0
  139. package/core/server/services/link-redirection/LinkRedirectRepository.js +7 -7
  140. package/core/server/services/link-redirection/LinkRedirectsService.js +123 -0
  141. package/core/server/services/link-redirection/README.md +151 -0
  142. package/core/server/services/link-redirection/RedirectEvent.js +24 -0
  143. package/core/server/services/link-redirection/index.js +1 -1
  144. package/core/server/services/link-tracking/LinkClickTrackingService.js +1 -1
  145. package/core/server/services/mail/index.js +1 -1
  146. package/core/server/services/mail-events/InMemoryMailEventRepository.js +2 -2
  147. package/core/server/services/mail-events/InMemoryMailEventRepository.ts +1 -1
  148. package/core/server/services/members-events/LastSeenAtUpdater.js +1 -1
  149. package/core/server/services/offers/service.js +1 -1
  150. package/core/server/services/recommendations/RecommendationServiceWrapper.js +8 -8
  151. package/core/server/services/recommendations/service/BookshelfClickEventRepository.js +48 -0
  152. package/core/server/services/recommendations/service/BookshelfClickEventRepository.ts +49 -0
  153. package/core/server/services/recommendations/service/BookshelfRecommendationRepository.js +98 -0
  154. package/core/server/services/recommendations/service/BookshelfRecommendationRepository.ts +117 -0
  155. package/core/server/services/recommendations/service/BookshelfRepository.js +134 -0
  156. package/core/server/services/recommendations/service/BookshelfRepository.ts +196 -0
  157. package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.js +48 -0
  158. package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.ts +49 -0
  159. package/core/server/services/recommendations/service/ClickEvent.js +33 -0
  160. package/core/server/services/recommendations/service/ClickEvent.ts +32 -0
  161. package/core/server/services/recommendations/service/InMemoryRecommendationRepository.js +19 -0
  162. package/core/server/services/recommendations/service/InMemoryRecommendationRepository.ts +20 -0
  163. package/core/server/services/recommendations/service/IncomingRecommendationController.js +34 -0
  164. package/core/server/services/recommendations/service/IncomingRecommendationController.ts +51 -0
  165. package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.js +25 -0
  166. package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.ts +37 -0
  167. package/core/server/services/recommendations/service/IncomingRecommendationService.js +93 -0
  168. package/core/server/services/recommendations/service/IncomingRecommendationService.ts +160 -0
  169. package/core/server/services/recommendations/service/Recommendation.js +140 -0
  170. package/core/server/services/recommendations/service/Recommendation.ts +201 -0
  171. package/core/server/services/recommendations/service/RecommendationController.js +208 -0
  172. package/core/server/services/recommendations/service/RecommendationController.ts +258 -0
  173. package/core/server/services/recommendations/service/RecommendationMetadataService.js +86 -0
  174. package/core/server/services/recommendations/service/RecommendationMetadataService.ts +128 -0
  175. package/core/server/services/recommendations/service/RecommendationRepository.js +2 -0
  176. package/core/server/services/recommendations/service/RecommendationRepository.ts +13 -0
  177. package/core/server/services/recommendations/service/RecommendationService.js +228 -0
  178. package/core/server/services/recommendations/service/RecommendationService.ts +281 -0
  179. package/core/server/services/recommendations/service/SubscribeEvent.js +33 -0
  180. package/core/server/services/recommendations/service/SubscribeEvent.ts +32 -0
  181. package/core/server/services/recommendations/service/UnsafeData.js +183 -0
  182. package/core/server/services/recommendations/service/UnsafeData.ts +217 -0
  183. package/core/server/services/recommendations/service/WellknownService.js +36 -0
  184. package/core/server/services/recommendations/service/WellknownService.ts +47 -0
  185. package/core/server/services/recommendations/service/index.js +31 -0
  186. package/core/server/services/recommendations/service/index.ts +15 -0
  187. package/core/server/services/recommendations/service/libraries.d.ts +5 -0
  188. package/core/server/services/slack-notifications/SlackNotifications.js +211 -0
  189. package/core/server/services/slack-notifications/SlackNotificationsService.js +90 -0
  190. package/core/server/services/slack-notifications/service.js +4 -6
  191. package/core/server/web/api/endpoints/admin/app.js +1 -21
  192. package/core/server/web/api/middleware/version-match.js +41 -0
  193. package/core/shared/labs.js +2 -2
  194. package/package.json +87 -104
  195. package/tsconfig.tsbuildinfo +1 -1
  196. package/yarn.lock +1470 -1540
  197. package/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
  198. package/components/tryghost-adapter-manager-5.115.1.tgz +0 -0
  199. package/components/tryghost-announcement-bar-settings-5.115.1.tgz +0 -0
  200. package/components/tryghost-constants-5.115.1.tgz +0 -0
  201. package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
  202. package/components/tryghost-data-generator-5.115.1.tgz +0 -0
  203. package/components/tryghost-domain-events-5.115.1.tgz +0 -0
  204. package/components/tryghost-donations-5.115.1.tgz +0 -0
  205. package/components/tryghost-email-addresses-5.115.1.tgz +0 -0
  206. package/components/tryghost-email-content-generator-5.115.1.tgz +0 -0
  207. package/components/tryghost-email-events-5.115.1.tgz +0 -0
  208. package/components/tryghost-email-service-5.115.1.tgz +0 -0
  209. package/components/tryghost-email-suppression-list-5.115.1.tgz +0 -0
  210. package/components/tryghost-express-dynamic-redirects-5.115.1.tgz +0 -0
  211. package/components/tryghost-ghost-5.115.1.tgz +0 -0
  212. package/components/tryghost-html-to-plaintext-5.115.1.tgz +0 -0
  213. package/components/tryghost-i18n-5.115.1.tgz +0 -0
  214. package/components/tryghost-importer-handler-content-files-5.115.1.tgz +0 -0
  215. package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
  216. package/components/tryghost-job-manager-5.115.1.tgz +0 -0
  217. package/components/tryghost-link-redirects-5.115.1.tgz +0 -0
  218. package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
  219. package/components/tryghost-magic-link-5.115.1.tgz +0 -0
  220. package/components/tryghost-mailgun-client-5.115.1.tgz +0 -0
  221. package/components/tryghost-member-events-5.115.1.tgz +0 -0
  222. package/components/tryghost-members-api-5.115.1.tgz +0 -0
  223. package/components/tryghost-members-csv-5.115.1.tgz +0 -0
  224. package/components/tryghost-members-payments-5.115.1.tgz +0 -0
  225. package/components/tryghost-minifier-5.115.1.tgz +0 -0
  226. package/components/tryghost-mw-version-match-5.115.1.tgz +0 -0
  227. package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
  228. package/components/tryghost-post-events-5.115.1.tgz +0 -0
  229. package/components/tryghost-posts-service-5.115.1.tgz +0 -0
  230. package/components/tryghost-recommendations-5.115.1.tgz +0 -0
  231. package/components/tryghost-security-5.115.1.tgz +0 -0
  232. package/components/tryghost-slack-notifications-5.115.1.tgz +0 -0
  233. package/components/tryghost-webmentions-5.115.1.tgz +0 -0
  234. package/core/built/admin/assets/chunk.524.2439684964c164c598ab.js +0 -35
  235. package/core/built/admin/assets/chunk.582.bf5a2bbb2c4eb69ef1e7.js +0 -37
  236. package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +0 -1
  237. package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +0 -1
  238. /package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js.LICENSE.txt → chunk.713.e9027c0cc3c56110f5da.js.LICENSE.txt} +0 -0
@@ -0,0 +1,48 @@
1
+ const probabilityDistributions = require('probability-distributions');
2
+
3
+ const generateEvents = ({
4
+ shape = 'flat',
5
+ trend = 'positive',
6
+ total = 0,
7
+ startTime = new Date(),
8
+ endTime = new Date()
9
+ } = {}) => {
10
+ if (total <= 0) {
11
+ return [];
12
+ }
13
+
14
+ let alpha = 0;
15
+ let beta = 0;
16
+ let positiveTrend = trend === 'positive';
17
+ switch (shape) {
18
+ case 'linear':
19
+ alpha = 2;
20
+ beta = 1;
21
+ break;
22
+ case 'ease-in':
23
+ alpha = 4;
24
+ beta = 1;
25
+ break;
26
+ case 'ease-out':
27
+ alpha = 1;
28
+ beta = 4;
29
+ positiveTrend = !positiveTrend;
30
+ break;
31
+ case 'flat':
32
+ alpha = 1;
33
+ beta = 1;
34
+ break;
35
+ }
36
+
37
+ const data = probabilityDistributions.rbeta(total, alpha, beta, 0);
38
+ const startTimeValue = startTime.valueOf();
39
+ const timeDifference = endTime.valueOf() - startTimeValue;
40
+ return data.map((x) => {
41
+ if (!positiveTrend) {
42
+ x = 1 - x;
43
+ }
44
+ return new Date(startTimeValue + timeDifference * x);
45
+ });
46
+ };
47
+
48
+ module.exports = generateEvents;
@@ -0,0 +1,13 @@
1
+ const {faker} = require('@faker-js/faker');
2
+
3
+ /**
4
+ * Adds another degree of randomness into some decisions
5
+ * @param {number} lowerThan Only this % of people will achieve this luck
6
+ * @returns {boolean} Whether this person is lucky enough for the condition
7
+ */
8
+ const luck = lowerThan => faker.datatype.number({
9
+ min: 1,
10
+ max: 100
11
+ }) <= lowerThan;
12
+
13
+ module.exports = {luck};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * This sorting algorithm is used to make sure that dependent tables are imported after their dependencies.
3
+ * @param {Array<Object>} objects Objects with a name and dependencies properties
4
+ * @returns Topologically sorted array of objects
5
+ */
6
+ module.exports = function topologicalSort(objects) {
7
+ // Create an empty result array to store the ordered objects
8
+ const result = [];
9
+ // Create a set to track visited objects during the DFS
10
+ const visited = new Set();
11
+
12
+ // Helper function to perform DFS
13
+ function dfs(name) {
14
+ if (visited.has(name)) {
15
+ return;
16
+ }
17
+
18
+ visited.add(name);
19
+ const dependencies = objects.find(item => item.name === name)?.dependencies || [];
20
+ for (const dependency of dependencies) {
21
+ dfs(dependency);
22
+ }
23
+
24
+ result.push(objects.find(item => item.name === name));
25
+ }
26
+
27
+ // Perform DFS on each object
28
+ for (const object of objects) {
29
+ dfs(object.name);
30
+ }
31
+
32
+ return result;
33
+ };
@@ -0,0 +1,161 @@
1
+ const path = require('path');
2
+ const errors = require('@tryghost/errors');
3
+
4
+ /**
5
+ * @typedef { function(new: Adapter, object) } AdapterConstructor
6
+ */
7
+
8
+ /**
9
+ * @typedef {object} Adapter
10
+ * @prop {string[]} requiredFns
11
+ */
12
+
13
+ module.exports = class AdapterManager {
14
+ /**
15
+ * @param {object} config
16
+ * @param {string[]} config.pathsToAdapters The paths to check, e.g. ['content/adapters', 'core/server/adapters']
17
+ * @param {(path: string) => AdapterConstructor} config.loadAdapterFromPath A function to load adapters, e.g. global.require
18
+ */
19
+ constructor({pathsToAdapters, loadAdapterFromPath}) {
20
+ /**
21
+ * @private
22
+ * @type {Object.<string, AdapterConstructor>}
23
+ */
24
+ this.baseClasses = {};
25
+
26
+ /**
27
+ * @private
28
+ * @type {Object.<string, Object.<string, Adapter>>}
29
+ */
30
+ this.instanceCache = {};
31
+
32
+ /**
33
+ * @private
34
+ * @type {string[]}
35
+ */
36
+ this.pathsToAdapters = pathsToAdapters;
37
+
38
+ /**
39
+ * @private
40
+ * @type {(path: string) => AdapterConstructor}
41
+ */
42
+ this.loadAdapterFromPath = loadAdapterFromPath;
43
+ }
44
+
45
+ /**
46
+ * Register an adapter type and the corresponding base class. Must be called before requesting adapters of that type
47
+ *
48
+ * @param {string} type The name for the type of adapter
49
+ * @param {AdapterConstructor} BaseClass The class from which all adapters of this type must extend
50
+ */
51
+ registerAdapter(type, BaseClass) {
52
+ this.instanceCache[type] = {};
53
+ this.baseClasses[type] = BaseClass;
54
+ }
55
+
56
+ /**
57
+ * Force recreation of all instances instead of reusing cached instances. Use when editing config file during tests.
58
+ */
59
+ clearInstanceCache() {
60
+ for (const key of Object.keys(this.instanceCache)) {
61
+ this.instanceCache[key] = {};
62
+ }
63
+ }
64
+
65
+ /**
66
+ * getAdapter
67
+ *
68
+ * @param {string} adapterName The name of the type of adapter, e.g. "storage" or "scheduling", optionally including the feature, e.g. "storage:files"
69
+ * @param {string} adapterClassName The active adapter instance class name e.g. "LocalFileStorage"
70
+ * @param {object} [config] The config the adapter could be instantiated with
71
+ *
72
+ * @returns {Adapter} The resolved and instantiated adapter
73
+ */
74
+ getAdapter(adapterName, adapterClassName, config) {
75
+ if (!adapterName || !adapterClassName) {
76
+ throw new errors.IncorrectUsageError({
77
+ message: 'getAdapter must be called with a adapterName and a adapterClassName.'
78
+ });
79
+ }
80
+
81
+ let adapterType;
82
+ if (adapterName.includes(':')) {
83
+ [adapterType] = adapterName.split(':');
84
+ } else {
85
+ adapterType = adapterName;
86
+ }
87
+
88
+ const adapterCache = this.instanceCache[adapterType];
89
+
90
+ if (!adapterCache) {
91
+ throw new errors.NotFoundError({
92
+ message: `Unknown adapter type ${adapterType}. Please register adapter.`
93
+ });
94
+ }
95
+
96
+ // @NOTE: example cache key value 'email:newsletters:custom-newsletter-adapter'
97
+ const adapterCacheKey = `${adapterName}:${adapterClassName}`;
98
+ if (adapterCache[adapterCacheKey]) {
99
+ return adapterCache[adapterCacheKey];
100
+ }
101
+
102
+ /** @type AdapterConstructor */
103
+ let Adapter;
104
+ for (const pathToAdapters of this.pathsToAdapters) {
105
+ const pathToAdapter = path.join(pathToAdapters, adapterType, adapterClassName);
106
+ try {
107
+ Adapter = this.loadAdapterFromPath(pathToAdapter);
108
+ if (Adapter) {
109
+ break;
110
+ }
111
+ } catch (err) {
112
+ // Catch runtime errors
113
+ if (err.code !== 'MODULE_NOT_FOUND') {
114
+ throw new errors.IncorrectUsageError({err});
115
+ }
116
+
117
+ // Catch missing dependencies BUT NOT missing adapter
118
+ if (!err.message.includes(pathToAdapter)) {
119
+ throw new errors.IncorrectUsageError({
120
+ message: `You are missing dependencies in your adapter ${pathToAdapter}`,
121
+ err
122
+ });
123
+ }
124
+ }
125
+ }
126
+
127
+ if (!Adapter) {
128
+ throw new errors.IncorrectUsageError({
129
+ message: `Unable to find ${adapterType} adapter ${adapterClassName} in ${this.pathsToAdapters}.`
130
+ });
131
+ }
132
+
133
+ const adapter = new Adapter(config);
134
+
135
+ if (!(adapter instanceof this.baseClasses[adapterType])) {
136
+ if (Object.getPrototypeOf(Adapter).name !== this.baseClasses[adapterType].name) {
137
+ throw new errors.IncorrectUsageError({
138
+ message: `${adapterType} adapter ${adapterClassName} does not inherit from the base class.`
139
+ });
140
+ }
141
+ }
142
+
143
+ if (!adapter.requiredFns) {
144
+ throw new errors.IncorrectUsageError({
145
+ message: `${adapterType} adapter ${adapterClassName} does not have the requiredFns.`
146
+ });
147
+ }
148
+
149
+ for (const requiredFn of adapter.requiredFns) {
150
+ if (typeof adapter[requiredFn] !== 'function') {
151
+ throw new errors.IncorrectUsageError({
152
+ message: `${adapterType} adapter ${adapterClassName} is missing the ${requiredFn} method.`
153
+ });
154
+ }
155
+ }
156
+
157
+ adapterCache[adapterCacheKey] = adapter;
158
+
159
+ return adapter;
160
+ }
161
+ };
@@ -1,4 +1,4 @@
1
- const AdapterManager = require('@tryghost/adapter-manager');
1
+ const AdapterManager = require('./AdapterManager');
2
2
  const getAdapterServiceConfig = require('./config');
3
3
  const resolveAdapterOptions = require('./options-resolver');
4
4
  const config = require('../../../shared/config');
@@ -0,0 +1,54 @@
1
+ const AnnouncementVisibilityValues = require('./AnnouncementVisibilityValues');
2
+
3
+ class AnnouncementBarSettings {
4
+ #getAnnouncementSettings;
5
+
6
+ static VisibilityValues = AnnouncementVisibilityValues;
7
+
8
+ /**
9
+ *
10
+ * @param {Object} deps
11
+ * @param {() => {announcement: string, announcement_visibility: string[], announcement_background: string}} deps.getAnnouncementSettings
12
+ */
13
+ constructor(deps) {
14
+ this.#getAnnouncementSettings = deps.getAnnouncementSettings;
15
+ }
16
+
17
+ /**
18
+ * @param {Object} [member]
19
+ * @param {string} member.status
20
+ * @returns {{announcement: string, announcement_background: string}}
21
+ */
22
+ getAnnouncementSettings(member) {
23
+ let announcement = undefined;
24
+
25
+ // NOTE: combination of 'free_members' & 'paid_members' makes just a 'members' filter
26
+ const announcementSettings = this.#getAnnouncementSettings();
27
+
28
+ if (announcementSettings.announcement) {
29
+ const visibilities = announcementSettings.announcement_visibility;
30
+ const announcementContent = announcementSettings.announcement;
31
+
32
+ if (visibilities.length === 0) {
33
+ announcement = undefined;
34
+ } else {
35
+ if (visibilities.includes(AnnouncementVisibilityValues.VISITORS) && !member) {
36
+ announcement = announcementContent;
37
+ } else if (visibilities.includes(AnnouncementVisibilityValues.FREE_MEMBERS) && (member?.status === 'free')) {
38
+ announcement = announcementContent;
39
+ } else if (visibilities.includes(AnnouncementVisibilityValues.PAID_MEMBERS) && (member && member.status !== 'free')) {
40
+ announcement = announcementContent;
41
+ }
42
+ }
43
+ }
44
+
45
+ if (announcement !== undefined) {
46
+ return {
47
+ announcement,
48
+ announcement_background: announcementSettings.announcement_background
49
+ };
50
+ }
51
+ }
52
+ }
53
+
54
+ module.exports = AnnouncementBarSettings;
@@ -0,0 +1,11 @@
1
+ // Available visibilities:
2
+ // 'visitors' - anonymous visitors
3
+ // 'free_members' - free members
4
+ // 'paid_members' - paid members (aka non-free members)
5
+ class AnnouncementVisibilityValues {
6
+ static VISITORS = 'visitors';
7
+ static FREE_MEMBERS = 'free_members';
8
+ static PAID_MEMBERS = 'paid_members';
9
+ }
10
+
11
+ module.exports = AnnouncementVisibilityValues;
@@ -1,5 +1,5 @@
1
1
  const settingsCache = require('../../../shared/settings-cache');
2
- const AnnouncementBarSettings = require('@tryghost/announcement-bar-settings');
2
+ const AnnouncementBarSettings = require('./AnnouncementBarSettings');
3
3
 
4
4
  const announcementBarService = new AnnouncementBarSettings({
5
5
  getAnnouncementSettings: () => ({
@@ -1,6 +1,6 @@
1
1
  const path = require('path');
2
2
  const VersionNotificationsDataService = require('./VersionNotificationsDataService');
3
- const EmailContentGenerator = require('@tryghost/email-content-generator');
3
+ const EmailContentGenerator = require('../lib/EmailContentGenerator');
4
4
 
5
5
  class APIVersionCompatibilityService {
6
6
  /**
@@ -46,6 +46,7 @@ totp.options = {
46
46
  * @prop {(req: Req, res: Res) => Promise<void>} sendAuthCodeToUser
47
47
  * @prop {(req: Req, res: Res) => Promise<boolean>} verifyAuthCodeForUser
48
48
  * @prop {(req: Req, res: Res) => Promise<boolean>} isVerifiedSession
49
+ * @prop {() => boolean} isVerificationRequired
49
50
  */
50
51
 
51
52
  /**
@@ -53,7 +54,7 @@ totp.options = {
53
54
  * @param {(req: Req, res: Res) => Promise<Session>} deps.getSession
54
55
  * @param {(data: {id: string}) => Promise<User>} deps.findUserById
55
56
  * @param {(req: Req) => string} deps.getOriginOfRequest
56
- * @param {(key: string) => string} deps.getSettingsCache
57
+ * @param {(key: 'require_email_mfa' | 'admin_session_secret' | 'title') => boolean | string} deps.getSettingsCache
57
58
  * @param {() => string} deps.getBlogLogo
58
59
  * @param {import('../../core/core/server/services/mail').GhostMailer} deps.mailer
59
60
  * @param {import('../../core/core/shared/labs')} deps.labs
@@ -96,6 +97,15 @@ module.exports = function createSessionService({
96
97
  }
97
98
  }
98
99
 
100
+ /**
101
+ * isVerificationRequired
102
+ * Determines if 2FA verification is required based on site settings
103
+ * @returns {boolean}
104
+ */
105
+ function isVerificationRequired() {
106
+ return getSettingsCache('require_email_mfa') === true;
107
+ }
108
+
99
109
  /**
100
110
  * createSessionForUser
101
111
  *
@@ -261,7 +271,7 @@ module.exports = function createSessionService({
261
271
  siteLogo: siteLogo,
262
272
  token: token,
263
273
  deviceDetails: await getDeviceDetails(session.user_agent, session.ip),
264
- is2FARequired: getSettingsCache('require_email_mfa')
274
+ is2FARequired: this.isVerificationRequired()
265
275
  });
266
276
 
267
277
  try {
@@ -310,8 +320,7 @@ module.exports = function createSessionService({
310
320
  async function removeUserForSession(req, res) {
311
321
  const session = await getSession(req, res);
312
322
 
313
- const requireMfa = getSettingsCache('require_email_mfa');
314
- if (requireMfa) {
323
+ if (this.isVerificationRequired()) {
315
324
  session.verified = undefined;
316
325
  }
317
326
 
@@ -359,6 +368,7 @@ module.exports = function createSessionService({
359
368
  isVerifiedSession,
360
369
  sendAuthCodeToUser,
361
370
  verifyAuthCodeForUser,
362
- generateAuthCodeForUser
371
+ generateAuthCodeForUser,
372
+ isVerificationRequired
363
373
  };
364
374
  };
@@ -1,7 +1,7 @@
1
1
  const config = require('../../../shared/config');
2
2
  const urlUtils = require('../../../shared/url-utils');
3
3
 
4
- const DynamicRedirectManager = require('@tryghost/express-dynamic-redirects');
4
+ const DynamicRedirectManager = require('../lib/DynamicRedirectManager');
5
5
  const CustomRedirectsAPI = require('./CustomRedirectsAPI');
6
6
  const validation = require('./validation');
7
7
  const {getBackupRedirectsFilePath} = require('./utils');
@@ -1,4 +1,4 @@
1
- const MailgunClient = require('@tryghost/mailgun-client');
1
+ const MailgunClient = require('../lib/MailgunClient');
2
2
 
3
3
  const DEFAULT_EVENT_FILTER = 'delivered OR opened OR failed OR unsubscribed OR complained';
4
4
  const PAGE_LIMIT = 300;
@@ -17,7 +17,7 @@ class EmailServiceWrapper {
17
17
 
18
18
  const {EmailService, EmailController, EmailRenderer, SendingService, BatchSendingService, EmailSegmenter, MailgunEmailProvider} = require('@tryghost/email-service');
19
19
  const {Post, Newsletter, Email, EmailBatch, EmailRecipient, Member} = require('../../models');
20
- const MailgunClient = require('@tryghost/mailgun-client');
20
+ const MailgunClient = require('../lib/MailgunClient');
21
21
  const configService = require('../../../shared/config');
22
22
  const settingsCache = require('../../../shared/settings-cache');
23
23
  const settingsHelpers = require('../settings-helpers');
@@ -53,7 +53,7 @@ class EmailServiceWrapper {
53
53
  });
54
54
  const i18nLanguage = labs.isSet('i18n') ? settingsCache.get('locale') || 'en' : 'en';
55
55
  const i18n = i18nLib(i18nLanguage, 'newsletter');
56
-
56
+
57
57
  events.on('settings.labs.edited', () => {
58
58
  if (labs.isSet('i18n')) {
59
59
  debug('labs i18n enabled, updating i18n to', settingsCache.get('locale'));
@@ -63,12 +63,12 @@ class EmailServiceWrapper {
63
63
  i18n.changeLanguage('en');
64
64
  }
65
65
  });
66
-
66
+
67
67
  events.on('settings.locale.edited', (model) => {
68
68
  if (labs.isSet('i18n')) {
69
69
  debug('locale changed, updating i18n to', model.get('value'));
70
70
  i18n.changeLanguage(model.get('value'));
71
- }
71
+ }
72
72
  });
73
73
 
74
74
  const mailgunEmailProvider = new MailgunEmailProvider({
@@ -1,5 +1,5 @@
1
1
  const {AbstractEmailSuppressionList, EmailSuppressionData, EmailSuppressedEvent} = require('@tryghost/email-suppression-list');
2
- const {SpamComplaintEvent, EmailBouncedEvent} = require('@tryghost/email-events');
2
+ const {SpamComplaintEvent, EmailBouncedEvent} = require('@tryghost/email-service');
3
3
  const DomainEvents = require('@tryghost/domain-events');
4
4
  const logging = require('@tryghost/logging');
5
5
  const models = require('../../models');
@@ -1,7 +1,7 @@
1
- const MailgunClient = require('@tryghost/mailgun-client');
2
1
  const models = require('../../models');
3
2
  const configService = require('../../../shared/config');
4
3
  const settingsCache = require('../../../shared/settings-cache');
4
+ const MailgunClient = require('../lib/MailgunClient');
5
5
  const MailgunEmailSuppressionList = require('./MailgunEmailSuppressionList');
6
6
 
7
7
  const mailgunClient = new MailgunClient({
@@ -0,0 +1,156 @@
1
+ const express = require('express');
2
+ const {parse: parseURL, format: formatURL} = require('url');
3
+ const {parse: parseQuerystring, stringify: formatQuerystring} = require('querystring');
4
+
5
+ class DynamicRedirectManager {
6
+ /**
7
+ * @param {object} config
8
+ * @param {number} config.permanentMaxAge
9
+ * @param {function} config.getSubdirectoryURL
10
+ */
11
+ constructor({permanentMaxAge, getSubdirectoryURL}) {
12
+ /** @private */
13
+ this.permanentMaxAge = permanentMaxAge;
14
+
15
+ this.getSubdirectoryURL = getSubdirectoryURL;
16
+
17
+ /** @private */
18
+ this.router = express.Router();
19
+ /** @private @type {Object.<string, {fromRegex: RegExp, to: string, options: {permanent: boolean}}>} */
20
+ this.redirects = {};
21
+
22
+ this.handleRequest = this.handleRequest.bind(this);
23
+ }
24
+
25
+ /**
26
+ * @private
27
+ * @param {string} string
28
+ * @returns {RegExp}
29
+ */
30
+ buildRegex(string) {
31
+ let flags = '';
32
+
33
+ if (string.startsWith('/') && string.endsWith('/i')) {
34
+ string = string.slice(1, -2);
35
+ flags += 'i';
36
+ }
37
+
38
+ if (string.endsWith('/')) {
39
+ string = string.slice(0, -1);
40
+ }
41
+
42
+ if (!string.endsWith('$')) {
43
+ string += '/?$';
44
+ }
45
+
46
+ return new RegExp(string, flags);
47
+ }
48
+
49
+ /**
50
+ * @private
51
+ * @param {string} redirectId
52
+ * @returns {void}
53
+ */
54
+ setupRedirect(redirectId) {
55
+ const {fromRegex, to, options: {permanent}} = this.redirects[redirectId];
56
+
57
+ this.router.get(fromRegex, (req, res) => {
58
+ const maxAge = permanent ? this.permanentMaxAge : 0;
59
+ const toURL = parseURL(to);
60
+ const toURLParams = parseQuerystring(toURL.query);
61
+ const currentURL = parseURL(req.url);
62
+ const currentURLParams = parseQuerystring(currentURL.query);
63
+ const params = Object.assign({}, currentURLParams, toURLParams);
64
+ const search = formatQuerystring(params);
65
+
66
+ toURL.pathname = currentURL.pathname.replace(fromRegex, toURL.pathname);
67
+ toURL.search = search !== '' ? `?${search}` : null;
68
+
69
+ /**
70
+ * Only if the url is internal should we prepend the Ghost subdirectory
71
+ * @see https://github.com/TryGhost/Ghost/issues/10776
72
+ */
73
+ if (!toURL.hostname) {
74
+ toURL.pathname = this.getSubdirectoryURL(toURL.pathname);
75
+ }
76
+
77
+ res.set({
78
+ 'Cache-Control': `public, max-age=${maxAge}`
79
+ });
80
+
81
+ res.redirect(permanent ? 301 : 302, formatURL(toURL));
82
+ });
83
+ }
84
+
85
+ /**
86
+ * @param {string} from
87
+ * @param {string} to
88
+ * @param {object} [options]
89
+ * @param {boolean} [options.permanent]
90
+ *
91
+ * @returns {string} The redirect ID
92
+ */
93
+ addRedirect(from, to, options = {}) {
94
+ try {
95
+ // encode "from" only if it's not a regex
96
+ try {
97
+ new RegExp(from);
98
+ } catch (e) {
99
+ from = encodeURI(from);
100
+ }
101
+
102
+ const fromRegex = this.buildRegex(from);
103
+ const redirectId = from;
104
+
105
+ this.redirects[redirectId] = {
106
+ fromRegex,
107
+ to,
108
+ options
109
+ };
110
+
111
+ this.setupRedirect(redirectId);
112
+
113
+ return redirectId;
114
+ } catch (error) {
115
+ if (error.message.match(/Invalid regular expression/gi)) {
116
+ return null;
117
+ }
118
+
119
+ throw error;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * @param {string} redirectId
125
+ * @returns {void}
126
+ */
127
+ removeRedirect(redirectId) {
128
+ delete this.redirects[redirectId];
129
+
130
+ this.router = express.Router();
131
+ Object.keys(this.redirects).forEach(id => this.setupRedirect(id));
132
+
133
+ return;
134
+ }
135
+
136
+ /**
137
+ * @returns {void}
138
+ */
139
+ removeAllRedirects() {
140
+ this.redirects = {};
141
+ this.router = express.Router();
142
+ }
143
+
144
+ /**
145
+ * @param {express.Request} req
146
+ * @param {express.Response} res
147
+ * @param {express.NextFunction} next
148
+ *
149
+ * @returns {void}
150
+ */
151
+ handleRequest(req, res, next) {
152
+ this.router(req, res, next);
153
+ }
154
+ }
155
+
156
+ module.exports = DynamicRedirectManager;