diff --git a/.gitignore b/.gitignore index b512c09d..34977ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +.idea \ No newline at end of file diff --git a/controllers/publishController.js b/controllers/publishController.js index 431c3020..fd54d347 100644 --- a/controllers/publishController.js +++ b/controllers/publishController.js @@ -42,7 +42,7 @@ module.exports = { }; return Promise.all([db.upsert(db.File, fileRecord, upsertCriteria, 'File'), db.upsert(db.Claim, fileRecord, upsertCriteria, 'Claim')]); }) - .then((fileRecordResults, claimRecordResults) => { + .then(() => { logger.debug('File and Claim records successfully created'); resolve(publishResults); // resolve the promise with the result from lbryApi.publishClaim; }) diff --git a/controllers/serveController.js b/controllers/serveController.js index b5e5bb06..aaa4eb22 100644 --- a/controllers/serveController.js +++ b/controllers/serveController.js @@ -1,9 +1,12 @@ const lbryApi = require('../helpers/lbryApi.js'); const db = require('../models'); const logger = require('winston'); -const { resolveAgainstClaimTable, serveFile, showFile, showFileLite, getShortClaimIdFromLongClaimId, getClaimIdByLongChannelId, getAllChannelClaims, getLongChannelId, getShortChannelIdFromLongChannelId, getLongClaimId } = require('../helpers/serveHelpers.js'); +const { serveFile, showFile, showFileLite } = require('../helpers/serveHelpers.js'); const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); -const { SERVE, SHOW, SHOWLITE } = require('../helpers/constants.js'); + +const SERVE = 'SERVE'; +const SHOW = 'SHOW'; +const SHOWLITE = 'SHOWLITE'; function checkForLocalAssetByClaimId (claimId, name) { return new Promise((resolve, reject) => { @@ -56,34 +59,35 @@ function getAssetByLongClaimId (fullClaimId, name) { } logger.debug('no local file found for this name and claimId'); // 2. if no local claim, resolve and get the claim - resolveAgainstClaimTable(name, fullClaimId) - .then(resolveResult => { - logger.debug('resolve result >> ', resolveResult); - // if no result, return early (claim doesn't exist or isn't free) - if (!resolveResult) { - return resolve(null); - } - let fileRecord = {}; - // get the claim - lbryApi.getClaim(`${name}#${fullClaimId}`) - .then(getResult => { - logger.debug('getResult >>', getResult); - fileRecord = createFileRecord(resolveResult); - fileRecord = addGetResultsToFileRecord(fileRecord, getResult); - // insert a record in the File table & Update Claim table - return db.File.create(fileRecord); - }) - .then(fileRecordResults => { - logger.debug('File record successfully updated'); - resolve(fileRecord); + db + .resolveClaim(name, fullClaimId) + .then(resolveResult => { + logger.debug('resolve result >> ', resolveResult); + // if no result, return early (claim doesn't exist or isn't free) + if (!resolveResult) { + return resolve(null); + } + let fileRecord = {}; + // get the claim + lbryApi.getClaim(`${name}#${fullClaimId}`) + .then(getResult => { + logger.debug('getResult >>', getResult); + fileRecord = createFileRecord(resolveResult); + fileRecord = addGetResultsToFileRecord(fileRecord, getResult); + // insert a record in the File table & Update Claim table + return db.File.create(fileRecord); + }) + .then(fileRecordResults => { + logger.debug('File record successfully updated'); + resolve(fileRecord); + }) + .catch(error => { + reject(error); + }); }) .catch(error => { reject(error); }); - }) - .catch(error => { - reject(error); - }); }) .catch(error => { reject(error); @@ -96,34 +100,36 @@ module.exports = { logger.debug('getting asset by claim'); return new Promise((resolve, reject) => { // 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); - }); + db + .getLongClaimId(claimName, claimId) + // 2. get the claim Id + .then(longClaimId => { + logger.debug('long claim id = ', longClaimId); + resolve(getAssetByLongClaimId(longClaimId, claimName)); + }) + .catch(error => { + reject(error); + }); }); }, getAssetByChannel (channelName, channelId, claimName) { logger.debug('getting asset by channel'); return new Promise((resolve, reject) => { // 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('asset claim id = ', claimId); - resolve(getAssetByLongClaimId(claimId, claimName)); - }) - .catch(error => { - reject(error); - }); + db + .getLongChannelId(channelName, channelId) + // 2. get the claim Id + .then(longChannelId => { + return db.getClaimIdByLongChannelId(longChannelId, claimName); + }) + // 3. get the asset by this claim id and name + .then(claimId => { + logger.debug('asset claim id = ', claimId); + resolve(getAssetByLongClaimId(claimId, claimName)); + }) + .catch(error => { + reject(error); + }); }); }, getChannelContents (channelName, channelId) { @@ -131,70 +137,70 @@ module.exports = { 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); - }); - } - return resolve(allChannelClaims); - }) - .catch(error => { - reject(error); - }); + db + .getLongChannelId(channelName, channelId) + // 2. get all claims for that channel + .then(result => { + longChannelId = result; + return db.getShortChannelIdFromLongChannelId(channelName, longChannelId); + }) + // 3. get all Claim records for this channel + .then(result => { + shortChannelId = result; + return db.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); + }); + } + 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'; + if (extension === 'gifv') { + fileInfo['fileExt'] = 'gifv'; } else { - fileInfo['fileExt'] = fileInfo.fileName.substring(fileInfo.fileName.lastIndexOf('.')); + fileInfo['fileExt'] = fileInfo.fileName.substring(fileInfo.fileName.lastIndexOf('.') + 1); } + // add stats table + postToStats(method, originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); // 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; - }); + return db + .getShortClaimIdFromLongClaimId(fileInfo.claimId, fileInfo.name) + .then(shortId => { + fileInfo['shortId'] = shortId; + return db.resolveClaim(fileInfo.name, fileInfo.claimId); + }) + .then(resolveResult => { + fileInfo['title'] = resolveResult.title; + fileInfo['description'] = resolveResult.description; + showFile(fileInfo, res); + return fileInfo; + }) + .catch(error => { + logger.error('throwing serve/show error...'); + throw error; + }); default: logger.error('I did not recognize that method'); break; diff --git a/controllers/statsController.js b/controllers/statsController.js index 98f954c6..cd103781 100644 --- a/controllers/statsController.js +++ b/controllers/statsController.js @@ -6,6 +6,7 @@ const googleApiKey = config.get('AnalyticsConfig.GoogleId'); module.exports = { postToStats (action, url, ipAddress, name, claimId, result) { + logger.debug('action:', action); // make sure the result is a string if (result && (typeof result !== 'string')) { result = result.toString(); @@ -89,18 +90,18 @@ module.exports = { let totalSuccess = 0; let totalFailure = 0; let percentSuccess; - // sumarise the data + // summarise the data for (let i = 0; i < data.length; i++) { let key = data[i].action + data[i].url; totalCount += 1; switch (data[i].action) { - case 'serve': + case 'SERVE': totalServe += 1; break; - case 'publish': + case 'PUBLISH': totalPublish += 1; break; - case 'show': + case 'SHOW': totalShow += 1; break; default: break; @@ -143,10 +144,9 @@ module.exports = { }, getTrendingClaims (startDate) { logger.debug('retrieving trending requests'); - const dateTime = startDate.toISOString().slice(0, 19).replace('T', ' '); return new Promise((resolve, reject) => { // get the raw requests data - db.sequelize.query(`SELECT COUNT(*), File.* FROM Request LEFT JOIN File ON Request.FileId = File.id WHERE FileId IS NOT NULL AND nsfw != 1 AND trendingEligible = 1 AND Request.createdAt > "${dateTime}" GROUP BY FileId ORDER BY COUNT(*) DESC LIMIT 25;`, { type: db.sequelize.QueryTypes.SELECT }) + db.getTrendingClaims(startDate) .then(results => { resolve(results); }) @@ -156,11 +156,11 @@ module.exports = { }); }); }, - getRecentClaims (startDate) { + getRecentClaims () { 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 }) + db.getRecentClaims() .then(results => { resolve(results); }) diff --git a/helpers/constants.js b/helpers/constants.js deleted file mode 100644 index 28c7b7f1..00000000 --- a/helpers/constants.js +++ /dev/null @@ -1,8 +0,0 @@ -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 34acf1ae..b270865f 100644 --- a/helpers/serveHelpers.js +++ b/helpers/serveHelpers.js @@ -1,5 +1,4 @@ const logger = require('winston'); -const db = require('../models'); function createOpenGraphInfo ({ fileType, claimId, name, fileName, fileExt }) { return { @@ -10,107 +9,6 @@ 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}`); @@ -144,124 +42,4 @@ module.exports = { const openGraphInfo = createOpenGraphInfo(fileInfo); res.status(200).render('showLite', { layout: 'show', fileInfo, openGraphInfo }); }, - 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); - } - }, - getShortClaimIdFromLongClaimId (claimId, claimName) { - return new Promise((resolve, reject) => { - 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 name')); - default: - return resolve(sortResult(result, claimId)); - } - }) - .catch(error => { - reject(error); - }); - }); - }, - getAllFreeClaims (name) { - return new Promise((resolve, reject) => { - 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: - return resolve(null); - default: - return resolve(result); - } - }) - .catch(error => { - reject(error); - }); - }); - }, - resolveAgainstClaimTable (name, claimId) { - return new Promise((resolve, reject) => { - 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: - return resolve(null); - case 1: - return resolve(result[0]); - default: - return new Error('more than one entry matches that name and claimID'); - } - }) - .catch(error => { - reject(error); - }); - }); - }, - 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/index.js b/models/index.js index fa8e8606..5316993e 100644 --- a/models/index.js +++ b/models/index.js @@ -9,8 +9,110 @@ const logger = require('winston'); const connectionUri = config.get('Database.MySqlConnectionUri'); const sequelize = new Sequelize(connectionUri, { logging: false, + pool : { + max : 5, + min : 0, + idle : 20000, + acquire: 20000, + }, }); +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; +} + +function getLongClaimIdFromShortClaimId (name, shortId) { + return new Promise((resolve, reject) => { + 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 getLongChannelIdFromShortChannelId (channelName, channelId) { + return new Promise((resolve, reject) => { + 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 => { + 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) { + return new Promise((resolve, reject) => { + 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 => { + switch (result.length) { + case 0: + throw new Error('That is an invalid Channel Name'); + default: + return resolve(result[0].claimId); + } + }) + .catch(error => { + reject(error); + }); + }); +} + sequelize .authenticate() .then(() => { @@ -37,6 +139,9 @@ Object.keys(db).forEach(modelName => { } }); +db.sequelize = sequelize; +db.Sequelize = Sequelize; + db['upsert'] = (Model, values, condition, tableName) => { return Model .findOne({ where: condition }) @@ -48,12 +153,152 @@ db['upsert'] = (Model, values, condition, tableName) => { logger.debug(`creating "${values.name}" "${values.claimId}" in db.${tableName}`); return Model.create(values); } - }).catch(function (error) { + }) + .catch(function (error) { logger.error('Sequelize findOne error', error); }); }; -db.sequelize = sequelize; -db.Sequelize = Sequelize; +db['getTrendingClaims'] = (startDate) => { + return db.sequelize.query(`SELECT COUNT(*), File.* FROM Request LEFT JOIN File ON Request.FileId = File.id WHERE FileId IS NOT NULL AND nsfw != 1 AND trendingEligible = 1 AND Request.createdAt > "${startDate}" GROUP BY FileId ORDER BY COUNT(*) DESC LIMIT 25;`, { type: db.sequelize.QueryTypes.SELECT }); +}; + +db['getRecentClaims'] = () => { + return db.sequelize.query(`SELECT * FROM File WHERE nsfw != 1 AND trendingEligible = 1 ORDER BY createdAt DESC LIMIT 25;`, { type: db.sequelize.QueryTypes.SELECT }); +}; + +db['getShortClaimIdFromLongClaimId'] = (claimId, claimName) => { + return new Promise((resolve, reject) => { + 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 name')); + default: + return resolve(sortResult(result, claimId)); + } + }) + .catch(error => { + reject(error); + }); + }); +}; + +db['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); + }); + }); +}; + +db['getAllFreeClaims'] = (name) => { + return new Promise((resolve, reject) => { + 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: + return resolve(null); + default: + return resolve(result); + } + }) + .catch(error => { + reject(error); + }); + }); +}; + +db['resolveClaim'] = (name, claimId) => { + return new Promise((resolve, reject) => { + 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: + return resolve(null); + case 1: + return resolve(result[0]); + default: + return new Error('more than one entry matches that name and claimID'); + } + }) + .catch(error => { + reject(error); + }); + }); +}; + +db['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); + }); + }); +}; + +db['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); + }); + }); +}; + +db['getLongClaimId'] = (claimName, claimId) => { + 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); + } +}; + +db['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); + } +}; module.exports = db; diff --git a/routes/home-routes.js b/routes/home-routes.js index 9afb7b66..ed3472f5 100644 --- a/routes/home-routes.js +++ b/routes/home-routes.js @@ -1,23 +1,12 @@ -const { postToStats, getTrendingClaims } = require('../controllers/statsController.js'); -const errorHandlers = require('../helpers/errorHandlers.js'); +const { postToStats } = require('../controllers/statsController.js'); module.exports = app => { // route for the home page - app.get('/', ({ headers, ip, originalUrl }, res) => { - // get yesterday's date - const startDate = new Date(); - startDate.setDate(startDate.getDate() - 3); - // send response - getTrendingClaims(startDate) - .then(result => { - res.status(200).render('index', { trendingAssets: result }); - }) - .catch(error => { - errorHandlers.handleRequestError(error, res); - }); + app.get('/', (req, res) => { + res.status(200).render('index'); }); // a catch-all route if someone visits a page that does not exist - app.use('*', ({ headers, originalUrl, ip }, res) => { + app.use('*', ({ originalUrl, ip }, res) => { // post to stats postToStats('show', originalUrl, ip, null, null, 'Error: 404'); // send response diff --git a/routes/page-routes.js b/routes/page-routes.js index b4189bd6..37c915b7 100644 --- a/routes/page-routes.js +++ b/routes/page-routes.js @@ -1,18 +1,22 @@ const errorHandlers = require('../helpers/errorHandlers.js'); -const { getAllFreeClaims } = require('../helpers/serveHelpers.js'); +const db = require('../models'); const { postToStats, getStatsSummary, getTrendingClaims, getRecentClaims } = require('../controllers/statsController.js'); module.exports = (app) => { // route to show 'about' page for spee.ch - app.get('/about', ({ ip, originalUrl }, res) => { + app.get('/about', (req, res) => { // get and render the content res.status(200).render('about'); }); // route to display a list of the trending images - app.get('/popular', ({ params, headers }, res) => { + app.get('/trending', (req, res) => { + res.status(301).redirect('/popular'); + }); + app.get('/popular', (req, res) => { const startDate = new Date(); startDate.setDate(startDate.getDate() - 1); - getTrendingClaims(startDate) + const dateTime = startDate.toISOString().slice(0, 19).replace('T', ' '); + getTrendingClaims(dateTime) .then(result => { // logger.debug(result); res.status(200).render('trending', { trendingAssets: result }); @@ -22,10 +26,8 @@ module.exports = (app) => { }); }); // 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) + app.get('/new', (req, res) => { + getRecentClaims() .then(result => { // logger.debug(result); res.status(200).render('new', { newClaims: result }); @@ -61,7 +63,8 @@ module.exports = (app) => { // route to display all free public claims at a given name app.get('/:name/all', ({ ip, originalUrl, params }, res) => { // get and render the content - getAllFreeClaims(params.name) + db + .getAllFreeClaims(params.name) .then(orderedFreeClaims => { if (!orderedFreeClaims) { res.status(307).render('noClaims'); diff --git a/routes/serve-routes.js b/routes/serve-routes.js index 6937c9a3..b7574e4b 100644 --- a/routes/serve-routes.js +++ b/routes/serve-routes.js @@ -1,7 +1,13 @@ const logger = require('winston'); const { getAssetByClaim, getChannelContents, getAssetByChannel, serveOrShowAsset } = require('../controllers/serveController.js'); const { handleRequestError } = require('../helpers/errorHandlers.js'); -const { SERVE, SHOW, SHOWLITE, CHANNEL, CLAIM, CHANNELID_INDICATOR } = require('../helpers/constants.js'); + +const SERVE = 'SERVE'; +const SHOW = 'SHOW'; +const SHOWLITE = 'SHOWLITE'; +const CHANNEL = 'CHANNEL'; +const CLAIM = 'CLAIM'; +const CHANNELID_INDICATOR = ':'; function isValidClaimId (claimId) { return ((claimId.length === 40) && !/[^A-Za-z0-9]/g.test(claimId)); @@ -36,18 +42,18 @@ module.exports = (app) => { let claimId = null; let channelId = null; let method; - let extension; + let fileExtension; // parse the name const positionOfExtension = name.indexOf('.'); if (positionOfExtension >= 0) { - extension = name.substring(positionOfExtension); + fileExtension = name.substring(positionOfExtension + 1); name = name.substring(0, positionOfExtension); /* patch because twitter player preview adds '>' before file extension */ if (name.indexOf('>') >= 0) { name = name.substring(0, name.indexOf('>')); } /* end patch */ - logger.debug('file extension =', extension); + logger.debug('file extension =', fileExtension); if (headers['accept'] && headers['accept'].split(',').includes('text/html')) { method = SHOWLITE; } else { @@ -84,11 +90,10 @@ module.exports = (app) => { getAsset(claimType, channelName, channelId, name, claimId) // 2. serve or show .then(fileInfo => { - logger.debug('fileInfo', fileInfo); if (!fileInfo) { res.status(200).render('noClaims'); } else { - return serveOrShowAsset(fileInfo, extension, method, headers, originalUrl, ip, res); + return serveOrShowAsset(fileInfo, fileExtension, method, headers, originalUrl, ip, res); } }) // 3. update the file @@ -139,7 +144,7 @@ module.exports = (app) => { if (headers['accept'] && headers['accept'].split(',').includes('text/html')) { method = SHOWLITE; } - fileExtension = name.substring(name.indexOf('.')); + fileExtension = name.substring(name.indexOf('.') + 1); name = name.substring(0, name.indexOf('.')); logger.debug('file extension =', fileExtension); } else { @@ -157,7 +162,7 @@ module.exports = (app) => { if (!fileInfo) { res.status(200).render('noClaims'); } else { - return serveOrShowAsset(fileInfo, null, method, headers, originalUrl, ip, res); + return serveOrShowAsset(fileInfo, fileExtension, method, headers, originalUrl, ip, res); } }) // 3. update the database diff --git a/routes/sockets-routes.js b/routes/sockets-routes.js index ecef3fc4..cd653e37 100644 --- a/routes/sockets-routes.js +++ b/routes/sockets-routes.js @@ -27,7 +27,7 @@ module.exports = (app, siofu, hostedContentPath) => { // listener for when file upload encounters an error uploader.on('error', ({ error }) => { logger.error('an error occured while uploading', error); - postToStats('publish', '/', null, null, null, error); + postToStats('PUBLISH', '/', null, null, null, error); socket.emit('publish-status', error); }); // listener for when file has been uploaded @@ -40,18 +40,18 @@ module.exports = (app, siofu, hostedContentPath) => { // publish the file publishController.publish(publishParams, file.name, file.meta.type) .then(result => { - postToStats('publish', '/', null, null, null, 'success'); + postToStats('PUBLISH', '/', null, null, null, 'success'); socket.emit('publish-complete', { name: publishParams.name, result }); }) .catch(error => { error = errorHandlers.handlePublishError(error); - postToStats('publish', '/', null, null, null, error); + postToStats('PUBLISH', '/', null, null, null, error); socket.emit('publish-failure', error); }); } else { logger.error(`An error occurred in uploading the client's file`); socket.emit('publish-failure', 'File uploaded, but with errors'); - postToStats('publish', '/', null, null, null, 'File uploaded, but with errors'); + postToStats('PUBLISH', '/', null, null, null, 'File uploaded, but with errors'); // to-do: remove the file if not done automatically } }); diff --git a/views/partials/asset.handlebars b/views/partials/asset.handlebars index 59d4deaa..eb607d24 100644 --- a/views/partials/asset.handlebars +++ b/views/partials/asset.handlebars @@ -1,8 +1,8 @@
{{/ifConditional}}