Merge branch 'feature/error-output' of https://github.com/ghalestrilo/p5.js-web-editor into feature/error-output

This commit is contained in:
ghalestrilo 2020-07-21 17:40:51 -03:00
commit 8f60b35c8e
20 changed files with 590 additions and 106 deletions

1
.gitignore vendored
View file

@ -19,3 +19,4 @@ localhost.key
privkey.pem privkey.pem
storybook-static storybook-static
duplicates.json

View file

@ -7,7 +7,7 @@ const fallbackLng = ['en-US'];
const availableLanguages = ['en-US', 'es-419']; const availableLanguages = ['en-US', 'es-419'];
const options = { const options = {
loadPath: 'locales/{{lng}}/translations.json', loadPath: '/locales/{{lng}}/translations.json',
requestOptions: { // used for fetch, can also be a function (payload) => ({ method: 'GET' }) requestOptions: { // used for fetch, can also be a function (payload) => ({ method: 'GET' })
mode: 'no-cors' mode: 'no-cors'
}, },

View file

@ -16,6 +16,8 @@ import {
import { clearState, saveState } from '../../../persistState'; import { clearState, saveState } from '../../../persistState';
const ROOT_URL = getConfig('API_URL'); const ROOT_URL = getConfig('API_URL');
const S3_BUCKET_URL_BASE = getConfig('S3_BUCKET_URL_BASE');
const S3_BUCKET = getConfig('S3_BUCKET');
export function setProject(project) { export function setProject(project) {
return { return {
@ -287,7 +289,7 @@ export function cloneProject(id) {
// duplicate all files hosted on S3 // duplicate all files hosted on S3
each(newFiles, (file, callback) => { each(newFiles, (file, callback) => {
if (file.url && file.url.includes('amazonaws')) { if (file.url && (file.url.includes(S3_BUCKET_URL_BASE) || file.url.includes(S3_BUCKET))) {
const formParams = { const formParams = {
url: file.url url: file.url
}; };

23
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "p5.js-web-editor", "name": "p5.js-web-editor",
"version": "1.0.5", "version": "1.0.6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -32207,17 +32207,15 @@
"integrity": "sha1-nYqSjH8sN1E8LQZOV7Pjw1bp+rs=" "integrity": "sha1-nYqSjH8sN1E8LQZOV7Pjw1bp+rs="
}, },
"react-redux": { "react-redux": {
"version": "5.1.2", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
"integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==", "integrity": "sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==",
"requires": { "requires": {
"@babel/runtime": "^7.1.2", "@babel/runtime": "^7.5.5",
"hoist-non-react-statics": "^3.3.0", "hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4", "loose-envify": "^1.4.0",
"loose-envify": "^1.1.0", "prop-types": "^15.7.2",
"prop-types": "^15.6.1", "react-is": "^16.9.0"
"react-is": "^16.6.0",
"react-lifecycles-compat": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"hoist-non-react-statics": { "hoist-non-react-statics": {
@ -32227,6 +32225,11 @@
"requires": { "requires": {
"react-is": "^16.7.0" "react-is": "^16.7.0"
} }
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
} }
} }
}, },

View file

@ -1,6 +1,6 @@
{ {
"name": "p5.js-web-editor", "name": "p5.js-web-editor",
"version": "1.0.5", "version": "1.0.6",
"description": "The web editor for p5.js.", "description": "The web editor for p5.js.",
"scripts": { "scripts": {
"clean": "rimraf dist", "clean": "rimraf dist",
@ -200,7 +200,7 @@
"react-helmet": "^5.1.3", "react-helmet": "^5.1.3",
"react-hot-loader": "^4.12.19", "react-hot-loader": "^4.12.19",
"react-i18next": "^11.5.0", "react-i18next": "^11.5.0",
"react-redux": "^5.1.2", "react-redux": "^7.2.0",
"react-router": "^3.2.5", "react-router": "^3.2.5",
"react-split-pane": "^0.1.89", "react-split-pane": "^0.1.89",
"react-tabs": "^2.3.1", "react-tabs": "^2.3.1",

View file

@ -24,7 +24,7 @@ passport.deserializeUser((id, done) => {
* Sign in using Email/Username and Password. * Sign in using Email/Username and Password.
*/ */
passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => { passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
User.findByMailOrName(email.toLowerCase()) User.findByEmailOrUsername(email)
.then((user) => { // eslint-disable-line consistent-return .then((user) => { // eslint-disable-line consistent-return
if (!user) { if (!user) {
return done(null, false, { msg: `Email ${email} not found.` }); return done(null, false, { msg: `Email ${email} not found.` });
@ -43,7 +43,7 @@ passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, don
* Authentificate using Basic Auth (Username + Api Key) * Authentificate using Basic Auth (Username + Api Key)
*/ */
passport.use(new BasicStrategy((userid, key, done) => { passport.use(new BasicStrategy((userid, key, done) => {
User.findOne({ username: userid }, (err, user) => { // eslint-disable-line consistent-return User.findByUsername(userid, (err, user) => { // eslint-disable-line consistent-return
if (err) { return done(err); } if (err) { return done(err); }
if (!user) { return done(null, false); } if (!user) { return done(null, false); }
user.findMatchingKey(key, (innerErr, isMatch, keyDocument) => { user.findMatchingKey(key, (innerErr, isMatch, keyDocument) => {
@ -98,9 +98,7 @@ passport.use(new GitHubStrategy({
const emails = getVerifiedEmails(profile.emails); const emails = getVerifiedEmails(profile.emails);
const primaryEmail = getPrimaryEmail(profile.emails); const primaryEmail = getPrimaryEmail(profile.emails);
User.findOne({ User.findByEmail(emails, (findByEmailErr, existingEmailUser) => {
email: { $in: emails },
}, (findByEmailErr, existingEmailUser) => {
if (existingEmailUser) { if (existingEmailUser) {
existingEmailUser.email = existingEmailUser.email || primaryEmail; existingEmailUser.email = existingEmailUser.email || primaryEmail;
existingEmailUser.github = profile.id; existingEmailUser.github = profile.id;
@ -141,11 +139,9 @@ passport.use(new GoogleStrategy({
const primaryEmail = profile._json.emails[0].value; const primaryEmail = profile._json.emails[0].value;
User.findOne({ User.findByEmail(primaryEmail, (findByEmailErr, existingEmailUser) => {
email: primaryEmail,
}, (findByEmailErr, existingEmailUser) => {
let username = profile._json.emails[0].value.split('@')[0]; let username = profile._json.emails[0].value.split('@')[0];
User.findOne({ username }, (findByUsernameErr, existingUsernameUser) => { User.findByUsername(username, (findByUsernameErr, existingUsernameUser) => {
if (existingUsernameUser) { if (existingUsernameUser) {
const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)]; const adj = friendlyWords.predicates[Math.floor(Math.random() * friendlyWords.predicates.length)];
username = slugify(`${username} ${adj}`); username = slugify(`${username} ${adj}`);

View file

@ -1,5 +1,5 @@
export const getObjectKey = jest.mock(); export const getObjectKey = jest.mock();
export const deleteObjectsFromS3 = jest.fn(); export const deleteObjectsFromS3 = jest.fn();
export const signS3 = jest.fn(); export const signS3 = jest.fn();
export const copyObjectInS3 = jest.fn(); export const copyObjectInS3RequestHandler = jest.fn();
export const listObjectsInS3ForUser = jest.fn(); export const listObjectsInS3ForUser = jest.fn();

View file

@ -28,7 +28,7 @@ function getExtension(filename) {
export function getObjectKey(url) { export function getObjectKey(url) {
const urlArray = url.split('/'); const urlArray = url.split('/');
let objectKey; let objectKey;
if (urlArray.length === 6) { if (urlArray.length === 5) {
const key = urlArray.pop(); const key = urlArray.pop();
const userId = urlArray.pop(); const userId = urlArray.pop();
objectKey = `${userId}/${key}`; objectKey = `${userId}/${key}`;
@ -98,24 +98,72 @@ export function signS3(req, res) {
res.json(result); res.json(result);
} }
export function copyObjectInS3(req, res) { export function copyObjectInS3(url, userId) {
const { url } = req.body; return new Promise((resolve, reject) => {
const objectKey = getObjectKey(url); const objectKey = getObjectKey(url);
const fileExtension = getExtension(objectKey); const fileExtension = getExtension(objectKey);
const newFilename = uuid.v4() + fileExtension; const newFilename = uuid.v4() + fileExtension;
const userId = req.user.id; const headParams = {
const params = { Bucket: `${process.env.S3_BUCKET}`,
Bucket: `${process.env.S3_BUCKET}`, Key: `${objectKey}`
CopySource: `${process.env.S3_BUCKET}/${objectKey}`, };
Key: `${userId}/${newFilename}`, client.s3.headObject(headParams, (headErr) => {
ACL: 'public-read' if (headErr) {
}; reject(new Error(`Object with key ${process.env.S3_BUCKET}/${objectKey} does not exist.`));
const copy = client.copyObject(params); return;
copy.on('err', (err) => { }
console.log(err); const params = {
Bucket: `${process.env.S3_BUCKET}`,
CopySource: `${process.env.S3_BUCKET}/${objectKey}`,
Key: `${userId}/${newFilename}`,
ACL: 'public-read'
};
const copy = client.copyObject(params);
copy.on('err', (err) => {
reject(err);
});
copy.on('end', (data) => {
resolve(`${s3Bucket}${userId}/${newFilename}`);
});
});
}); });
copy.on('end', (data) => { }
res.json({ url: `${s3Bucket}${userId}/${newFilename}` });
export function copyObjectInS3RequestHandler(req, res) {
const { url } = req.body;
copyObjectInS3(url, req.user.id).then((newUrl) => {
res.json({ url: newUrl });
});
}
export function moveObjectToUserInS3(url, userId) {
return new Promise((resolve, reject) => {
const objectKey = getObjectKey(url);
const fileExtension = getExtension(objectKey);
const newFilename = uuid.v4() + fileExtension;
const headParams = {
Bucket: `${process.env.S3_BUCKET}`,
Key: `${objectKey}`
};
client.s3.headObject(headParams, (headErr) => {
if (headErr) {
reject(new Error(`Object with key ${process.env.S3_BUCKET}/${objectKey} does not exist.`));
return;
}
const params = {
Bucket: `${process.env.S3_BUCKET}`,
CopySource: `${process.env.S3_BUCKET}/${objectKey}`,
Key: `${userId}/${newFilename}`,
ACL: 'public-read'
};
const move = client.moveObject(params);
move.on('err', (err) => {
reject(err);
});
move.on('end', (data) => {
resolve(`${s3Bucket}${userId}/${newFilename}`);
});
});
}); });
} }

View file

@ -11,7 +11,7 @@ export default function collectionForUserExists(username, collectionId, callback
} }
function findUser() { function findUser() {
return User.findOne({ username }); return User.findByUsername(username);
} }
function findCollection(owner) { function findCollection(owner) {

View file

@ -3,7 +3,8 @@ import User from '../../models/user';
async function getOwnerUserId(req) { async function getOwnerUserId(req) {
if (req.params.username) { if (req.params.username) {
const user = await User.findOne({ username: req.params.username }); const user =
await User.findByUsername(req.params.username);
if (user && user._id) { if (user && user._id) {
return user._id; return user._id;
} }

View file

@ -64,7 +64,7 @@ export function updateProject(req, res) {
export function getProject(req, res) { export function getProject(req, res) {
const { project_id: projectId, username } = req.params; const { project_id: projectId, username } = req.params;
User.findOne({ username }, (err, user) => { // eslint-disable-line User.findByUsername(username, (err, user) => { // eslint-disable-line
if (!user) { if (!user) {
return res.status(404).send({ message: 'Project with that username does not exist' }); return res.status(404).send({ message: 'Project with that username does not exist' });
} }
@ -141,7 +141,7 @@ export function projectExists(projectId, callback) {
} }
export function projectForUserExists(username, projectId, callback) { export function projectForUserExists(username, projectId, callback) {
User.findOne({ username }, (err, user) => { User.findByUsername(username, (err, user) => {
if (!user) { if (!user) {
callback(false); callback(false);
return; return;

View file

@ -7,7 +7,7 @@ const UserNotFoundError = createApplicationErrorClass('UserNotFoundError');
function getProjectsForUserName(username) { function getProjectsForUserName(username) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
User.findOne({ username }, (err, user) => { User.findByUsername(username, (err, user) => {
if (err) { if (err) {
reject(err); reject(err);
return; return;

View file

@ -30,71 +30,63 @@ const random = (done) => {
}; };
export function findUserByUsername(username, cb) { export function findUserByUsername(username, cb) {
User.findOne( User.findByUsername(username, (err, user) => {
{ username }, cb(user);
(err, user) => { });
cb(user);
}
);
} }
export function createUser(req, res, next) { export function createUser(req, res, next) {
const { username, email } = req.body;
const { password } = req.body;
const emailLowerCase = email.toLowerCase();
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
random((tokenError, token) => { random((tokenError, token) => {
const user = new User({ const user = new User({
username: req.body.username, username,
email: req.body.email, email: emailLowerCase,
password: req.body.password, password,
verified: User.EmailConfirmation.Sent, verified: User.EmailConfirmation.Sent,
verifiedToken: token, verifiedToken: token,
verifiedTokenExpires: EMAIL_VERIFY_TOKEN_EXPIRY_TIME, verifiedTokenExpires: EMAIL_VERIFY_TOKEN_EXPIRY_TIME,
}); });
User.findOne( User.findByEmailAndUsername(email, username, (err, existingUser) => {
{ if (err) {
$or: [ res.status(404).send({ error: err });
{ email: req.body.email }, return;
{ username: req.body.username } }
]
},
(err, existingUser) => {
if (err) {
res.status(404).send({ error: err });
return;
}
if (existingUser) { if (existingUser) {
const fieldInUse = existingUser.email === req.body.email ? 'Email' : 'Username'; const fieldInUse = existingUser.email.toLowerCase() === emailLowerCase ? 'Email' : 'Username';
res.status(422).send({ error: `${fieldInUse} is in use` }); res.status(422).send({ error: `${fieldInUse} is in use` });
return;
}
user.save((saveErr) => {
if (saveErr) {
next(saveErr);
return; return;
} }
user.save((saveErr) => { req.logIn(user, (loginErr) => {
if (saveErr) { if (loginErr) {
next(saveErr); next(loginErr);
return; return;
} }
req.logIn(user, (loginErr) => {
if (loginErr) {
next(loginErr);
return;
}
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http'; const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
const mailOptions = renderEmailConfirmation({ const mailOptions = renderEmailConfirmation({
body: { body: {
domain: `${protocol}://${req.headers.host}`, domain: `${protocol}://${req.headers.host}`,
link: `${protocol}://${req.headers.host}/verify?t=${token}` link: `${protocol}://${req.headers.host}/verify?t=${token}`
}, },
to: req.user.email, to: req.user.email,
}); });
mail.send(mailOptions, (mailErr, result) => { // eslint-disable-line no-unused-vars mail.send(mailOptions, (mailErr, result) => { // eslint-disable-line no-unused-vars
res.json(userResponse(req.user)); res.json(userResponse(req.user));
});
}); });
}); });
} });
); });
}); });
} }
@ -103,7 +95,10 @@ export function duplicateUserCheck(req, res) {
const value = req.query[checkType]; const value = req.query[checkType];
const query = {}; const query = {};
query[checkType] = value; query[checkType] = value;
User.findOne(query, (err, user) => { // Don't want to use findByEmailOrUsername here, because in this case we do
// want to use case-insensitive search for usernames to prevent username
// duplicates, which overrides the default behavior.
User.findOne(query).collation({ locale: 'en', strength: 2 }).exec((err, user) => {
if (user) { if (user) {
return res.json({ return res.json({
exists: true, exists: true,
@ -147,7 +142,7 @@ export function resetPasswordInitiate(req, res) {
async.waterfall([ async.waterfall([
random, random,
(token, done) => { (token, done) => {
User.findOne({ email: req.body.email }, (err, user) => { User.findByEmail(req.body.email, (err, user) => {
if (!user) { if (!user) {
res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' }); res.json({ success: true, message: 'If the email is registered with the editor, an email has been sent.' });
return; return;
@ -277,7 +272,7 @@ export function updatePassword(req, res) {
} }
export function userExists(username, callback) { export function userExists(username, callback) {
User.findOne({ username }, (err, user) => ( User.findByUsername(username, (err, user) => (
user ? callback(true) : callback(false) user ? callback(true) : callback(false)
)); ));
} }

View file

@ -0,0 +1,237 @@
import mongoose from 'mongoose';
import fs from 'fs';
import User from '../models/user';
import Project from '../models/project';
import Collection from '../models/collection';
import { moveObjectToUserInS3, copyObjectInS3 } from '../controllers/aws.controller';
import mail from '../utils/mail';
import { renderAccountConsolidation } from '../views/mail';
const mongoConnectionString = process.env.MONGO_URL;
const { ObjectId } = mongoose.Types;
// Connect to MongoDB
mongoose.Promise = global.Promise;
mongoose.connect(mongoConnectionString, { useNewUrlParser: true, useUnifiedTopology: true });
mongoose.set('useCreateIndex', true);
mongoose.connection.on('error', () => {
console.error('MongoDB Connection Error. Please make sure that MongoDB is running.');
process.exit(1);
});
/*
* Requires the MongoDB Node.js Driver
* https://mongodb.github.io/node-mongodb-native
*/
const agg = [
{
$project: {
email: {
$toLower: [
'$email'
]
}
}
}, {
$group: {
_id: '$email',
total: {
$sum: 1
}
}
}, {
$match: {
total: {
$gt: 1
}
}
}, {
$sort: {
total: -1
}
}
];
// steps to make this work
// iterate through the results
// check if any files are on AWS
// if so, move them to the right user bucket
// then, update the user to currentUser
// then, after updating all of the projects
// also update the collections
// delete other users
// update user email so it is all lowercase
// then, send the email
// then, figure out how to iterate through all of the users.
// create list of duplicate users
// User.aggregate(agg).then((result) => {
// return fs.writeFile('duplicates.json', JSON.stringify(result), () => {
// console.log('File written.');
// process.exit(0);
// });
// });
let currentUser = null;
let duplicates = null;
fs.readFile('duplicates.json', async (err, file) => {
const result = JSON.parse(file);
for (let i = 3000; i < result.length; i += 1) {
console.log('Index: ', i);
const email = result[i]._id;
console.log(email);
await consolidateAccount(email); // eslint-disable-line
}
process.exit(0);
});
async function consolidateAccount(email) {
return User.find({ email }).collation({ locale: 'en', strength: 2 })
.sort({ createdAt: 1 }).exec()
.then((result) => {
[currentUser, ...duplicates] = result;
console.log('Current User: ', currentUser._id, ' ', currentUser.email);
duplicates = duplicates.map(dup => dup._id);
console.log('Duplicates: ', duplicates);
return Project.find({
user: { $in: duplicates }
}).exec();
}).then((sketches) => {
const saveSketchPromises = [];
sketches.forEach((sketch) => {
console.log('SketchId: ', sketch._id);
console.log('UserId: ', sketch.user);
const moveSketchFilesPromises = [];
sketch.files.forEach((file) => {
// if the file url contains sketch user
if (file.url && file.url.includes(process.env.S3_BUCKET_URL_BASE) && !file.url.includes(currentUser._id)) {
if (file.url.includes(sketch.user)) {
const fileSavePromise = moveObjectToUserInS3(file.url, currentUser._id)
.then((newUrl) => {
file.url = newUrl;
}).catch((err) => {
console.log('Move Error:');
console.log(err);
});
moveSketchFilesPromises.push(fileSavePromise);
} else {
const fileSavePromise = copyObjectInS3(file.url, currentUser._id)
.then((newUrl) => {
file.url = newUrl;
}).catch((err) => {
console.log('Copy Error:');
console.log(err);
});
moveSketchFilesPromises.push(fileSavePromise);
}
}
});
const sketchSavePromise = Promise.all(moveSketchFilesPromises).then(() => {
sketch.user = ObjectId(currentUser._id);
return sketch.save();
});
saveSketchPromises.push(sketchSavePromise);
});
return Promise.all(saveSketchPromises);
}).then(() => {
console.log('Moved and updated all sketches.');
return Collection.updateMany(
{ owner: { $in: duplicates } },
{ $set: { owner: ObjectId(currentUser.id) } }
);
}).then(() => {
console.log('Moved and updated all collections.');
return User.deleteMany({ _id: { $in: duplicates } });
}).then(() => {
console.log('Deleted other user accounts.');
currentUser.email = currentUser.email.toLowerCase();
return currentUser.save();
}).then(() => {
console.log('Migrated email to lowercase.');
// const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
const mailOptions = renderAccountConsolidation({
body: {
domain: 'https://editor.p5js.org',
username: currentUser.username,
email: currentUser.email
},
to: currentUser.email,
});
return new Promise((resolve, reject) => {
mail.send(mailOptions, (mailErr, result) => {
console.log('Sent email.');
if (mailErr) {
return reject(mailErr);
}
return resolve(result);
});
});
}).catch((err) => {
console.log(err);
process.exit(1);
});
}
// let duplicates = [
// "5ce3d936e0f9df0022d8330c",
// "5cff843f091745001e83c070",
// "5d246f5db489e6001eaee6e9"
// ];
// let currentUser = null;
// User.deleteMany({ _id: { $in: duplicates } }).then(() => {
// return User.findOne({ "email": "Silverstar09@hotmail.com" })
// }).then((result) => {
// currentUser = result;
// console.log('Deleted other user accounts.');
// currentUser.email = currentUser.email.toLowerCase();
// return currentUser.save();
// }).then(() => {
// const mailOptions = renderAccountConsolidation({
// body: {
// domain: 'https://editor.p5js.org',
// username: currentUser.username,
// email: currentUser.email
// },
// to: currentUser.email,
// });
// return new Promise((resolve, reject) => {
// mail.send(mailOptions, (mailErr, result) => {
// console.log('Sent email.');
// if (mailErr) {
// return reject(mailErr);
// }
// return resolve(result);
// });
// });
// });
// import s3 from '@auth0/s3';
// const client = s3.createClient({
// maxAsyncS3: 20,
// s3RetryCount: 3,
// s3RetryDelay: 1000,
// multipartUploadThreshold: 20971520, // this is the default (20 MB)
// multipartUploadSize: 15728640, // this is the default (15 MB)
// s3Options: {
// accessKeyId: `${process.env.AWS_ACCESS_KEY}`,
// secretAccessKey: `${process.env.AWS_SECRET_KEY}`,
// region: `${process.env.AWS_REGION}`
// },
// });
// const headParams = {
// Bucket: `${process.env.S3_BUCKET}`,
// Key: "5c9de807f6bccf0017da7927/8b9d95ae-7ddd-452a-b398-672392c4ac43.png"
// };
// client.s3.headObject(headParams, (err, data) => {
// console.log(err);
// console.log(data);
// });

View file

@ -1,7 +1,8 @@
require('@babel/register'); require('@babel/register');
require('@babel/polyfill'); require('@babel/polyfill');
const path = require('path'); const path = require('path');
require('dotenv').config({ path: path.resolve('.env') }); require('dotenv').config({ path: path.resolve('.env.production') });
require('./populateTotalSize'); require('./emailConsolidation');
// require('./populateTotalSize');
// require('./moveBucket'); // require('./moveBucket');
// require('./truncate'); // require('./truncate');

View file

@ -141,17 +141,86 @@ userSchema.methods.findMatchingKey = function findMatchingKey(candidateKey, cb)
if (!foundOne) cb('Matching API key not found !', false, null); if (!foundOne) cb('Matching API key not found !', false, null);
}; };
userSchema.statics.findByMailOrName = function findByMailOrName(email) { /**
*
* Queries User collection by email and returns one User document.
*
* @param {string|string[]} email - Email string or array of email strings
* @callback [cb] - Optional error-first callback that passes User document
* @return {Promise<Object>} - Returns Promise fulfilled by User document
*/
userSchema.statics.findByEmail = function findByEmail(email, cb) {
let query;
if (Array.isArray(email)) {
query = {
email: { $in: email }
};
} else {
query = {
email
};
}
// Email addresses should be case-insensitive unique
// In MongoDB, you must use collation in order to do a case-insensitive query
return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb);
};
/**
*
* Queries User collection by username and returns one User document.
*
* @param {string} username - Username string
* @callback [cb] - Optional error-first callback that passes User document
* @return {Promise<Object>} - Returns Promise fulfilled by User document
*/
userSchema.statics.findByUsername = function findByUsername(username, cb) {
const query = { const query = {
$or: [{ username
email,
}, {
username: email,
}],
}; };
return this.findOne(query).exec(); return this.findOne(query, cb);
};
/**
*
* Queries User collection using email or username with optional callback.
* This function will determine automatically whether the data passed is
* a username or email.
*
* @param {string} value - Email or username
* @callback [cb] - Optional error-first callback that passes User document
* @return {Promise<Object>} - Returns Promise fulfilled by User document
*/
userSchema.statics.findByEmailOrUsername = function findByEmailOrUsername(value, cb) {
const isEmail = value.indexOf('@') > -1;
if (isEmail) {
return this.findByEmail(value, cb);
}
return this.findByUsername(value, cb);
};
/**
*
* Queries User collection, performing a MongoDB logical or with the email
* and username (i.e. if either one matches, will return the first document).
*
* @param {string} email
* @param {string} username
* @callback [cb] - Optional error-first callback that passes User document
* @return {Promise<Object>} - Returns Promise fulfilled by User document
*/
userSchema.statics.findByEmailAndUsername = function findByEmailAndUsername(email, username, cb) {
const query = {
$or: [
{ email },
{ username }
]
};
return this.findOne(query).collation({ locale: 'en', strength: 2 }).exec(cb);
}; };
userSchema.statics.EmailConfirmation = EmailConfirmationStates; userSchema.statics.EmailConfirmation = EmailConfirmationStates;
userSchema.index({ username: 1 }, { collation: { locale: 'en', strength: 2 } });
userSchema.index({ email: 1 }, { collation: { locale: 'en', strength: 2 } });
export default mongoose.model('User', userSchema); export default mongoose.model('User', userSchema);

View file

@ -5,7 +5,7 @@ import isAuthenticated from '../utils/isAuthenticated';
const router = new Router(); const router = new Router();
router.post('/S3/sign', isAuthenticated, AWSController.signS3); router.post('/S3/sign', isAuthenticated, AWSController.signS3);
router.post('/S3/copy', isAuthenticated, AWSController.copyObjectInS3); router.post('/S3/copy', isAuthenticated, AWSController.copyObjectInS3RequestHandler);
router.delete('/S3/:userId?/:objectKey', isAuthenticated, AWSController.deleteObjectFromS3); router.delete('/S3/:userId?/:objectKey', isAuthenticated, AWSController.deleteObjectFromS3);
router.get('/S3/objects', AWSController.listObjectsInS3ForUserRequestHandler); router.get('/S3/objects', AWSController.listObjectsInS3ForUserRequestHandler);

View file

@ -75,7 +75,18 @@ app.use(corsMiddleware);
app.options('*', corsMiddleware); app.options('*', corsMiddleware);
// Body parser, cookie parser, sessions, serve public assets // Body parser, cookie parser, sessions, serve public assets
app.use('/locales', Express.static(path.resolve(__dirname, '../dist/static/locales'), { cacheControl: false })); app.use(
'/locales',
Express.static(
path.resolve(__dirname, '../dist/static/locales'),
{
// Browsers must revalidate for changes to the locale files
// It doesn't actually mean "don't cache this file"
// See: https://jakearchibald.com/2016/caching-best-practices/
setHeaders: res => res.setHeader('Cache-Control', 'no-cache')
}
)
);
app.use(Express.static(path.resolve(__dirname, '../dist/static'), { app.use(Express.static(path.resolve(__dirname, '../dist/static'), {
maxAge: process.env.STATIC_MAX_AGE || (process.env.NODE_ENV === 'production' ? '1d' : '0') maxAge: process.env.STATIC_MAX_AGE || (process.env.NODE_ENV === 'production' ? '1d' : '0')
})); }));

View file

@ -0,0 +1,78 @@
export default ({
domain,
headingText,
greetingText,
messageText,
username,
email,
message2Text,
resetPasswordLink,
directLinkText,
resetPasswordText,
noteText,
meta
}) => (
`
<mjml>
<mj-head>
<mj-raw>
<meta name="keywords" content="${meta.keywords}" />
<meta name="description" content="${meta.description}" />
</mj-raw>
</mj-head>
<mj-body>
<mj-container>
<mj-section>
<mj-column>
<mj-image width="192" src="${domain}/images/p5js-square-logo.png" alt="p5.js" />
<mj-divider border-color="#ed225d"></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text font-size="20px" color="#333333" font-family="sans-serif">
${headingText}
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text color="#333333">
${greetingText}
</mj-text>
<mj-text color="#333333">
${messageText}
</mj-text>
<mj-text color="#333333">
<span style="font-weight:bold;">Username:</span> ${username}
</mj-text>
<mj-text color="#333333">
<span style="font-weight:bold;">Email:</span> ${email}
</mj-text>
<mj-text color="#333333">
${message2Text}
</mj-text>
<mj-button background-color="#ed225d" href="${domain}/${resetPasswordLink}">
${resetPasswordText}
</mj-button>
</mj-column>
</mj-section>
<mj-section>
<mj-column>
<mj-text color="#333333">
${directLinkText}
</mj-text>
<mj-text align="center" color="#333333"><a href="${domain}/${resetPasswordLink}">${domain}/${resetPasswordLink}</a></mj-text>
<mj-text color="#333333">
${noteText}
</mj-text>
</mj-column>
</mj-section>
</mj-container>
</mj-body>
</mjml>
`
);

View file

@ -1,5 +1,47 @@
import renderMjml from '../utils/renderMjml'; import renderMjml from '../utils/renderMjml';
import mailLayout from './mailLayout'; import mailLayout from './mailLayout';
import consolidationMailLayout from './consolidationMailLayout';
export const renderAccountConsolidation = (data) => {
const subject = 'p5.js Web Editor Account Consolidation';
const templateOptions = {
domain: data.body.domain,
headingText: 'Account Consolidation',
greetingText: 'Hello,',
messageText: `You're receiving this message because you previous registered for the
<a href="https://editor.p5js.org">p5.js Web Editor</a>
using the same email address multiple times. In order to fix bugs and prevent future bugs,
your accounts have been consolidated to the first account you created. You can login with
the following email and username:`,
username: data.body.username,
email: data.body.email,
message2Text: `All of your sketches and collections have been preserved and have not been modified.
If you have forgotten your password you can reset it:`,
resetPasswordLink: 'reset-password',
resetPasswordText: 'Reset Password',
directLinkText: 'Or copy and paste the following URL into your browser:',
noteText: `We are grateful for your patience and understanding. Thank you for supporting p5.js and the
p5.js Web Editor!`,
meta: {
keywords: 'p5.js, p5.js web editor, web editor, processing, code editor',
description: 'A web editor for p5.js, a JavaScript library with the goal'
+ ' of making coding accessible to artists, designers, educators, and beginners.'
}
};
// Return MJML string
const template = consolidationMailLayout(templateOptions);
// Render MJML to HTML string
const html = renderMjml(template);
// Return options to send mail
return Object.assign(
{},
data,
{ html, subject },
);
};
export const renderResetPassword = (data) => { export const renderResetPassword = (data) => {
const subject = 'p5.js Web Editor Password Reset'; const subject = 'p5.js Web Editor Password Reset';