ghost 5.4.1 → 5.7.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 (117) hide show
  1. package/PRIVACY.md +3 -2
  2. package/components/tryghost-adapter-manager-0.0.0.tgz +0 -0
  3. package/components/tryghost-api-version-compatibility-service-0.0.0.tgz +0 -0
  4. package/components/tryghost-bootstrap-socket-0.0.0.tgz +0 -0
  5. package/components/tryghost-constants-0.0.0.tgz +0 -0
  6. package/components/tryghost-custom-theme-settings-service-0.0.0.tgz +0 -0
  7. package/components/tryghost-domain-events-0.0.0.tgz +0 -0
  8. package/components/tryghost-email-analytics-provider-mailgun-0.0.0.tgz +0 -0
  9. package/components/tryghost-email-analytics-service-0.0.0.tgz +0 -0
  10. package/components/tryghost-email-content-generator-0.0.0.tgz +0 -0
  11. package/components/tryghost-express-dynamic-redirects-0.0.0.tgz +0 -0
  12. package/components/tryghost-extract-api-key-0.0.0.tgz +0 -0
  13. package/components/tryghost-job-manager-0.0.0.tgz +0 -0
  14. package/components/tryghost-magic-link-0.0.0.tgz +0 -0
  15. package/components/tryghost-member-analytics-service-0.0.0.tgz +0 -0
  16. package/components/tryghost-member-events-0.0.0.tgz +0 -0
  17. package/components/tryghost-members-analytics-ingress-0.0.0.tgz +0 -0
  18. package/components/tryghost-members-api-0.0.0.tgz +0 -0
  19. package/components/tryghost-members-csv-0.0.0.tgz +0 -0
  20. package/components/tryghost-members-events-service-0.0.0.tgz +0 -0
  21. package/components/tryghost-members-importer-0.0.0.tgz +0 -0
  22. package/components/tryghost-members-offers-0.0.0.tgz +0 -0
  23. package/components/tryghost-members-payments-0.0.0.tgz +0 -0
  24. package/components/tryghost-members-ssr-0.0.0.tgz +0 -0
  25. package/components/tryghost-members-stripe-service-0.0.0.tgz +0 -0
  26. package/components/tryghost-minifier-0.0.0.tgz +0 -0
  27. package/components/tryghost-mw-api-version-mismatch-0.0.0.tgz +0 -0
  28. package/components/tryghost-mw-error-handler-0.0.0.tgz +0 -0
  29. package/components/tryghost-mw-session-from-token-0.0.0.tgz +0 -0
  30. package/components/tryghost-mw-update-user-last-seen-0.0.0.tgz +0 -0
  31. package/components/tryghost-mw-vhost-0.0.0.tgz +0 -0
  32. package/components/tryghost-package-json-0.0.0.tgz +0 -0
  33. package/components/tryghost-security-0.0.0.tgz +0 -0
  34. package/components/tryghost-session-service-0.0.0.tgz +0 -0
  35. package/components/tryghost-settings-path-manager-0.0.0.tgz +0 -0
  36. package/components/tryghost-update-check-service-0.0.0.tgz +0 -0
  37. package/components/tryghost-verification-trigger-0.0.0.tgz +0 -0
  38. package/components/tryghost-version-notifications-data-service-0.0.0.tgz +0 -0
  39. package/content/themes/casper/assets/built/global.css +1 -1
  40. package/content/themes/casper/assets/built/global.css.map +1 -1
  41. package/content/themes/casper/assets/built/screen.css +1 -1
  42. package/content/themes/casper/assets/built/screen.css.map +1 -1
  43. package/content/themes/casper/assets/css/screen.css +9 -1
  44. package/content/themes/casper/gulpfile.js +1 -1
  45. package/content/themes/casper/package.json +9 -9
  46. package/content/themes/casper/yarn.lock +1154 -1249
  47. package/core/boot.js +6 -1
  48. package/core/built/assets/{chunk.3.dc389a0f93cb5fabd695.js → chunk.3.33097bb5eb150719bdd2.js} +19 -19
  49. package/core/built/assets/fonts/Inter.ttf +0 -0
  50. package/core/built/assets/ghost-dark-1bdd57aba1fa4a23388121740454dab2.css +1 -0
  51. package/core/built/assets/ghost.min-8f5c061e0892b93adecc2b9e37ad2f3a.css +1 -0
  52. package/core/built/assets/{ghost.min-36b64813b14c45075770658269d4b478.js → ghost.min-ff9ba089fd81cb40831f4b62e63a2ca9.js} +3015 -2874
  53. package/core/built/assets/icons/event-comment.svg +3 -0
  54. package/core/built/assets/{vendor.min-be0129c9c6897c9f10425e2402881d77.js → vendor.min-3dd40d3052381526f38fd290d13baa47.js} +2394 -924
  55. package/core/frontend/helpers/comments.js +39 -14
  56. package/core/frontend/helpers/ghost_head.js +22 -4
  57. package/core/frontend/helpers/img_url.js +67 -6
  58. package/core/frontend/utils/frontend-apps.js +33 -0
  59. package/core/frontend/web/middleware/handle-image-sizes.js +7 -11
  60. package/core/server/api/endpoints/{comments-comments.js → comments-members.js} +24 -43
  61. package/core/server/api/endpoints/index.js +2 -6
  62. package/core/server/api/endpoints/utils/serializers/output/config.js +2 -1
  63. package/core/server/api/endpoints/utils/serializers/output/mappers/activity-feed-events.js +17 -0
  64. package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +18 -0
  65. package/core/server/api/endpoints/utils/serializers/output/mappers/index.js +1 -0
  66. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +11 -0
  67. package/core/server/api/endpoints/utils/serializers/output/members.js +12 -1
  68. package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +4 -0
  69. package/core/server/data/exporter/table-lists.js +2 -1
  70. package/core/server/data/migrations/versions/5.3/2022-07-06-09-17-add-ghost-explore-integration.js +0 -1
  71. package/core/server/data/migrations/versions/5.3/2022-07-06-09-26-add-ghost-explore-integration-api-key.js +0 -1
  72. package/core/server/data/migrations/versions/5.5/2022-07-18-14-29-add-comment-reporting-permissions.js +10 -0
  73. package/core/server/data/migrations/versions/5.5/2022-07-18-14-31-drop-reports-reason.js +3 -0
  74. package/core/server/data/migrations/versions/5.5/2022-07-18-14-32-drop-nullable-member-id-from-likes.js +4 -0
  75. package/core/server/data/migrations/versions/5.5/2022-07-18-14-33-fix-comments-on-delete-foreign-keys.js +119 -0
  76. package/core/server/data/migrations/versions/5.5/2022-07-21-08-56-add-jobs-table.js +11 -0
  77. package/core/server/data/migrations/versions/5.6/2022-07-27-13-40-change-explore-type.js +24 -0
  78. package/core/server/data/schema/commands.js +7 -2
  79. package/core/server/data/schema/fixtures/fixtures.json +6 -1
  80. package/core/server/data/schema/schema.js +12 -4
  81. package/core/server/ghost-server.js +0 -22
  82. package/core/server/models/comment-report.js +34 -0
  83. package/core/server/models/comment.js +8 -7
  84. package/core/server/models/job.js +9 -0
  85. package/core/server/services/bulk-email/bulk-email-processor.js +6 -0
  86. package/core/server/services/comments/controller.js +82 -0
  87. package/core/server/services/comments/email-templates/new-comment-reply.hbs +2 -2
  88. package/core/server/services/comments/email-templates/new-comment-reply.txt.js +7 -8
  89. package/core/server/services/comments/email-templates/new-comment.hbs +2 -2
  90. package/core/server/services/comments/email-templates/new-comment.txt.js +7 -6
  91. package/core/server/services/comments/email-templates/report.hbs +199 -0
  92. package/core/server/services/comments/email-templates/report.txt.js +16 -0
  93. package/core/server/services/comments/emails.js +57 -1
  94. package/core/server/services/comments/index.js +6 -1
  95. package/core/server/services/comments/service.js +291 -9
  96. package/core/server/services/jobs/job-service.js +24 -1
  97. package/core/server/services/mail/GhostMailer.js +1 -0
  98. package/core/server/services/mega/email-preview.js +5 -1
  99. package/core/server/services/mega/mega.js +2 -4
  100. package/core/server/services/mega/post-email-serializer.js +97 -2
  101. package/core/server/services/mega/segment-parser.js +10 -1
  102. package/core/server/services/members/api.js +2 -1
  103. package/core/server/services/members/service.js +9 -4
  104. package/core/server/services/public-config/config.js +2 -1
  105. package/core/server/services/stripe/service.js +9 -1
  106. package/core/server/web/admin/views/default-prod.html +4 -4
  107. package/core/server/web/admin/views/default.html +4 -4
  108. package/core/server/web/api/testmode/jobs/graceful-job.js +2 -2
  109. package/core/server/web/api/testmode/routes.js +14 -0
  110. package/core/server/web/comments/routes.js +10 -8
  111. package/core/shared/config/defaults.json +12 -7
  112. package/core/shared/config/env/config.testing.json +3 -2
  113. package/core/shared/labs.js +5 -2
  114. package/package.json +92 -59
  115. package/yarn.lock +1821 -2011
  116. package/core/built/assets/ghost-dark-739c1f5546bd048eeeb253965ef36712.css +0 -1
  117. package/core/built/assets/ghost.min-5211776b9497f36fac8c9e5f2584cbcc.css +0 -1
