Resolve channels #150

Merged
bones7242 merged 23 commits from resolve-channels into master 2017-08-25 18:35:40 +02:00
3 changed files with 164 additions and 226 deletions
Showing only changes of commit c4a4f93a32 - Show all commits

View file

@ -1,7 +1,11 @@
const lbryApi = require('../helpers/lbryApi.js'); const lbryApi = require('../helpers/lbryApi.js');
const db = require('../models'); const db = require('../models');
const logger = require('winston'); const logger = require('winston');
const { getTopFreeClaim, getFullClaimIdFromShortId, resolveAgainstClaimTable, getClaimIdByChannel } = require('../helpers/serveHelpers.js'); const { getTopFreeClaim, getFullClaimIdFromShortId, resolveAgainstClaimTable, serveFile, showFile, showFileLite, getShortClaimIdFromLongClaimId, getClaimIdByLongChannelId, getAllChannelClaims, getLongChannelId, getShortChannelIdFromLongChannelId } = require('../helpers/serveHelpers.js');
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
kauffj commented 2017-08-25 19:21:21 +02:00 (Migrated from github.com)
Review

This looks like it could use better organization. I'd be inclined to put a lot of these inside of the modal folder, but I'd suggest looking at what other conventions are used in this framework before doing that.

This looks like it could use better organization. I'd be inclined to put a lot of these inside of the `modal` folder, but I'd suggest looking at what other conventions are used in this framework before doing that.
bones7242 commented 2017-09-08 02:27:09 +02:00 (Migrated from github.com)
Review

I get a little confused sometimes on the line between controller and model responsibilities. But, yes, after looking at it and re-reading some MVC notes, I think that all the database related functions should go in the model. I rearranged as such.
af046e9d36

I get a little confused sometimes on the line between controller and model responsibilities. But, yes, after looking at it and re-reading some MVC notes, I think that all the database related functions should go in the model. I rearranged as such. https://github.com/lbryio/spee.ch/commit/af046e9d3655a57bf9cd67d3a67a54d4d44ed802
const SERVE = 'SERVE';
const SHOW = 'SHOW';
const SHOWLITE = 'SHOWLITE';
function checkForLocalAssetByClaimId (claimId, name) { function checkForLocalAssetByClaimId (claimId, name) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -90,19 +94,6 @@ function getAssetByClaimId (fullClaimId, name) {
} }
module.exports = { module.exports = {
getAssetByChannel (channelName, channelId, claimName) {
logger.debug('channelId =', channelId);
return new Promise((resolve, reject) => {
getClaimIdByChannel(channelName, channelId, claimName)
.then(claimId => {
logger.debug('claim id = ', claimId);
resolve(getAssetByClaimId(claimId, claimName));
})
.catch(error => {
reject(error);
});
});
},
getAssetByShortId: function (shortId, name) { getAssetByShortId: function (shortId, name) {
logger.debug('...getting asset by short id...'); logger.debug('...getting asset by short id...');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -143,10 +134,97 @@ module.exports = {
}); });
}); });
}, },
getChannelByName (name) { getAssetByChannel (channelName, channelId, claimName) {
logger.debug('...getting channel by channel name...'); logger.debug('channelId =', channelId);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
reject(new Error('This feature is not yet supported')); // 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(getAssetByClaimId(claimId, claimName));
})
.catch(error => {
reject(error);
});
}); });
}, },
getChannelContents (channelName, channelId) {
return new Promise((resolve, reject) => {
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);
});
});
},
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;
}
},
}; };

View file

