diff --git a/README.md b/README.md
index f27c9835..5669b79c 100644
--- a/README.md
+++ b/README.md
@@ -24,19 +24,23 @@ spee.ch is a single-serving site that reads and publishes images and videos to a
## API
#### GET
-* /api/resolve/:name
- * example: `curl https://spee.ch/api/resolve/doitlive`
-* /api/claim_list/:name
- * 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`
+* /api/claim-resolve/:name
+ * example: `curl https://spee.ch/api/claim-resolve/doitlive`
+* /api/claim-list/:name
+ * example: `curl https://spee.ch/api/claim-list/doitlive`
+* /api/claim-is-available/:name (
+ * returns `true`/`false` for whether a name is available through spee.ch
+ * example: `curl https://spee.ch/api/claim-is-available/doitlive`
+* /api/channel-is-available/:name (
+ * returns `true`/`false` for whether a channel is available through spee.ch
+ * example: `curl https://spee.ch/api/channel-is-available/@CoolChannel`
#### POST
-* /api/publish
- * example: `curl -X POST -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/publish`
+* /api/claim-publish
+ * example: `curl -X POST -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim-publish`
* Parameters:
* `name`
- * `file` (.mp4, .jpeg, .jpg, .gif, or .png)
+ * `file` (must be type .mp4, .jpeg, .jpg, .gif, or .png)
* `nsfw` (optional)
* `license` (optional)
* `title` (optional)
diff --git a/config/slackConfig.js b/config/slackConfig.js
index 84ab3376..109671f2 100644
--- a/config/slackConfig.js
+++ b/config/slackConfig.js
@@ -4,27 +4,30 @@ const winstonSlackWebHook = require('winston-slack-webhook').SlackWebHook;
module.exports = (winston) => {
if (config.logging.slackWebHook) {
// add a transport for errors to slack
- winston.add(winstonSlackWebHook, {
- name : 'slack-errors-transport',
- level : 'warn',
- webhookUrl: config.logging.slackWebHook,
- channel : config.logging.slackErrorChannel,
- username : 'spee.ch',
- iconEmoji : ':face_with_head_bandage:',
- });
- winston.add(winstonSlackWebHook, {
- name : 'slack-info-transport',
- level : 'info',
- webhookUrl: config.logging.slackWebHook,
- channel : config.logging.slackInfoChannel,
- username : 'spee.ch',
- iconEmoji : ':nerd_face:',
- });
+ if (config.logging.slackErrorChannel) {
+ winston.add(winstonSlackWebHook, {
+ name : 'slack-errors-transport',
+ level : 'warn',
+ webhookUrl: config.logging.slackWebHook,
+ channel : config.logging.slackErrorChannel,
+ username : 'spee.ch',
+ iconEmoji : ':face_with_head_bandage:',
+ });
+ };
+ if (config.logging.slackInfoChannel) {
+ winston.add(winstonSlackWebHook, {
+ name : 'slack-info-transport',
+ level : 'info',
+ webhookUrl: config.logging.slackWebHook,
+ channel : config.logging.slackInfoChannel,
+ username : 'spee.ch',
+ iconEmoji : ':nerd_face:',
+ });
+ };
// send test message
winston.error('Slack "error" logging is online.');
- winston.warn('Slack "warning" logging is online.');
winston.info('Slack "info" logging is online.');
} else {
- winston.warn('Slack logging is not enabled because no SLACK_WEB_HOOK env var provided.');
+ winston.warn('Slack logging is not enabled because no slackWebHook config var provided.');
}
};
diff --git a/controllers/serveController.js b/controllers/serveController.js
index 233906c5..e2e9ff13 100644
--- a/controllers/serveController.js
+++ b/controllers/serveController.js
@@ -1,253 +1,90 @@
-const lbryApi = require('../helpers/lbryApi.js');
const db = require('../models');
const logger = require('winston');
-const { serveFile, showFile, showFileLite } = require('../helpers/serveHelpers.js');
-const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
+const { returnPaginatedChannelViewData } = require('../helpers/channelPagination.js');
-const SERVE = 'SERVE';
-const SHOW = 'SHOW';
-const SHOWLITE = 'SHOWLITE';
-const DEFAULT_THUMBNAIL = 'https://spee.ch/assets/img/video_thumb_default.png';
const NO_CHANNEL = 'NO_CHANNEL';
const NO_CLAIM = 'NO_CLAIM';
-
-function checkForLocalAssetByClaimId (claimId, name) {
- logger.debug(`checkForLocalAssetsByClaimId(${claimId}, ${name}`);
- return new Promise((resolve, reject) => {
- db.File
- .findOne({where: { name, claimId }})
- .then(result => {
- if (result) {
- resolve(result.dataValues);
- } else {
- resolve(null);
- }
- })
- .catch(error => {
- reject(error);
- });
- });
-}
-
-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,
- outpoint,
- height,
- address,
- fileName: '',
- filePath: '',
- fileType: '',
- nsfw,
- };
-}
-
-function getAssetByLongClaimId (fullClaimId, name) {
- logger.debug('...getting asset by claim Id...');
- return new Promise((resolve, reject) => {
- // 1. check locally for claim
- checkForLocalAssetByClaimId(fullClaimId, name)
- .then(dataValues => {
- // if a result was found, return early with the result
- if (dataValues) {
- logger.debug('found a local file for this name and claimId');
- resolve(dataValues);
- return;
- }
- logger.debug('no local file found for this name and claimId');
- // 2. if no local claim, resolve and get the claim
- db.Claim
- .resolveClaim(name, fullClaimId)
- .then(resolveResult => {
- // if no result, return early (claim doesn't exist or isn't free)
- if (!resolveResult) {
- resolve(NO_CLAIM);
- return;
- }
- logger.debug('resolve result >> ', resolveResult.dataValues);
- 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(() => {
- logger.debug('File record successfully updated');
- resolve(fileRecord);
- })
- .catch(error => {
- reject(error);
- });
- })
- .catch(error => {
- reject(error);
- });
- })
- .catch(error => {
- reject(error);
- });
- });
-}
-
-function chooseThumbnail (claimInfo, defaultThumbnail) {
- if (!claimInfo.thumbnail || claimInfo.thumbnail.trim() === '') {
- return defaultThumbnail;
- }
- return claimInfo.thumbnail;
-}
+const NO_FILE = 'NO_FILE';
module.exports = {
- getAssetByClaim (claimName, claimId) {
- logger.debug(`getAssetByClaim(${claimName}, ${claimId})`);
- return new Promise((resolve, reject) => {
- db.Claim.getLongClaimId(claimName, claimId) // 1. get the long claim id
- .then(result => { // 2. get the asset using the long claim id
- logger.debug('long claim id ===', result);
- if (result === NO_CLAIM) {
- logger.debug('resolving NO_CLAIM');
- resolve(NO_CLAIM);
- return;
- }
- resolve(getAssetByLongClaimId(result, claimName));
- })
- .catch(error => {
- reject(error);
- });
- });
- },
- getAssetByChannel (channelName, channelId, claimName) {
- logger.debug('getting asset by channel');
- return new Promise((resolve, reject) => {
- db.Certificate.getLongChannelId(channelName, channelId) // 1. get the long channel id
- .then(result => { // 2. get the long claim Id
- if (result === NO_CHANNEL) {
- resolve(NO_CHANNEL);
- return;
- }
- return db.Claim.getClaimIdByLongChannelId(result, claimName);
- })
- .then(result => { // 3. get the asset using the long claim id
- logger.debug('asset claim id =', result);
- if (result === NO_CHANNEL || result === NO_CLAIM) {
- resolve(result);
- return;
- }
- resolve(getAssetByLongClaimId(result, claimName));
- })
- .catch(error => {
- reject(error);
- });
- });
- },
- getChannelContents (channelName, channelId) {
- return new Promise((resolve, reject) => {
- let longChannelId;
- let shortChannelId;
- db.Certificate.getLongChannelId(channelName, channelId) // 1. get the long channel Id
- .then(result => { // 2. get all claims for that channel
- if (result === NO_CHANNEL) {
- return NO_CHANNEL;
- }
- longChannelId = result;
- return db.Certificate.getShortChannelIdFromLongChannelId(longChannelId, channelName);
- })
- .then(result => { // 3. get all Claim records for this channel
- if (result === NO_CHANNEL) {
- return NO_CHANNEL;
- }
- shortChannelId = result;
- return db.Claim.getAllChannelClaims(longChannelId);
- })
- .then(result => { // 4. add extra data not available from Claim table
- if (result === NO_CHANNEL) {
- resolve(NO_CHANNEL);
- return;
- }
- if (result) {
- result.forEach(element => {
- const fileExtenstion = element.contentType.substring(element.contentType.lastIndexOf('/') + 1);
- element['showUrlLong'] = `/${channelName}:${longChannelId}/${element.name}`;
- element['directUrlLong'] = `/${channelName}:${longChannelId}/${element.name}.${fileExtenstion}`;
- element['showUrlShort'] = `/${channelName}:${shortChannelId}/${element.name}`;
- element['directUrlShort'] = `/${channelName}:${shortChannelId}/${element.name}.${fileExtenstion}`;
- element['thumbnail'] = chooseThumbnail(element, DEFAULT_THUMBNAIL);
- });
- }
- resolve({
- channelName,
- longChannelId,
- shortChannelId,
- claims: result,
- });
- })
- .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';
+ getClaimId (channelName, channelClaimId, name, claimId) {
+ if (channelName) {
+ return module.exports.getClaimIdByChannel(channelName, channelClaimId, name);
} else {
- fileInfo['fileExt'] = fileInfo.fileName.substring(fileInfo.fileName.lastIndexOf('.') + 1);
- }
- // add a record to the 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);
- return fileInfo;
- case SHOWLITE:
- return db.Claim.resolveClaim(fileInfo.name, fileInfo.claimId)
- .then(claimRecord => {
- fileInfo['title'] = claimRecord.title;
- fileInfo['description'] = claimRecord.description;
- showFileLite(fileInfo, res);
- return fileInfo;
- })
- .catch(error => {
- logger.error('throwing serverOrShowAsset SHOWLITE error...');
- throw error;
- });
- case SHOW:
- return db.Claim
- .getShortClaimIdFromLongClaimId(fileInfo.claimId, fileInfo.name)
- .then(shortId => {
- fileInfo['shortId'] = shortId;
- return db.Claim.resolveClaim(fileInfo.name, fileInfo.claimId);
- })
- .then(resolveResult => {
- logger.debug('resolve result >>', resolveResult.dataValues);
- fileInfo['thumbnail'] = chooseThumbnail(resolveResult, DEFAULT_THUMBNAIL);
- fileInfo['title'] = resolveResult.title;
- fileInfo['description'] = resolveResult.description;
- if (resolveResult.certificateId) { fileInfo['certificateId'] = resolveResult.certificateId };
- if (resolveResult.channelName) { fileInfo['channelName'] = resolveResult.channelName };
- showFile(fileInfo, res);
- return fileInfo;
- })
- .catch(error => {
- logger.error('throwing serverOrShowAsset SHOW error...');
- throw error;
- });
- default:
- logger.error('I did not recognize that method');
- break;
+ return module.exports.getClaimIdByClaim(name, claimId);
}
},
+ getClaimIdByClaim (claimName, claimId) {
+ logger.debug(`getClaimIdByClaim(${claimName}, ${claimId})`);
+ return new Promise((resolve, reject) => {
+ db.Claim.getLongClaimId(claimName, claimId)
+ .then(longClaimId => {
+ if (!longClaimId) {
+ resolve(NO_CLAIM);
+ }
+ resolve(longClaimId);
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ },
+ getClaimIdByChannel (channelName, channelClaimId, claimName) {
+ logger.debug(`getClaimIdByChannel(${channelName}, ${channelClaimId}, ${claimName})`);
+ return new Promise((resolve, reject) => {
+ db.Certificate.getLongChannelId(channelName, channelClaimId) // 1. get the long channel id
+ .then(longChannelId => {
+ if (!longChannelId) {
+ return [null, null];
+ }
+ return Promise.all([longChannelId, db.Claim.getClaimIdByLongChannelId(longChannelId, claimName)]); // 2. get the long claim id
+ })
+ .then(([longChannelId, longClaimId]) => {
+ if (!longChannelId) {
+ return resolve(NO_CHANNEL);
+ }
+ if (!longClaimId) {
+ return resolve(NO_CLAIM);
+ }
+ resolve(longClaimId);
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ },
+ getChannelViewData (channelName, channelClaimId, query) {
+ return new Promise((resolve, reject) => {
+ // 1. get the long channel Id (make sure channel exists)
+ db.Certificate.getLongChannelId(channelName, channelClaimId)
+ .then(longChannelClaimId => {
+ if (!longChannelClaimId) {
+ return [null, null, null];
+ }
+ // 2. get the short ID and all claims for that channel
+ return Promise.all([longChannelClaimId, db.Certificate.getShortChannelIdFromLongChannelId(longChannelClaimId, channelName), db.Claim.getAllChannelClaims(longChannelClaimId)]);
+ })
+ .then(([longChannelClaimId, shortChannelClaimId, channelClaimsArray]) => {
+ if (!longChannelClaimId) {
+ return resolve(NO_CHANNEL);
+ }
+ // 3. format the data for the view, including pagination
+ let paginatedChannelViewData = returnPaginatedChannelViewData(channelName, longChannelClaimId, shortChannelClaimId, channelClaimsArray, query);
+ // 4. return all the channel information and contents
+ resolve(paginatedChannelViewData);
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ },
+ getLocalFileRecord (claimId, name) {
+ return db.File.findOne({where: {claimId, name}})
+ .then(file => {
+ if (!file) {
+ return NO_FILE;
+ }
+ return file.dataValues;
+ });
+ },
};
diff --git a/controllers/statsController.js b/controllers/statsController.js
index 6f3d4475..68215767 100644
--- a/controllers/statsController.js
+++ b/controllers/statsController.js
@@ -70,25 +70,23 @@ module.exports = {
});
},
getTrendingClaims (startDate) {
- logger.debug('retrieving trending requests');
+ logger.debug('retrieving trending');
return new Promise((resolve, reject) => {
// get the raw requests data
- db.getTrendingClaims(startDate)
- .then(results => {
- if (results) {
- results.forEach(element => {
- const fileExtenstion = element.fileType.substring(element.fileType.lastIndexOf('/') + 1);
- element['showUrlLong'] = `/${element.claimId}/${element.name}`;
- 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/video_thumb_default.png';
+ db.getTrendingFiles(startDate)
+ .then(fileArray => {
+ let claimsPromiseArray = [];
+ if (fileArray) {
+ fileArray.forEach(file => {
+ claimsPromiseArray.push(db.Claim.resolveClaim(file.name, file.claimId));
});
+ return Promise.all(claimsPromiseArray);
}
- resolve(results);
+ })
+ .then(claimsArray => {
+ resolve(claimsArray);
})
.catch(error => {
- logger.error('sequelize error >>', error);
reject(error);
});
});
diff --git a/helpers/channelPagination.js b/helpers/channelPagination.js
new file mode 100644
index 00000000..d454a9ff
--- /dev/null
+++ b/helpers/channelPagination.js
@@ -0,0 +1,71 @@
+const CLAIMS_PER_PAGE = 10;
+
+module.exports = {
+ returnPaginatedChannelViewData (channelName, longChannelClaimId, shortChannelClaimId, claims, query) {
+ const totalPages = module.exports.determineTotalPages(claims);
+ const paginationPage = module.exports.getPageFromQuery(query);
+ const viewData = {
+ channelName : channelName,
+ longChannelClaimId : longChannelClaimId,
+ shortChannelClaimId: shortChannelClaimId,
+ claims : module.exports.extractPageFromClaims(claims, paginationPage),
+ previousPage : module.exports.determinePreviousPage(paginationPage),
+ currentPage : paginationPage,
+ nextPage : module.exports.determineNextPage(totalPages, paginationPage),
+ totalPages : totalPages,
+ totalResults : module.exports.determineTotalClaims(claims),
+ };
+ return viewData;
+ },
+ getPageFromQuery (query) {
+ if (query.p) {
+ return parseInt(query.p);
+ }
+ return 1;
+ },
+ extractPageFromClaims (claims, pageNumber) {
+ if (!claims) {
+ return []; // if no claims, return this default
+ }
+ // logger.debug('claims is array?', Array.isArray(claims));
+ // logger.debug(`pageNumber ${pageNumber} is number?`, Number.isInteger(pageNumber));
+ const claimStartIndex = (pageNumber - 1) * CLAIMS_PER_PAGE;
+ const claimEndIndex = claimStartIndex + 10;
+ const pageOfClaims = claims.slice(claimStartIndex, claimEndIndex);
+ return pageOfClaims;
+ },
+ determineTotalPages (claims) {
+ if (!claims) {
+ return 0;
+ } else {
+ const totalClaims = claims.length;
+ if (totalClaims < CLAIMS_PER_PAGE) {
+ return 1;
+ }
+ const fullPages = Math.floor(totalClaims / CLAIMS_PER_PAGE);
+ const remainder = totalClaims % CLAIMS_PER_PAGE;
+ if (remainder === 0) {
+ return fullPages;
+ }
+ return fullPages + 1;
+ }
+ },
+ determinePreviousPage (currentPage) {
+ if (currentPage === 1) {
+ return null;
+ }
+ return currentPage - 1;
+ },
+ determineNextPage (totalPages, currentPage) {
+ if (currentPage === totalPages) {
+ return null;
+ }
+ return currentPage + 1;
+ },
+ determineTotalClaims (claims) {
+ if (!claims) {
+ return 0;
+ }
+ return claims.length;
+ },
+};
diff --git a/helpers/errorHandlers.js b/helpers/errorHandlers.js
index aa1f723d..4a825399 100644
--- a/helpers/errorHandlers.js
+++ b/helpers/errorHandlers.js
@@ -1,5 +1,4 @@
const logger = require('winston');
-const { postToStats } = require('../controllers/statsController.js');
module.exports = {
returnErrorMessageAndStatus: function (error) {
@@ -33,17 +32,15 @@ module.exports = {
}
return [status, message];
},
- handleRequestError: function (action, originalUrl, ip, error, res) {
+ handleRequestError: function (originalUrl, ip, error, res) {
logger.error(`Request Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error));
- postToStats(action, originalUrl, ip, null, null, error);
const [status, message] = module.exports.returnErrorMessageAndStatus(error);
res
.status(status)
.render('requestError', module.exports.createErrorResponsePayload(status, message));
},
- handleApiError: function (action, originalUrl, ip, error, res) {
- logger.error(`Api ${action} Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error));
- postToStats(action, originalUrl, ip, null, null, error);
+ handleApiError: function (originalUrl, ip, error, res) {
+ logger.error(`Api Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error));
const [status, message] = module.exports.returnErrorMessageAndStatus(error);
res
.status(status)
diff --git a/helpers/handlebarsHelpers.js b/helpers/handlebarsHelpers.js
index 59275e0c..d2258e03 100644
--- a/helpers/handlebarsHelpers.js
+++ b/helpers/handlebarsHelpers.js
@@ -2,6 +2,10 @@ const Handlebars = require('handlebars');
const config = require('../config/speechConfig.js');
module.exports = {
+ placeCommonHeaderTags () {
+ const headerBoilerplate = `
Spee.ch`;
+ return new Handlebars.SafeString(headerBoilerplate);
+ },
googleAnalytics () {
const googleApiKey = config.analytics.googleId;
const gaCode = ``;
return new Handlebars.SafeString(gaCode);
},
- addOpenGraph (title, mimeType, showUrl, source, description, thumbnail) {
- if (title === null || title.trim() === '') {
- title = 'Spee.ch';
- }
- if (description === null || description.trim() === '') {
- description = 'Open-source, decentralized image and video sharing.';
- }
- const ogTitle = ``;
- const ogUrl = ``;
- const ogSiteName = ``;
- const ogDescription = ``;
- const ogImageWidth = '';
- const ogImageHeight = '';
- const basicTags = `${ogTitle} ${ogUrl} ${ogSiteName} ${ogDescription} ${ogImageWidth} ${ogImageHeight}`;
- let ogImage = ``;
- let ogImageType = ``;
- let ogType = ``;
- if (mimeType === 'video/mp4') {
- const ogVideo = ``;
- const ogVideoSecureUrl = ``;
- const ogVideoType = ``;
- ogImage = ``;
- ogImageType = ``;
- ogType = ``;
- return new Handlebars.SafeString(`${basicTags} ${ogImage} ${ogImageType} ${ogType} ${ogVideo} ${ogVideoSecureUrl} ${ogVideoType}`);
+ addOpenGraph ({ ogTitle, contentType, ogDescription, thumbnail, showUrl, source, ogThumbnailContentType }) {
+ const ogTitleTag = ``;
+ const ogUrlTag = ``;
+ const ogSiteNameTag = ``;
+ const ogDescriptionTag = ``;
+ const ogImageWidthTag = '';
+ const ogImageHeightTag = '';
+ const basicTags = `${ogTitleTag} ${ogUrlTag} ${ogSiteNameTag} ${ogDescriptionTag} ${ogImageWidthTag} ${ogImageHeightTag}`;
+ let ogImageTag = ``;
+ let ogImageTypeTag = ``;
+ let ogTypeTag = ``;
+ if (contentType === 'video/mp4') {
+ const ogVideoTag = ``;
+ const ogVideoSecureUrlTag = ``;
+ const ogVideoTypeTag = ``;
+ ogImageTag = ``;
+ ogImageTypeTag = ``;
+ ogTypeTag = ``;
+ return new Handlebars.SafeString(`${basicTags} ${ogImageTag} ${ogImageTypeTag} ${ogTypeTag} ${ogVideoTag} ${ogVideoSecureUrlTag} ${ogVideoTypeTag}`);
} else {
- if (mimeType === 'image/gif') {
- ogType = ``;
+ if (contentType === 'image/gif') {
+ ogTypeTag = ``;
};
- return new Handlebars.SafeString(`${basicTags} ${ogImage} ${ogImageType} ${ogType}`);
+ return new Handlebars.SafeString(`${basicTags} ${ogImageTag} ${ogImageTypeTag} ${ogTypeTag}`);
}
},
- addTwitterCard (mimeType, source, embedUrl, directFileUrl) {
+ addTwitterCard ({ contentType, source, embedUrl, directFileUrl }) {
const basicTwitterTags = ``;
- if (mimeType === 'video/mp4') {
+ if (contentType === 'video/mp4') {
const twitterName = '';
const twitterPlayer = ``;
const twitterPlayerWidth = '';
diff --git a/helpers/lbryApi.js b/helpers/lbryApi.js
index bfd3139c..0474ca36 100644
--- a/helpers/lbryApi.js
+++ b/helpers/lbryApi.js
@@ -10,7 +10,6 @@ function handleLbrynetResponse ({ data }, resolve, reject) {
reject(data.result.error);
return;
};
- // logger.debug('data.result', data.result);
resolve(data.result);
return;
}
diff --git a/helpers/lbryUri.js b/helpers/lbryUri.js
new file mode 100644
index 00000000..cc5e1d9e
--- /dev/null
+++ b/helpers/lbryUri.js
@@ -0,0 +1,90 @@
+const logger = require('winston');
+
+module.exports = {
+ REGEXP_INVALID_CLAIM : /[^A-Za-z0-9-]/g,
+ REGEXP_INVALID_CHANNEL: /[^A-Za-z0-9-@]/g,
+ REGEXP_ADDRESS : /^b(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/,
+ CHANNEL_CHAR : '@',
+ parseIdentifier : function (identifier) {
+ logger.debug('parsing identifier:', identifier);
+ const componentsRegex = new RegExp(
+ '([^:$#/]*)' + // value (stops at the first separator or end)
+ '([:$#]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end)
+ );
+ const [proto, value, modifierSeperator, modifier] = componentsRegex
+ .exec(identifier)
+ .map(match => match || null);
+ logger.debug(`${proto}, ${value}, ${modifierSeperator}, ${modifier}`);
+
+ // Validate and process name
+ const isChannel = value.startsWith(module.exports.CHANNEL_CHAR);
+ const channelName = isChannel ? value : null;
+ let claimId;
+ if (isChannel) {
+ if (!channelName) {
+ throw new Error('No channel name after @.');
+ }
+ const nameBadChars = (channelName).match(module.exports.REGEXP_INVALID_CHANNEL);
+ if (nameBadChars) {
+ throw new Error(`Invalid characters in channel name: ${nameBadChars.join(', ')}.`);
+ }
+ } else {
+ claimId = value;
+ }
+
+ // Validate and process modifier
+ let channelClaimId;
+ if (modifierSeperator) {
+ if (!modifier) {
+ throw new Error(`No modifier provided after separator ${modifierSeperator}.`);
+ }
+
+ if (modifierSeperator === ':') {
+ channelClaimId = modifier;
+ } else {
+ throw new Error(`The ${modifierSeperator} modifier is not currently supported.`);
+ }
+ }
+ return {
+ isChannel,
+ channelName,
+ channelClaimId,
+ claimId,
+ };
+ },
+ parseName: function (name) {
+ logger.debug('parsing name:', name);
+ const componentsRegex = new RegExp(
+ '([^:$#/.]*)' + // name (stops at the first modifier)
+ '([:$#.]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end)
+ );
+ const [proto, claimName, modifierSeperator, modifier] = componentsRegex
+ .exec(name)
+ .map(match => match || null);
+ logger.debug(`${proto}, ${claimName}, ${modifierSeperator}, ${modifier}`);
+
+ // Validate and process name
+ if (!claimName) {
+ throw new Error('No claim name provided before .');
+ }
+ const nameBadChars = (claimName).match(module.exports.REGEXP_INVALID_CLAIM);
+ if (nameBadChars) {
+ throw new Error(`Invalid characters in claim name: ${nameBadChars.join(', ')}.`);
+ }
+ // Validate and process modifier
+ let isServeRequest = false;
+ if (modifierSeperator) {
+ if (!modifier) {
+ throw new Error(`No file extension provided after separator ${modifierSeperator}.`);
+ }
+ if (modifierSeperator !== '.') {
+ throw new Error(`The ${modifierSeperator} modifier is not supported in the claim name`);
+ }
+ isServeRequest = true;
+ }
+ return {
+ claimName,
+ isServeRequest,
+ };
+ },
+};
diff --git a/helpers/sequelizeHelpers.js b/helpers/sequelizeHelpers.js
index 3b36749d..0297f676 100644
--- a/helpers/sequelizeHelpers.js
+++ b/helpers/sequelizeHelpers.js
@@ -1,23 +1,23 @@
module.exports = {
- returnShortId: function (result, longId) {
+ returnShortId: function (claimsArray, longId) {
let claimIndex;
- let shortId = longId.substring(0, 1); // default sort id is the first letter
+ let shortId = longId.substring(0, 1); // default short id is the first letter
let shortIdLength = 0;
// find the index of this claim id
- claimIndex = result.findIndex(element => {
+ claimIndex = claimsArray.findIndex(element => {
return element.claimId === longId;
});
if (claimIndex < 0) {
throw new Error('claim id not found in claims list');
}
// get an array of all claims with lower height
- let possibleMatches = result.slice(0, claimIndex);
+ let possibleMatches = claimsArray.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 (element.claimId && (element.claimId.substring(0, shortIdLength) === shortId));
});
}
return shortId;
diff --git a/helpers/serveHelpers.js b/helpers/serveHelpers.js
index 6e7c610a..352e4748 100644
--- a/helpers/serveHelpers.js
+++ b/helpers/serveHelpers.js
@@ -1,45 +1,23 @@
const logger = require('winston');
-function createOpenGraphInfo ({ fileType, claimId, name, fileName, fileExt }) {
- return {
- embedUrl : `https://spee.ch/embed/${claimId}/${name}`,
- showUrl : `https://spee.ch/${claimId}/${name}`,
- source : `https://spee.ch/${claimId}/${name}.${fileExt}`,
- directFileUrl: `https://spee.ch/${claimId}/${name}.${fileExt}`,
- };
-}
-
module.exports = {
- serveFile ({ fileName, fileType, filePath }, res) {
- logger.verbose(`serving file ${fileName}`);
- // set default options
- let options = {
+ serveFile ({ filePath, fileType }, claimId, name, res) {
+ logger.verbose(`serving ${name}#${claimId}`);
+ // set response options
+ const headerContentType = fileType || 'image/jpeg';
+ const options = {
headers: {
'X-Content-Type-Options': 'nosniff',
- 'Content-Type' : fileType,
+ 'Content-Type' : headerContentType,
},
};
- // adjust default options as needed
- switch (fileType) {
- case 'image/jpeg':
- case 'image/gif':
- case 'image/png':
- case 'video/mp4':
- break;
- default:
- logger.warn('sending file with unknown type as .jpeg');
- options['headers']['Content-Type'] = 'image/jpeg';
- break;
- }
// send the file
res.status(200).sendFile(filePath, options);
},
- showFile (fileInfo, res) {
- const openGraphInfo = createOpenGraphInfo(fileInfo);
- res.status(200).render('show', { layout: 'show', fileInfo, openGraphInfo });
+ showFile (claimInfo, shortId, res) {
+ res.status(200).render('show', { layout: 'show', claimInfo, shortId });
},
- showFileLite (fileInfo, res) {
- const openGraphInfo = createOpenGraphInfo(fileInfo);
- res.status(200).render('showLite', { layout: 'showlite', fileInfo, openGraphInfo });
+ showFileLite (claimInfo, shortId, res) {
+ res.status(200).render('showLite', { layout: 'showlite', claimInfo, shortId });
},
};
diff --git a/models/certificate.js b/models/certificate.js
index 37dafbaa..206b3e40 100644
--- a/models/certificate.js
+++ b/models/certificate.js
@@ -1,6 +1,5 @@
const logger = require('winston');
const { returnShortId } = require('../helpers/sequelizeHelpers.js');
-const NO_CHANNEL = 'NO_CHANNEL';
module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
const Certificate = sequelize.define(
@@ -123,14 +122,14 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
- Certificate.getLongChannelIdFromShortChannelId = function (channelName, channelId) {
+ Certificate.getLongChannelIdFromShortChannelId = function (channelName, channelClaimId) {
return new Promise((resolve, reject) => {
this
.findAll({
where: {
name : channelName,
claimId: {
- $like: `${channelId}%`,
+ $like: `${channelClaimId}%`,
},
},
order: [['height', 'ASC']],
@@ -138,7 +137,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
.then(result => {
switch (result.length) {
case 0:
- return resolve(NO_CHANNEL);
+ return resolve(null);
default: // note results must be sorted
return resolve(result[0].claimId);
}
@@ -160,7 +159,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
.then(result => {
switch (result.length) {
case 0:
- return resolve(NO_CHANNEL);
+ return resolve(null);
default:
return resolve(result[0].claimId);
}
@@ -171,12 +170,29 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
- Certificate.getLongChannelId = function (channelName, channelId) {
- logger.debug(`getLongChannelId(${channelName}, ${channelId})`);
- if (channelId && (channelId.length === 40)) { // if a full channel id is provided
- return new Promise((resolve, reject) => resolve(channelId));
- } else if (channelId && channelId.length < 40) { // if a short channel id is provided
- return this.getLongChannelIdFromShortChannelId(channelName, channelId);
+ Certificate.validateLongChannelId = function (name, claimId) {
+ return new Promise((resolve, reject) => {
+ this.findOne({
+ where: {name, claimId},
+ })
+ .then(result => {
+ if (!result) {
+ return resolve(null);
+ };
+ resolve(claimId);
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ };
+
+ Certificate.getLongChannelId = function (channelName, channelClaimId) {
+ logger.debug(`getLongChannelId(${channelName}, ${channelClaimId})`);
+ if (channelClaimId && (channelClaimId.length === 40)) { // if a full channel id is provided
+ return this.validateLongChannelId(channelName, channelClaimId);
+ } else if (channelClaimId && channelClaimId.length < 40) { // if a short channel id is provided
+ return this.getLongChannelIdFromShortChannelId(channelName, channelClaimId);
} else {
return this.getLongChannelIdFromChannelName(channelName); // if no channel id provided
}
diff --git a/models/claim.js b/models/claim.js
index 0c7289fd..5ec31da7 100644
--- a/models/claim.js
+++ b/models/claim.js
@@ -1,6 +1,89 @@
const logger = require('winston');
const { returnShortId } = require('../helpers/sequelizeHelpers.js');
-const NO_CLAIM = 'NO_CLAIM';
+const DEFAULT_THUMBNAIL = 'https://spee.ch/assets/img/video_thumb_default.png';
+const DEFAULT_TITLE = 'Spee {
const Claim = sequelize.define(
@@ -159,7 +242,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
};
Claim.getShortClaimIdFromLongClaimId = function (claimId, claimName) {
- logger.debug(`Claim.getShortClaimIdFromLongClaimId for ${claimId}#${claimId}`);
+ logger.debug(`Claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
return new Promise((resolve, reject) => {
this
.findAll({
@@ -180,20 +263,27 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
- Claim.getAllChannelClaims = function (channelId) {
- logger.debug(`Claim.getAllChannelClaims for ${channelId}`);
+ Claim.getAllChannelClaims = function (channelClaimId) {
+ logger.debug(`Claim.getAllChannelClaims for ${channelClaimId}`);
return new Promise((resolve, reject) => {
this
.findAll({
- where: { certificateId: channelId },
+ where: { certificateId: channelClaimId },
order: [['height', 'ASC']],
+ raw : true, // returns an array of only data, not an array of instances
})
- .then(result => {
- switch (result.length) {
+ .then(channelClaimsArray => {
+ // logger.debug('channelclaimsarray length:', channelClaimsArray.length);
+ switch (channelClaimsArray.length) {
case 0:
return resolve(null);
default:
- return resolve(result);
+ channelClaimsArray.forEach(claim => {
+ claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType);
+ claim['thumbnail'] = determineThumbnail(claim.thumbnail, DEFAULT_THUMBNAIL);
+ return claim;
+ });
+ return resolve(channelClaimsArray);
}
})
.catch(error => {
@@ -202,18 +292,18 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
- Claim.getClaimIdByLongChannelId = function (channelId, claimName) {
- logger.debug(`finding claim id for claim ${claimName} from channel ${channelId}`);
+ Claim.getClaimIdByLongChannelId = function (channelClaimId, claimName) {
+ logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`);
return new Promise((resolve, reject) => {
this
.findAll({
- where: { name: claimName, certificateId: channelId },
+ where: { name: claimName, certificateId: channelClaimId },
order: [['id', 'ASC']],
})
.then(result => {
switch (result.length) {
case 0:
- return resolve(NO_CLAIM);
+ return resolve(null);
case 1:
return resolve(result[0].claimId);
default:
@@ -241,7 +331,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
.then(result => {
switch (result.length) {
case 0:
- return resolve(NO_CLAIM);
+ return resolve(null);
default: // note results must be sorted
return resolve(result[0].claimId);
}
@@ -260,12 +350,12 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
order: [['effectiveAmount', 'DESC'], ['height', 'ASC']], // note: maybe height and effective amount need to switch?
})
.then(result => {
+ logger.debug('length of result', result.length);
switch (result.length) {
case 0:
- return resolve(NO_CLAIM);
+ return resolve(null);
default:
- logger.debug('getTopFreeClaimIdByClaimName result:', result.dataValues);
- return resolve(result[0].claimId);
+ return resolve(result[0].dataValues.claimId);
}
})
.catch(error => {
@@ -274,10 +364,27 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
});
};
+ Claim.validateLongClaimId = function (name, claimId) {
+ return new Promise((resolve, reject) => {
+ this.findOne({
+ where: {name, claimId},
+ })
+ .then(result => {
+ if (!result) {
+ return resolve(null);
+ };
+ resolve(claimId);
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ };
+
Claim.getLongClaimId = function (claimName, claimId) {
logger.debug(`getLongClaimId(${claimName}, ${claimId})`);
if (claimId && (claimId.length === 40)) { // if a full claim id is provided
- return new Promise((resolve, reject) => resolve(claimId));
+ return this.validateLongClaimId(claimName, claimId);
} else if (claimId && claimId.length < 40) {
return this.getLongClaimIdFromShortClaimId(claimName, claimId); // if a short claim id is provided
} else {
@@ -291,15 +398,16 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
.findAll({
where: { name, claimId },
})
- .then(result => {
- switch (result.length) {
+ .then(claimArray => {
+ logger.debug('claims found on resolve:', claimArray.length);
+ switch (claimArray.length) {
case 0:
return resolve(null);
case 1:
- return resolve(result[0]);
+ return resolve(prepareClaimData(claimArray[0].dataValues));
default:
- logger.warn(`more than one entry matches that name (${name}) and claimID (${claimId})`);
- return resolve(result[0]);
+ logger.error(`more than one entry matches that name (${name}) and claimID (${claimId})`);
+ return resolve(prepareClaimData(claimArray[0].dataValues));
}
})
.catch(error => {
diff --git a/models/index.js b/models/index.js
index 4bc622fd..b959586c 100644
--- a/models/index.js
+++ b/models/index.js
@@ -69,11 +69,12 @@ db.upsert = (Model, values, condition, tableName) => {
})
.catch(function (error) {
logger.error(`${tableName}.upsert error`, error);
+ throw error;
});
};
-// add a 'getTrendingClaims' method to the db object
-db.getTrendingClaims = (startDate) => {
+// add a 'getTrendingFiles' method to the db object. note: change this to get claims directly. might need new association between Request and Claim
+db.getTrendingFiles = (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 });
};
diff --git a/public/assets/css/general.css b/public/assets/css/general.css
index 8ef7376b..57c26354 100644
--- a/public/assets/css/general.css
+++ b/public/assets/css/general.css
@@ -99,6 +99,10 @@ h3, p {
font-size: small;
}
+#show-body > .fine-print {
+ text-align: center;
+}
+
.blue {
color: #4156C5;
}
@@ -153,9 +157,7 @@ a, a:visited {
.link--primary, .link--primary:visited {
color: #4156C5;
}
-.link--primary.fine-print {
- text-align: center;
-}
+
.link--nav {
color: black;
border-bottom: 2px solid white;
@@ -296,10 +298,6 @@ a, a:visited {
color: red;
}
-.info-message-placeholder {
-
-}
-
/* INPUT FIELDS */
/* blocks */
@@ -347,14 +345,6 @@ option {
cursor: pointer;
}
-#claim-name-input {
-
-}
-
-#input-success-claim-name {
-
-}
-
.span--relative {
position: relative;
}
@@ -511,27 +501,34 @@ table {
width: calc(100% - 1rem);
}
-/* Show page */
+/* Assets */
-.video-show, .gifv-show, .image-show {
- display: block;
- width: 100%;
+.asset {
+ max-width: 100%;
}
-#video-player, .showlite-asset {
- display: block;
- margin: 0 auto;
- background-color: #fff;
+
+#show-body #asset-boilerpate {
+ display: none;
+}
+
+#showlite-body #asset-display-component {
+ max-width: 50%;
+ text-align: center;
+}
+
+/* video */
+
+#video-asset {
+ background-color: #000000;
cursor: pointer;
}
-#showlite-body #video-player {
- margin-top: 2%;
- padding: 6px;
- max-width: 50%;
+#showlite-body #video-asset {
+ background-color: #ffffff;
+ width: calc(100% - 12px - 12px - 2px);
+ margin: 6px;
+ padding: 6px;
border: 1px solid #d0d0d0;
}
-.showlite-asset {
- max-width: 100%;
-}
/* item lists */
diff --git a/public/assets/css/mediaQueries.css b/public/assets/css/mediaQueries.css
index 03ecbfdd..52eca278 100644
--- a/public/assets/css/mediaQueries.css
+++ b/public/assets/css/mediaQueries.css
@@ -36,10 +36,14 @@
padding-right: 1.5em;
}
- .showlite-asset {
+ #showlite-body #asset-display-component {
max-width: 100%;
}
+ #showlite-body #asset-status {
+ padding: 2em;
+ }
+
}
@media (max-width: 500px) {
diff --git a/public/assets/js/assetConstructor.js b/public/assets/js/assetConstructor.js
new file mode 100644
index 00000000..bb001b0d
--- /dev/null
+++ b/public/assets/js/assetConstructor.js
@@ -0,0 +1,139 @@
+const Asset = function () {
+ this.data = {};
+ this.addPlayPauseToVideo = function () {
+ const that = this;
+ const video = document.getElementById('video-asset');
+ if (video) {
+ // add event listener for click
+ video.addEventListener('click', ()=> {
+ that.playOrPause(video);
+ });
+ // add event listener for space bar
+ document.body.onkeyup = (event) => {
+ if (event.keyCode == 32) {
+ that.playOrPause(video);
+ }
+ };
+ }
+ };
+ this.playOrPause = function(video){
+ if (video.paused == true) {
+ video.play();
+ }
+ else{
+ video.pause();
+ }
+ };
+ this.showAsset = function () {
+ this.hideAssetStatus();
+ this.showAssetHolder();
+ if (!this.data.src) {
+ return console.log('error: src is not set')
+ }
+ if (!this.data.contentType) {
+ return console.log('error: contentType is not set')
+ }
+ if (this.data.contentType === 'video/mp4') {
+ this.showVideo();
+ } else {
+ this.showImage();
+ }
+ };
+ this.showVideo = function () {
+ console.log('showing video', this.data.src);
+ const video = document.getElementById('video-asset');
+ const source = document.createElement('source');
+ source.setAttribute('src', this.data.src);
+ video.appendChild(source);
+ video.play();
+ };
+ this.showImage = function () {
+ console.log('showing image', this.data.src);
+ const asset = document.getElementById('image-asset');
+ asset.setAttribute('src', this.data.src);
+ };
+ this.hideAssetStatus = function () {
+ const assetStatus = document.getElementById('asset-status');
+ assetStatus.hidden = true;
+ };
+ this.showAssetHolder =function () {
+ const assetHolder = document.getElementById('asset-holder');
+ assetHolder.hidden = false;
+ };
+ this.showSearchMessage = function () {
+ const searchMessage = document.getElementById('searching-message');
+ searchMessage.hidden = false;
+ };
+ this.showFailureMessage = function (msg) {
+ console.log(msg);
+ const searchMessage = document.getElementById('searching-message');
+ const failureMessage = document.getElementById('failure-message');
+ const errorMessage = document.getElementById('error-message');
+ searchMessage.hidden = true;
+ failureMessage.hidden = false;
+ errorMessage.innerText = msg;
+ };
+ this.checkFileAndRenderAsset = function () {
+ const that = this;
+ this.isFileAvailable()
+ .then(isAvailable => {
+ if (!isAvailable) {
+ console.log('file is not yet available on spee.ch');
+ that.showSearchMessage();
+ return that.getAssetOnSpeech();
+ }
+ })
+ .then(() => {
+ that.showAsset();
+ })
+ .catch(error => {
+ that.showFailureMessage(error);
+ })
+ };
+ this.isFileAvailable = function () {
+ console.log(`checking if file is available for ${this.data.claimName}#${this.data.claimId}`)
+ const uri = `/api/file-is-available/${this.data.claimName}/${this.data.claimId}`;
+ const xhr = new XMLHttpRequest();
+ return new Promise((resolve, reject) => {
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ const response = JSON.parse(xhr.response);
+ if (xhr.status == 200) {
+ console.log('isFileAvailable succeeded:', response);
+ if (response.message === true) {
+ resolve(true);
+ } else {
+ resolve(false);
+ }
+ } else {
+ console.log('isFileAvailable failed:', response);
+ reject('Well this sucks, but we can\'t seem to phone home');
+ }
+ }
+ };
+ xhr.send();
+ })
+ };
+ this.getAssetOnSpeech = function() {
+ console.log(`getting claim for ${this.data.claimName}#${this.data.claimId}`)
+ const uri = `/api/claim-get/${this.data.claimName}/${this.data.claimId}`;
+ const xhr = new XMLHttpRequest();
+ return new Promise((resolve, reject) => {
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ const response = JSON.parse(xhr.response);
+ if (xhr.status == 200) {
+ console.log('getAssetOnSpeech succeeded:', response)
+ resolve(true);
+ } else {
+ console.log('getAssetOnSpeech failed:', response);
+ reject(response.message);
+ }
+ }
+ };
+ xhr.send();
+ })
+ };
+};
\ No newline at end of file
diff --git a/public/assets/js/progressBarConstructor.js b/public/assets/js/progressBarConstructor.js
new file mode 100644
index 00000000..468cf178
--- /dev/null
+++ b/public/assets/js/progressBarConstructor.js
@@ -0,0 +1,45 @@
+const ProgressBar = function() {
+ this.data = {
+ x: 0,
+ adder: 1,
+ bars: [],
+ };
+ this.barHolder = document.getElementById('bar-holder');
+ this.createProgressBar = function (size) {
+ this.data['size'] = size;
+ for (var i = 0; i < size; i++) {
+ const bar = document.createElement('span');
+ bar.innerText = '| ';
+ bar.setAttribute('class', 'progress-bar progress-bar--inactive');
+ this.barHolder.appendChild(bar);
+ this.data.bars.push(bar);
+ }
+ };
+ this.startProgressBar = function () {
+ this.updateInterval = setInterval(this.updateProgressBar.bind(this), 300);
+ };
+ this.updateProgressBar = function () {
+ const x = this.data.x;
+ const adder = this.data.adder;
+ const size = this.data.size;
+ // update the appropriate bar
+ if (x > -1 && x < size){
+ if (adder === 1){
+ this.data.bars[x].setAttribute('class', 'progress-bar progress-bar--active');
+ } else {
+ this.data.bars[x].setAttribute('class', 'progress-bar progress-bar--inactive');
+ }
+ }
+ // update adder
+ if (x === size){
+ this.data['adder'] = -1;
+ } else if ( x === -1){
+ this.data['adder'] = 1;
+ }
+ // update x
+ this.data['x'] = x + adder;
+ };
+ this.stopProgressBar = function () {
+ clearInterval(this.updateInterval);
+ };
+};
\ No newline at end of file
diff --git a/public/assets/js/publishFileFunctions.js b/public/assets/js/publishFileFunctions.js
index 19c273ab..1f552bb8 100644
--- a/public/assets/js/publishFileFunctions.js
+++ b/public/assets/js/publishFileFunctions.js
@@ -102,7 +102,7 @@ const publishFileFunctions = {
return fd;
},
publishFile: function (file, metadata) {
- var uri = "/api/publish";
+ var uri = "/api/claim-publish";
var xhr = new XMLHttpRequest();
var fd = this.appendDataToFormData(file, metadata);
var that = this;
diff --git a/public/assets/js/showFunctions.js b/public/assets/js/showFunctions.js
deleted file mode 100644
index 94047cd3..00000000
--- a/public/assets/js/showFunctions.js
+++ /dev/null
@@ -1,23 +0,0 @@
-function playOrPause(video){
- if (video.paused == true) {
- video.play();
- }
- else{
- video.pause();
- }
-}
-
-// if a video player is present, set the listeners
-const video = document.getElementById('video-player');
-if (video) {
- // add event listener for click
- video.addEventListener('click', ()=> {
- playOrPause(video);
- });
- // add event listener for space bar
- document.body.onkeyup = (event) => {
- if (event.keyCode == 32) {
- playOrPause(video);
- }
- };
-}
\ No newline at end of file
diff --git a/public/assets/js/validationFunctions.js b/public/assets/js/validationFunctions.js
index f79cc4fa..85d97868 100644
--- a/public/assets/js/validationFunctions.js
+++ b/public/assets/js/validationFunctions.js
@@ -119,13 +119,13 @@ const validationFunctions = {
checkClaimName: function (name) {
const successDisplayElement = document.getElementById('input-success-claim-name');
const errorDisplayElement = document.getElementById('input-error-claim-name');
- this.checkAvailability(name, successDisplayElement, errorDisplayElement, this.validateClaimName, 'Sorry, that ending is already taken', '/api/isClaimAvailable/');
+ this.checkAvailability(name, successDisplayElement, errorDisplayElement, this.validateClaimName, 'Sorry, that ending is already taken', '/api/claim-is-available/');
},
checkChannelName: function (name) {
const successDisplayElement = document.getElementById('input-success-channel-name');
const errorDisplayElement = document.getElementById('input-error-channel-name');
name = `@${name}`;
- this.checkAvailability(name, successDisplayElement, errorDisplayElement, this.validateChannelName, 'Sorry, that name is already taken', '/api/isChannelAvailable/');
+ this.checkAvailability(name, successDisplayElement, errorDisplayElement, this.validateChannelName, 'Sorry, that name is already taken', '/api/channel-is-available/');
},
// validation function which checks all aspects of the publish submission
validateFilePublishSubmission: function (stagedFiles, metadata) {
@@ -162,7 +162,7 @@ const validationFunctions = {
return;
}
// if all validation passes, check availability of the name (note: do we need to re-validate channel name vs. credentials as well?)
- return that.isNameAvailable(claimName, '/api/isClaimAvailable/')
+ return that.isNameAvailable(claimName, '/api/claim-is-available/')
.then(result => {
if (result) {
resolve();
@@ -193,7 +193,7 @@ const validationFunctions = {
return reject(error);
}
// 3. if all validation passes, check availability of the name
- that.isNameAvailable(channelName, '/api/isChannelAvailable/') // validate the availability
+ that.isNameAvailable(channelName, '/api/channel-is-available/') // validate the availability
.then(function(result) {
if (result) {
resolve();
diff --git a/routes/api-routes.js b/routes/api-routes.js
index 38b5b1e8..95bf222b 100644
--- a/routes/api-routes.js
+++ b/routes/api-routes.js
@@ -4,36 +4,93 @@ const config = require('../config/speechConfig.js');
const multipartMiddleware = multipart({uploadDir: config.files.uploadDirectory});
const db = require('../models');
const { publish } = require('../controllers/publishController.js');
-const { getClaimList, resolveUri } = require('../helpers/lbryApi.js');
+const { getClaimList, resolveUri, getClaim } = require('../helpers/lbryApi.js');
const { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
const errorHandlers = require('../helpers/errorHandlers.js');
-const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
const { authenticateOrSkip } = require('../auth/authentication.js');
+function addGetResultsToFileData (fileInfo, getResult) {
+ fileInfo.fileName = getResult.file_name;
+ fileInfo.filePath = getResult.download_path;
+ return fileInfo;
+}
+
+function createFileData ({ name, claimId, outpoint, height, address, nsfw, contentType }) {
+ return {
+ name,
+ claimId,
+ outpoint,
+ height,
+ address,
+ fileName: '',
+ filePath: '',
+ fileType: contentType,
+ nsfw,
+ };
+}
+
module.exports = (app) => {
// route to run a claim_list request on the daemon
- app.get('/api/claim_list/:name', ({ headers, ip, originalUrl, params }, res) => {
- // google analytics
- sendGoogleAnalytics('SERVE', headers, ip, originalUrl);
- // serve the content
+ app.get('/api/claim-list/:name', ({ ip, originalUrl, params }, res) => {
getClaimList(params.name)
.then(claimsList => {
- postToStats('serve', originalUrl, ip, null, null, 'success');
res.status(200).json(claimsList);
})
.catch(error => {
- errorHandlers.handleApiError('claim_list', originalUrl, ip, error, res);
+ errorHandlers.handleApiError(originalUrl, ip, error, res);
});
});
+ // route to see if asset is available locally
+ app.get('/api/file-is-available/:name/:claimId', ({ ip, originalUrl, params }, res) => {
+ const name = params.name;
+ const claimId = params.claimId;
+ let isLocalFileAvailable = false;
+ db.File.findOne({where: {name, claimId}})
+ .then(result => {
+ if (result) {
+ isLocalFileAvailable = true;
+ }
+ res.status(200).json({status: 'success', message: isLocalFileAvailable});
+ })
+ .catch(error => {
+ errorHandlers.handleApiError(originalUrl, ip, error, res);
+ });
+ });
+ // route to get an asset
+ app.get('/api/claim-get/:name/:claimId', ({ ip, originalUrl, params }, res) => {
+ const name = params.name;
+ const claimId = params.claimId;
+ // resolve the claim
+ db.Claim.resolveClaim(name, claimId)
+ .then(resolveResult => {
+ // make sure a claim actually exists at that uri
+ if (!resolveResult) {
+ throw new Error('No matching uri found in Claim table');
+ }
+ let fileData = createFileData(resolveResult);
+ // get the claim
+ return Promise.all([fileData, getClaim(`${name}#${claimId}`)]);
+ })
+ .then(([ fileData, getResult ]) => {
+ fileData = addGetResultsToFileData(fileData, getResult);
+ return Promise.all([db.upsert(db.File, fileData, {name, claimId}, 'File'), getResult]);
+ })
+ .then(([ fileRecord, {message, completed} ]) => {
+ res.status(200).json({ status: 'success', message, completed });
+ })
+ .catch(error => {
+ errorHandlers.handleApiError(originalUrl, ip, error, res);
+ });
+ });
+
// route to check whether spee.ch has published to a claim
- app.get('/api/isClaimAvailable/:name', ({ params }, res) => {
- // send response
+ app.get('/api/claim-is-available/:name', ({ params }, res) => {
checkClaimNameAvailability(params.name)
.then(result => {
if (result === true) {
res.status(200).json(true);
} else {
- logger.debug(`Rejecting '${params.name}' because that name has already been claimed on spee.ch`);
+ // logger.debug(`Rejecting '${params.name}' because that name has already been claimed on spee.ch`);
res.status(200).json(false);
}
})
@@ -42,37 +99,32 @@ module.exports = (app) => {
});
});
// route to check whether spee.ch has published to a channel
- app.get('/api/isChannelAvailable/:name', ({ params }, res) => {
+ app.get('/api/channel-is-available/:name', ({ params }, res) => {
checkChannelAvailability(params.name)
.then(result => {
if (result === true) {
res.status(200).json(true);
} else {
- logger.debug(`Rejecting '${params.name}' because that channel has already been claimed on spee.ch`);
+ // logger.debug(`Rejecting '${params.name}' because that channel has already been claimed on spee.ch`);
res.status(200).json(false);
}
})
.catch(error => {
- logger.debug('api/isChannelAvailable/ error', error);
res.status(500).json(error);
});
});
// route to run a resolve request on the daemon
- app.get('/api/resolve/:uri', ({ headers, ip, originalUrl, params }, res) => {
- // google analytics
- sendGoogleAnalytics('SERVE', headers, ip, originalUrl);
- // serve content
+ app.get('/api/claim-resolve/:uri', ({ headers, ip, originalUrl, params }, res) => {
resolveUri(params.uri)
.then(resolvedUri => {
- postToStats('serve', originalUrl, ip, null, null, 'success');
res.status(200).json(resolvedUri);
})
.catch(error => {
- errorHandlers.handleApiError('resolve', originalUrl, ip, error, res);
+ errorHandlers.handleApiError(originalUrl, ip, error, res);
});
});
// route to run a publish request on the daemon
- app.post('/api/publish', multipartMiddleware, ({ body, files, ip, originalUrl, user }, res) => {
+ app.post('/api/claim-publish', multipartMiddleware, ({ body, files, ip, originalUrl, user }, res) => {
let file, fileName, filePath, fileType, name, nsfw, license, title, description, thumbnail, anonymous, skipAuth, channelName, channelPassword;
// validate that mandatory parts of the request are present
try {
@@ -123,7 +175,7 @@ module.exports = (app) => {
}
}
channelName = cleanseChannelName(channelName);
- logger.debug(`/api/publish > name: ${name}, license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}" nsfw: "${nsfw}"`);
+ logger.debug(`name: ${name}, license: ${license} title: "${title}" description: "${description}" channelName: "${channelName}" channelPassword: "${channelPassword}" nsfw: "${nsfw}"`);
// check channel authorization
authenticateOrSkip(skipAuth, channelName, channelPassword)
.then(authenticated => {
@@ -156,13 +208,11 @@ module.exports = (app) => {
});
})
.catch(error => {
- errorHandlers.handleApiError('publish', originalUrl, ip, error, res);
+ errorHandlers.handleApiError(originalUrl, ip, error, res);
});
});
-
// route to get a short claim id from long claim Id
- app.get('/api/shortClaimId/:longId/:name', ({ originalUrl, ip, params }, res) => {
- // serve content
+ app.get('/api/claim-shorten-id/:longId/:name', ({ params }, res) => {
db.Claim.getShortClaimIdFromLongClaimId(params.longId, params.name)
.then(shortId => {
res.status(200).json(shortId);
@@ -173,8 +223,7 @@ module.exports = (app) => {
});
});
// route to get a short channel id from long channel Id
- app.get('/api/shortChannelId/:longId/:name', ({ ip, originalUrl, params }, res) => {
- // serve content
+ app.get('/api/channel-shorten-id/:longId/:name', ({ ip, originalUrl, params }, res) => {
db.Certificate.getShortChannelIdFromLongChannelId(params.longId, params.name)
.then(shortId => {
logger.debug('sending back short channel id', shortId);
@@ -182,7 +231,7 @@ module.exports = (app) => {
})
.catch(error => {
logger.error('api error getting short channel id', error);
- errorHandlers.handleApiError('short channel id', originalUrl, ip, error, res);
+ errorHandlers.handleApiError(originalUrl, ip, error, res);
});
});
};
diff --git a/routes/home-routes.js b/routes/home-routes.js
index ed3472f5..0879194a 100644
--- a/routes/home-routes.js
+++ b/routes/home-routes.js
@@ -1,5 +1,3 @@
-const { postToStats } = require('../controllers/statsController.js');
-
module.exports = app => {
// route for the home page
app.get('/', (req, res) => {
@@ -7,8 +5,6 @@ module.exports = app => {
});
// a catch-all route if someone visits a page that does not exist
app.use('*', ({ originalUrl, ip }, res) => {
- // post to stats
- postToStats('show', originalUrl, ip, null, null, 'Error: 404');
// send response
res.status(404).render('fourOhFour');
});
diff --git a/routes/page-routes.js b/routes/page-routes.js
index ac56b889..450f55e0 100644
--- a/routes/page-routes.js
+++ b/routes/page-routes.js
@@ -30,24 +30,22 @@ module.exports = (app) => {
const dateTime = startDate.toISOString().slice(0, 19).replace('T', ' ');
getTrendingClaims(dateTime)
.then(result => {
- // logger.debug(result);
res.status(200).render('popular', {
trendingAssets: result,
});
})
.catch(error => {
- errorHandlers.handleRequestError('popular', originalUrl, ip, error, res);
+ errorHandlers.handleRequestError(originalUrl, ip, error, res);
});
});
// route to display a list of the trending images
app.get('/new', ({ ip, originalUrl }, res) => {
getRecentClaims()
.then(result => {
- // logger.debug(result);
res.status(200).render('new', { newClaims: result });
})
.catch(error => {
- errorHandlers.handleRequestError('new', originalUrl, ip, error, res);
+ errorHandlers.handleRequestError(originalUrl, ip, error, res);
});
});
// route to send embedable video player (for twitter)
diff --git a/routes/serve-routes.js b/routes/serve-routes.js
index eef33f93..cab39ed5 100644
--- a/routes/serve-routes.js
+++ b/routes/serve-routes.js
@@ -1,258 +1,205 @@
const logger = require('winston');
-const { getAssetByClaim, getChannelContents, getAssetByChannel, serveOrShowAsset } = require('../controllers/serveController.js');
+const { getClaimId, getChannelViewData, getLocalFileRecord } = require('../controllers/serveController.js');
+const serveHelpers = require('../helpers/serveHelpers.js');
const { handleRequestError } = require('../helpers/errorHandlers.js');
+const { postToStats } = require('../controllers/statsController.js');
+const db = require('../models');
+const lbryUri = require('../helpers/lbryUri.js');
const SERVE = 'SERVE';
const SHOW = 'SHOW';
const SHOWLITE = 'SHOWLITE';
-const CHANNEL = 'CHANNEL';
-const CLAIM = 'CLAIM';
-const CLAIM_ID_CHAR = ':';
-const CHANNEL_CHAR = '@';
-const CLAIMS_PER_PAGE = 10;
const NO_CHANNEL = 'NO_CHANNEL';
const NO_CLAIM = 'NO_CLAIM';
+const NO_FILE = 'NO_FILE';
function isValidClaimId (claimId) {
return ((claimId.length === 40) && !/[^A-Za-z0-9]/g.test(claimId));
}
function isValidShortId (claimId) {
- return claimId.length === 1; // really it should evaluate the short url itself
+ return claimId.length === 1; // it should really evaluate the short url itself
}
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);
+function sendChannelInfoAndContentToClient (channelPageData, res) {
+ if (channelPageData === NO_CHANNEL) {
+ res.status(200).render('noChannel');
+ } else {
+ res.status(200).render('channel', channelPageData);
+ }
+}
+
+function showChannelPageToClient (channelName, channelClaimId, originalUrl, ip, query, res) {
+ // 1. retrieve the channel contents
+ getChannelViewData(channelName, channelClaimId, query)
+ .then(channelViewData => {
+ sendChannelInfoAndContentToClient(channelViewData, res);
+ })
+ .catch(error => {
+ handleRequestError(originalUrl, ip, error, res);
+ });
+}
+
+function clientAcceptsHtml (headers) {
+ return headers['accept'] && headers['accept'].split(',').includes('text/html');
+}
+
+function determineResponseType (isServeRequest, headers) {
+ let responseType;
+ if (isServeRequest) {
+ responseType = SERVE;
+ if (clientAcceptsHtml(headers)) { // this is in case a serve request comes from a browser
+ responseType = SHOWLITE;
+ }
+ } else {
+ responseType = SHOW;
+ if (!clientAcceptsHtml(headers)) { // this is in case someone embeds a show url
+ responseType = SERVE;
+ }
+ }
+ return responseType;
+}
+
+function showAssetToClient (claimId, name, res) {
+ return Promise
+ .all([db.Claim.resolveClaim(name, claimId), db.Claim.getShortClaimIdFromLongClaimId(claimId, name)])
+ .then(([claimInfo, shortClaimId]) => {
+ logger.debug('claimInfo:', claimInfo);
+ logger.debug('shortClaimId:', shortClaimId);
+ return serveHelpers.showFile(claimInfo, shortClaimId, res);
+ })
+ .catch(error => {
+ throw error;
+ });
+}
+
+function showLiteAssetToClient (claimId, name, res) {
+ return Promise
+ .all([db.Claim.resolveClaim(name, claimId), db.Claim.getShortClaimIdFromLongClaimId(claimId, name)])
+ .then(([claimInfo, shortClaimId]) => {
+ logger.debug('claimInfo:', claimInfo);
+ logger.debug('shortClaimId:', shortClaimId);
+ return serveHelpers.showFileLite(claimInfo, shortClaimId, res);
+ })
+ .catch(error => {
+ throw error;
+ });
+}
+
+function serveAssetToClient (claimId, name, res) {
+ return getLocalFileRecord(claimId, name)
+ .then(fileInfo => {
+ logger.debug('fileInfo:', fileInfo);
+ if (fileInfo === NO_FILE) {
+ return res.status(307).redirect(`/api/claim-get/${name}/${claimId}`);
+ }
+ return serveHelpers.serveFile(fileInfo, claimId, name, res);
+ })
+ .catch(error => {
+ throw error;
+ });
+}
+
+function showOrServeAsset (responseType, claimId, claimName, res) {
+ switch (responseType) {
+ case SHOW:
+ return showAssetToClient(claimId, claimName, res);
+ case SHOWLITE:
+ return showLiteAssetToClient(claimId, claimName, res);
+ case SERVE:
+ return serveAssetToClient(claimId, claimName, res);
default:
- return new Error('that claim type was not found');
+ break;
}
}
-function getPage (query) {
- if (query.p) {
- return parseInt(query.p);
+function flipClaimNameAndIdForBackwardsCompatibility (identifier, name) {
+ // this is a patch for backwards compatability with 'spee.ch/name/claim_id' url format
+ if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) {
+ const tempName = name;
+ name = identifier;
+ identifier = tempName;
}
- return 1;
+ return [identifier, name];
}
-function extractPageFromClaims (claims, pageNumber) {
- logger.debug('claims is array?', Array.isArray(claims));
- logger.debug(`pageNumber ${pageNumber} is number?`, Number.isInteger(pageNumber));
- const claimStartIndex = (pageNumber - 1) * CLAIMS_PER_PAGE;
- const claimEndIndex = claimStartIndex + 10;
- const pageOfClaims = claims.slice(claimStartIndex, claimEndIndex);
- return pageOfClaims;
-}
-
-function determineTotalPages (totalClaims) {
- if (totalClaims === 0) {
- return 0;
- }
- if (totalClaims < CLAIMS_PER_PAGE) {
- return 1;
- }
- const fullPages = Math.floor(totalClaims / CLAIMS_PER_PAGE);
- const remainder = totalClaims % CLAIMS_PER_PAGE;
- if (remainder === 0) {
- return fullPages;
- }
- return fullPages + 1;
-}
-
-function determinePreviousPage (currentPage) {
- if (currentPage === 1) {
- return null;
- }
- return currentPage - 1;
-}
-
-function determineNextPage (totalPages, currentPage) {
- if (currentPage === totalPages) {
- return null;
- }
- return currentPage + 1;
+function logRequestData (responseType, claimName, channelName, claimId) {
+ logger.debug('responseType ===', responseType);
+ logger.debug('claim name === ', claimName);
+ logger.debug('channel name ===', channelName);
+ logger.debug('claim id ===', claimId);
}
module.exports = (app) => {
- // route to serve a specific asset
+ // route to serve a specific asset using the channel or claim id
app.get('/:identifier/:name', ({ headers, ip, originalUrl, params }, res) => {
- let identifier = params.identifier;
- let name = params.name;
- let claimOrChannel;
- let channelName = null;
- let claimId = null;
- let channelId = null;
- let method;
- let fileExtension;
- // parse the name
- const positionOfExtension = name.indexOf('.');
- if (positionOfExtension >= 0) {
- 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 =', fileExtension);
- if (headers['accept'] && headers['accept'].split(',').includes('text/html')) {
- method = SHOWLITE;
- } else {
- method = SERVE;
- }
- } else {
- method = SHOW;
- if (!headers['accept'] || !headers['accept'].split(',').includes('text/html')) {
- method = SERVE;
- }
+ let isChannel, channelName, channelClaimId, claimId, claimName, isServeRequest;
+ try {
+ ({ isChannel, channelName, channelClaimId, claimId } = lbryUri.parseIdentifier(params.identifier));
+ ({ claimName, isServeRequest } = lbryUri.parseName(params.name));
+ } catch (error) {
+ return handleRequestError(originalUrl, ip, error, res);
}
- /* patch for backwards compatability with spee.ch/name/claim_id */
- if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) {
- let tempName = name;
- name = identifier;
- identifier = tempName;
+ if (!isChannel) {
+ [claimId, claimName] = flipClaimNameAndIdForBackwardsCompatibility(claimId, claimName);
}
- /* end patch */
- logger.debug('claim name =', name);
- logger.debug('method =', method);
- // parse identifier for whether it is a channel, short url, or claim_id
- if (identifier.charAt(0) === '@') {
- channelName = identifier;
- claimOrChannel = CHANNEL;
- const channelIdIndex = channelName.indexOf(CLAIM_ID_CHAR);
- if (channelIdIndex !== -1) {
- channelId = channelName.substring(channelIdIndex + 1);
- channelName = channelName.substring(0, channelIdIndex);
+ let responseType = determineResponseType(isServeRequest, headers);
+ // log the request data for debugging
+ logRequestData(responseType, claimName, channelName, claimId);
+ // get the claim Id and then serve/show the asset
+ getClaimId(channelName, channelClaimId, claimName, claimId)
+ .then(fullClaimId => {
+ if (fullClaimId === NO_CLAIM) {
+ return res.status(200).render('noClaim');
+ } else if (fullClaimId === NO_CHANNEL) {
+ return res.status(200).render('noChannel');
}
- logger.debug('channel name =', channelName);
- } else {
- claimId = identifier;
- logger.debug('claim id =', claimId);
- claimOrChannel = CLAIM;
- }
- // 1. retrieve the asset and information
- getAsset(claimOrChannel, channelName, channelId, name, claimId)
- // 2. serve or show
- .then(result => {
- logger.debug('getAsset result:', result);
- if (result === NO_CLAIM) {
- res.status(200).render('noClaim');
- return;
- } else if (result === NO_CHANNEL) {
- res.status(200).render('noChannel');
- return;
- }
- return serveOrShowAsset(result, fileExtension, method, headers, originalUrl, ip, res);
- })
- // 3. update the file
- .then(fileInfoForUpdate => {
- // if needed, this is where we would update the file
+ showOrServeAsset(responseType, fullClaimId, claimName, res);
+ postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'success');
})
.catch(error => {
- handleRequestError('serve', originalUrl, ip, error, res);
+ handleRequestError(originalUrl, ip, error, res);
});
});
- // route to serve the winning asset at a claim
- app.get('/:name', ({ headers, ip, originalUrl, params, query }, res) => {
- // parse name param
- let name = params.name;
- let method;
- let fileExtension;
- let channelName = null;
- let channelId = null;
- // (a) handle channel requests
- if (name.charAt(0) === CHANNEL_CHAR) {
- channelName = name;
- const paginationPage = getPage(query);
- const channelIdIndex = channelName.indexOf(CLAIM_ID_CHAR);
- if (channelIdIndex !== -1) {
- channelId = channelName.substring(channelIdIndex + 1);
- channelName = channelName.substring(0, channelIdIndex);
- }
- logger.debug('channel name =', channelName);
- logger.debug('channel Id =', channelId);
- // 1. retrieve the channel contents
- getChannelContents(channelName, channelId)
- // 2. respond to the request
- .then(result => {
- if (result === NO_CHANNEL) { // no channel found
- res.status(200).render('noChannel');
- } else if (!result.claims) { // channel found, but no claims
- res.status(200).render('channel', {
- layout : 'channel',
- channelName : result.channelName,
- longChannelId : result.longChannelId,
- shortChannelId: result.shortChannelId,
- claims : [],
- previousPage : 0,
- currentPage : 0,
- nextPage : 0,
- totalPages : 0,
- totalResults : 0,
- });
- } else { // channel found, with claims
- const totalPages = determineTotalPages(result.claims.length);
- res.status(200).render('channel', {
- layout : 'channel',
- channelName : result.channelName,
- longChannelId : result.longChannelId,
- shortChannelId: result.shortChannelId,
- claims : extractPageFromClaims(result.claims, paginationPage),
- previousPage : determinePreviousPage(paginationPage),
- currentPage : paginationPage,
- nextPage : determineNextPage(totalPages, paginationPage),
- totalPages : totalPages,
- totalResults : result.claims.length,
- });
- }
- })
- .catch(error => {
- handleRequestError('serve', originalUrl, ip, error, res);
- });
- // (b) handle stream requests
+ // route to serve the winning asset at a claim or a channel page
+ app.get('/:identifier', ({ headers, ip, originalUrl, params, query }, res) => {
+ let isChannel, channelName, channelClaimId;
+ try {
+ ({ isChannel, channelName, channelClaimId } = lbryUri.parseIdentifier(params.identifier));
+ } catch (error) {
+ return handleRequestError(originalUrl, ip, error, res);
+ }
+ if (isChannel) {
+ // log the request data for debugging
+ logRequestData(null, null, channelName, null);
+ // handle showing the channel page
+ showChannelPageToClient(channelName, channelClaimId, originalUrl, ip, query, res);
} else {
- if (name.indexOf('.') !== -1) {
- method = SERVE;
- if (headers['accept'] && headers['accept'].split(',').includes('text/html')) {
- method = SHOWLITE;
- }
- fileExtension = name.substring(name.indexOf('.') + 1);
- name = name.substring(0, name.indexOf('.'));
- logger.debug('file extension =', fileExtension);
- } else {
- method = SHOW;
- if (!headers['accept'] || !headers['accept'].split(',').includes('text/html')) {
- method = SERVE;
- }
+ let claimName, isServeRequest;
+ try {
+ ({claimName, isServeRequest} = lbryUri.parseName(params.identifier));
+ } catch (error) {
+ return handleRequestError(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(result => {
- logger.debug('getAsset result', result);
- if (result === NO_CLAIM) {
- res.status(200).render('noClaim');
- } else {
- return serveOrShowAsset(result, fileExtension, 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);
- });
+ let responseType = determineResponseType(isServeRequest, headers);
+ // log the request data for debugging
+ logRequestData(responseType, claimName, null, null);
+ // get the claim Id and then serve/show the asset
+ getClaimId(null, null, claimName, null)
+ .then(fullClaimId => {
+ if (fullClaimId === NO_CLAIM) {
+ return res.status(200).render('noClaim');
+ }
+ showOrServeAsset(responseType, fullClaimId, claimName, res);
+ postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'success');
+ })
+ .catch(error => {
+ handleRequestError(originalUrl, ip, error, res);
+ });
}
});
};
diff --git a/testpage.html b/testpage.html
new file mode 100644
index 00000000..65dcdb74
--- /dev/null
+++ b/testpage.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Test Page
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/views/channel.handlebars b/views/channel.handlebars
index d512e675..37eee568 100644
--- a/views/channel.handlebars
+++ b/views/channel.handlebars
@@ -1,10 +1,10 @@
{{#ifConditional this.totalPages '===' 0}}
-
There is no content in {{this.channelName}}:{{this.longChannelId}} yet. Upload some!
+
There is no content in {{this.channelName}}:{{this.longChannelClaimId}} yet. Upload some!
{{/ifConditional}}
{{#ifConditional this.totalPages '>=' 1}}
-
Below are the contents for {{this.channelName}}:{{this.longChannelId}}
+
Below are the contents for {{this.channelName}}:{{this.longChannelClaimId}}
{{#each this.claims}}
{{> gridItem}}
@@ -14,21 +14,21 @@
{{#ifConditional this.totalPages '>' 1}}
{{/ifConditional}}
@@ -51,4 +51,4 @@
});
-
\ No newline at end of file
+
diff --git a/views/layouts/channel.handlebars b/views/layouts/channel.handlebars
index 7aef87e1..c2832136 100644
--- a/views/layouts/channel.handlebars
+++ b/views/layouts/channel.handlebars
@@ -1,13 +1,7 @@
-
-
-
-
Spee.ch
-
-
-
+ {{ placeCommonHeaderTags }}
@@ -27,4 +21,4 @@
{{> navBar}}
{{{ body }}}
-
\ No newline at end of file
+