@@ -0,0 +1,119 @@
1
+ const {addForeign, dropForeign} = require('../../../schema/commands');
2
+ const logging = require('@tryghost/logging');
3
+ const {createTransactionalMigration} = require('../../utils');
4
+
5
+ module.exports = createTransactionalMigration(
6
+ async function up(knex) {
7
+ logging.info('Adding on delete SET NULL for comments');
8
+
9
+ await dropForeign({
10
+ fromTable: 'comments',
11
+ fromColumn: 'member_id',
12
+ toTable: 'members',
13
+ toColumn: 'id',
14
+ transaction: knex
15
+ });
16
+
17
+ await addForeign({
18
+ fromTable: 'comments',
19
+ fromColumn: 'member_id',
20
+ toTable: 'members',
21
+ toColumn: 'id',
22
+ setNullDelete: true,
23
+ transaction: knex
24
+ });
25
+
26
+ logging.info('Adding on delete CASCADE for comment_likes');
27
+
28
+ await dropForeign({
29
+ fromTable: 'comment_likes',
30
+ fromColumn: 'member_id',
31
+ toTable: 'members',
32
+ toColumn: 'id',
33
+ transaction: knex
34
+ });
35
+
36
+ await addForeign({
37
+ fromTable: 'comment_likes',
38
+ fromColumn: 'member_id',
39
+ toTable: 'members',
40
+ toColumn: 'id',
41
+ cascadeDelete: true,
42
+ transaction: knex
43
+ });
44
+
45
+ logging.info('Adding on delete SET NULL for comment_reports');
46
+
47
+ await dropForeign({
48
+ fromTable: 'comment_reports',
49
+ fromColumn: 'member_id',
50
+ toTable: 'members',
51
+ toColumn: 'id',
52
+ transaction: knex
53
+ });
54
+
55
+ await addForeign({
56
+ fromTable: 'comment_reports',
57
+ fromColumn: 'member_id',
58
+ toTable: 'members',
59
+ toColumn: 'id',
60
+ setNullDelete: true,
61
+ transaction: knex
62
+ });
63
+ },
64
+ async function down(knex) {
65
+ logging.info('Restoring foreign key for comments');
66
+
67
+ await dropForeign({
68
+ fromTable: 'comments',
69
+ fromColumn: 'member_id',
70
+ toTable: 'members',
71
+ toColumn: 'id',
72
+ transaction: knex
73
+ });
74
+
75
+ await addForeign({
76
+ fromTable: 'comments',
77
+ fromColumn: 'member_id',
78
+ toTable: 'members',
79
+ toColumn: 'id',
80
+ transaction: knex
81
+ });
82
+
83
+ logging.info('Restoring foreign key for comment_likes');
84
+
85
+ await dropForeign({
86
+ fromTable: 'comment_likes',
87
+ fromColumn: 'member_id',
88
+ toTable: 'members',
89
+ toColumn: 'id',
90
+ transaction: knex
91
+ });
92
+
93
+ await addForeign({
94
+ fromTable: 'comment_likes',
95
+ fromColumn: 'member_id',
96
+ toTable: 'members',
97
+ toColumn: 'id',
98
+ transaction: knex
99
+ });
100
+
101
+ logging.info('Restoring foreign key for comment_reports');
102
+
103
+ await dropForeign({
104
+ fromTable: 'comment_reports',
105
+ fromColumn: 'member_id',
106
+ toTable: 'members',
107
+ toColumn: 'id',
108
+ transaction: knex
109
+ });
110
+
111
+ await addForeign({
112
+ fromTable: 'comment_reports',
113
+ fromColumn: 'member_id',
114
+ toTable: 'members',
115
+ toColumn: 'id',
116
+ transaction: knex
117
+ });
118
+ }
119
+ );
@@ -0,0 +1,11 @@
1
+ const {addTable} = require('../../utils');
2
+
3
+ module.exports = addTable('jobs', {
4
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
5
+ name: {type: 'string', maxlength: 191, nullable: false, unique: true},
6
+ status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'queued', validations: {isIn: [['started', 'finished', 'failed', 'queued']]}},
7
+ started_at: {type: 'dateTime', nullable: true},
8
+ finished_at: {type: 'dateTime', nullable: true},
9
+ created_at: {type: 'dateTime', nullable: false},
10
+ updated_at: {type: 'dateTime', nullable: true}
11
+ });
@@ -0,0 +1,24 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createTransactionalMigration} = require('../../utils');
3
+
4
+ module.exports = createTransactionalMigration(
5
+ async function up(knex) {
6
+ logging.info('Changing Ghost Explore Integration to type "builtin"');
7
+ await knex('integrations')
8
+ .where({
9
+ name: 'Ghost Explore',
10
+ slug: 'ghost-explore'
11
+ })
12
+ .update('type', 'builtin');
13
+ },
14
+ async function down(knex) {
15
+ logging.info('Changing Ghost Explore Integration to type "internal"');
16
+
17
+ await knex('integrations')
18
+ .where({
19
+ name: 'Ghost Explore',
20
+ slug: 'ghost-explore'
21
+ })
22
+ .update('type', 'internal');
23
+ }
24
+ );
@@ -50,6 +50,8 @@ function addTableColumn(tableName, table, columnName, columnSpec = schema[tableN
50
50
  }
51
51
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'cascadeDelete') && columnSpec.cascadeDelete === true) {
52
52
  column.onDelete('CASCADE');
53
+ } else if (Object.prototype.hasOwnProperty.call(columnSpec, 'setNullDelete') && columnSpec.setNullDelete === true) {
54
+ column.onDelete('SET NULL');
53
55
  }
