diff --git a/LICENCE.md b/LICENCE.md
index 95a6ae4..e6f7a9d 100644
--- a/LICENCE.md
+++ b/LICENCE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 Dewilde Alexandre, Dubois Brieuc and Fischer Nicolas
+Copyright (c) 2020 Dewilde Alexandre, Dubois Brieuc and Technicguy Theo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Privacy Policy - CodeWe.docx b/Privacy Policy - CodeWe.docx
deleted file mode 100644
index b4cb012..0000000
Binary files a/Privacy Policy - CodeWe.docx and /dev/null differ
diff --git a/Terms and Conditions - CodeWe.docx b/Terms and Conditions - CodeWe.docx
deleted file mode 100644
index 3919a63..0000000
Binary files a/Terms and Conditions - CodeWe.docx and /dev/null differ
diff --git a/src/app.js b/src/app.js
index b150310..9e401e2 100644
--- a/src/app.js
+++ b/src/app.js
@@ -16,10 +16,22 @@ const index = require('./routes/index');
const editor = require('./routes/editor');
const legal = require('./routes/legal');
const config = require('./config/config');
+const ssl = config.SSL;
const app = express();
app.disable("x-powered-by");
+if (ssl) {
+ app.use('*', function (req, res, next) {
+ if (req.secure) {
+ next();
+ } else {
+ const target = (req.headers.host.includes(':') ? req.headers.host.split(':')[0] : req.headers.host) + ':' + config.PORT;
+ res.redirect('https://' + target + req.url);
+ }
+ });
+}
+
// Configure views folder
nunjucks.configure(path.join(__dirname, 'views'), {
autoescape: true,
@@ -51,7 +63,6 @@ app.use('/', index);
app.use('/editor', editor);
app.use('/legal', legal);
-
// 404 error
app.all('*', (req, res) => {
res.status(404).render('404.html', {production: config.PRODUCTION, client_versobe: config.CLIENT_VERBOSE});
diff --git a/src/config/config dist.json b/src/config/config dist.json
index fd4ec00..4a547f3 100644
--- a/src/config/config dist.json
+++ b/src/config/config dist.json
@@ -1,21 +1,16 @@
{
"DEBUG" : true,
- "HOST": "localhost",
- "PORT": 5000,
- "DB_TYPE": "mysql",
"PRODUCTION": false,
"CLIENT_VERBOSE": 2,
+ "HOST": "localhost",
+ "PORT": 5000,
+ "DB_URL": "mongodb://host:port/?retryWrites=true&w=majority",
+ "DAYS_TO_DELETE_DOCUMENT": 7,
"DISCORD_WEBHOOK": null,
"SSL": false,
"KEY_FILE_SSL": null,
"CERT_FILE_SSL" : null,
- "DB_CONFIG": {
- "DB_HOST": "db",
- "DB_USERNAME": "root",
- "DB_PASSWORD": "root",
- "DB_DATABASE": "codewe",
- "DB_PORT": "3306"
- },
+ "REDIRECT_PORT": null,
"METRICS": false,
"METRICS_PORT": 8000
}
diff --git a/src/db/MongoDB.js b/src/db/MongoDB.js
index 7497b92..2a86831 100644
--- a/src/db/MongoDB.js
+++ b/src/db/MongoDB.js
@@ -1,4 +1,5 @@
const { MongoClient, ObjectID } = require("mongodb");
+var crypto = require('crypto');
const configs = require('../config/config');
const utils = require('../utils');
@@ -12,9 +13,11 @@ const baseCode = [
class MongoDB {
- constructor (username, password, host, database, port) {
- let url = `mongodb://${username}:${password}@${host}:${port}/?retryWrites=true&w=majority`;
- this.client = new MongoClient(url);
+ constructor (url) {
+ this.client = new MongoClient(url, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true
+ });
}
async connect () {
@@ -22,6 +25,7 @@ class MongoDB {
this.db = await this.client.connect();
this.codeWe = await this.db.db('codewe');
this.documentsCollection = await this.codeWe.collection('codewe');
+ this.usersCollection = await this.codeWe.collection('users');
} catch (err) {
if (configs.DEBUG) {
console.error('Error with db connection');
@@ -30,7 +34,7 @@ class MongoDB {
}
}
- async createDocument () {
+ async createDocument (language) {
let doc = {
content: baseCode,
creationDate: Date.now(),
@@ -38,21 +42,21 @@ class MongoDB {
customDocumentName: '',
documentOwner: '',
editors: [],
- linkEdit: '',
+ documentLink: '',
linkView: '',
- language: '',
+ language: language,
tab: 4
};
try {
let results = (await this.documentsCollection.insertOne(doc));
const documentLink = utils.uuid(results.insertedId.toString());
- this.documentsCollection.updateOne({_id: results.insertedId}, {$set: {documentLink: documentLink}})
+ const linkView = utils.uuid(documentLink);
+ this.documentsCollection.updateOne({_id: results.insertedId}, {$set: {documentLink: documentLink, linkView: linkView}});
return documentLink;
} catch (err) {
if (configs.DEBUG) {
console.error('Error when creating a new document');
}
- throw new Error(err);
}
}
@@ -64,43 +68,70 @@ class MongoDB {
if (configs.DEBUG) {
console.error('Error when fetching document');
}
- throw new Error(err);
+ }
+ }
+
+ async createUser(userId, secretToken) {
+ try {
+ await this.usersCollection.insertOne({
+ userId: userId,
+ secretToken: crypto.createHash('sha256').update(secretToken).digest('base64')
+ });
+ return 'Success';
+ } catch (err) {
+ if (configs.DEBUG) {
+ console.error('Error when creating user');
+ }
+ }
+ }
+
+ async checkUserSecretToken(userId, secretToken) {
+ try {
+ const user = await this.usersCollection.findOne({userId: userId});
+ return (user.secretToken == crypto.createHash('sha256').update(secretToken).digest('base64'));
+ } catch (err) {
+ if (configs.DEBUG) {
+ console.error('Error when checking user secret token');
+ }
+ return 'Error';
}
}
async setLine (documentLink, uuid, content) {
try {
- await this.documentsCollection.updateOne({documentLink: documentLink, 'content.uuid': uuid}, {$set: {'content.$.content': content}});
+ await this.documentsCollection.updateOne({documentLink: documentLink, 'content.uuid': uuid}, {$set: {'content.$.content': content.slice(0, 5000)}});
+ return 'Succes';
} catch (err) {
if (configs.DEBUG) {
console.error('Error when changing line content');
}
- throw new Error(err);
}
}
async newLine (documentLink, previousUuid, uuid, content) {
// Insert a line at the right place
- //TODO is it possible in one operation ?
+ //TODO is it possible in one operation ?
// TODO is it possible to implement with bulk?
try {
let doc = await this.documentsCollection.findOne({documentLink: documentLink});
let index = doc.content.findIndex(line => {
return line.uuid == previousUuid;
});
- this.documentsCollection.updateOne({documentLink: documentLink}, {
- $push: {
- content: {
- $each : [{uuid: uuid, content: content}],
- $position : index + 1
+ if (index) {
+ this.documentsCollection.updateOne({documentLink: documentLink}, {
+ $push: {
+ content: {
+ $each : [{uuid: uuid, content: content.slice(0, 5000)}],
+ $position : index + 1
+ }
}
- }
- });
+ });
+ }
+ return 'Succes';
} catch (err) {
if (configs.DEBUG) {
console.error('Error when adding a new line to document');
}
- throw new Error(err);
}
}
@@ -108,50 +139,100 @@ class MongoDB {
try {
// Delete line at the right place
await this.documentsCollection.updateOne({documentLink: documentLink}, {$pull: {content: {uuid: uuid}}});
+ return 'Succes';
} catch (err) {
if (configs.DEBUG) {
console.error('Error when deleting a line in document');
}
- throw new Error(err);
}
}
+ async changeParam(documentLink, param, newValue) {
+ try {
+ const update = {};
+ update[param] = newValue;
+ await this.documentsCollection.updateOne({documentLink: documentLink}, {$set: update});
+ return 'Succes';
+ } catch (err) {
+ if (configs.DEBUG) {
+ console.error(err);
+ }
+ }
+ }
+
+ async changeCustomName(documentLink, newName) {
+ return this.changeParam(documentLink, 'customDocumentName', newName);
+ }
+
+ async changeTabSize(documentLink, newTabSize) {
+ if (Number.isInteger(newTabSize)) {
+ return this.changeParam(documentLink, 'tab', newTabSize);
+ }
+ }
+
+ async changeLanguage(documentLink, newLanguage) {
+ if (["python"].includes(newLanguage)) {
+ return this.changeParam(documentLink, 'language', newLanguage);
+ }
+ }
+
+ async addNewEditors(documentLink, newEditorsId) {
+ try {
+ await this.documentsCollection.updateOne({documentLink: documentLink}, {$addToSet: {editors: newEditorsId}});
+ return 'Success';
+ } catch (err) {
+ if (configs.DEBUG) {
+ console.error(error);
+ }
+ }
+ }
+
+ async updateLastViewedDate(documentLink) {
+ return this.changeParam(documentLink, 'lastViewedDate', Date.now());
+ }
+
+ async deleteOldDocuments(days) {
+ const oldTimestamp = Date.now() - 1000 * 60 * 60 * 24 * days;
+ return this.documentsCollection.deleteMany({'lastViewedDate': {$lt : oldTimestamp} });
+ }
+
async applyRequests (documentLink, requests) {
// TODO look to use bulk write
+ let success = true;
try {
+ // Avoid too many requests
+ requests = requests.slice(0, 50);
for (let request of requests) {
let requestType = request.type;
let data = request.data;
+ let results = ""
switch (requestType) {
case 'set-line':
- await this.setLine(documentLink, data.id, data.content);
+ results = await this.setLine(documentLink, data.id, data.content);
+ if (!results) success = false;
break;
case 'new-line':
- await this.newLine(documentLink, data.previous, data.id, data.content);
+ results = await this.newLine(documentLink, data.previous, data.id, data.content);
+ if (!results) success = false;
break;
case 'delete-line':
- await this.deleteLine(documentLink, data.id);
+ results = await this.deleteLine(documentLink, data.id);
+ if (!results) success = false;
break;
}
}
+ return success;
} catch (err) {
if (configs.DEBUG) {
console.error('Error when applying requests');
}
- throw new Error(err);
}
}
}
function getDB () {
- db = new MongoDB(
- configs.DB_CONFIG.DB_USERNAME,
- configs.DB_CONFIG.DB_PASSWORD,
- configs.DB_CONFIG.DB_HOST,
- configs.DB_CONFIG.DB_DATABASE,
- configs.DB_CONFIG.DB_PORT
- );
+ db = new MongoDB(configs.DB_URL);
db.connect();
return db;
}
diff --git a/src/publics/js/dev/page/editor/editable.js b/src/publics/js/dev/page/editor/editable.js
index 71ed89f..36778ea 100644
--- a/src/publics/js/dev/page/editor/editable.js
+++ b/src/publics/js/dev/page/editor/editable.js
@@ -137,16 +137,18 @@ export default class Editable{
const line = getNodeFromAttribute('uuid');
- if(!line) return;
+ if(!line){
+ e.preventDefault();
+ temporaryCardAlert('Editor', 'Sorry, your action has been canceled because you are not on any line.', 5000);
+ return;
+ }
if(!anchorParent.hasAttribute('uuid')
|| !focusParent.hasAttribute('uuid')
|| ((Caret.getBeginPosition(line) === 0
|| Caret.getEndPosition(line) === 0)
) && anchorParent !== focusParent){
- e.preventDefault();
- temporaryCardAlert('Override', 'Sorry, you can\'t override the first char of a line', 5000);
- return;
+ Caret.setRangeStart(line, 1);
}
switch (e.keyCode) {
@@ -155,6 +157,11 @@ export default class Editable{
this.insertTab();
break;
case 13: // enter
+ if(e.shiftKey){
+ temporaryCardAlert('Shift+Enter', 'Please just use Enter to avoid any bugs.', 5000);
+ e.preventDefault();
+ return;
+ }
if(this.keepSpace){
Debug.debug('Prevent action when trying to add new line (key is probably maintain).');
e.preventDefault();
diff --git a/src/publics/js/dev/utils/caret.js b/src/publics/js/dev/utils/caret.js
index 4799907..534a543 100644
--- a/src/publics/js/dev/utils/caret.js
+++ b/src/publics/js/dev/utils/caret.js
@@ -67,6 +67,22 @@ export default class Caret{
}
}
+
+ /**
+ * Set the start range of the user caret on specified position in element or children
+ * @param {HTMLElement|Node} element
+ * @param {number} position
+ */
+ static setRangeStart(element, position) {
+ if (position >= 0) {
+ let selection = document.getSelection();
+
+ let range = Caret.createRange(element, {count: position});
+ selection.getRangeAt(0).setStart(range.endContainer, range.endOffset);
+
+ }
+ }
+
/**
* Get the position of the end of the user selection
* Based on https://stackoverflow.com/a/4812022/11247647
diff --git a/src/routes/editor.js b/src/routes/editor.js
index 4f22964..1431290 100644
--- a/src/routes/editor.js
+++ b/src/routes/editor.js
@@ -41,10 +41,11 @@ const router = express.Router();
router.get('/:docId', async (req, res, next) => {
try {
let document = (await db.getDocument(req.params.docId));
- if (document) {
+ if (document) { // && (document.public || (document.editors.includes(req.body.userId) && db.checkUserSecretToken(req.body.userId, secretkey)))
document.document_id = req.params.docId;
res.render('editor.html', {document: document, production: config.PRODUCTION, client_versobe: config.CLIENT_VERBOSE});
}
+ // else if (!document.public)
else {
res.status(404).render('404.html', {production: config.PRODUCTION, client_versobe: config.CLIENT_VERBOSE})
}
diff --git a/src/routes/index.js b/src/routes/index.js
index 9d0ccb7..da1c297 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -52,8 +52,14 @@ router.get('/', (req, res) => {
*/
router.post('/create_document', async (req, res, next) => {
try {
- let documentId = await db.createDocument();
- res.redirect(`/editor/${documentId}`);
+ //const language = req.body.language
+ let documentId = await db.createDocument('python');
+ if (documentId) {
+ res.redirect(`/editor/${documentId}`);
+ }
+ else {
+ res.status(500);
+ }
} catch (err) {
next(err);
}
diff --git a/src/routes/legal.js b/src/routes/legal.js
index 60bab8f..0da9949 100644
--- a/src/routes/legal.js
+++ b/src/routes/legal.js
@@ -8,7 +8,7 @@
*
*/
- /**
+/**
* express module
* @const
*/
@@ -22,14 +22,28 @@ const config = require('../config/config');
const router = express.Router();
/**
-* Route serving termsofservice
-* @name get/termsofservice
-* @function
-* @memberof modules:routes/legal
-* @inner
-*/
+ * Route serving termsofservice
+ * @name get/termsofservice
+ * @function
+ * @memberof modules:routes/legal
+ * @inner
+ */
router.get(['/tos', '/tac', '/termsofservice', '/terms-of-service'], (req, res) => {
- res.render('legal/tos.html', {production: config.PRODUCTION, client_versobe: config.CLIENT_VERBOSE});
+ res.render('legal/tos.html', {
+ production: config.PRODUCTION,
+ client_versobe: config.CLIENT_VERBOSE
+ });
+});
+
+/**
+ * Route serving termsofservice PDF
+ * @name get/termsofservice
+ * @function
+ * @memberof modules:routes/legal
+ * @inner
+ */
+router.get(['/tos-pdf', '/tac-pdf', '/termsofservice-pdf', '/terms-of-service-pdf'], (req, res) => {
+ res.download("./views/legal/tos-pdf.pdf")
});
/**
@@ -59,7 +73,21 @@ router.get([
* @inner
*/
router.get(['/privacy', '/privacy-policy', '/privacypolicy'], (req, res) => {
- res.render('legal/privacy.html', {production: config.PRODUCTION, client_versobe: config.CLIENT_VERBOSE});
+ res.render('legal/privacy.html', {
+ production: config.PRODUCTION,
+ client_versobe: config.CLIENT_VERBOSE
+ });
+});
+
+/**
+ * Route serving privacy policy PDF
+ * @name get/privacy
+ * @function
+ * @memberof modules:routes/legal
+ * @inner
+ */
+router.get(['/privacy-pdf', '/privacy-policy-pdf', '/privacypolicy-pdf'], (req, res) => {
+ res.download("./views/legal/privacy-pdf.pdf")
});
/**
@@ -70,7 +98,10 @@ router.get(['/privacy', '/privacy-policy', '/privacypolicy'], (req, res) => {
* @inner
*/
router.get(['/privacy/archive/:date', '/privacy-policy/archive/:date', '/privacypolicy/archive/:date'], (req, res) => {
- res.render(`legal/archive/privacy-${req.params.date}.html`, {production: config.PRODUCTION, client_versobe: config.CLIENT_VERBOSE});
+ res.render(`legal/archive/privacy-${req.params.date}.html`, {
+ production: config.PRODUCTION,
+ client_versobe: config.CLIENT_VERBOSE
+ });
});
/**
@@ -80,8 +111,11 @@ router.get(['/privacy/archive/:date', '/privacy-policy/archive/:date', '/privacy
* @memberof modules:routes/legal
* @inner
*/
-router.get('/license', (req, res) => {
- res.render('legal/licence.html', {production: config.PRODUCTION, client_versobe: config.CLIENT_VERBOSE});
+router.get('/licence', (req, res) => {
+ res.render('legal/licence.html', {
+ production: config.PRODUCTION,
+ client_versobe: config.CLIENT_VERBOSE
+ });
});
module.exports = router;
diff --git a/src/server.js b/src/server.js
index d3b1541..9042037 100644
--- a/src/server.js
+++ b/src/server.js
@@ -3,7 +3,7 @@
* @author Alexandre Dewilde
* @date 15/11/2020
* @version 1.0.0
- *
+ *
*/
const fs = require('fs');
const path = require('path');
@@ -14,8 +14,9 @@ const host = configs.HOST;
const port = configs.PORT;
const DEBUG = configs.DEBUG;
const ssl = configs.SSL;
+const metrics = configs.METRICS;
const sslKeyPath = configs.KEY_FILE_SSL;
-const sslCertPath = configs.CERT_FILE_SSL;
+const sslCertPath = configs.CERT_FILE_SSL;
const options = ssl ? {
key: fs.readFileSync(sslKeyPath.startsWith('/') ? sslKeyPath : path.join(__dirname, sslKeyPath), 'utf8'),
@@ -27,9 +28,11 @@ const config = require('./config/config');
const http = ssl ? require('https') : require('http');
const server = http.createServer(options, app);
-if (configs.METRICS) {
- const {metricsApp} = require('./metricsApp');
- var metricsServer = http.createServer(metricsApp);
+
+if (ssl && configs.REDIRECT_PORT !== null) {
+ require('http').createServer(app).listen(configs.REDIRECT_PORT, host, () => {
+ console.log(`http requests from ${configs.REDIRECT_PORT} are redirected to https on ${configs.PORT}`)
+ })
}
// config websockets
@@ -39,9 +42,11 @@ require('./socket/socket')(wss);
server.listen(port, host, () => {
- console.log('Server Started!');
+ console.log(`Server Started on ${host}:${port}`);
});
-if (config.METRICS) {
+if (metrics) {
+ const {metricsApp} = require('./metricsApp');
+ const metricsServer = require('http').createServer(metricsApp);
metricsServer.listen(config.METRICS_PORT);
}
diff --git a/src/socket/socket.js b/src/socket/socket.js
index 27568b8..8ae3ac0 100644
--- a/src/socket/socket.js
+++ b/src/socket/socket.js
@@ -32,6 +32,19 @@ module.exports = function (wss) {
socket.isAlive = true;
const uuid = utils.uuid(Math.random().toString());
+ const broadcastRoomExceptSender = (data, event, valueEvent) => {
+ Object.entries(rooms[data.room]).forEach(([, sock]) => {
+ if(sock === socket) {
+ const backValue = {
+ code: "OK",
+ time: Date.now(),
+ };
+ backValue[event] = valueEvent;
+ sock.send(JSON.stringify(backValue));
+ }else sock.send(JSON.stringify(data));
+ });
+ }
+
const leave = room => {
if(! rooms[room][uuid]) return;
// if the one exiting is the last one, destroy the room
@@ -48,18 +61,17 @@ module.exports = function (wss) {
switch (data.event) {
case 'update':
try {
- Object.entries(rooms[data.room]).forEach(([, sock]) => {
- if(sock === socket) {
- sock.send(JSON.stringify({
- code: "OK",
- uuid: data['uuid'],
- time: Date.now(),
- }));
- }else sock.send(JSON.stringify(data));
- });
- db.applyRequests(data.room, data.data);
+ // let document = db.getDocument(data.room);
+ // if (document.public || (document.editors.include(userId) and db.checkUsersSecretToken(userId, secretToken)))
+ broadcastRoomExceptSender(data, 'uuid', data.uuid);
+ const succesUpdatingDate = db.updateLastViewedDate(data.room);
+ const succesUpdate = db.applyRequests(data.room, data.data);
+ // /!\ Bad event
+ // if (!succesUpdatingDate || !succesUpdate) socket.send(JSON.stringify({event: 'update', success: false}));
} catch (err) {
- throw new Error(err);
+ if (config.DEBUG) {
+ console.error(err);
+ }
}
break;
case 'join':
@@ -71,6 +83,40 @@ module.exports = function (wss) {
rooms[data.room][uuid] = socket;
}
break;
+
+ case 'language':
+ try {
+ broadcastRoomExceptSender(data, 'language', data.language);
+ const success = db.changeLanguage(data.room, data.language);
+ if (!success) socket.send(JSON.stringify({event: 'language', success: false}));
+ } catch (err) {
+ if (config.DEBUG) {
+ console.error(err);
+ }
+ }
+ break;
+ case 'changeTabSize':
+ try {
+ broadcastRoomExceptSender(data, 'tabSize', data.tabSize);
+ const success = db.changeTabSize(data.room, data.tabSize);
+ if (!success) socket.send(JSON.stringify({event: 'changeTabSize', success: false}));
+ } catch (err) {
+ if (config.DEBUG) {
+ console.error(err);
+ }
+ }
+ break;
+ case 'changeCustomName':
+ try {
+ broadcastRoomExceptSender(data, 'customName', data.customName);
+ let success = db.changeCustomName(data.customName);
+ if (!success) socket.send(JSON.stringify({event: 'changeCustomName', success: false}));
+ } catch (err) {
+ if (config.DEBUG) {
+ console.error(err);
+ }
+ }
+ break;
case 'ping':
socket.send('pong');
break;
@@ -79,7 +125,7 @@ module.exports = function (wss) {
break;
case 'report': // Send issue to hook
if (hook) {
- hook.warn('Report', data.data.content);
+ hook.warn('Report', data.data.content.slice(0, 5000));
}
}
@@ -117,5 +163,10 @@ module.exports = function (wss) {
prom.connected.set(wss.clients.size);
}, 5000);
+ // delete old documents
+ setInterval(() => {
+ db.deleteOldDocuments(config.DAYS_TO_DELETE_DOCUMENT);
+ }, 1000 * 60 * 60 * 24);
+
}
diff --git a/src/views/component/footer.html b/src/views/component/footer.html
index 38725c2..9c3629c 100644
--- a/src/views/component/footer.html
+++ b/src/views/component/footer.html
@@ -8,7 +8,7 @@
Support
Report issue
- Copyright © 2020 Dewilde Alexandre, Dubois Brieuc and Fischer Nicolas
+ Copyright © 2020 Dewilde Alexandre, Dubois Brieuc and Technicguy Theo