@ -10,16 +10,6 @@ function createOpenGraphInfo ({ fileType, claimId, name, fileName, fileExt }) {
}; };
} }
function 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);
}
};
function getLongChannelIdFromShortChannelId (channelName, channelId) { function getLongChannelIdFromShortChannelId (channelName, channelId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.debug(`finding long channel id for ${channelName}:${channelId}`); logger.debug(`finding long channel id for ${channelName}:${channelId}`);
@ -61,125 +51,28 @@ function getLongChannelIdFromChannelName (channelName) {
}); });
} }
function getClaimIdByLongChannelId (channelId, claimName) { function sortResult (result, longId) {
return new Promise((resolve, reject) => { let claimIndex;
logger.debug(`finding claim id for claim "${claimName}" from channel "${channelId}"`); let shortId = longId.substring(0, 1); // default sort id is the first letter
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);
});
});
}
function getAllChannelClaims (channelId) {
return new Promise((resolve, reject) => {
logger.debug(`finding all claims in channel "${channelId}"`);
db.sequelize.query(`SELECT * 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);
});
});
}
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 getShortChannelId (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:
let certificateIndex;
let shortId = longChannelId.substring(0, 1); // default sort id is the first letter
let shortIdLength = 0; let shortIdLength = 0;
// find the index of this certificate // find the index of this certificate
certificateIndex = result.findIndex(element => { claimIndex = result.findIndex(element => {
return element.claimId === longChannelId; return element.claimId === longId;
}); });
if (certificateIndex < 0) { throw new Error('claimid not found in possible sorted list') } if (claimIndex < 0) { throw new Error('claimid not found in possible sorted list') }
// get an array of all certificates with lower height // get an array of all certificates with lower height
let possibleMatches = result.slice(0, certificateIndex); let possibleMatches = result.slice(0, claimIndex);
// remove certificates with the same prefixes until none are left. // remove certificates with the same prefixes until none are left.
while (possibleMatches.length > 0) { while (possibleMatches.length > 0) {
shortIdLength += 1; shortIdLength += 1;
shortId = longChannelId.substring(0, shortIdLength); shortId = longId.substring(0, shortIdLength);
possibleMatches = possibleMatches.filter(element => { possibleMatches = possibleMatches.filter(element => {
return (element.claimId.substring(0, shortIdLength) === shortId); return (element.claimId.substring(0, shortIdLength) === shortId);
}); });
} }
// return the short Id // return the short Id
logger.debug('short channel id ===', shortId); logger.debug('short channel id ===', shortId);
return resolve(shortId); return shortId;
}
})
.catch(error => {
reject(error);
});
});
} }
module.exports = { module.exports = {
@ -233,18 +126,16 @@ module.exports = {
}); });
}); });
}, },
getShortIdFromClaimId (claimId, height, name) { getShortClaimIdFromLongClaimId (claimId, claimName) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.debug('finding short claim id from full claim id'); logger.debug('finding short channel id');
db.sequelize.query(`SELECT claimId, height FROM Claim WHERE name = '${name}' ORDER BY claimId;`, { type: db.sequelize.QueryTypes.SELECT }) db.sequelize.query(`SELECT claimId, height FROM Claim WHERE name = '${claimName}' ORDER BY height;`, { type: db.sequelize.QueryTypes.SELECT })
.then(result => { .then(result => {
switch (result.length) { switch (result.length) {
case 0: case 0:
return reject(new Error('That is an invalid claim name')); return reject(new Error('That is an invalid claim name'));
default: // note results must be sorted default:
const shortId = determineShortClaimId(claimId, height, result); return resolve(sortResult(result, claimId));
logger.debug('short claim id ===', shortId);
return resolve(shortId);
} }
}) })
.catch(error => { .catch(error => {
@ -302,47 +193,60 @@ module.exports = {
}); });
}); });
}, },
getClaimIdByChannel (channelName, channelId, claimName) { getClaimIdByLongChannelId (channelId, claimName) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 1. get the long channel id logger.debug(`finding claim id for claim "${claimName}" from channel "${channelId}"`);
getLongChannelId(channelName, channelId) db.sequelize.query(`SELECT claimId FROM Claim WHERE name = '${claimName}' AND certificateId = '${channelId}' LIMIT 1;`, { type: db.sequelize.QueryTypes.SELECT })
// 2. get the claim Id .then(result => {
.then(longChannelId => { switch (result.length) {
return getClaimIdByLongChannelId(longChannelId, claimName); case 0:
}) return reject(new Error('There is no such claim for that channel'));
.then(claimId => { default:
return resolve(claimId); return resolve(result[0].claimId);
}
}) })
.catch(error => { .catch(error => {
reject(error); reject(error);
}); });
}); });
}, },
getChannelContents (channelName, channelId) { getAllChannelClaims (channelId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let longChannelId; logger.debug(`finding all claims in channel "${channelId}"`);
let shortChannelId; db.sequelize.query(`SELECT * FROM Claim WHERE certificateId = '${channelId}' ORDeR BY height DESC;`, { type: db.sequelize.QueryTypes.SELECT })
// 1. get the long channel Id
getLongChannelId(channelName, channelId)
// 2. get all claims for that channel
.then(result => { .then(result => {
longChannelId = result; switch (result.length) {
return getShortChannelId(channelName, longChannelId); case 0:
}) return resolve(null);
.then(result => { default:
shortChannelId = result; return resolve(result);
return getAllChannelClaims(longChannelId); }
}) })
.then(allChannelClaims => { .catch(error => {
if (allChannelClaims) { reject(error);
allChannelClaims.forEach(element => { });
element['channelName'] = channelName; });
element['longChannelId'] = longChannelId; },
element['shortChannelId'] = shortChannelId; getLongChannelId (channelName, channelId) {
element['fileExtension'] = element.contentType.substring(element.contentType.lastIndexOf('/') + 1); 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));
} }
return resolve(allChannelClaims);
}) })
.catch(error => { .catch(error => {
reject(error); reject(error);

View file

@ -1,8 +1,6 @@
const logger = require('winston'); const logger = require('winston');
const { serveFile, showFile, showFileLite, getShortIdFromClaimId, resolveAgainstClaimTable, getChannelContents } = require('../helpers/serveHelpers.js'); const { getAssetByShortId, getAssetByClaimId, getAssetByName, getChannelContents, getAssetByChannel, serveOrShowAsset } = require('../controllers/serveController.js');
const { getAssetByChannel, getAssetByShortId, getAssetByClaimId, getAssetByName } = require('../controllers/serveController.js');
const { handleRequestError } = require('../helpers/errorHandlers.js'); const { handleRequestError } = require('../helpers/errorHandlers.js');
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
const SERVE = 'SERVE'; const SERVE = 'SERVE';
const SHOW = 'SHOW'; const SHOW = 'SHOW';
const SHOWLITE = 'SHOWLITE'; const SHOWLITE = 'SHOWLITE';
@ -27,48 +25,6 @@ function getAsset (claimType, channelName, channelId, shortId, fullClaimId, name
} }
} }
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;
}
}
function isValidClaimId (claimId) { function isValidClaimId (claimId) {
return ((claimId.length === 40) && !/[^A-Za-z0-9]/g.test(claimId)); return ((claimId.length === 40) && !/[^A-Za-z0-9]/g.test(claimId));
} }