meteor3: Comments methods and mail fan-out to async
comments.insert/edit/remove/like/dislike use *Async collection APIs; requireOwner/toggle helpers are async; onCommentAdd iterates users with forEachAsync. subscriptions.* methods were already async. REST smoke stays byte-identical (12/12).
This commit is contained in:
parent
feaf66fe34
commit
66bab7a43b
3 changed files with 25 additions and 20 deletions
|
|
@ -236,6 +236,11 @@ MONGO_CONTAINER=tcef-mongo7 MONGO_SHELL=mongosh MONGO_PORT=27019 ./smoke/smoke.s
|
||||||
- Note: the module-level `new Counter(...)` (natestrauser:publish-performant-counts)
|
- Note: the module-level `new Counter(...)` (natestrauser:publish-performant-counts)
|
||||||
behind `falsePositivesTotal` boots fine; the publication itself is unused by
|
behind `falsePositivesTotal` boots fine; the publication itself is unused by
|
||||||
the current UI — watch it if re-enabled.
|
the current UI — watch it if re-enabled.
|
||||||
|
- ✅ **Comments methods → async** (`comments.insert/edit/remove/like/dislike`):
|
||||||
|
`findOne/insert/update/remove` → `*Async`, helpers (`requireOwner`, `toggle`)
|
||||||
|
async, `users.findOneAsync` for the username. `onCommentAdd` mail fan-out:
|
||||||
|
`users.find().forEach` → `forEachAsync`. Still fire-and-forget from the
|
||||||
|
insert. Comments UI staging verification pending (pre-existing note below).
|
||||||
|
|
||||||
### Still TODO before the web (not just REST) is fully deployable on 3.x
|
### Still TODO before the web (not just REST) is fully deployable on 3.x
|
||||||
- **React 16 → 18** render root + verify ancient react-* libs.
|
- **React 16 → 18** render root + verify ancient react-* libs.
|
||||||
|
|
|
||||||
|
|
@ -14,27 +14,27 @@ function usernameOf(user) {
|
||||||
: '';
|
: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function requireOwner(commentId, userId) {
|
async function requireOwner(commentId, userId) {
|
||||||
const comment = Comments.findOne(commentId);
|
const comment = await Comments.findOneAsync(commentId);
|
||||||
if (!comment) throw new Meteor.Error('404', 'Comment not found');
|
if (!comment) throw new Meteor.Error('404', 'Comment not found');
|
||||||
if (comment.userId !== userId) throw new Meteor.Error('403', 'Not your comment');
|
if (comment.userId !== userId) throw new Meteor.Error('403', 'Not your comment');
|
||||||
return comment;
|
return comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle(commentId, field, otherField, userId) {
|
async function toggle(commentId, field, otherField, userId) {
|
||||||
check(commentId, String);
|
check(commentId, String);
|
||||||
if (!userId) throw new Meteor.Error('403', 'Login required');
|
if (!userId) throw new Meteor.Error('403', 'Login required');
|
||||||
const comment = Comments.findOne(commentId);
|
const comment = await Comments.findOneAsync(commentId);
|
||||||
if (!comment) throw new Meteor.Error('404', 'Comment not found');
|
if (!comment) throw new Meteor.Error('404', 'Comment not found');
|
||||||
const has = (comment[field] || []).includes(userId);
|
const has = (comment[field] || []).includes(userId);
|
||||||
const modifier = has
|
const modifier = has
|
||||||
? { $pull: { [field]: userId } }
|
? { $pull: { [field]: userId } }
|
||||||
: { $addToSet: { [field]: userId }, $pull: { [otherField]: userId } };
|
: { $addToSet: { [field]: userId }, $pull: { [otherField]: userId } };
|
||||||
Comments.update(commentId, modifier);
|
await Comments.updateAsync(commentId, modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
'comments.insert': function commentsInsert(referenceId, content) {
|
'comments.insert': async function commentsInsert(referenceId, content) {
|
||||||
check(referenceId, String);
|
check(referenceId, String);
|
||||||
check(content, String);
|
check(content, String);
|
||||||
if (!this.userId) throw new Meteor.Error('403', 'Login required to comment');
|
if (!this.userId) throw new Meteor.Error('403', 'Login required to comment');
|
||||||
|
|
@ -47,7 +47,7 @@ Meteor.methods({
|
||||||
referenceId,
|
referenceId,
|
||||||
content: text,
|
content: text,
|
||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
username: usernameOf(Meteor.users.findOne(this.userId)),
|
username: usernameOf(await Meteor.users.findOneAsync(this.userId)),
|
||||||
media: getMediaFromContent(text),
|
media: getMediaFromContent(text),
|
||||||
likes: [],
|
likes: [],
|
||||||
dislikes: [],
|
dislikes: [],
|
||||||
|
|
@ -55,7 +55,7 @@ Meteor.methods({
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now
|
updatedAt: now
|
||||||
};
|
};
|
||||||
const _id = Comments.insert(doc);
|
const _id = await Comments.insertAsync(doc);
|
||||||
// Fire-and-forget: notify other commenters of this fire. Never let a mail
|
// Fire-and-forget: notify other commenters of this fire. Never let a mail
|
||||||
// failure break the insert.
|
// failure break the insert.
|
||||||
try {
|
try {
|
||||||
|
|
@ -66,32 +66,32 @@ Meteor.methods({
|
||||||
return _id;
|
return _id;
|
||||||
},
|
},
|
||||||
|
|
||||||
'comments.edit': function commentsEdit(commentId, content) {
|
'comments.edit': async function commentsEdit(commentId, content) {
|
||||||
check(commentId, String);
|
check(commentId, String);
|
||||||
check(content, String);
|
check(content, String);
|
||||||
if (!this.userId) throw new Meteor.Error('403', 'Login required');
|
if (!this.userId) throw new Meteor.Error('403', 'Login required');
|
||||||
requireOwner(commentId, this.userId);
|
await requireOwner(commentId, this.userId);
|
||||||
const text = content.trim();
|
const text = content.trim();
|
||||||
if (!text) throw new Meteor.Error('400', 'Empty comment');
|
if (!text) throw new Meteor.Error('400', 'Empty comment');
|
||||||
if (text.length > MAX_LEN) throw new Meteor.Error('400', 'Comment too long');
|
if (text.length > MAX_LEN) throw new Meteor.Error('400', 'Comment too long');
|
||||||
Comments.update(commentId, {
|
await Comments.updateAsync(commentId, {
|
||||||
$set: { content: text, media: getMediaFromContent(text), updatedAt: new Date() }
|
$set: { content: text, media: getMediaFromContent(text), updatedAt: new Date() }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
'comments.remove': function commentsRemove(commentId) {
|
'comments.remove': async function commentsRemove(commentId) {
|
||||||
check(commentId, String);
|
check(commentId, String);
|
||||||
if (!this.userId) throw new Meteor.Error('403', 'Login required');
|
if (!this.userId) throw new Meteor.Error('403', 'Login required');
|
||||||
requireOwner(commentId, this.userId);
|
await requireOwner(commentId, this.userId);
|
||||||
Comments.remove(commentId);
|
await Comments.removeAsync(commentId);
|
||||||
},
|
},
|
||||||
|
|
||||||
'comments.like': function commentsLike(commentId) {
|
'comments.like': async function commentsLike(commentId) {
|
||||||
toggle(commentId, 'likes', 'dislikes', this.userId);
|
await toggle(commentId, 'likes', 'dislikes', this.userId);
|
||||||
},
|
},
|
||||||
|
|
||||||
'comments.dislike': function commentsDislike(commentId) {
|
'comments.dislike': async function commentsDislike(commentId) {
|
||||||
toggle(commentId, 'dislikes', 'likes', this.userId);
|
await toggle(commentId, 'dislikes', 'likes', this.userId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ export default function onCommentAdd(payload) {
|
||||||
const { referenceId, userId } = payload;
|
const { referenceId, userId } = payload;
|
||||||
const query = { referenceId, userId: { $ne: userId } };
|
const query = { referenceId, userId: { $ne: userId } };
|
||||||
|
|
||||||
Comments.rawCollection().distinct('userId', query).then((users) => {
|
Comments.rawCollection().distinct('userId', query).then(async (users) => {
|
||||||
const path = referenceId.replace(/fire-/, 'fire/archive/');
|
const path = referenceId.replace(/fire-/, 'fire/archive/');
|
||||||
const fireUrl = Meteor.absoluteUrl(path);
|
const fireUrl = Meteor.absoluteUrl(path);
|
||||||
Meteor.users.find({ _id: { $in: users } }).forEach((user) => {
|
await Meteor.users.find({ _id: { $in: users } }).forEachAsync((user) => {
|
||||||
const { firstName, emailAddress } = getEmailOf(user);
|
const { firstName, emailAddress } = getEmailOf(user);
|
||||||
if (emailAddress) {
|
if (emailAddress) {
|
||||||
const emailOpts = {
|
const emailOpts = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue