diff --git a/controllers/serveController.js b/controllers/serveController.js index a836c67b..b3dfb700 100644 --- a/controllers/serveController.js +++ b/controllers/serveController.js @@ -195,4 +195,7 @@ module.exports = { }); return deferred; }, + getAllClaims (claimName) { + return getAllFreePublicClaims(claimName); + }, }; diff --git a/controllers/showController.js b/controllers/showController.js deleted file mode 100644 index c5fa80a8..00000000 --- a/controllers/showController.js +++ /dev/null @@ -1,7 +0,0 @@ -const getAllFreePublicClaims = require('../helpers/functions/getAllFreePublicClaims.js'); - -module.exports = { - getAllClaims (claimName) { - return getAllFreePublicClaims(claimName); - }, -}; diff --git a/helpers/libraries/errorHandlers.js b/helpers/libraries/errorHandlers.js index e07632a5..b7ea5a37 100644 --- a/helpers/libraries/errorHandlers.js +++ b/helpers/libraries/errorHandlers.js @@ -3,16 +3,19 @@ const { postToStats } = require('../../controllers/statsController.js'); module.exports = { handleRequestError (action, originalUrl, ip, error, res) { - logger.error('Request Error >>', error.message); + logger.error('Request Error >>', error); if (error.response) { postToStats(action, originalUrl, ip, error.response.data.error.messsage); res.status(error.response.status).send(error.response.data.error.message); } else if (error.code === 'ECONNREFUSED') { postToStats(action, originalUrl, ip, 'Connection refused. The daemon may not be running.'); res.status(503).send('Connection refused. The daemon may not be running.'); - } else { + } else if (error.message) { postToStats(action, originalUrl, ip, error); res.status(400).send(error.message); + } else { + postToStats(action, originalUrl, ip, error); + res.status(400).send(error); } }, handlePublishError (error) { diff --git a/public/assets/css/allStyle.css b/public/assets/css/allStyle.css index 1feb4c45..ee0991fa 100644 --- a/public/assets/css/allStyle.css +++ b/public/assets/css/allStyle.css @@ -14,19 +14,17 @@ .main { float: left; - width: 75%; - margin-right: 1%; - padding-right: 1%; - border-right: 1px lightgrey solid; - margin-bottom: 5px; + width: 65%; + } .sidebar { - float: left; - width: 22%; + float: right; + width: 33%; } footer { + display: inline-block; width: 100%; margin-bottom: 2px; padding-bottom: 2px; @@ -43,7 +41,6 @@ footer { .panel { overflow: auto; word-wrap: break-word; - margin-bottom: 1em; } .col-left, .col-right { @@ -64,7 +61,8 @@ footer { /* text */ -a { +a, a:visited { + color: blue; text-decoration: none; } h1 { @@ -73,11 +71,16 @@ h1 { h2 { font-size: medium; + margin-top: 1em; border-top: 1px #999 solid; background-color: lightgray; padding: 6px; } +.subheader { + margin-top: 0px; +} + h4 { padding: 3px; } @@ -105,6 +108,10 @@ table { float: right; } +.wrap-words { + word-wrap: break-word; +} + @media (max-width: 1250px) { .wrapper { diff --git a/public/assets/css/componentStyle.css b/public/assets/css/componentStyle.css index ef8a795b..0937c2ce 100644 --- a/public/assets/css/componentStyle.css +++ b/public/assets/css/componentStyle.css @@ -12,12 +12,53 @@ margin: 2px 5px 2px 5px; } +/* show routes */ +.show-asset { + width: 100%; + margin-bottom: 1em; + margin-top: 1em; +} + +.show-asset-lite { + margin: 0px; +} + +.panel.links { + font-size: small; +} + +input.link { + width: 80%; +} + +button.copy-button { + padding: 4px; + float: right; +} + +.metadata-table { + font-size: small; + border-collapse: collapse; + margin-bottom: 1em; +} + +.metadata-row { + border-bottom: 1px solid lightgrey; + margin: 2px; +} + +.left-column { + width: 30%; + font-weight: bold; + vertical-align: top; +} + /* learn more */ .learn-more { text-align: center; margin-top: 2px; padding-top: 2px; - border-top: 1px lightgrey solid; + border-top: 1px solid lightgrey; } /* examples */ @@ -30,16 +71,17 @@ .example-image, .example-code { float: left; - margin: 5px 15px 5px 0px; + margin: 2%; } .example-image { - width: 20%; + width: 21%; } .example-code { - padding: 2em; - width: 60%; + float: right; + padding: 4%; + width: 62%; background-color: lightgrey; font-family: monospace; color: #666; @@ -82,6 +124,7 @@ #image-preview { display: none; + margin-bottom: 1em; } /* meme */ @@ -115,6 +158,10 @@ canvas { font-size: small; } + .show-asset-lite { + width: 100%; + } + } @media (max-width: 475px) { diff --git a/public/assets/js/claimPublish.js b/public/assets/js/claimPublish.js index e43424aa..ef657746 100644 --- a/public/assets/js/claimPublish.js +++ b/public/assets/js/claimPublish.js @@ -3,81 +3,6 @@ var socket = io(); var uploader = new SocketIOFileUpload(socket); var stagedFiles = null; -/* helper functions */ -// create a progress animation -function createProgressBar(element, size){ - var x = 1; - var adder = 1; - function addOne(){ - var bars = '

|'; - for (var i = 0; i < x; i++){ bars += ' | '; } - bars += '

'; - element.innerHTML = bars; - if (x === size){ - adder = -1; - } else if ( x === 0){ - adder = 1; - } - x += adder; - }; - setInterval(addOne, 300); -} -// preview file and stage the image for upload -function previewAndStageFile(selectedFile){ - var preview = document.getElementById('image-preview'); - var dropzone = document.getElementById('drop-zone'); - var previewReader = new FileReader(); - var nameInput = document.getElementById('publish-name'); - - preview.style.display = 'block'; - dropzone.style.display = 'none'; - - previewReader.onloadend = function () { - preview.src = previewReader.result; - }; - - if (selectedFile) { - previewReader.readAsDataURL(selectedFile); // reads the data and sets the img src - if (nameInput.value === "") { - nameInput.value = selectedFile.name.substring(0, selectedFile.name.indexOf('.')); - } - stagedFiles = [selectedFile]; // stores the selected file for upload - } else { - preview.src = ''; - } -} -// update the publish status -function updatePublishStatus(msg){ - document.getElementById('publish-status').innerHTML = msg; -} -// process the drop-zone drop -function drop_handler(ev) { - ev.preventDefault(); - // if dropped items aren't files, reject them - var dt = ev.dataTransfer; - if (dt.items) { - if (dt.items[0].kind == 'file') { - var droppedFile = dt.items[0].getAsFile(); - previewAndStageFile(droppedFile); - } - } -} -// prevent the browser's default drag behavior -function dragover_handler(ev) { - ev.preventDefault(); -} -// remove all of the drag data -function dragend_handler(ev) { - var dt = ev.dataTransfer; - if (dt.items) { - for (var i = 0; i < dt.items.length; i++) { - dt.items.remove(i); - } - } else { - ev.dataTransfer.clearData(); - } -} - /* configure the submit button */ document.getElementById('publish-submit').addEventListener('click', function(event){ event.preventDefault(); @@ -132,6 +57,12 @@ document.getElementById('publish-submit').addEventListener('click', function(eve }) /* socketio-file-upload listeners */ +uploader.maxFileSize = 5000000; +uploader.addEventListener("error", function(data){ + if (data.code === 1) { + alert("Sorry, uploading is limitted to 5 megabytes."); + } +}); uploader.addEventListener('start', function(event){ var name = document.getElementById('publish-name').value; var license = document.getElementById('publish-license').value; @@ -161,33 +92,16 @@ socket.on('publish-status', function(msg){ updatePublishStatus(msg); }); socket.on('publish-failure', function(msg){ - document.getElementById('publish-active-area').innerHTML = '

' + JSON.stringify(msg) + '

--(✖╭╮✖)→

For help, post the above error text in the #speech channel on the lbry slack'; + document.getElementById('publish-active-area').innerHTML = '

--(✖╭╮✖)→

' + JSON.stringify(msg) + '

For help, post the above error text in the #speech channel on the lbry slack'; }); socket.on('publish-complete', function(msg){ var publishResults; - var directUrl = '/' + msg.name + '/' + msg.result.claim_id; + var showUrl = '/show/' + msg.name + '/' + msg.result.claim_id; // build new publish area - publishResults = '

Your publish is complete! View it here:

'; - publishResults += '

spee.ch' + directUrl + '

'; - publishResults += '

'; - publishResults += '

View the transaction details

'; - publishResults += '

'; + publishResults = '

Your publish is complete! You are being redirected to it now.

'; + publishResults += '

If you do not get redirected, click here.

'; // update publish area document.getElementById('publish-active-area').innerHTML = publishResults; - // update the link holder - document.getElementById('direct-link-holder').innerText = 'https://spee.ch' + directUrl; - // enable copy-to-clipboard - var copyBtn = document.querySelector('.copy-button'); - copyBtn.addEventListener('click', function(event) { - // select the text - var text = document.getElementById('direct-link-holder'); - text.select(); - try { - var successful = document.execCommand('copy'); - var msg = successful ? 'successful' : 'unsuccessful'; - } catch (err) { - alert('Oops, unable to copy'); - } - }); + window.location.href = showUrl; }); \ No newline at end of file diff --git a/public/assets/js/generalFunctions.js b/public/assets/js/generalFunctions.js index 2151357b..521e26fc 100644 --- a/public/assets/js/generalFunctions.js +++ b/public/assets/js/generalFunctions.js @@ -15,4 +15,43 @@ function toggleSection(event){ masterElement.innerText = "[open]"; masterElement.dataset.open = "false"; } +} + +// create a progress animation +function createProgressBar(element, size){ + var x = 1; + var adder = 1; + function addOne(){ + var bars = '

|'; + for (var i = 0; i < x; i++){ bars += ' | '; } + bars += '

'; + element.innerHTML = bars; + if (x === size){ + adder = -1; + } else if ( x === 0){ + adder = 1; + } + x += adder; + }; + setInterval(addOne, 300); +} + +function dataURItoBlob(dataURI) { + // convert base64/URLEncoded data component to raw binary data held in a string + var byteString; + if (dataURI.split(',')[0].indexOf('base64') >= 0) + byteString = atob(dataURI.split(',')[1]); + else + byteString = unescape(dataURI.split(',')[1]); + + // separate out the mime component + var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + + // write the bytes of the string to a typed array + var ia = new Uint8Array(byteString.length); + for (var i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + return new Blob([ia], {type:mimeString}); } \ No newline at end of file diff --git a/public/assets/js/memePublish.js b/public/assets/js/memePublish.js index aeb249d9..67c13597 100644 --- a/public/assets/js/memePublish.js +++ b/public/assets/js/memePublish.js @@ -7,97 +7,6 @@ var license = 'Creative Commons'; var nsfw = false; var nameInput = document.getElementById("publish-name"); -function dataURItoBlob(dataURI) { - // convert base64/URLEncoded data component to raw binary data held in a string - var byteString; - if (dataURI.split(',')[0].indexOf('base64') >= 0) - byteString = atob(dataURI.split(',')[1]); - else - byteString = unescape(dataURI.split(',')[1]); - - // separate out the mime component - var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; - - // write the bytes of the string to a typed array - var ia = new Uint8Array(byteString.length); - for (var i = 0; i < byteString.length; i++) { - ia[i] = byteString.charCodeAt(i); - } - - return new Blob([ia], {type:mimeString}); -} - -function startPublish() { - //download the image - var dataUrl = canvas.toDataURL('image/jpeg'); // canvas defined in memeDraw.js - var blob = dataURItoBlob(dataUrl) - var fileName = nameInput.value + ".jpg"; //note: need to dynamically grab type - var file = new File([blob], fileName, {type: 'image/jpeg', lastModified: Date.now()}); - stageAndPublish(file); -}; - -/* helper functions */ -// create a progress animation -function createProgressBar(element, size){ - var x = 1; - var adder = 1; - function addOne(){ - var bars = '

|'; - for (var i = 0; i < x; i++){ bars += ' | '; } - bars += '

'; - element.innerHTML = bars; - if (x === size){ - adder = -1; - } else if ( x === 0){ - adder = 1; - } - x += adder; - }; - setInterval(addOne, 300); -} - -function stageAndPublish(file) { - var name = nameInput.value; - var invalidCharacters = /[^A-Za-z0-9,-]/.exec(name); - // validate 'name' - if (invalidCharacters) { - alert(invalidCharacters + ' is not allowed. A-Z, a-z, 0-9, "_" and "-" only.'); - return; - } else if (name.length < 1) { - alert("You must enter a name for your claim"); - return; - } - // stage files - stagedFiles = [file]; // stores the selected file for - // make sure a file was selected - if (stagedFiles) { - // make sure only 1 file was selected - if (stagedFiles.length < 1) { - alert("A file is needed"); - return; - } - // make sure the content type is acceptable - switch (stagedFiles[0].type) { - case "image/png": - case "image/jpeg": - case "image/gif": - case "video/mp4": - uploader.submitFiles(stagedFiles); - break; - default: - alert("Only .png, .jpeg, .gif, and .mp4 files are currently supported"); - break; - } - } else { - alert("Please select a file"); - } -} - -// update the publish status -function updatePublishStatus(msg){ - document.getElementById('publish-status').innerHTML = msg; -} - /* socketio-file-upload listeners */ uploader.addEventListener('start', function(event){ event.file.meta.name = nameInput.value; diff --git a/public/assets/js/publishFunctions.js b/public/assets/js/publishFunctions.js new file mode 100644 index 00000000..1c02be73 --- /dev/null +++ b/public/assets/js/publishFunctions.js @@ -0,0 +1,107 @@ +// update the publish status +function updatePublishStatus(msg){ + document.getElementById('publish-status').innerHTML = msg; +} + +/* regular publish helper functions */ + +function previewAndStageFile(selectedFile){ + var preview = document.getElementById('image-preview'); + var dropzone = document.getElementById('drop-zone'); + var previewReader = new FileReader(); + var nameInput = document.getElementById('publish-name'); + + preview.style.display = 'block'; + dropzone.style.display = 'none'; + + previewReader.onloadend = function () { + preview.src = previewReader.result; + }; + + if (selectedFile) { + previewReader.readAsDataURL(selectedFile); // reads the data and sets the img src + if (nameInput.value === "") { + nameInput.value = selectedFile.name.substring(0, selectedFile.name.indexOf('.')); + } + stagedFiles = [selectedFile]; // stores the selected file for upload + } else { + preview.src = ''; + } +} + +/* drop zone function s*/ + +function drop_handler(ev) { + ev.preventDefault(); + // if dropped items aren't files, reject them + var dt = ev.dataTransfer; + if (dt.items) { + if (dt.items[0].kind == 'file') { + var droppedFile = dt.items[0].getAsFile(); + previewAndStageFile(droppedFile); + } + } +} + +function dragover_handler(ev) { + ev.preventDefault(); +} + +function dragend_handler(ev) { + var dt = ev.dataTransfer; + if (dt.items) { + for (var i = 0; i < dt.items.length; i++) { + dt.items.remove(i); + } + } else { + ev.dataTransfer.clearData(); + } +} + +/* meme publish functions */ + +function startPublish() { + //download the image + var dataUrl = canvas.toDataURL('image/jpeg'); // canvas defined in memeDraw.js + var blob = dataURItoBlob(dataUrl) + var fileName = nameInput.value + ".jpg"; //note: need to dynamically grab type + var file = new File([blob], fileName, {type: 'image/jpeg', lastModified: Date.now()}); + stageAndPublish(file); +}; + +function stageAndPublish(file) { + var name = nameInput.value; + var invalidCharacters = /[^A-Za-z0-9,-]/.exec(name); + // validate 'name' + if (invalidCharacters) { + alert(invalidCharacters + ' is not allowed. A-Z, a-z, 0-9, "_" and "-" only.'); + return; + } else if (name.length < 1) { + alert("You must enter a name for your claim"); + return; + } + // stage files + stagedFiles = [file]; // stores the selected file for + // make sure a file was selected + if (stagedFiles) { + // make sure only 1 file was selected + if (stagedFiles.length < 1) { + alert("A file is needed"); + return; + } + // make sure the content type is acceptable + switch (stagedFiles[0].type) { + case "image/png": + case "image/jpeg": + case "image/gif": + case "video/mp4": + uploader.submitFiles(stagedFiles); + break; + default: + alert("Only .png, .jpeg, .gif, and .mp4 files are currently supported"); + break; + } + } else { + alert("Please select a file"); + } +} \ No newline at end of file diff --git a/routes/api-routes.js b/routes/api-routes.js index a49c5e58..a0065a58 100644 --- a/routes/api-routes.js +++ b/routes/api-routes.js @@ -9,11 +9,9 @@ const { postToStats, sendGoogleAnalytics } = require('../controllers/statsContro module.exports = app => { // route to run a claim_list request on the daemon - app.get('/api/claim_list/:name', ({ ip, originalUrl, params }, res) => { + app.get('/api/claim_list/:name', ({ headers, ip, originalUrl, params }, res) => { // google analytics - sendGoogleAnalytics('serve', ip, originalUrl); - // log - logger.verbose(`GET request on ${originalUrl} from ${ip}`); + sendGoogleAnalytics('serve', headers, ip, originalUrl); // serve the content lbryApi .getClaimsList(params.name) @@ -27,8 +25,6 @@ module.exports = app => { }); // route to check whether spee.ch has published to a claim app.get('/api/isClaimAvailable/:name', ({ ip, originalUrl, params }, res) => { - // log - logger.verbose(`GET request on ${originalUrl} from ${ip}`); // send response publishController .checkNameAvailability(params.name) @@ -45,11 +41,9 @@ module.exports = app => { }); }); // route to run a resolve request on the daemon - app.get('/api/resolve/:uri', ({ ip, originalUrl, params }, res) => { + app.get('/api/resolve/:uri', ({ headers, ip, originalUrl, params }, res) => { // google analytics - sendGoogleAnalytics('serve', ip, originalUrl); - // log - logger.verbose(`GET request on ${originalUrl} from ${ip}`); + sendGoogleAnalytics('serve', headers, ip, originalUrl); // serve content lbryApi .resolveUri(params.uri) @@ -62,18 +56,22 @@ module.exports = app => { }); }); // route to run a publish request on the daemon - app.post('/api/publish', multipartMiddleware, ({ body, files, ip, originalUrl }, res) => { + app.post('/api/publish', multipartMiddleware, ({ body, files, headers, ip, originalUrl }, res) => { // google analytics - sendGoogleAnalytics('publish', ip, originalUrl); - // log - logger.verbose(`POST request on ${originalUrl} from ${ip}`); + sendGoogleAnalytics('publish', headers, ip, originalUrl); // validate that a file was provided const file = files.speech || files.null; + logger.debug(file); if (!file) { postToStats('publish', originalUrl, ip, 'Error: file'); res.status(400).send('Error: No file was submitted or the key used was incorrect. Files posted through this route must use a key of "speech" or null'); return; } + // check if the size is 5 mb or less + if (file.size > 5000000) { + res.status(400).send('Error: only files of 5 megabytes or less are allowed'); + return; + } // validate name const name = body.name || file.name.substring(0, file.name.indexOf('.')); const invalidCharacters = /[^A-Za-z0-9,-]/.exec(name); diff --git a/routes/home-routes.js b/routes/home-routes.js index c1ace31b..81fd3219 100644 --- a/routes/home-routes.js +++ b/routes/home-routes.js @@ -4,15 +4,12 @@ const { postToStats } = require('../controllers/statsController.js'); module.exports = app => { // route for the home page app.get('/', ({ headers, ip, originalUrl }, res) => { - // logging - logger.verbose(`GET request on ${originalUrl} from ${ip}`); // send response res.status(200).render('index'); }); // a catch-all route if someone visits a page that does not exist app.use('*', ({ originalUrl, ip }, res) => { - // logging - logger.error(`Get request on ${originalUrl} from ${ip} which was a 404`); + logger.error(`404 on ${originalUrl}`); // post to stats postToStats('show', originalUrl, ip, 'Error: 404'); // send response diff --git a/routes/serve-routes.js b/routes/serve-routes.js index fee6b68f..c53a2627 100644 --- a/routes/serve-routes.js +++ b/routes/serve-routes.js @@ -1,19 +1,18 @@ -const errorHandlers = require('../helpers/libraries/errorHandlers.js'); -const serveController = require('../controllers/serveController.js'); const logger = require('winston'); +const { getClaimByClaimId, getClaimByName } = require('../controllers/serveController.js'); const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); +const errorHandlers = require('../helpers/libraries/errorHandlers.js'); function serveFile ({ fileName, fileType, filePath }, res) { logger.info(`serving file ${fileName}`); // set default options - const options = { + let options = { headers: { 'X-Content-Type-Options': 'nosniff', 'Content-Type' : fileType, }, }; // adjust default options as needed - // eslint-disable-next-line camelcase switch (fileType) { case 'image/jpeg': break; @@ -24,7 +23,7 @@ function serveFile ({ fileName, fileType, filePath }, res) { case 'video/mp4': break; default: - logger.warn('sending unknown file type as .jpeg'); + logger.warn('sending file with unknown type as .jpeg'); options['headers']['Content-Type'] = 'image/jpeg'; break; } @@ -35,44 +34,54 @@ function serveFile ({ fileName, fileType, filePath }, res) { function sendAnalyticsAndLog (headers, ip, originalUrl) { // google analytics sendGoogleAnalytics('serve', headers, ip, originalUrl); - // logging - logger.verbose(`GET request on ${originalUrl} from ${ip}`); } module.exports = (app) => { - // route to fetch one free public claim + // route to serve a specific asset app.get('/:name/:claim_id', ({ headers, ip, originalUrl, params }, res) => { sendAnalyticsAndLog(headers, ip, originalUrl); // begin image-serve processes - serveController - .getClaimByClaimId(params.name, params.claim_id) + getClaimByClaimId(params.name, params.claim_id) .then(fileInfo => { // check to make sure a file was found if (!fileInfo) { res.status(307).render('noClaims'); return; } - postToStats('serve', originalUrl, ip, 'success'); - serveFile(fileInfo, res); + // serve the file or the show route + const mimetypes = headers['accept'].split(','); + if (mimetypes.includes('text/html')) { + postToStats('show', originalUrl, ip, 'success'); + res.status(200).render('showLite', { fileInfo }); + } else { + postToStats('serve', originalUrl, ip, 'success'); + serveFile(fileInfo, res); + } }) .catch(error => { errorHandlers.handleRequestError('serve', originalUrl, ip, error, res); }); }); - // route to fetch one free public claim + // route to serve the winning claim app.get('/:name', ({ headers, ip, originalUrl, params }, res) => { sendAnalyticsAndLog(headers, ip, originalUrl); // begin image-serve processes - serveController - .getClaimByName(params.name) + getClaimByName(params.name) .then(fileInfo => { // check to make sure a file was found if (!fileInfo) { res.status(307).render('noClaims'); return; } - postToStats('serve', originalUrl, ip, 'success'); - serveFile(fileInfo, res); + // serve the file or the show route + const mimetypes = headers['accept'].split(','); + if (mimetypes.includes('text/html')) { + postToStats('show', originalUrl, ip, 'success'); + res.status(200).render('showLite', { fileInfo }); + } else { + postToStats('serve', originalUrl, ip, 'success'); + serveFile(fileInfo, res); + } }) .catch(error => { errorHandlers.handleRequestError('serve', originalUrl, ip, error, res); diff --git a/routes/show-routes.js b/routes/show-routes.js index 6a791d43..d662d6c9 100644 --- a/routes/show-routes.js +++ b/routes/show-routes.js @@ -1,18 +1,15 @@ -const logger = require('winston'); const errorHandlers = require('../helpers/libraries/errorHandlers.js'); -const { getAllClaims } = require('../controllers/showController.js'); +const { getClaimByClaimId, getClaimByName, getAllClaims } = require('../controllers/serveController.js'); const { getStatsSummary, postToStats } = require('../controllers/statsController.js'); module.exports = (app) => { // route to show 'about' page for spee.ch app.get('/about', ({ ip, originalUrl }, res) => { - logger.verbose(`POST request on ${originalUrl} from ${ip}`); // get and render the content res.status(200).render('about'); }); // route to show the meme-fodder meme maker app.get('/meme-fodder/play', ({ ip, originalUrl }, res) => { - logger.verbose(`POST request on ${originalUrl} from ${ip}`); // get and render the content getAllClaims('meme-fodder') .then(orderedFreePublicClaims => { @@ -25,7 +22,6 @@ module.exports = (app) => { }); // route to show statistics for spee.ch app.get('/stats', ({ ip, originalUrl }, res) => { - logger.verbose(`POST request on ${originalUrl} from ${ip}`); // get and render the content getStatsSummary() .then(result => { @@ -38,7 +34,6 @@ module.exports = (app) => { }); // route to display all free public claims at a given name app.get('/:name/all', ({ ip, originalUrl, params }, res) => { - logger.verbose(`POST request on ${originalUrl} from ${ip}`); // get and render the content getAllClaims(params.name) .then(orderedFreePublicClaims => { @@ -53,4 +48,40 @@ module.exports = (app) => { errorHandlers.handleRequestError('show', originalUrl, ip, error, res); }); }); + // route to show a specific asset + app.get('/show/:name/:claim_id', ({ ip, originalUrl, params }, res) => { + // begin image-serve processes + getClaimByClaimId(params.name, params.claim_id) + .then(fileInfo => { + // check to make sure a file was found + if (!fileInfo) { + res.status(307).render('noClaims'); + return; + } + // serve the file or the show route + postToStats('show', originalUrl, ip, 'success'); + res.status(200).render('show', { fileInfo }); + }) + .catch(error => { + errorHandlers.handleRequestError('serve', originalUrl, ip, error, res); + }); + }); + // route to show the winning free, public claim + app.get('/show/:name', ({ ip, originalUrl, params }, res) => { + // get and render the content + getClaimByName(params.name) + .then(fileInfo => { + // check to make sure a file was found + if (!fileInfo) { + res.status(307).render('noClaims'); + return; + } + // serve the show route + postToStats('show', originalUrl, ip, 'success'); + res.status(200).render('show', { fileInfo }); + }) + .catch(error => { + errorHandlers.handleRequestError('serve', originalUrl, ip, error, res); + }); + }); }; diff --git a/routes/sockets-routes.js b/routes/sockets-routes.js index 7ca4b092..7f3179e4 100644 --- a/routes/sockets-routes.js +++ b/routes/sockets-routes.js @@ -1,12 +1,14 @@ const logger = require('winston'); +const fs = require('fs'); const publishController = require('../controllers/publishController.js'); const publishHelpers = require('../helpers/libraries/publishHelpers.js'); const errorHandlers = require('../helpers/libraries/errorHandlers.js'); const { postToStats } = require('../controllers/statsController.js'); module.exports = (app, siofu, hostedContentPath) => { - const http = require('http').Server(app); - const io = require('socket.io')(http); + const http = require('http'); + const server = http.Server(app); + const io = require('socket.io')(server); io.on('connection', socket => { logger.silly('a user connected via sockets'); @@ -55,11 +57,47 @@ module.exports = (app, siofu, hostedContentPath) => { // to-do: remove the file if not done automatically } }); + // handle asset requests + socket.on('asset-request', filePath => { + logger.debug('received a request for the following file', filePath); + fs.readFile(filePath, (err, assetBuffer) => { + if (err) { + logger.error('fs.readFile error', err); + return; + }; + // set the data + const fileExtension = filePath.substring(filePath.lastIndexOf('.')); + let data = { + type : null, + buffer: assetBuffer.toString('base64'), + }; + switch (fileExtension) { + case '.jpeg' || '.jpg': + data['type'] = 'image/jpeg'; + break; + case '.gif': + data['type'] = 'image/gif'; + break; + case '.png': + data['type'] = 'image/png'; + break; + case '.mp4': + data['type'] = 'video/mp4'; + break; + default: + data['type'] = 'image/jpeg'; + logger.warn('showing file with unknown type as jpeg'); + break; + } + // send the asset + socket.emit('asset-transfer', data); + }); + }); // handle disconnect socket.on('disconnect', () => { logger.silly('a user disconnected via sockets'); }); }); - return http; + return server; }; diff --git a/server.js b/server.js index 0df1158c..359c99a5 100644 --- a/server.js +++ b/server.js @@ -29,6 +29,10 @@ app.enable('trust proxy'); // trust the proxy to get ip address for us app.use(bodyParser.json()); // for parsing application/json app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded app.use(siofu.router); +app.use((req, res, next) => { // logging middleware + winston.verbose(`Request on ${req.originalUrl} from ${req.ip}`); + next(); +}); // configure handlebars & register it with Express app const hbs = expressHandlebars.create({ @@ -49,6 +53,28 @@ const hbs = expressHandlebars.create({ ` ); }, + ifConditional (varOne, operator, varTwo, options) { + switch (operator) { + case '===': + return (varOne === varTwo) ? options.fn(this) : options.inverse(this); + case '!==': + return (varOne !== varTwo) ? options.fn(this) : options.inverse(this); + case '<': + return (varOne < varTwo) ? options.fn(this) : options.inverse(this); + case '<=': + return (varOne <= varTwo) ? options.fn(this) : options.inverse(this); + case '>': + return (varOne > varTwo) ? options.fn(this) : options.inverse(this); + case '>=': + return (varOne >= varTwo) ? options.fn(this) : options.inverse(this); + case '&&': + return (varOne && varTwo) ? options.fn(this) : options.inverse(this); + case '||': + return (varOne || varTwo) ? options.fn(this) : options.inverse(this); + default: + return options.inverse(this); + } + }, }, }); app.engine('handlebars', hbs.engine); @@ -61,13 +87,13 @@ require('./routes/serve-routes.js')(app); require('./routes/home-routes.js')(app); // require socket.io routes -const http = require('./routes/sockets-routes.js')(app, siofu, hostedContentPath); +const server = require('./routes/sockets-routes.js')(app, siofu, hostedContentPath); // sync sequelize // wrap the server in socket.io to intercept incoming sockets requests // start server db.sequelize.sync().then(() => { - http.listen(PORT, () => { + server.listen(PORT, () => { winston.info('Trusting proxy?', app.get('trust proxy')); winston.info(`Server is listening on PORT ${PORT}`); }); diff --git a/views/index.handlebars b/views/index.handlebars index cde6ed02..0e1302f9 100644 --- a/views/index.handlebars +++ b/views/index.handlebars @@ -9,4 +9,5 @@ + diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 8e239631..ee2311da 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -9,8 +9,8 @@ - {{{ body }}} + {{{ body }}} {{ googleAnalytics }} diff --git a/views/partials/asset.handlebars b/views/partials/asset.handlebars new file mode 100644 index 00000000..d43640f7 --- /dev/null +++ b/views/partials/asset.handlebars @@ -0,0 +1,35 @@ +
+
+

loading...

+
+
+ + + \ No newline at end of file diff --git a/views/partials/assetInfo.handlebars b/views/partials/assetInfo.handlebars new file mode 100644 index 00000000..b93576a2 --- /dev/null +++ b/views/partials/assetInfo.handlebars @@ -0,0 +1,68 @@ +
+

Name

+

{{fileInfo.name}} +

+ +
+

Metadata

+ + + + + + + + + + + + + + + + + +
fileType{{#if fileInfo.fileType}} + {{fileInfo.fileType}} + {{else}} + unknown + {{/if}} +
+
+ + \ No newline at end of file diff --git a/views/partials/documentation.handlebars b/views/partials/documentation.handlebars index 21113514..45dec680 100644 --- a/views/partials/documentation.handlebars +++ b/views/partials/documentation.handlebars @@ -9,13 +9,23 @@ https://spee.ch/:name + https://spee.ch/show/:name + https://spee.ch/:name/:claim_id + https://spee.ch/show/:name/:claim_id + https://spee.ch/:name/all