From 7baf2dd75c18f5f42f096859348e6fd93ac1402a Mon Sep 17 00:00:00 2001 From: bill bittner Date: Thu, 5 Oct 2017 14:48:08 -0700 Subject: [PATCH 1/3] updated post api to receive curl requests --- auth/authentication.js | 10 +- controllers/publishController.js | 45 +++--- helpers/errorHandlers.js | 12 +- helpers/publishHelpers.js | 132 ++++++++++------ package.json | 1 + public/assets/js/validationFunctions.js | 192 ++++++++++++++---------- routes/api-routes.js | 84 +++++++---- routes/sockets-routes.js | 11 +- 8 files changed, 300 insertions(+), 187 deletions(-) diff --git a/auth/authentication.js b/auth/authentication.js index 6c6cf441..631aee66 100644 --- a/auth/authentication.js +++ b/auth/authentication.js @@ -2,21 +2,23 @@ const db = require('../models'); const logger = require('winston'); module.exports = { - authenticateApiPublish (username, password) { + authenticateChannelCredentials (channelName, userPassword) { return new Promise((resolve, reject) => { - if (username === 'none') { + if (!channelName) { resolve(true); return; } + const userName = channelName.substring(1); + logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`); db.User - .findOne({where: {userName: username}}) + .findOne({where: { userName }}) .then(user => { if (!user) { logger.debug('no user found'); resolve(false); return; } - if (!user.validPassword(password, user.password)) { + if (!user.validPassword(userPassword, user.password)) { logger.debug('incorrect password'); resolve(false); return; diff --git a/controllers/publishController.js b/controllers/publishController.js index 2ef07bdf..698ac7b7 100644 --- a/controllers/publishController.js +++ b/controllers/publishController.js @@ -7,24 +7,23 @@ module.exports = { publish (publishParams, fileName, fileType) { return new Promise((resolve, reject) => { let publishResults = {}; - // 1. make sure the name is available - publishHelpers.checkClaimNameAvailability(publishParams.name) - // 2. publish the file - .then(result => { - if (result === true) { - return lbryApi.publishClaim(publishParams); - } else { - return new Error('That name is already in use by spee.ch.'); - } - }) - // 3. upsert File record (update is in case the claim has been published before by this daemon) + // 1. publish the file + lbryApi.publishClaim(publishParams) + // 2. upsert File record (update is in case the claim has been published before by this daemon) .then(tx => { logger.info(`Successfully published ${fileName}`, tx); publishResults = tx; return db.Channel.findOne({where: {channelName: publishParams.channel_name}}); }) .then(user => { - if (user) { logger.debug('successfully found user in User table') } else { logger.error('user for publish not found in User table') }; + let certificateId; + if (user) { + certificateId = user.channelClaimId; + logger.debug('successfully found user in User table'); + } else { + certificateId = null; + logger.debug('user for publish not found in User table'); + }; const fileRecord = { name : publishParams.name, claimId : publishResults.claim_id, @@ -39,17 +38,17 @@ module.exports = { nsfw : publishParams.metadata.nsfw, }; const claimRecord = { - name : publishParams.name, - claimId : publishResults.claim_id, - title : publishParams.metadata.title, - description : publishParams.metadata.description, - address : publishParams.claim_address, - outpoint : `${publishResults.txid}:${publishResults.nout}`, - height : 0, - contentType : fileType, - nsfw : publishParams.metadata.nsfw, - certificateId: user.channelClaimId, - amount : publishParams.bid, + name : publishParams.name, + claimId : publishResults.claim_id, + title : publishParams.metadata.title, + description: publishParams.metadata.description, + address : publishParams.claim_address, + outpoint : `${publishResults.txid}:${publishResults.nout}`, + height : 0, + contentType: fileType, + nsfw : publishParams.metadata.nsfw, + certificateId, + amount : publishParams.bid, }; const upsertCriteria = { name : publishParams.name, diff --git a/helpers/errorHandlers.js b/helpers/errorHandlers.js index d1ac23cc..af549e30 100644 --- a/helpers/errorHandlers.js +++ b/helpers/errorHandlers.js @@ -30,8 +30,16 @@ module.exports = { logger.error('Publish Error:', useObjectPropertiesIfNoKeys(error)); if (error.code === 'ECONNREFUSED') { return 'Connection refused. The daemon may not be running.'; - } else if (error.response.data.error) { - return error.response.data.error.message; + } else if (error.response) { + if (error.response.data) { + if (error.response.data.message) { + return error.response.data.message; + } else if (error.response.data.error) { + return error.response.data.error.message; + } + return error.response.data; + } + return error.response; } else { return error; } diff --git a/helpers/publishHelpers.js b/helpers/publishHelpers.js index 2db37d46..7eecc00d 100644 --- a/helpers/publishHelpers.js +++ b/helpers/publishHelpers.js @@ -1,78 +1,130 @@ const logger = require('winston'); -const config = require('config'); const fs = require('fs'); const db = require('../models'); +const config = require('config'); module.exports = { - validateFile (file, name, license, nsfw) { + validateApiPublishRequest (body, files) { + if (!body) { + throw new Error('no body found in request'); + } + if (!body.name) { + throw new Error('no name field found in request'); + } + if (!body.nsfw) { + throw new Error('no nsfw field found in request'); + } + if (!files) { + throw new Error('no files found in request'); + } + if (!files.file) { + throw new Error('no file with key of [file] found in request'); + } + }, + validatePublishSubmission (file, claimName, nsfw) { + try { + module.exports.validateFile(file); + module.exports.validateClaimName(claimName); + module.exports.validateNSFW(nsfw); + } catch (error) { + throw error; + } + }, + validateFile (file) { if (!file) { - throw new Error('No file was submitted or the key used was incorrect. Files posted through this route must use a key of "speech" or null'); + logger.debug('publish > file validation > no file found'); + throw new Error('no file provided'); + } + // check the file name + if (/'/.test(file.name)) { + logger.debug('publish > file validation > file name had apostrophe in it'); + throw new Error('apostrophes are not allowed in the file name'); } // check file type and size switch (file.type) { case 'image/jpeg': + case 'image/jpg': case 'image/png': + if (file.size > 10000000) { + logger.debug('publish > file validation > .jpeg/.jpg/.png was too big'); + throw new Error('Sorry, images are limited to 10 megabytes.'); + } + break; case 'image/gif': if (file.size > 50000000) { - throw new Error('Your image exceeds the 50 megabyte limit.'); + logger.debug('publish > file validation > .gif was too big'); + throw new Error('Sorry, .gifs are limited to 50 megabytes.'); } break; case 'video/mp4': if (file.size > 50000000) { - throw new Error('Your video exceeds the 50 megabyte limit.'); + logger.debug('publish > file validation > .mp4 was too big'); + throw new Error('Sorry, videos are limited to 50 megabytes.'); } break; default: - throw new Error('The ' + file.Type + ' content type is not supported. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.'); + logger.debug('publish > file validation > unrecognized file type'); + throw new Error('The ' + file.type + ' content type is not supported. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.'); } - // validate claim name - const invalidCharacters = /[^A-Za-z0-9,-]/.exec(name); + return file; + }, + validateClaimName (claimName) { + const invalidCharacters = /[^A-Za-z0-9,-]/.exec(claimName); if (invalidCharacters) { - throw new Error('The url name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"'); + throw new Error('The claim name you provided is not allowed. Only the following characters are allowed: A-Z, a-z, 0-9, and "-"'); } - // validate license + }, + validateLicense (license) { if ((license.indexOf('Public Domain') === -1) && (license.indexOf('Creative Commons') === -1)) { - throw new Error('Only posts with a "Public Domain" license, or one of the Creative Commons licenses are eligible for publishing through spee.ch'); + throw new Error('Only posts with a "Public Domain" or "Creative Commons" license are eligible for publishing through spee.ch'); } + }, + cleanseNSFW (nsfw) { switch (nsfw) { case true: - case false: - case 'true': - case 'false': case 'on': + case 'true': + case 1: + case '1': + return true; + case false: + case 'false': case 'off': case 0: case '0': - case 1: - case '1': - break; + return false; default: - throw new Error('NSFW value was not accepted. NSFW must be set to either true, false, "on", or "off"'); + return null; } }, - createPublishParams (name, filePath, title, description, license, nsfw, channel) { - logger.debug(`Creating Publish Parameters for "${name}"`); - const claimAddress = config.get('WalletConfig.LbryClaimAddress'); - const defaultChannel = config.get('WalletConfig.DefaultChannel'); - // filter nsfw and ensure it is a boolean - if (nsfw === false) { - nsfw = false; - } else if (typeof nsfw === 'string') { - if (nsfw.toLowerCase === 'false' || nsfw.toLowerCase === 'off' || nsfw === '0') { - nsfw = false; + cleanseChannelName (channelName) { + if (channelName) { + if (channelName.indexOf('@') !== 0) { + channelName = `@${channelName}`; } - } else if (nsfw === 0) { - nsfw = false; - } else { - nsfw = true; } - // provide defaults for title & description - if (title === null || title === '') { + return channelName; + }, + validateNSFW (nsfw) { + if (nsfw === true || nsfw === false) { + return; + } + throw new Error('NSFW must be set to either true or false'); + }, + createPublishParams (filePath, name, title, description, license, nsfw, channelName) { + logger.debug(`Creating Publish Parameters`); + // provide defaults for title + if (title === null || title.trim() === '') { title = name; } + // provide default for description if (description === null || description.trim() === '') { description = `${name} published via spee.ch`; } + // provide default for license + if (license === null || license.trim() === '') { + license = 'All Rights Reserved'; + } // create the publish params const publishParams = { name, @@ -86,16 +138,12 @@ module.exports = { license, nsfw, }, - claim_address: claimAddress, + claim_address: config.get('WalletConfig.LbryClaimAddress'), }; - // add channel if applicable - if (channel !== 'none') { - publishParams['channel_name'] = channel; - } else { - publishParams['channel_name'] = defaultChannel; + // add channel to params, if applicable + if (channelName) { + publishParams['channel_name'] = channelName; } - - logger.debug('publishParams:', publishParams); return publishParams; }, deleteTemporaryFile (filePath) { diff --git a/package.json b/package.json index e1aa4a85..820b2af6 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "express": "^4.15.2", "express-handlebars": "^3.0.0", "express-session": "^1.15.5", + "form-data": "^2.3.1", "helmet": "^3.8.1", "mysql2": "^1.3.5", "nodemon": "^1.11.0", diff --git a/public/assets/js/validationFunctions.js b/public/assets/js/validationFunctions.js index 7d4f2e00..34ce67d6 100644 --- a/public/assets/js/validationFunctions.js +++ b/public/assets/js/validationFunctions.js @@ -2,46 +2,56 @@ // validation function which checks the proposed file's type, size, and name function validateFile(file) { - if (!file) { - throw new Error('no file provided'); - } - if (/'/.test(file.name)) { - throw new Error('apostrophes are not allowed in the file name'); - } - // validate size and type - switch (file.type) { - case 'image/jpeg': - case 'image/jpg': - case 'image/png': - case 'image/gif': - if (file.size > 50000000){ - throw new Error('Sorry, images are limited to 50 megabytes.'); - } - break; - case 'video/mp4': - if (file.size > 50000000){ - throw new Error('Sorry, videos are limited to 50 megabytes.'); - } - break; - default: - throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.') - } + if (!file) { + console.log('no file found'); + throw new Error('no file provided'); + } + if (/'/.test(file.name)) { + console.log('file name had apostrophe in it'); + throw new Error('apostrophes are not allowed in the file name'); + } + // validate size and type + switch (file.type) { + case 'image/jpeg': + case 'image/jpg': + case 'image/png': + if (file.size > 10000000){ + console.log('file was too big'); + throw new Error('Sorry, images are limited to 10 megabytes.'); + } + break; + case 'image/gif': + if (file.size > 50000000){ + console.log('file was too big'); + throw new Error('Sorry, .gifs are limited to 50 megabytes.'); + } + break; + case 'video/mp4': + if (file.size > 50000000){ + console.log('file was too big'); + throw new Error('Sorry, videos are limited to 50 megabytes.'); + } + break; + default: + console.log('file type is not supported'); + throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.') + } } // validation function that checks to make sure the claim name is valid function validateClaimName (name) { - // ensure a name was entered - if (name.length < 1) { - throw new NameError("You must enter a name for your url"); - } - // validate the characters in the 'name' field - const invalidCharacters = /[^A-Za-z0-9,-]/g.exec(name); - if (invalidCharacters) { - throw new NameError('"' + invalidCharacters + '" characters are not allowed in the url.'); - } + // ensure a name was entered + if (name.length < 1) { + throw new NameError("You must enter a name for your url"); + } + // validate the characters in the 'name' field + const invalidCharacters = /[^A-Za-z0-9,-]/g.exec(name); + if (invalidCharacters) { + throw new NameError('"' + invalidCharacters + '" characters are not allowed in the url.'); + } } function validateChannelName (name) { - name = name.substring(name.indexOf('@') + 1); + name = name.substring(name.indexOf('@') + 1); // ensure a name was entered if (name.length < 1) { throw new ChannelNameError("You must enter a name for your channel"); @@ -60,9 +70,9 @@ function validatePassword (password) { } function cleanseClaimName(name) { - name = name.replace(/\s+/g, '-'); // replace spaces with dashes - name = name.replace(/[^A-Za-z0-9-]/g, ''); // remove all characters that are not A-Z, a-z, 0-9, or '-' - return name; + name = name.replace(/\s+/g, '-'); // replace spaces with dashes + name = name.replace(/[^A-Za-z0-9-]/g, ''); // remove all characters that are not A-Z, a-z, 0-9, or '-' + return name; } // validation functions to check claim & channel name eligibility as the inputs change @@ -99,14 +109,14 @@ function checkAvailability(name, successDisplayElement, errorDisplayElement, val // check to make sure it is available isNameAvailable(name, apiUrl) .then(result => { - console.log('result:', result) - if (result === true) { + console.log('result:', result) + if (result === true) { hideError(errorDisplayElement); showSuccess(successDisplayElement) - } else { + } else { hideSuccess(successDisplayElement); showError(errorDisplayElement, errorMessage); - } + } }) .catch(error => { hideSuccess(successDisplayElement); @@ -119,58 +129,69 @@ function checkAvailability(name, successDisplayElement, errorDisplayElement, val } function checkClaimName(name){ - const successDisplayElement = document.getElementById('input-success-claim-name'); - const errorDisplayElement = document.getElementById('input-error-claim-name'); - checkAvailability(name, successDisplayElement, errorDisplayElement, validateClaimName, isNameAvailable, 'Sorry, that url ending has been taken by another user', '/api/isClaimAvailable/'); + const successDisplayElement = document.getElementById('input-success-claim-name'); + const errorDisplayElement = document.getElementById('input-error-claim-name'); + checkAvailability(name, successDisplayElement, errorDisplayElement, validateClaimName, isNameAvailable, 'Sorry, that url ending has been taken', '/api/isClaimAvailable/'); } function checkChannelName(name){ const successDisplayElement = document.getElementById('input-success-channel-name'); const errorDisplayElement = document.getElementById('input-error-channel-name'); name = `@${name}`; - checkAvailability(name, successDisplayElement, errorDisplayElement, validateChannelName, isNameAvailable, 'Sorry, that Channel has been taken by another user', '/api/isChannelAvailable/'); + checkAvailability(name, successDisplayElement, errorDisplayElement, validateChannelName, isNameAvailable, 'Sorry, that Channel name has been taken by another user', '/api/isChannelAvailable/'); } // validation function which checks all aspects of the publish submission function validateFilePublishSubmission(stagedFiles, claimName, channelName){ - return new Promise(function (resolve, reject) { - // 1. make sure only 1 file was selected - if (!stagedFiles) { - return reject(new FileError("Please select a file")); - } else if (stagedFiles.length > 1) { - return reject(new FileError("Only one file is allowed at a time")); - } - // 2. validate the file's name, type, and size - try { - validateFile(stagedFiles[0]); - } catch (error) { - return reject(error); - } - // 3. validate that a channel was chosen - if (channelName === 'new' || channelName === 'login') { - return reject(new ChannelNameError("Please select a valid channel")); + console.log(`validating publish submission > name: ${claimName} channel: ${channelName} file:`, stagedFiles); + return new Promise(function (resolve, reject) { + // 1. make sure 1 file was staged + if (!stagedFiles) { + reject(new FileError("Please select a file")); + return; + } else if (stagedFiles.length > 1) { + reject(new FileError("Only one file is allowed at a time")); + return; + } + // 2. validate the file's name, type, and size + try { + validateFile(stagedFiles[0]); + } catch (error) { + reject(error); + return; + } + // 3. validate that a channel was chosen + if (channelName === 'new' || channelName === 'login') { + reject(new ChannelNameError("Please log in to a channel")); + return; }; - // 4. validate the claim name - try { - validateClaimName(claimName); - } catch (error) { - return reject(error); - } - // if all validation passes, check availability of the name - isNameAvailable(claimName, '/api/isClaimAvailable/') - .then(() => { - resolve(); - }) - .catch(error => { - reject(error); - }); - }); + // 4. validate the claim name + try { + validateClaimName(claimName); + } catch (error) { + reject(error); + return; + } + // if all validation passes, check availability of the name (note: do we need to re-validate channel name vs. credentials as well?) + return isNameAvailable(claimName, '/api/isClaimAvailable/') + .then(result => { + if (result) { + resolve(); + } else { + reject(new NameError('that url ending is already taken')); + } + }) + .catch(error => { + reject(error); + }); + }); } -// validation function which checks all aspects of the publish submission -function validateNewChannelSubmission(channelName, password){ +// validation function which checks all aspects of a new channel submission +function validateNewChannelSubmission(userName, password){ + const channelName = `@${userName}`; return new Promise(function (resolve, reject) { - // 1. validate name + // 1. validate name try { validateChannelName(channelName); } catch (error) { @@ -184,12 +205,17 @@ function validateNewChannelSubmission(channelName, password){ } // 3. if all validation passes, check availability of the name isNameAvailable(channelName, '/api/isChannelAvailable/') // validate the availability - .then(() => { - console.log('channel is avaliable'); - resolve(); + .then(result => { + if (result) { + console.log('channel is available'); + resolve(); + } else { + console.log('channel is not available'); + reject(new ChannelNameError('that channel name has already been taken')); + } }) .catch( error => { - console.log('error: channel is not avaliable'); + console.log('error evaluating channel name availability', error); reject(error); }); }); diff --git a/routes/api-routes.js b/routes/api-routes.js index e88d4668..cf8c0af3 100644 --- a/routes/api-routes.js +++ b/routes/api-routes.js @@ -4,10 +4,10 @@ const multipartMiddleware = multipart(); const db = require('../models'); const { publish } = require('../controllers/publishController.js'); const { getClaimList, resolveUri } = require('../helpers/lbryApi.js'); -const { createPublishParams, validateFile, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js'); +const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseNSFW, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js'); const errorHandlers = require('../helpers/errorHandlers.js'); const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); -const { authenticateApiPublish } = require('../auth/authentication.js'); +const { authenticateChannelCredentials } = require('../auth/authentication.js'); module.exports = (app) => { // route to run a claim_list request on the daemon @@ -71,52 +71,80 @@ module.exports = (app) => { }); }); // route to run a publish request on the daemon - app.post('/api/publish', multipartMiddleware, ({ body, files, headers, ip, originalUrl }, res) => { - // google analytics - sendGoogleAnalytics('PUBLISH', headers, ip, originalUrl); - // validate that a file was provided - const file = files.speech || files.null; - const name = body.name || file.name.substring(0, file.name.indexOf('.')); - const title = body.title || null; - const description = body.description || null; - const license = body.license || 'No License Provided'; - const nsfw = body.nsfw || null; - const channelName = body.channelName || 'none'; - const channelPassword = body.channelPassword || null; - logger.debug(`name: ${name}, license: ${license}, nsfw: ${nsfw}`); + app.post('/api/publish', multipartMiddleware, (req, res) => { + logger.debug(req); + const body = req.body; + const files = req.files; try { - validateFile(file, name, license, nsfw); + validateApiPublishRequest(body, files); } catch (error) { - postToStats('publish', originalUrl, ip, null, null, error.message); - logger.debug('rejected >>', error.message); - res.status(400).send(error.message); + logger.debug('publish request rejected, insufficient request parameters'); + res.status(400).json({success: false, message: error.message}); return; } + // required inputs + const file = files.file; const fileName = file.name; const filePath = file.path; const fileType = file.type; - // channel authorization - authenticateApiPublish(channelName, channelPassword) + const name = body.name; + let nsfw = body.nsfw; + // cleanse nsfw + nsfw = cleanseNSFW(nsfw); + // validate file, name, license, and nsfw + try { + validatePublishSubmission(file, name, nsfw); + } catch (error) { + logger.debug('publish request rejected'); + res.status(400).json({success: false, message: error.message}); + return; + } + logger.debug(`name: ${name}, nsfw: ${nsfw}`); + // optional inputs + const license = body.license || null; + const title = body.title || null; + const description = body.description || null; + let channelName = body.channelName || null; + channelName = cleanseChannelName(channelName); + const channelPassword = body.channelPassword || null; + logger.debug(`license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}"`); + // check channel authorization + authenticateChannelCredentials(channelName, channelPassword) .then(result => { if (!result) { - res.status(401).send('Authentication failed, you do not have access to that channel'); - throw new Error('authentication failed'); + throw new Error('Authentication failed, you do not have access to that channel'); } - return createPublishParams(name, filePath, title, description, license, nsfw, channelName); + // make sure the claim name is available + return checkClaimNameAvailability(name); + }) + .then(result => { + if (!result) { + throw new Error('That name is already in use by spee.ch.'); + } + // create publish parameters object + return createPublishParams(filePath, name, title, description, license, nsfw, channelName); }) - // create publish parameters object .then(publishParams => { + logger.debug('publishParams:', publishParams); + // publish the asset return publish(publishParams, fileName, fileType); }) - // publish the asset .then(result => { - postToStats('publish', originalUrl, ip, null, null, 'success'); - res.status(200).json(result); + // postToStats('publish', originalUrl, ip, null, null, 'success'); + res.status(200).json({ + success: true, + message: { + url : `spee.ch/${result.claim_id}/${name}`, + lbryTx: result, + }, + }); }) .catch(error => { logger.error('publish api error', error); + res.status(400).json({success: false, message: error.message}); }); }); + // route to get a short claim id from long claim Id app.get('/api/shortClaimId/:longId/:name', ({ originalUrl, ip, params }, res) => { // serve content diff --git a/routes/sockets-routes.js b/routes/sockets-routes.js index c7dadd54..1cf5dae9 100644 --- a/routes/sockets-routes.js +++ b/routes/sockets-routes.js @@ -1,6 +1,6 @@ const logger = require('winston'); -const publishController = require('../controllers/publishController.js'); -const publishHelpers = require('../helpers/publishHelpers.js'); +const { publish } = require('../controllers/publishController.js'); +const { createPublishParams } = require('../helpers/publishHelpers.js'); const errorHandlers = require('../helpers/errorHandlers.js'); const { postToStats } = require('../controllers/statsController.js'); @@ -40,12 +40,13 @@ module.exports = (app, siofu, hostedContentPath) => { NOTE: need to validate that client has the credentials to the channel they chose otherwise they could circumvent security client side. */ - + let channelName = file.meta.cannel; + if (channelName === 'none') channelName = null; // prepare the publish parameters - const publishParams = publishHelpers.createPublishParams(file.meta.name, file.pathName, file.meta.title, file.meta.description, file.meta.license, file.meta.nsfw, file.meta.channel); + const publishParams = createPublishParams(file.pathName, file.meta.name, file.meta.title, file.meta.description, file.meta.license, file.meta.nsfw, channelName); logger.debug(publishParams); // publish the file - publishController.publish(publishParams, file.name, file.meta.type) + publish(publishParams, file.name, file.meta.type) .then(result => { postToStats('PUBLISH', '/', null, null, null, 'success'); socket.emit('publish-complete', { name: publishParams.name, result }); -- 2.45.2 From be0bf27a1493ba9469902e2114e35edf70a3038a Mon Sep 17 00:00:00 2001 From: bill bittner Date: Thu, 5 Oct 2017 15:14:50 -0700 Subject: [PATCH 2/3] traded in new video thumb --- controllers/serveController.js | 2 +- controllers/statsController.js | 2 +- public/assets/img/video_thumb_default.png | Bin 0 -> 59192 bytes routes/sockets-routes.js | 2 +- views/new.handlebars | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 public/assets/img/video_thumb_default.png diff --git a/controllers/serveController.js b/controllers/serveController.js index be7de126..9bbd654a 100644 --- a/controllers/serveController.js +++ b/controllers/serveController.js @@ -7,7 +7,7 @@ const { postToStats, sendGoogleAnalytics } = require('../controllers/statsContro const SERVE = 'SERVE'; const SHOW = 'SHOW'; const SHOWLITE = 'SHOWLITE'; -const DEFAULT_THUMBNAIL = 'https://spee.ch/assets/img/content-freedom-large.png'; +const DEFAULT_THUMBNAIL = 'https://spee.ch/assets/img/video_thumb_default.png'; function checkForLocalAssetByClaimId (claimId, name) { return new Promise((resolve, reject) => { diff --git a/controllers/statsController.js b/controllers/statsController.js index 3ee05e77..8ece2323 100644 --- a/controllers/statsController.js +++ b/controllers/statsController.js @@ -155,7 +155,7 @@ module.exports = { element['directUrlLong'] = `/${element.claimId}/${element.name}.${fileExtenstion}`; element['directUrlShort'] = `/${element.claimId}/${element.name}.${fileExtenstion}`; element['contentType'] = element.fileType; - element['thumbnail'] = 'https://spee.ch/assets/img/content-freedom-large.png'; + element['thumbnail'] = 'https://spee.ch/assets/img/video_thumb_default.png'; }); } resolve(results); diff --git a/public/assets/img/video_thumb_default.png b/public/assets/img/video_thumb_default.png new file mode 100644 index 0000000000000000000000000000000000000000..318746da634e3cdf79774ab5035cdecdae5b16b3 GIT binary patch literal 59192 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 { NOTE: need to validate that client has the credentials to the channel they chose otherwise they could circumvent security client side. */ - let channelName = file.meta.cannel; + let channelName = file.meta.channel; if (channelName === 'none') channelName = null; // prepare the publish parameters const publishParams = createPublishParams(file.pathName, file.meta.name, file.meta.title, file.meta.description, file.meta.license, file.meta.nsfw, channelName); diff --git a/views/new.handlebars b/views/new.handlebars index 8b6cbbfb..cf34c851 100644 --- a/views/new.handlebars +++ b/views/new.handlebars @@ -7,7 +7,7 @@
{{#ifConditional this.fileType '===' 'video/mp4'}} - + {{else}} {{/ifConditional}} -- 2.45.2 From b198a35a9b6799694ec679f8cd8ed84ff88536ef Mon Sep 17 00:00:00 2001 From: bill bittner Date: Thu, 5 Oct 2017 15:37:20 -0700 Subject: [PATCH 3/3] updated documentation --- README.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 54b75478..183df749 100644 --- a/README.md +++ b/README.md @@ -28,28 +28,24 @@ spee.ch is a single-serving site that reads and publishes images and videos to a #### GET * /api/resolve/:name - * a successfull request returns the resolve results for the claim at that name in JSON format + * example: `curl https://spee.ch/api/resolve/doitlive` * /api/claim_list/:name - * a successfull request returns a list of claims at that claim name in JSON format -* /api/isClaimAvailable/:name - * a successfull request returns a boolean: `true` if the name is still available, `false` if the name has already been published to by spee.ch. + * example: `curl https://spee.ch/api/claim_list/doitlive` +* /api/isClaimAvailable/:name (returns `true`/`false` for whether a name is available through spee.ch) + * example: `curl https://spee.ch/api/isClaimAvailable/doitlive` #### POST * /api/publish - * request parameters: - * body (form-data): - * name: string (optional) - * defaults to the file's name, sans extension - * names can only contain the following characters: `A-Z`, `a-z`, `_`, or `-` - * license: string (optional) - * defaults to "No License Provided" - * only "Public Domain" or "Creative Commons" licenses are allowed - * nsfw: string, number, or boolean (optional) - * defaults `true` - * nsfw can be a string ("on"/"off"), number (0 or 1), or boolean (`true`/`false`) - * files: - * the `files` object submitted must use "speech" or "null" as the key for the file's value object - * a successfull request will return the transaction details resulting from your published claim in JSON format + * example: `curl -X POST -F 'name=MyPictureName' -F 'nsfw=false' -F 'file=@/path/to/my/picture.jpeg' https://spee.ch/api/publish` + * Parameters: + * name (string) + * nsfw (boolean) + * file (.mp4, .jpeg, .jpg, .gif, or .png) + * license (string, optional) + * title (string, optional) + * description (string, optional) + * channelName(string, optional) + * channelPassword (string, optional) ## bugs If you find a bug or experience a problem, please report your issue here on github and find us in the lbry slack! -- 2.45.2