54
56
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'defaultTo')) {
55
57
  column.defaultTo(columnSpec.defaultTo);
@@ -206,10 +208,11 @@ async function hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, trans
206
208
  * @param {string} configuration.fromColumn - column of the table to add the foreign key to
207
209
  * @param {string} configuration.toTable - name of the table to point the foreign key to
208
210
  * @param {string} configuration.toColumn - column of the table to point the foreign key to
209
- * @param {Boolean} configuration.cascadeDelete - adds the "on delete cascade" option if true
211
+ * @param {Boolean} [configuration.cascadeDelete] - adds the "on delete cascade" option if true
212
+ * @param {Boolean} [configuration.setNullDelete] - adds the "on delete SET NULL" option if true
210
213
  * @param {import('knex')} configuration.transaction - connection object containing knex reference
211
214
  */
212
- async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDelete = false, transaction = db.knex}) {
215
+ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDelete = false, setNullDelete = false, transaction = db.knex}) {
213
216
  if (DatabaseInfo.isSQLite(transaction)) {
214
217
  const foreignKeyExists = await hasForeignSQLite({fromTable, fromColumn, toTable, toColumn, transaction});
215
218
  if (foreignKeyExists) {
@@ -232,6 +235,8 @@ async function addForeign({fromTable, fromColumn, toTable, toColumn, cascadeDele
232
235
  await transaction.schema.table(fromTable, function (table) {
233
236
  if (cascadeDelete) {
234
237
  table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('CASCADE');
238
+ } else if (setNullDelete) {
239
+ table.foreign(fromColumn).references(`${toTable}.${toColumn}`).onDelete('SET NULL');
235
240
  } else {
236
241
  table.foreign(fromColumn).references(`${toTable}.${toColumn}`);
237
242
  }
@@ -610,6 +610,11 @@
610
610
  "name": "Unlike comments",
611
611
  "action_type": "unlike",
612
612
  "object_type": "comment"
613
+ },
614
+ {
615
+ "name": "Report comments",
616
+ "action_type": "report",
617
+ "object_type": "comment"
613
618
  }
614
619
  ]
615
620
  },
@@ -667,7 +672,7 @@
667
672
  "slug": "ghost-explore",
668
673
  "name": "Ghost Explore",
669
674
  "description": "Built-in Ghost Explore integration",
670
- "type": "internal",
675
+ "type": "builtin",
671
676
  "api_keys": [{"type": "admin", "role": "Ghost Explore Integration"}]
672
677
  },
673
678
  {
@@ -750,7 +750,7 @@ module.exports = {
750
750
  comments: {
751
751
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
752
752
  post_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'posts.id', cascadeDelete: true},
753
- member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id'},
753
+ member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
754
754
  parent_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'comments.id'},
755
755
  status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'published', validations: {isIn: [['published', 'hidden', 'deleted']]}},
756
756
  html: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
@@ -761,16 +761,24 @@ module.exports = {
761
761
  comment_likes: {
762
762
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
763
763
  comment_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'comments.id', cascadeDelete: true},
764
- member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id'},
764
+ member_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'members.id', cascadeDelete: true},
765
765
  created_at: {type: 'dateTime', nullable: false},
766
766
  updated_at: {type: 'dateTime', nullable: false}
767
767
  },
768
768
  comment_reports: {
769
769
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
770
770
  comment_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'comments.id', cascadeDelete: true},
771
- member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id'},
772
- reason: {type: 'text', maxlength: 65535, nullable: false},
771
+ member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
773
772
  created_at: {type: 'dateTime', nullable: false},
774
773
  updated_at: {type: 'dateTime', nullable: false}
774
+ },
775
+ jobs: {
776
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
777
+ name: {type: 'string', maxlength: 191, nullable: false, unique: true},
778
+ status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'queued', validations: {isIn: [['started', 'finished', 'failed', 'queued']]}},
779
+ started_at: {type: 'dateTime', nullable: true},
780
+ finished_at: {type: 'dateTime', nullable: true},
781
+ created_at: {type: 'dateTime', nullable: false},
782
+ updated_at: {type: 'dateTime', nullable: true}
775
783
  }
