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! 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/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/helpers/errorHandlers.js b/helpers/errorHandlers.js index 596a4601..17d22eb9 100644 --- a/helpers/errorHandlers.js +++ b/helpers/errorHandlers.js @@ -26,6 +26,23 @@ module.exports = { res.status(400).send(error); } }, + handlePublishError (error) { + logger.error('Publish Error:', useObjectPropertiesIfNoKeys(error)); + if (error.code === 'ECONNREFUSED') { + return 'Connection refused. The daemon may not be running.'; + } 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; + } useObjectPropertiesIfNoKeys (err) { return useObjectPropertiesIfNoKeys(err); }, 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/img/video_thumb_default.png b/public/assets/img/video_thumb_default.png new file mode 100644 index 00000000..318746da Binary files /dev/null and b/public/assets/img/video_thumb_default.png differ diff --git a/public/assets/js/validationFunctions.js b/public/assets/js/validationFunctions.js index 39c4021b..83f6ff71 100644 --- a/public/assets/js/validationFunctions.js +++ b/public/assets/js/validationFunctions.js @@ -1,57 +1,55 @@ - - // validation function which checks the proposed file's type, size, and name function validateFile(file) { - 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) { + 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){ + case 'image/gif': + if (file.size > 50000000){ console.log('file was too big'); - throw new Error('Sorry, videos are limited to 50 megabytes.'); - } - break; - default: + 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.') - } + 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"); @@ -70,9 +68,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 @@ -109,14 +107,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); @@ -129,9 +127,9 @@ 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', '/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){ @@ -143,55 +141,55 @@ function checkChannelName(name){ // validation function which checks all aspects of the publish submission function validateFilePublishSubmission(stagedFiles, claimName, channelName){ - 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")); + 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")); + } 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); + // 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")); + // 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) { - 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); - }); - }); + } + // 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 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) { @@ -210,7 +208,7 @@ function validateNewChannelSubmission(userName, password){ console.log('channel is available'); resolve(); } else { - console.log('channel is not avaliable'); + console.log('channel is not available'); reject(new ChannelNameError('that channel name has already been taken')); } }) 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 f20b8001..6b523c41 100644 --- a/routes/sockets-routes.js +++ b/routes/sockets-routes.js @@ -1,7 +1,7 @@ const logger = require('winston'); -const publishController = require('../controllers/publishController.js'); -const publishHelpers = require('../helpers/publishHelpers.js'); -const { useObjectPropertiesIfNoKeys } = require('../helpers/errorHandlers.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'); module.exports = (app, siofu, hostedContentPath) => { @@ -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.channel; + 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 => { socket.emit('publish-complete', { name: publishParams.name, result }); postToStats('PUBLISH', '/', null, null, null, 'success'); diff --git a/views/new.handlebars b/views/new.handlebars index 4e09d809..3e8870c7 100644 --- a/views/new.handlebars +++ b/views/new.handlebars @@ -7,7 +7,7 @@