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:
vjrj 2026-07-14 06:52:32 +02:00
parent feaf66fe34
commit 66bab7a43b
3 changed files with 25 additions and 20 deletions

View file

@ -14,27 +14,27 @@ function usernameOf(user) {
: '';
}
function requireOwner(commentId, userId) {
const comment = Comments.findOne(commentId);
async function requireOwner(commentId, userId) {
const comment = await Comments.findOneAsync(commentId);
if (!comment) throw new Meteor.Error('404', 'Comment not found');
if (comment.userId !== userId) throw new Meteor.Error('403', 'Not your comment');
return comment;
}
function toggle(commentId, field, otherField, userId) {
async function toggle(commentId, field, otherField, userId) {
check(commentId, String);
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');
const has = (comment[field] || []).includes(userId);
const modifier = has
? { $pull: { [field]: userId } }
: { $addToSet: { [field]: userId }, $pull: { [otherField]: userId } };
Comments.update(commentId, modifier);
await Comments.updateAsync(commentId, modifier);
}
Meteor.methods({
'comments.insert': function commentsInsert(referenceId, content) {
'comments.insert': async function commentsInsert(referenceId, content) {
check(referenceId, String);
check(content, String);
if (!this.userId) throw new Meteor.Error('403', 'Login required to comment');
@ -47,7 +47,7 @@ Meteor.methods({
referenceId,
content: text,
userId: this.userId,
username: usernameOf(Meteor.users.findOne(this.userId)),
username: usernameOf(await Meteor.users.findOneAsync(this.userId)),
media: getMediaFromContent(text),
likes: [],
dislikes: [],
@ -55,7 +55,7 @@ Meteor.methods({
createdAt: 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
// failure break the insert.
try {
@ -66,32 +66,32 @@ Meteor.methods({
return _id;
},
'comments.edit': function commentsEdit(commentId, content) {
'comments.edit': async function commentsEdit(commentId, content) {
check(commentId, String);
check(content, String);
if (!this.userId) throw new Meteor.Error('403', 'Login required');
requireOwner(commentId, this.userId);
await requireOwner(commentId, this.userId);
const text = content.trim();
if (!text) throw new Meteor.Error('400', 'Empty comment');
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() }
});
},
'comments.remove': function commentsRemove(commentId) {
'comments.remove': async function commentsRemove(commentId) {
check(commentId, String);
if (!this.userId) throw new Meteor.Error('403', 'Login required');
requireOwner(commentId, this.userId);
Comments.remove(commentId);
await requireOwner(commentId, this.userId);
await Comments.removeAsync(commentId);
},
'comments.like': function commentsLike(commentId) {
toggle(commentId, 'likes', 'dislikes', this.userId);
'comments.like': async function commentsLike(commentId) {
await toggle(commentId, 'likes', 'dislikes', this.userId);
},
'comments.dislike': function commentsDislike(commentId) {
toggle(commentId, 'dislikes', 'likes', this.userId);
'comments.dislike': async function commentsDislike(commentId) {
await toggle(commentId, 'dislikes', 'likes', this.userId);
}
});

View file

@ -14,10 +14,10 @@ export default function onCommentAdd(payload) {
const { referenceId, userId } = payload;
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 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);
if (emailAddress) {
const emailOpts = {