Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ DATABASE_PASS=escapp
DATABASE_NAME=escapp
DATABASE_HOST=localhost
DATABASE_URL=postgres://escapp:escapp@localhost:5432/escapp
SMTP_HOST=smtp.resend.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USERNAME=resend
SMTP_PASSWORD=
SMTP_FROM=noreply@example.com
UPLOADS_PATH=public/uploads
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ RUN npm ci

COPY . .

RUN mkdir -p public/uploads

ENV APP_NAME='localhost'
ENV APP_SSL='false'
ENV DATABASE_USER='escapp'
ENV DATABASE_PASS='escapp'
ENV DATABASE_NAME='escapp'
ENV DATABASE_HOST='db'
ENV DATABASE_SSL='false'
ENV SMTP_HOST='smtp.resend.com'
ENV SMTP_PORT='465'
ENV SMTP_SECURE='true'
ENV SMTP_USERNAME='resend'
ENV UPLOADS_PATH='public/uploads'
ENV NODE_ENV='development'
ENV PORT='3000'

Expand Down
3 changes: 1 addition & 2 deletions controllers/assets_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ exports.uploadAssets = async (req, res) => {
try {
await attHelper.checksCloudinaryEnv();
// Save the new asset into Cloudinary:
uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options_zip);
uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options_zip, req.file);
} catch (err) {
res.status(500);
res.send(err);
Expand Down Expand Up @@ -103,4 +103,3 @@ exports.browse = async (req, res, next) => {
next(err);
}
};

11 changes: 5 additions & 6 deletions controllers/escapeRoom_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const Sequelize = require("sequelize");
const {QueryTypes} = require("sequelize");
const sequelize = require("../models");
const {models} = sequelize;
const cloudinary = require("cloudinary");
const query = require("../queries");
const attHelper = require("../helpers/attachments");
const {nextStep, prevStep} = require("../helpers/progress");
Expand Down Expand Up @@ -88,7 +87,7 @@ exports.index = async (req, res, next) => {
} else {
const pageArray = paginate(page, pages, 5);

res.render("escapeRooms/index.ejs", {escapeRooms, cloudinary, user, count, page, pages, pageArray});
res.render("escapeRooms/index.ejs", {escapeRooms, "cloudinary": attHelper, user, count, page, pages, pageArray});
}
} catch (error) {
next(error);
Expand All @@ -107,9 +106,9 @@ exports.show = async (req, res) => {
const howManyRetos = await models.retosSuperados.count({"where": {"success": true, "teamId": team.id }});
const finished = howManyRetos === escapeRoom.puzzles.length;

res.render("escapeRooms/showStudent", {escapeRoom, cloudinary, participant, team, finished});
res.render("escapeRooms/showStudent", {escapeRoom, "cloudinary": attHelper, participant, team, finished});
} else {
res.render("escapeRooms/show", {escapeRoom, cloudinary, hostName, "email": req.session.user.username});
res.render("escapeRooms/show", {escapeRoom, "cloudinary": attHelper, hostName, "email": req.session.user.username});
}
};

Expand Down Expand Up @@ -142,7 +141,7 @@ exports.create = async (req, res) => {

try {
await attHelper.checksCloudinaryEnv();
const uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options);
const uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options, req.file);

