Reaches Meteor 2.3 building + running against Mongo 3.2, REST smoke test
byte-identical to the 1.6.1.1 baseline (all 12 Flutter endpoints green).
Dead/abandoned Atmosphere packages removed or replaced (the upgrade blockers):
- arkham:comments-ui (no Meteor 2.x build; pinned accounts-password@1.x):
reimplemented the fire-page comments as a React feature:
imports/api/Comments/ (collection, server methods, publication, media
analyzers, new-fire-comment email) + imports/ui/components/Comments/CommentsBox.
Comment text now rendered as safe plain text + image/youtube embed (was
markdown-to-HTML). Wired into Fires.js; sitemaps.js + migration v11 updated
to the new collection. Old Blaze/startup comments files deleted.
- nimble:restivus (REST API; pinned accounts-password@1.3.3, CoffeeScript source
that crashes this build host): vendored as a local package
packages/nimble-restivus with the .coffee precompiled to plain JS and the
accounts-password constraint loosened to 2.x. REST behavior unchanged
(verified by smoke test). This also dropped the entire iron:router stack.
- maximum:server-transform (+ peerlibrary:*, meteorhacks:zones/inject-initial):
unused; removal broke Meteor.publishTransformed in FalsePositives publications
-> replaced with plain Meteor.publish (no transform was configured).
- less, markdown (no source files), and the dead test stack
(meteortesting:mocha, practicalmeteor:chai, xolvio:cleaner) which transitively
pulled coffeescript@1.0.17 — the build plugin that crashed meteor-tool
(node_contextify assertion) on this machine. Removing it unblocked the build.
- fourseven:scss 4.5.4 -> 4.14.1 (node-sass 4.5.3 doesn't build on node 12).
npm-mongo driver at 2.3 is 3.9.x — still compatible with the production Mongo
3.2 replica set.
102 lines
3.4 KiB
JavaScript
102 lines
3.4 KiB
JavaScript
/* eslint-disable import/no-absolute-path */
|
|
import { Meteor } from 'meteor/meteor';
|
|
import { check } from 'meteor/check';
|
|
import Comments from '/imports/api/Comments/Comments';
|
|
import getMediaFromContent from '/imports/api/Comments/mediaAnalyzers';
|
|
import rateLimit from '/imports/modules/rate-limit';
|
|
import onCommentAdd from '/imports/api/Comments/server/onCommentAdd';
|
|
|
|
const MAX_LEN = 5000;
|
|
|
|
function usernameOf(user) {
|
|
return (user && user.profile && user.profile.name && user.profile.name.first)
|
|
? user.profile.name.first
|
|
: '';
|
|
}
|
|
|
|
function requireOwner(commentId, userId) {
|
|
const comment = Comments.findOne(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) {
|
|
check(commentId, String);
|
|
if (!userId) throw new Meteor.Error('403', 'Login required');
|
|
const comment = Comments.findOne(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);
|
|
}
|
|
|
|
Meteor.methods({
|
|
'comments.insert': function commentsInsert(referenceId, content) {
|
|
check(referenceId, String);
|
|
check(content, String);
|
|
if (!this.userId) throw new Meteor.Error('403', 'Login required to comment');
|
|
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');
|
|
|
|
const now = new Date();
|
|
const doc = {
|
|
referenceId,
|
|
content: text,
|
|
userId: this.userId,
|
|
username: usernameOf(Meteor.users.findOne(this.userId)),
|
|
media: getMediaFromContent(text),
|
|
likes: [],
|
|
dislikes: [],
|
|
status: 'approved',
|
|
createdAt: now,
|
|
updatedAt: now
|
|
};
|
|
const _id = Comments.insert(doc);
|
|
// Fire-and-forget: notify other commenters of this fire. Never let a mail
|
|
// failure break the insert.
|
|
try {
|
|
onCommentAdd({ ...doc, _id });
|
|
} catch (e) {
|
|
console.warn(`comments onCommentAdd failed: ${e}`);
|
|
}
|
|
return _id;
|
|
},
|
|
|
|
'comments.edit': function commentsEdit(commentId, content) {
|
|
check(commentId, String);
|
|
check(content, String);
|
|
if (!this.userId) throw new Meteor.Error('403', 'Login required');
|
|
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, {
|
|
$set: { content: text, media: getMediaFromContent(text), updatedAt: new Date() }
|
|
});
|
|
},
|
|
|
|
'comments.remove': function commentsRemove(commentId) {
|
|
check(commentId, String);
|
|
if (!this.userId) throw new Meteor.Error('403', 'Login required');
|
|
requireOwner(commentId, this.userId);
|
|
Comments.remove(commentId);
|
|
},
|
|
|
|
'comments.like': function commentsLike(commentId) {
|
|
toggle(commentId, 'likes', 'dislikes', this.userId);
|
|
},
|
|
|
|
'comments.dislike': function commentsDislike(commentId) {
|
|
toggle(commentId, 'dislikes', 'likes', this.userId);
|
|
}
|
|
});
|
|
|
|
rateLimit({
|
|
methods: ['comments.insert', 'comments.edit', 'comments.remove', 'comments.like', 'comments.dislike'],
|
|
limit: 5,
|
|
timeRange: 1000
|
|
});
|