776
784
  };
@@ -213,9 +213,6 @@ class GhostServer {
213
213
  * Internal Method for TestMode.
214
214
  */
215
215
  _startTestMode() {
216
- // This is horrible and very temporary
217
- const jobService = require('./services/jobs');
218
-
219
216
  // Output how many connections are open every 5 seconds
220
217
  const connectionInterval = setInterval(() => this.httpServer.getConnections(
221
218
  (err, connections) => logging.warn(`${connections} connections currently open`)
@@ -226,25 +223,6 @@ class GhostServer {
226
223
  clearInterval(connectionInterval);
227
224
  logging.warn('Server has fully closed');
228
225
  });
229
-
230
- // Output job queue length every 5 seconds
231
- setInterval(() => {
232
- logging.warn(`${jobService.queue.length()} jobs in the queue. Idle: ${jobService.queue.idle()}`);
233
-
234
- const runningScheduledjobs = Object.keys(jobService.bree.workers);
235
- if (Object.keys(jobService.bree.workers).length) {
236
- logging.warn(`${Object.keys(jobService.bree.workers).length} jobs running: ${runningScheduledjobs}`);
237
- }
238
-
239
- const scheduledJobs = Object.keys(jobService.bree.intervals);
240
- if (Object.keys(jobService.bree.intervals).length) {
241
- logging.warn(`${Object.keys(jobService.bree.intervals).length} scheduled jobs: ${scheduledJobs}`);
242
- }
243
-
244
- if (runningScheduledjobs.length === 0 && scheduledJobs.length === 0) {
245
- logging.warn('No scheduled or running jobs');
246
- }
247
- }, 5000);
248
226
  }
249
227
 
250
228
  /**
@@ -0,0 +1,34 @@
1
+ const ghostBookshelf = require('./base');
2
+
3
+ const CommentReport = ghostBookshelf.Model.extend({
4
+ tableName: 'comment_reports',
5
+
6
+ defaults: function defaults() {
7
+ return {};
8
+ },
9
+
10
+ comment() {
11
+ return this.belongsTo('Comment', 'comment_id');
12
+ },
13
+
14
+ member() {
15
+ return this.belongsTo('Member', 'member_id');
16
+ },
17
+
18
+ emitChange: function emitChange(event, options) {
19
+ const eventToTrigger = 'comment_report' + '.' + event;
20
+ ghostBookshelf.Model.prototype.emitChange.bind(this)(this, eventToTrigger, options);
21
+ },
22
+
23
+ onCreated: function onCreated(model, options) {
24
+ ghostBookshelf.Model.prototype.onCreated.apply(this, arguments);
25
+
26
+ model.emitChange('added', options);
27
+ }
28
+ }, {
29
+
30
+ });
31
+
32
+ module.exports = {
33
+ CommentReport: ghostBookshelf.model('CommentReport', CommentReport)
34
+ };
@@ -2,7 +2,6 @@ const ghostBookshelf = require('./base');
2
2
  const _ = require('lodash');
3
3
  const errors = require('@tryghost/errors');
4
4
  const tpl = require('@tryghost/tpl');
5
- const commentsService = require('../services/comments');
6
5
 
7
6
  const messages = {
8
7
  commentNotFound: 'Comment could not be found',
@@ -70,14 +69,14 @@ const Comment = ghostBookshelf.Model.extend({
70
69
  onCreated: function onCreated(model, options) {
71
70
  ghostBookshelf.Model.prototype.onCreated.apply(this, arguments);
72
71
 
73
- if (!options.context.internal) {
74
- commentsService.api.sendNewCommentNotifications(model);
75
- }
76
-
77
72
  model.emitChange('added', options);
78
73
  },
79
74
 
80
- enforcedFilters: function enforcedFilters() {
75
+ enforcedFilters: function enforcedFilters(options) {
76
+ if (options.context && options.context.user) {
77
+ return null;
78
+ }
79
+
81
80
  return 'parent_id:null';
82
81
  }
83
82
 
@@ -151,7 +150,9 @@ const Comment = ghostBookshelf.Model.extend({
151
150
  defaultRelations: function defaultRelations(methodName, options) {
152
151
  // @todo: the default relations are not working for 'add' when we add it below
153
152
  if (['findAll', 'findPage', 'edit', 'findOne'].indexOf(methodName) !== -1) {
154
- options.withRelated = _.union(['member', 'likes', 'replies', 'replies.member', 'replies.likes'], options.withRelated || []);
153
+ if (!options.withRelated || options.withRelated.length === 0) {
154
+ options.withRelated = ['member', 'likes', 'replies', 'replies.member', 'replies.likes'];
155
+ }
155
156
  }
156
157
 
157
158
  return options;
@@ -0,0 +1,9 @@
1
+ const ghostBookshelf = require('./base');
2
+
3
+ const Job = ghostBookshelf.Model.extend({
4
+ tableName: 'jobs'
5
+ });
6
+
7
+ module.exports = {
8
+ Job: ghostBookshelf.model('Job', Job)
9
+ };
@@ -7,6 +7,7 @@ const logging = require('@tryghost/logging');
7
7
  const models = require('../../models');
8
8
  const mailgunProvider = require('./mailgun');
9
9
  const sentry = require('../../../shared/sentry');
10
+ const labs = require('../../../shared/labs');
10
11
  const debug = require('@tryghost/debug')('mega');
11
12
  const postEmailSerializer = require('../mega/post-email-serializer');
12
13
 
@@ -170,6 +171,11 @@ module.exports = {
170
171
  // Load newsletter data on email
171
172
  await emailBatchModel.relations.email.getLazyRelation('newsletter', {require: false, ...knexOptions});
172
173
 
174
+ if (labs.isSet('newsletterPaywall')) {
175
+ // Load post data on email - for content gating on paywall
176
+ await emailBatchModel.relations.email.getLazyRelation('post', {require: false, ...knexOptions});
177
+ }
178
+
173
179
  // send the email
174
180
  const sendResponse = await this.send(emailBatchModel.relations.email.toJSON(), recipientRows, memberSegment);
175
181
 
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @typedef {import('../../api/shared/frame')} Frame
3
+ */
4
+
5
+ const {MethodNotAllowedError} = require('@tryghost/errors');
6
+ const tpl = require('@tryghost/tpl');
7
+
8
+ const messages = {
9
+ cannotDestroyComments: 'You cannot destroy comments.'
10
+ };
11
+
12
+ module.exports = class CommentsController {
13
+ /**
14
+ * @param {import('./service')} service
15
+ */
16
+ constructor(service) {
17
+ this.service = service;
18
+ }
19
+
20
+ /**
21
+ * @param {Frame} frame
22
+ */
23
+ async browse(frame) {
24
+ return this.service.getComments(frame.options);
25
+ }
26
+
27
+ /**
28
+ * @param {Frame} frame
29
+ */
30
+ async read(frame) {
31
+ return await this.service.getCommentByID(frame.data.id, frame.options);
32
+ }
33
+
34
+ /**
35
+ * @param {Frame} frame
36
+ */
37
+ async edit(frame) {
38
+ if (frame.data.comments[0].status === 'deleted') {
39
+ return await this.service.deleteComment(
40
+ frame.options.id,
41
+ frame?.options?.context?.member?.id,
42
+ frame.options
43
+ );
44
+ }
45
+
46
+ return await this.service.editCommentContent(
47
+ frame.options.id,
48
+ frame?.options?.context?.member?.id,
49
+ frame.data.comments[0].html,
50
+ frame.options
51
+ );
52
+ }
53
+
54
+ /**
55
+ * @param {Frame} frame
56
+ */
57
+ async add(frame) {
58
+ const data = frame.data.comments[0];
59
+
60
+ if (data.parent_id) {
61
+ return await this.service.replyToComment(
62
+ data.parent_id,
63
+ frame.options.context.member.id,
64
+ data.html,
65
+ frame.options
66
+ );
67
+ }
68
+
69
+ return await this.service.commentOnPost(
70
+ data.post_id,
71
+ frame.options.context.member.id,
72
+ data.html,
73
+ frame.options
74
+ );
75
+ }
76
+
77
+ async destroy() {
78
+ throw new MethodNotAllowedError({
79
+ message: tpl(messages.cannotDestroyComments)
80
+ });
81
+ }
82
+ };
@@ -116,7 +116,7 @@
116
116
  <tr>
117
117
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
118
118
  <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there,</p>
119
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just replied to your comment on <a href="https://ghost.org">{{postTitle}}</a>.</p>
119
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just replied to your comment on <a href="{{postUrl}}" target="_blank">{{postTitle}}</a>.</p>
120
120
 
121
121
  <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
122
122
  <tbody>
@@ -196,4 +196,4 @@
196
196
  </tr>
197
197
  </table>
198
198
  </body>
199
- </html>
199
+ </html>
@@ -1,14 +1,13 @@
1
1
  module.exports = function (data) {
2
- return `
3
- Hey there,
2
+ // Be careful when you indent the email, because whitespaces are visible in emails!
3
+ return `Hey there,
4
4
 
5
- Someone just replied to your comment on "${data.postTitle}"
5
+ Someone just replied to your comment on "${data.postTitle}"
6
6
 
7
- ${data.postUrl}#comments-area
7
+ ${data.postUrl}#ghost-comments-root
8
8
 
9
- ---
9
+ ---
10
10
 
11
- Sent to ${data.toEmail} from ${data.siteDomain}.
12
- You can manage your notification preferences at ${data.profileUrl}.
13
- `;
11
+ Sent to ${data.toEmail} from ${data.siteDomain}.
12
+ You can manage your notification preferences at ${data.profileUrl}.`;
14
13
  };
@@ -116,7 +116,7 @@
116
116
  <tr>
117
117
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
118
118
  <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there,</p>
119
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just left a comment on your post <a href="https://ghost.org">{{postTitle}}</a>.</p>
119
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 32px;">Someone just left a comment on your post <a href="{{postUrl}}" target="_blank">{{postTitle}}</a>.</p>
120
120
 
121
121
  <table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box; background: #F9F9FA; border-radius: 7px;">
122
122
  <tbody>
@@ -196,4 +196,4 @@
196
196
  </tr>
197
197
  </table>
198
198
  </body>
199
- </html>
199
+ </html>
@@ -1,14 +1,15 @@
1
1
  module.exports = function (data) {
2
+ // Be careful when you indent the email, because whitespaces are visible in emails!
2
3
  return `
3
- Hey there,
4
+ Hey there,
4
5
 
5
- Someone just posted a comment on your post "${data.postTitle}"
6
+ Someone just posted a comment on your post "${data.postTitle}"
6
7
 
7
- ${data.postUrl}#comments-area
8
+ ${data.postUrl}#ghost-comments-root
8
9
 
9
- ---
10
+ ---
10
11
 
11
- Sent to ${data.toEmail} from ${data.siteDomain}.
12
- You can manage your notification preferences at ${data.staffUrl}.
12
+ Sent to ${data.toEmail} from ${data.siteDomain}.
13
+ You can manage your notification preferences at ${data.staffUrl}.
13
14
  `;
14
15
  };