try {
await models.attachment.create({
Expand Down Expand Up @@ -219,7 +218,7 @@ exports.update = async (req, res) => {
try {
// Save the new attachment into Cloudinary:
await attHelper.checksCloudinaryEnv();
const uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options);
const uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options, req.file);
// Remember the public_id of the old image.
const old_public_id = er.attachment ? er.attachment.public_id : null;
let attachment = await er.getAttachment();
Expand Down
2 changes: 1 addition & 1 deletion controllers/hint_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ exports.pistasUpdate = async (req, res) => {
} else {
try {
await attHelper.checksCloudinaryEnv();
const uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options_zip);
const uploadResult = await attHelper.uploadResource(req.file.path, attHelper.cloudinary_upload_options_zip, req.file);
const old_public_id = escapeRoom.hintApp ? escapeRoom.hintApp.public_id : null; // Update the attachment into the data base.

const att = await escapeRoom.getHintApp();
Expand Down
8 changes: 7 additions & 1 deletion deploy/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ DATABASE_NAME=
DATABASE_USER=
DATABASE_PASS=
CLOUDINARY_URL=
SENDGRID_API_KEY=
SMTP_HOST=smtp.resend.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USERNAME=resend
SMTP_PASSWORD=
SMTP_FROM=
UPLOADS_PATH=public/uploads
67 changes: 41 additions & 26 deletions helpers/attachments.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ const fs = require("fs");
const path = require("path");
const crypto = require("crypto");

const getUploadPath = () => process.env.UPLOADS_PATH || path.join("public", "uploads");

const safeExtension = (file) => {
const extension = path.extname(file && file.originalname || "").toLowerCase();

return extension.match(/^\.[a-z0-9]+$/) ? extension : "";
};

const uploadResourceToCloudinary = (src, options) => new Promise((resolve, reject) => {
cloudinary.v2.uploader.upload(
src,
Expand All @@ -20,33 +28,50 @@ const uploadResourceToCloudinary = (src, options) => new Promise((resolve, rejec
);
});

const uploadResourceToFileSystem = (src) => new Promise(function (resolve, reject) {
const uploadResourceToFileSystem = (src, file) => new Promise(function (resolve, reject) {
const salt = `${Math.round(new Date().valueOf() * Math.random())}`;
const public_id = crypto.createHmac("sha256", salt).update(src).
digest("hex");
const public_id = `${crypto.createHmac("sha256", salt).update(src).
digest("hex")}${safeExtension(file)}`;

const url = path.join("/uploads", public_id);
const url = path.posix.join("/uploads", public_id);

const destination = path.join(getUploadPath(), public_id);

fs.mkdir(path.dirname(destination), {"recursive": true}, function (dirError) {
if (dirError) {
reject(dirError);
return;
}
fs.copyFile(src, destination, fs.constants.COPYFILE_EXCL, function (error) {
if (error) {
reject(error);
} else {
resolve({
public_id,
url
});
}
});
});
});

const destination = path.join("public", "uploads", public_id);
const deleteResourceFromFileSystem = (public_id) => new Promise(function (resolve) {
const destination = path.join(getUploadPath(), public_id);

fs.copyFile(src, destination, fs.constants.COPYFILE_EXCL, function (error) {
fs.unlink(destination, function (error) {
if (error) {
reject(error);
} else {
resolve({
public_id,
url
});
console.error("Failed to delete file from filesystem", error);
}
resolve();
});
});

exports.uploadResource = function (src, options) {
exports.uploadResource = function (src, options, file) {
return new Promise(function (resolve) {
if (process.env.CLOUDINARY_URL) {
resolve(uploadResourceToCloudinary(src, options));
} else {
resolve(uploadResourceToFileSystem(src));
resolve(uploadResourceToFileSystem(src, file));
}
});
};
Expand All @@ -61,11 +86,7 @@ exports.deleteResource = async function (public_id, model) {
// Delete from Cloudinary.
cloudinary.api.delete_resources(public_id);
} else {
const destination = path.join("public", "uploads", public_id); // Delete from local file system.

fs.unlink(destination, function (error) {
console.error("Failed to delete file from filesystem", error);
});
await deleteResourceFromFileSystem(public_id);
}
};

Expand Down Expand Up @@ -110,13 +131,7 @@ exports.video = function (public_id, options) {
"</video>";
};

exports.checksCloudinaryEnv = () => new Promise((resolve, reject) => {
if (process.env.CLOUDINARY_URL) {
resolve();
} else {
reject(new Error("The CLOUDINARY_URL env variable is not defined."));
}
});
exports.checksCloudinaryEnv = () => Promise.resolve();

exports.cloudinary_upload_options = {
"folder": "/escapeRoom/attachments",
Expand Down
27 changes: 22 additions & 5 deletions helpers/mailer.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
const sgMail = require("@sendgrid/mail");
const nodemailer = require("nodemailer");

sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const getSmtpPort = () => parseInt(process.env.SMTP_PORT || "465", 10);

const isSmtpSecure = () => {
if (process.env.SMTP_SECURE === undefined) {
return getSmtpPort() === 465;
}

return process.env.SMTP_SECURE === "true";
};

const getTransporter = () => nodemailer.createTransport({
"host": process.env.SMTP_HOST || "smtp.resend.com",
"port": getSmtpPort(),
"secure": isSmtpSecure(),
"auth": {
"user": process.env.SMTP_USERNAME || "resend",
"pass": process.env.SMTP_PASSWORD
}
});

exports.resetPasswordEmail = (to, subject = "escapp", text, html) => {
const from = `noreply@${process.env.APP_NAME || "escapp.dit.upm.es"}`;
const from = process.env.SMTP_FROM || `noreply@${process.env.APP_NAME || "escapp.dit.upm.es"}`;
const msg = {
to,
from,
Expand All @@ -12,6 +30,5 @@ exports.resetPasswordEmail = (to, subject = "escapp", text, html) => {
html
};

return sgMail.send(msg);
return getTransporter().sendMail(msg);
};

6 changes: 3 additions & 3 deletions helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const {Op} = Sequelize;
const sequelize = require("../models");
const {models} = sequelize;
const {nextStep, prevStep} = require("./progress");
const cloudinary = require("cloudinary");
const attHelper = require("../helpers/attachments");
const ejs = require("ejs");
const queries = require("../queries");
const {OK, NOT_A_PARTICIPANT, PARTICIPANT, NOK, NOT_ACTIVE, NOT_STARTED, TOO_LATE, AUTHOR, ERROR} = require("../helpers/apiCodes");
Expand Down Expand Up @@ -54,7 +54,7 @@ exports.playInterface = async (name, req, res, next) => {
if (isAdmin || isAuthor) {
res.render("escapeRooms/play/play", {
"escapeRoom": req.escapeRoom,
cloudinary,
"cloudinary": attHelper,
"team": {
"turno": req.turn,
"retos": []
Expand Down Expand Up @@ -113,7 +113,7 @@ exports.playInterface = async (name, req, res, next) => {
await exports.automaticallySetAttendance(team, req.session.user.id, req.escapeRoom.automaticAttendance);
const hints = await models.requestedHint.findAll({"where": {"teamId": team.id, "success": true}, "include": [{"model": models.hint, "include": [{"model": models.puzzle, "attributes": ["order"]}]}], "order": [["createdAt", "ASC"]]});

res.render("escapeRooms/play/play", {"escapeRoom": req.escapeRoom, "hostName": process.env.APP_NAME ? `https://${process.env.APP_NAME}` : "http://localhost:3000", cloudinary, "teams": [], team, token, "userId": req.session.user.id, "turnoId": team.turno.id, "teamId": team.id, "isStudent": true, "hints": hints || [], "endPoint": name, "layout": false });
res.render("escapeRooms/play/play", {"escapeRoom": req.escapeRoom, "hostName": process.env.APP_NAME ? `https://${process.env.APP_NAME}` : "http://localhost:3000", "cloudinary": attHelper, "teams": [], team, token, "userId": req.session.user.id, "turnoId": team.turno.id, "teamId": team.id, "isStudent": true, "hints": hints || [], "endPoint": name, "layout": false });
} catch (err) {
next(err);
}
Expand Down
Loading
Loading