diff --git a/controllers/serveController.js b/controllers/serveController.js index fe1ed0a6..b5e5bb06 100644 --- a/controllers/serveController.js +++ b/controllers/serveController.js @@ -1,7 +1,9 @@ const lbryApi = require('../helpers/lbryApi.js'); const db = require('../models'); const logger = require('winston'); -const { getTopFreeClaim, getFullClaimIdFromShortId, resolveAgainstClaimTable } = require('../helpers/serveHelpers.js'); +const { resolveAgainstClaimTable, serveFile, showFile, showFileLite, getShortClaimIdFromLongClaimId, getClaimIdByLongChannelId, getAllChannelClaims, getLongChannelId, getShortChannelIdFromLongChannelId, getLongClaimId } = require('../helpers/serveHelpers.js'); +const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); +const { SERVE, SHOW, SHOWLITE } = require('../helpers/constants.js'); function checkForLocalAssetByClaimId (claimId, name) { return new Promise((resolve, reject) => { @@ -20,19 +22,28 @@ function checkForLocalAssetByClaimId (claimId, name) { }); } -function formatGetResultsToFileInfo ({ name, claim_id, outpoint, file_name, download_path, mime_type, metadata }) { +function addGetResultsToFileRecord (fileInfo, getResult) { + fileInfo.fileName = getResult.file_name; + fileInfo.filePath = getResult.download_path; + fileInfo.fileType = getResult.mime_type; + return fileInfo; +} + +function createFileRecord ({ name, claimId, outpoint, height, address, nsfw }) { return { name, - claimId : claim_id, + claimId, outpoint, - fileName: file_name, - filePath: download_path, - fileType: mime_type, - nsfw : metadata.stream.metadata.nsfw, + height, + address, + fileName: '', + filePath: '', + fileType: '', + nsfw, }; } -function getAssetByClaimId (fullClaimId, name) { +function getAssetByLongClaimId (fullClaimId, name) { logger.debug('...getting asset by claim Id...'); return new Promise((resolve, reject) => { // 1. check locally for claim @@ -57,9 +68,8 @@ function getAssetByClaimId (fullClaimId, name) { lbryApi.getClaim(`${name}#${fullClaimId}`) .then(getResult => { logger.debug('getResult >>', getResult); - fileRecord = formatGetResultsToFileInfo(getResult); - fileRecord['address'] = (resolveResult.address || 0); - fileRecord['height'] = resolveResult.height; + fileRecord = createFileRecord(resolveResult); + fileRecord = addGetResultsToFileRecord(fileRecord, getResult); // insert a record in the File table & Update Claim table return db.File.create(fileRecord); }) @@ -82,53 +92,112 @@ function getAssetByClaimId (fullClaimId, name) { } module.exports = { - getAssetByChannel (channelName, name) { - logger.debug('...getting asset by channel...'); + getAssetByClaim (claimName, claimId) { + logger.debug('getting asset by claim'); return new Promise((resolve, reject) => { - // temporarily throw error - reject(new Error('channel names are not currently supported')); - // get the claim id - // get the asset by claim Id + // 1. get the long claim id + getLongClaimId(claimName, claimId) // here + // 2. get the claim Id + .then(longClaimId => { + logger.debug('long claim id = ', longClaimId); + resolve(getAssetByLongClaimId(longClaimId, claimName)); + }) + .catch(error => { + reject(error); + }); }); }, - getAssetByShortId: function (shortId, name) { - logger.debug('...getting asset by short id...'); + getAssetByChannel (channelName, channelId, claimName) { + logger.debug('getting asset by channel'); return new Promise((resolve, reject) => { - // get the full claimId - getFullClaimIdFromShortId(shortId, name) - // get the asset by the claimId + // 1. get the long channel id + getLongChannelId(channelName, channelId) + // 2. get the claim Id + .then(longChannelId => { + return getClaimIdByLongChannelId(longChannelId, claimName); + }) + // 3. get the asset by this claim id and name .then(claimId => { - logger.debug('claim id =', claimId); - resolve(getAssetByClaimId(claimId, name)); + logger.debug('asset claim id = ', claimId); + resolve(getAssetByLongClaimId(claimId, claimName)); }) .catch(error => { reject(error); }); }); }, - getAssetByClaimId (fullClaimId, name) { - return getAssetByClaimId(fullClaimId, name); - }, - getAssetByName (name) { - logger.debug('...getting asset by claim name...'); + getChannelContents (channelName, channelId) { return new Promise((resolve, reject) => { - // 1. get a list of the free public claims - getTopFreeClaim(name) - // 2. check locally for the top claim - .then(topFreeClaim => { - // if no claims were found, return null - if (!topFreeClaim) { - return resolve(null); + let longChannelId; + let shortChannelId; + // 1. get the long channel Id + getLongChannelId(channelName, channelId) + // 2. get all claims for that channel + .then(result => { + longChannelId = result; + return getShortChannelIdFromLongChannelId(channelName, longChannelId); + }) + // 3. get all Claim records for this channel + .then(result => { + shortChannelId = result; + return getAllChannelClaims(longChannelId); + }) + // 4. add extra data not available from Claim table + .then(allChannelClaims => { + if (allChannelClaims) { + allChannelClaims.forEach(element => { + element['channelName'] = channelName; + element['longChannelId'] = longChannelId; + element['shortChannelId'] = shortChannelId; + element['fileExtension'] = element.contentType.substring(element.contentType.lastIndexOf('/') + 1); + }); } - // parse the result - const claimId = topFreeClaim.claimId; - logger.debug('top free claim id =', claimId); - // get the asset - resolve(getAssetByClaimId(claimId, name)); + return resolve(allChannelClaims); }) .catch(error => { reject(error); }); }); }, + serveOrShowAsset (fileInfo, extension, method, headers, originalUrl, ip, res) { + // add file extension to the file info + if (extension === '.gifv') { + fileInfo['fileExt'] = '.gifv'; + } else { + fileInfo['fileExt'] = fileInfo.fileName.substring(fileInfo.fileName.lastIndexOf('.')); + } + // serve or show + switch (method) { + case SERVE: + serveFile(fileInfo, res); + sendGoogleAnalytics(method, headers, ip, originalUrl); + postToStats('serve', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); + return fileInfo; + case SHOWLITE: + showFileLite(fileInfo, res); + postToStats('show', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); + return fileInfo; + case SHOW: + return getShortClaimIdFromLongClaimId(fileInfo.claimId, fileInfo.name) + .then(shortId => { + fileInfo['shortId'] = shortId; + return resolveAgainstClaimTable(fileInfo.name, fileInfo.claimId); + }) + .then(resolveResult => { + logger.debug('resolve result', resolveResult); + fileInfo['title'] = resolveResult.title; + fileInfo['description'] = resolveResult.description; + showFile(fileInfo, res); + postToStats('show', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); + return fileInfo; + }) + .catch(error => { + console.log('thowing error...'); + throw error; + }); + default: + logger.error('I did not recognize that method'); + break; + } + }, }; diff --git a/controllers/statsController.js b/controllers/statsController.js index 0b6d7666..98f954c6 100644 --- a/controllers/statsController.js +++ b/controllers/statsController.js @@ -156,4 +156,18 @@ module.exports = { }); }); }, + getRecentClaims (startDate) { + logger.debug('retrieving most recent claims'); + return new Promise((resolve, reject) => { + // get the raw requests data + db.sequelize.query(`SELECT * FROM File WHERE nsfw != 1 AND trendingEligible = 1 ORDER BY createdAt DESC LIMIT 25;`, { type: db.sequelize.QueryTypes.SELECT }) + .then(results => { + resolve(results); + }) + .catch(error => { + logger.error('sequelize error', error); + reject(error); + }); + }); + }, }; diff --git a/helpers/constants.js b/helpers/constants.js new file mode 100644 index 00000000..28c7b7f1 --- /dev/null +++ b/helpers/constants.js @@ -0,0 +1,8 @@ +module.exports = { + SERVE : 'SERVE', + SHOW : 'SHOW', + SHOWLITE : 'SHOWLITE', + CHANNEL : 'CHANNEL', + CLAIM : 'CLAIM', + CHANNELID_INDICATOR: ':', +}; diff --git a/helpers/serveHelpers.js b/helpers/serveHelpers.js index 188cf915..34acf1ae 100644 --- a/helpers/serveHelpers.js +++ b/helpers/serveHelpers.js @@ -1,53 +1,6 @@ const logger = require('winston'); const db = require('../models'); -function determineShortClaimId (claimId, height, claimList) { - logger.debug('determining short url based on claim id and claim list'); - logger.debug('claimlist starting length:', claimList.length); - // remove this claim from the claim list, if it exists - claimList = claimList.filter(claim => { - return claim.claimId !== claimId; - }); - logger.debug('claim list length without this claim:', claimList.length); - // If there are no other claims, return the first letter of the claim id... - if (claimList.length === 0) { - return claimId.substring(0, 1); - // ...otherwise determine the proper short id. - } else { - const claimListCopy = claimList; - let i = 0; - // find the longest shared prefix (there is a better way to do this that filters, checks next filter, then filters (i.e. combine this step and next)) - while (claimList.length !== 0) { - i++; - claimList = claimList.filter(claim => { - const otherClaimIdSegmentToCompare = claim.claimId.substring(0, i); - const thisClaimIdSegmentToCompare = claimId.substring(0, i); - logger.debug('compare:', otherClaimIdSegmentToCompare, '===', thisClaimIdSegmentToCompare, '?'); - return (otherClaimIdSegmentToCompare === thisClaimIdSegmentToCompare); - }); - } - // use that longest shared prefix to get only those competing claims - const lastMatchIndex = i - 1; - const lastMatch = claimId.substring(0, lastMatchIndex); - logger.debug('last match index:', lastMatchIndex, 'last match:', lastMatch); - if (lastMatchIndex === 0) { // if no other claims share a prefix, return with first letter. - return claimId.substring(0, 1); - } - const allMatchingClaimsAtLastMatch = claimListCopy.filter(claim => { - return (claim.claimId.substring(0, lastMatchIndex) === lastMatch); - }); - // for those that share the longest shared prefix: see which came first in time. whichever is earliest, the others take the extra character - const sortedMatchingClaims = allMatchingClaimsAtLastMatch.sort((a, b) => { - return (a.height < b.height); - }); - // compare to the earliest one, if it is earlier, this claim takes the extra character - if (sortedMatchingClaims[0].height < height) { - return claimId.substring(0, lastMatchIndex + 1); - } - return claimId.substring(0, lastMatchIndex); - } -} - function createOpenGraphInfo ({ fileType, claimId, name, fileName, fileExt }) { return { embedUrl : `https://spee.ch/embed/${claimId}/${name}`, @@ -57,6 +10,107 @@ function createOpenGraphInfo ({ fileType, claimId, name, fileName, fileExt }) { }; } +function getLongChannelIdFromShortChannelId (channelName, channelId) { + return new Promise((resolve, reject) => { + logger.debug(`finding long channel id for ${channelName}:${channelId}`); + // get the long channel Id + db.sequelize.query(`SELECT claimId, height FROM Certificate WHERE name = '${channelName}' AND claimId LIKE '${channelId}%' ORDER BY height ASC LIMIT 1;`, { type: db.sequelize.QueryTypes.SELECT }) + .then(result => { + logger.debug('result >>', result); + switch (result.length) { + case 0: + throw new Error('That is an invalid Short Channel Id'); + default: // note results must be sorted + return resolve(result[0].claimId); + } + }) + .catch(error => { + reject(error); + }); + }); +} + +function getLongChannelIdFromChannelName (channelName) { + // select the top top channel id + return new Promise((resolve, reject) => { + logger.debug(`finding long channel id for ${channelName}`); + // get the long channel Id + db.sequelize.query(`SELECT claimId, amount, height FROM Certificate WHERE name = '${channelName}' ORDER BY amount DESC, height ASC LIMIT 1;`, { type: db.sequelize.QueryTypes.SELECT }) + .then(result => { + logger.debug('result >>', result); + switch (result.length) { + case 0: + throw new Error('That is an invalid Channel Name'); + default: + return resolve(result[0].claimId); + } + }) + .catch(error => { + reject(error); + }); + }); +} + +function getLongClaimIdFromShortClaimId (name, shortId) { + return new Promise((resolve, reject) => { + logger.debug('getting claim_id from short url'); + // use the daemon to check for claims list + db.sequelize.query(`SELECT claimId FROM Claim WHERE name = '${name}' AND claimId LIKE '${shortId}%' ORDER BY height ASC LIMIT 1;`, { type: db.sequelize.QueryTypes.SELECT }) + .then(result => { + switch (result.length) { + case 0: + return reject(new Error('That is an invalid Short Claim Id')); + default: // note results must be sorted + return resolve(result[0].claimId); + } + }) + .catch(error => { + reject(error); + }); + }); +} + +function getTopFreeClaimIdByClaimName (name) { + return new Promise((resolve, reject) => { + db.sequelize.query(`SELECT claimId FROM Claim WHERE name = '${name}' ORDER BY amount DESC, height ASC LIMIT 1`, { type: db.sequelize.QueryTypes.SELECT }) + .then(result => { + switch (result.length) { + case 0: + return resolve(null); + default: + return resolve(result[0].claimId); + } + }) + .catch(error => { + reject(error); + }); + }); +}; + +function sortResult (result, longId) { + let claimIndex; + let shortId = longId.substring(0, 1); // default sort id is the first letter + let shortIdLength = 0; + // find the index of this certificate + claimIndex = result.findIndex(element => { + return element.claimId === longId; + }); + if (claimIndex < 0) { throw new Error('claimid not found in possible sorted list') } + // get an array of all certificates with lower height + let possibleMatches = result.slice(0, claimIndex); + // remove certificates with the same prefixes until none are left. + while (possibleMatches.length > 0) { + shortIdLength += 1; + shortId = longId.substring(0, shortIdLength); + possibleMatches = possibleMatches.filter(element => { + return (element.claimId.substring(0, shortIdLength) === shortId); + }); + } + // return the short Id + logger.debug('short channel id ===', shortId); + return shortId; +} + module.exports = { serveFile ({ fileName, fileType, filePath }, res) { logger.info(`serving file ${fileName}`); @@ -90,36 +144,25 @@ module.exports = { const openGraphInfo = createOpenGraphInfo(fileInfo); res.status(200).render('showLite', { layout: 'show', fileInfo, openGraphInfo }); }, - getFullClaimIdFromShortId (shortId, name) { - return new Promise((resolve, reject) => { - logger.debug('getting claim_id from short url'); - // use the daemon to check for claims list - db.sequelize.query(`SELECT claimId FROM Claim WHERE name = '${name}' AND claimId LIKE '${shortId}%' ORDER BY height ASC LIMIT 1;`, { type: db.sequelize.QueryTypes.SELECT }) - .then(result => { - switch (result.length) { - case 0: - return reject(new Error('That is an invalid Short Id')); - default: // note results must be sorted - return resolve(result[0].claimId); - } - }) - .catch(error => { - reject(error); - }); - }); + getLongClaimId (claimName, claimId) { // read the various inputs and decide how to return the long claim id + if (claimId && (claimId.length === 40)) { + return new Promise((resolve, reject) => resolve(claimId)); + } else if (claimId && claimId.length < 40) { + return getLongClaimIdFromShortClaimId(claimName, claimId); // need to create this function + } else { // if no claim id provided + return getTopFreeClaimIdByClaimName(claimName); + } }, - getShortIdFromClaimId (claimId, height, name) { + getShortClaimIdFromLongClaimId (claimId, claimName) { return new Promise((resolve, reject) => { - logger.debug('finding short claim id from full claim id'); - db.sequelize.query(`SELECT claimId, height FROM Claim WHERE name = '${name}' ORDER BY claimId;`, { type: db.sequelize.QueryTypes.SELECT }) + logger.debug('finding short channel id'); + db.sequelize.query(`SELECT claimId, height FROM Claim WHERE name = '${claimName}' ORDER BY height;`, { type: db.sequelize.QueryTypes.SELECT }) .then(result => { switch (result.length) { case 0: - return reject(new Error('That is an invalid Claim Id')); - default: // note results must be sorted - const shortId = determineShortClaimId(claimId, height, result); - logger.debug('short claim id ===', shortId); - return resolve(shortId); + return reject(new Error('That is an invalid claim name')); + default: + return resolve(sortResult(result, claimId)); } }) .catch(error => { @@ -129,7 +172,7 @@ module.exports = { }, getAllFreeClaims (name) { return new Promise((resolve, reject) => { - db.sequelize.query(`SELECT * FROM Claim WHERE name = '${name}' ORDER BY amount DESC, height ASC`, { type: db.sequelize.QueryTypes.SELECT }) + db.sequelize.query(`SELECT name, claimId, outpoint, height, address FROM Claim WHERE name = '${name}' ORDER BY amount DESC, height ASC`, { type: db.sequelize.QueryTypes.SELECT }) .then(result => { switch (result.length) { case 0: @@ -143,25 +186,9 @@ module.exports = { }); }); }, - getTopFreeClaim (name) { - return new Promise((resolve, reject) => { - db.sequelize.query(`SELECT * FROM Claim WHERE name = '${name}' ORDER BY amount DESC, height ASC LIMIT 1`, { type: db.sequelize.QueryTypes.SELECT }) - .then(result => { - switch (result.length) { - case 0: - return resolve(null); - default: - return resolve(result[0]); - } - }) - .catch(error => { - reject(error); - }); - }); - }, resolveAgainstClaimTable (name, claimId) { return new Promise((resolve, reject) => { - db.sequelize.query(`SELECT * FROM Claim WHERE name = '${name}' AND claimId = '${claimId}'`, { type: db.sequelize.QueryTypes.SELECT }) + db.sequelize.query(`SELECT name, claimId, outpoint, height, address, title, description FROM Claim WHERE name = '${name}' AND claimId = '${claimId}'`, { type: db.sequelize.QueryTypes.SELECT }) .then(result => { switch (result.length) { case 0: @@ -177,4 +204,64 @@ module.exports = { }); }); }, + getClaimIdByLongChannelId (channelId, claimName) { + return new Promise((resolve, reject) => { + logger.debug(`finding claim id for claim "${claimName}" from channel "${channelId}"`); + db.sequelize.query(`SELECT claimId FROM Claim WHERE name = '${claimName}' AND certificateId = '${channelId}' LIMIT 1;`, { type: db.sequelize.QueryTypes.SELECT }) + .then(result => { + switch (result.length) { + case 0: + return reject(new Error('There is no such claim for that channel')); + default: + return resolve(result[0].claimId); + } + }) + .catch(error => { + reject(error); + }); + }); + }, + getAllChannelClaims (channelId) { + return new Promise((resolve, reject) => { + logger.debug(`finding all claims in channel "${channelId}"`); + db.sequelize.query(`SELECT name, claimId, outpoint, height, address, contentType, title, description, license FROM Claim WHERE certificateId = '${channelId}' ORDeR BY height DESC;`, { type: db.sequelize.QueryTypes.SELECT }) + .then(result => { + switch (result.length) { + case 0: + return resolve(null); + default: + return resolve(result); + } + }) + .catch(error => { + reject(error); + }); + }); + }, + getLongChannelId (channelName, channelId) { + if (channelId && (channelId.length === 40)) { // full channel id + return new Promise((resolve, reject) => resolve(channelId)); + } else if (channelId && channelId.length < 40) { // short channel id + return getLongChannelIdFromShortChannelId(channelName, channelId); + } else { + return getLongChannelIdFromChannelName(channelName); + } + }, + getShortChannelIdFromLongChannelId (channelName, longChannelId) { + return new Promise((resolve, reject) => { + logger.debug('finding short channel id'); + db.sequelize.query(`SELECT claimId, height FROM Certificate WHERE name = '${channelName}' ORDER BY height;`, { type: db.sequelize.QueryTypes.SELECT }) + .then(result => { + switch (result.length) { + case 0: + return reject(new Error('That is an invalid channel name')); + default: + return resolve(sortResult(result, longChannelId)); + } + }) + .catch(error => { + reject(error); + }); + }); + }, }; diff --git a/models/certificate.js b/models/certificate.js new file mode 100644 index 00000000..702e8783 --- /dev/null +++ b/models/certificate.js @@ -0,0 +1,91 @@ +module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, ARRAY, DECIMAL, DOUBLE }) => { + const Certificate = sequelize.define( + 'Certificate', + { + address: { + type : STRING, + default: null, + }, + amount: { + type : STRING, + default: null, + }, + claimId: { + type : STRING, + default: null, + }, + claimSequence: { + type : INTEGER, + default: null, + }, + decodedClaim: { + type : BOOLEAN, + default: null, + }, + depth: { + type : INTEGER, + default: null, + }, + effectiveAmount: { + type : STRING, + default: null, + }, + hasSignature: { + type : BOOLEAN, + default: null, + }, + height: { + type : STRING, + default: null, + }, + hex: { + type : TEXT('long'), + default: null, + }, + name: { + type : STRING, + default: null, + }, + nout: { + type : INTEGER, + default: null, + }, + txid: { + type : STRING, + default: null, + }, + validAtHeight: { + type : STRING, + default: null, + }, + outpoint: { + type : STRING, + default: null, + }, + valueVersion: { + type : STRING, + default: null, + }, + claimType: { + type : STRING, + default: null, + }, + certificateVersion: { + type : STRING, + default: null, + }, + keyType: { + type : STRING, + default: null, + }, + publicKey: { + type : TEXT('long'), + default: null, + }, + }, + { + freezeTableName: true, + } + ); + return Certificate; +}; diff --git a/public/assets/css/componentStyle.css b/public/assets/css/componentStyle.css index 2aefbb13..fd6d0deb 100644 --- a/public/assets/css/componentStyle.css +++ b/public/assets/css/componentStyle.css @@ -163,7 +163,7 @@ button.copy-button { overflow: auto; } -.all-claims-img { +.all-claims-asset { width: 20%; float: left; margin: 5px 30px 5px 0px; @@ -187,7 +187,7 @@ button.copy-button { @media (max-width: 750px) { - .all-claims-img { + .all-claims-asset { width:30%; } @@ -214,7 +214,7 @@ button.copy-button { margin-right: 2em; } - .all-claims-img { + .all-claims-asset { width:50%; } diff --git a/routes/page-routes.js b/routes/page-routes.js index c94b1472..b4189bd6 100644 --- a/routes/page-routes.js +++ b/routes/page-routes.js @@ -1,6 +1,6 @@ const errorHandlers = require('../helpers/errorHandlers.js'); const { getAllFreeClaims } = require('../helpers/serveHelpers.js'); -const { postToStats, getStatsSummary, getTrendingClaims } = require('../controllers/statsController.js'); +const { postToStats, getStatsSummary, getTrendingClaims, getRecentClaims } = require('../controllers/statsController.js'); module.exports = (app) => { // route to show 'about' page for spee.ch @@ -9,7 +9,7 @@ module.exports = (app) => { res.status(200).render('about'); }); // route to display a list of the trending images - app.get('/trending', ({ params, headers }, res) => { + app.get('/popular', ({ params, headers }, res) => { const startDate = new Date(); startDate.setDate(startDate.getDate() - 1); getTrendingClaims(startDate) @@ -21,6 +21,19 @@ module.exports = (app) => { errorHandlers.handleRequestError(error, res); }); }); + // route to display a list of the trending images + app.get('/new', ({ params, headers }, res) => { + const startDate = new Date(); + startDate.setDate(startDate.getDate() - 1); + getRecentClaims(startDate) + .then(result => { + // logger.debug(result); + res.status(200).render('new', { newClaims: result }); + }) + .catch(error => { + errorHandlers.handleRequestError(error, res); + }); + }); // route to show statistics for spee.ch app.get('/stats', ({ ip, originalUrl }, res) => { // get and render the content diff --git a/routes/serve-routes.js b/routes/serve-routes.js index cfbab943..6937c9a3 100644 --- a/routes/serve-routes.js +++ b/routes/serve-routes.js @@ -1,72 +1,7 @@ const logger = require('winston'); -const { serveFile, showFile, showFileLite, getShortIdFromClaimId, resolveAgainstClaimTable } = require('../helpers/serveHelpers.js'); -const { getAssetByChannel, getAssetByShortId, getAssetByClaimId, getAssetByName } = require('../controllers/serveController.js'); +const { getAssetByClaim, getChannelContents, getAssetByChannel, serveOrShowAsset } = require('../controllers/serveController.js'); const { handleRequestError } = require('../helpers/errorHandlers.js'); -const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); -const SERVE = 'SERVE'; -const SHOW = 'SHOW'; -const SHOWLITE = 'SHOWLITE'; -const CHANNEL = 'CHANNEL'; -const SHORTURL = 'SHORTURL'; -const CLAIMID = 'CLAIMID'; -const NAME = 'NAME'; - -function getAsset (claimType, channelName, shortId, fullClaimId, name) { - switch (claimType) { - case CHANNEL: - return getAssetByChannel(channelName, name); - case SHORTURL: - return getAssetByShortId(shortId, name); - case CLAIMID: - return getAssetByClaimId(fullClaimId, name); - case NAME: - return getAssetByName(name); - default: - return new Error('that claim type was not found'); - } -} - -function serveOrShowAsset (fileInfo, extension, method, headers, originalUrl, ip, res) { - // add file extension to the file info - if (extension === '.gifv') { - fileInfo['fileExt'] = '.gifv'; - } else { - fileInfo['fileExt'] = fileInfo.fileName.substring(fileInfo.fileName.lastIndexOf('.')); - } - // serve or show - switch (method) { - case SERVE: - serveFile(fileInfo, res); - sendGoogleAnalytics(method, headers, ip, originalUrl); - postToStats('serve', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); - return fileInfo; - case SHOWLITE: - showFileLite(fileInfo, res); - postToStats('show', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); - return fileInfo; - case SHOW: - return getShortIdFromClaimId(fileInfo.claimId, fileInfo.height, fileInfo.name) - .then(shortId => { - fileInfo['shortId'] = shortId; - return resolveAgainstClaimTable(fileInfo.name, fileInfo.claimId); - }) - .then(resolveResult => { - logger.debug('resolve result', resolveResult); - fileInfo['title'] = resolveResult.title; - fileInfo['description'] = resolveResult.description; - showFile(fileInfo, res); - postToStats('show', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); - return fileInfo; - }) - .catch(error => { - console.log('thowing error...'); - throw error; - }); - default: - logger.error('I did not recognize that method'); - break; - } -} +const { SERVE, SHOW, SHOWLITE, CHANNEL, CLAIM, CHANNELID_INDICATOR } = require('../helpers/constants.js'); function isValidClaimId (claimId) { return ((claimId.length === 40) && !/[^A-Za-z0-9]/g.test(claimId)); @@ -80,6 +15,17 @@ function isValidShortIdOrClaimId (input) { return (isValidClaimId(input) || isValidShortId(input)); } +function getAsset (claimType, channelName, channelId, name, claimId) { + switch (claimType) { + case CHANNEL: + return getAssetByChannel(channelName, channelId, name); + case CLAIM: + return getAssetByClaim(name, claimId); + default: + return new Error('that claim type was not found'); + } +} + module.exports = (app) => { // route to serve a specific asset app.get('/:identifier/:name', ({ headers, ip, originalUrl, params }, res) => { @@ -87,8 +33,8 @@ module.exports = (app) => { let name = params.name; let claimType; let channelName = null; - let shortId = null; - let fullClaimId = null; + let claimId = null; + let channelId = null; let method; let extension; // parse the name @@ -121,24 +67,21 @@ module.exports = (app) => { logger.debug('method =', method); // parse identifier for whether it is a channel, short url, or claim_id if (identifier.charAt(0) === '@') { - channelName = identifier.substring(1); - logger.debug('channel name =', channelName); + channelName = identifier; claimType = CHANNEL; - } else if (identifier.length === 40) { - fullClaimId = identifier; - logger.debug('full claim id =', fullClaimId); - claimType = CLAIMID; - } else if (identifier.length < 40) { - shortId = identifier; - logger.debug('short claim id =', shortId); - claimType = SHORTURL; + const channelIdIndex = channelName.indexOf(CHANNELID_INDICATOR); + if (channelIdIndex !== -1) { + channelId = channelName.substring(channelIdIndex + 1); + channelName = channelName.substring(0, channelIdIndex); + } + logger.debug('channel name =', channelName); } else { - logger.error('The URL provided could not be parsed'); - res.send('that url is invalid'); - return; - }; + claimId = identifier; + logger.debug('claim id =', claimId); + claimType = CLAIM; + } // 1. retrieve the asset and information - getAsset(claimType, channelName, shortId, fullClaimId, name) + getAsset(claimType, channelName, channelId, name, claimId) // 2. serve or show .then(fileInfo => { logger.debug('fileInfo', fileInfo); @@ -162,38 +105,68 @@ module.exports = (app) => { let name = params.name; let method; let fileExtension; - if (name.indexOf('.') !== -1) { - method = SERVE; - if (headers['accept'] && headers['accept'].split(',').includes('text/html')) { - method = SHOWLITE; + let channelName = null; + let channelId = null; + if (name.charAt(0) === '@') { + channelName = name; + const channelIdIndex = channelName.indexOf(CHANNELID_INDICATOR); + if (channelIdIndex !== -1) { + channelId = channelName.substring(channelIdIndex + 1); + channelName = channelName.substring(0, channelIdIndex); } - fileExtension = name.substring(name.indexOf('.')); - name = name.substring(0, name.indexOf('.')); - logger.debug('file extension =', fileExtension); + logger.debug('channel name =', channelName); + logger.debug('channel Id =', channelId); + // 1. retrieve the channel contents + getChannelContents(channelName, channelId) + // 2. respond to the request + .then(channelContents => { + if (!channelContents) { + res.status(200).render('noChannel'); + } else { + const handlebarsData = { + channelName, + channelContents, + }; + res.status(200).render('channel', handlebarsData); + } + }) + .catch(error => { + handleRequestError('serve', originalUrl, ip, error, res); + }); } else { - method = SHOW; - if (headers['accept'] && !headers['accept'].split(',').includes('text/html')) { + if (name.indexOf('.') !== -1) { method = SERVE; - } - } - logger.debug('claim name = ', name); - logger.debug('method =', method); - // 1. retrieve the asset and information - getAsset(NAME, null, null, null, name) - // 2. serve or show - .then(fileInfo => { - if (!fileInfo) { - res.status(200).render('noClaims'); + if (headers['accept'] && headers['accept'].split(',').includes('text/html')) { + method = SHOWLITE; + } + fileExtension = name.substring(name.indexOf('.')); + name = name.substring(0, name.indexOf('.')); + logger.debug('file extension =', fileExtension); } else { - return serveOrShowAsset(fileInfo, null, method, headers, originalUrl, ip, res); + method = SHOW; + if (headers['accept'] && !headers['accept'].split(',').includes('text/html')) { + method = SERVE; + } } - }) - // 3. update the database - .then(fileInfoForUpdate => { - // if needed, this is where we would update the file - }) - .catch(error => { - handleRequestError('serve', originalUrl, ip, error, res); - }); + logger.debug('claim name = ', name); + logger.debug('method =', method); + // 1. retrieve the asset and information + getAsset(CLAIM, null, null, name, null) + // 2. respond to the request + .then(fileInfo => { + if (!fileInfo) { + res.status(200).render('noClaims'); + } else { + return serveOrShowAsset(fileInfo, null, method, headers, originalUrl, ip, res); + } + }) + // 3. update the database + .then(fileInfoForUpdate => { + // if needed, this is where we would update the file + }) + .catch(error => { + handleRequestError('serve', originalUrl, ip, error, res); + }); + } }); }; diff --git a/views/allClaims.handlebars b/views/allClaims.handlebars index 1f5f9f3c..9531a577 100644 --- a/views/allClaims.handlebars +++ b/views/allClaims.handlebars @@ -5,7 +5,7 @@
These are all the free, public assets at that claim. You can publish more at spee.ch.
{{#each claims}}Below are all the free claims in this channel.
+ {{#each channelContents}} +