Development #287
39 changed files with 1073 additions and 820 deletions
22
README.md
22
README.md
|
@ -24,19 +24,23 @@ spee.ch is a single-serving site that reads and publishes images and videos to a
|
||||||
## API
|
## API
|
||||||
|
|
||||||
#### GET
|
#### GET
|
||||||
* /api/resolve/:name
|
* /api/claim-resolve/:name
|
||||||
* example: `curl https://spee.ch/api/resolve/doitlive`
|
* example: `curl https://spee.ch/api/claim-resolve/doitlive`
|
||||||
* /api/claim_list/:name
|
* /api/claim-list/:name
|
||||||
* example: `curl https://spee.ch/api/claim_list/doitlive`
|
* example: `curl https://spee.ch/api/claim-list/doitlive`
|
||||||
* /api/isClaimAvailable/:name (returns `true`/`false` for whether a name is available through spee.ch)
|
* /api/claim-is-available/:name (
|
||||||
* example: `curl https://spee.ch/api/isClaimAvailable/doitlive`
|
* 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
|
#### POST
|
||||||
* /api/publish
|
* /api/claim-publish
|
||||||
* example: `curl -X POST -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/publish`
|
* example: `curl -X POST -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim-publish`
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* `name`
|
* `name`
|
||||||
* `file` (.mp4, .jpeg, .jpg, .gif, or .png)
|
* `file` (must be type .mp4, .jpeg, .jpg, .gif, or .png)
|
||||||
* `nsfw` (optional)
|
* `nsfw` (optional)
|
||||||
* `license` (optional)
|
* `license` (optional)
|
||||||
* `title` (optional)
|
* `title` (optional)
|
||||||
|
|
|
@ -4,27 +4,30 @@ const winstonSlackWebHook = require('winston-slack-webhook').SlackWebHook;
|
||||||
module.exports = (winston) => {
|
module.exports = (winston) => {
|
||||||
if (config.logging.slackWebHook) {
|
if (config.logging.slackWebHook) {
|
||||||
// add a transport for errors to slack
|
// add a transport for errors to slack
|
||||||
winston.add(winstonSlackWebHook, {
|
if (config.logging.slackErrorChannel) {
|
||||||
name : 'slack-errors-transport',
|
winston.add(winstonSlackWebHook, {
|
||||||
level : 'warn',
|
name : 'slack-errors-transport',
|
||||||
webhookUrl: config.logging.slackWebHook,
|
level : 'warn',
|
||||||
channel : config.logging.slackErrorChannel,
|
webhookUrl: config.logging.slackWebHook,
|
||||||
username : 'spee.ch',
|
channel : config.logging.slackErrorChannel,
|
||||||
iconEmoji : ':face_with_head_bandage:',
|
username : 'spee.ch',
|
||||||
});
|
iconEmoji : ':face_with_head_bandage:',
|
||||||
winston.add(winstonSlackWebHook, {
|
});
|
||||||
name : 'slack-info-transport',
|
};
|
||||||
level : 'info',
|
if (config.logging.slackInfoChannel) {
|
||||||
webhookUrl: config.logging.slackWebHook,
|
winston.add(winstonSlackWebHook, {
|
||||||
channel : config.logging.slackInfoChannel,
|
name : 'slack-info-transport',
|
||||||
username : 'spee.ch',
|
level : 'info',
|
||||||
iconEmoji : ':nerd_face:',
|
webhookUrl: config.logging.slackWebHook,
|
||||||
});
|
channel : config.logging.slackInfoChannel,
|
||||||
|
username : 'spee.ch',
|
||||||
|
iconEmoji : ':nerd_face:',
|
||||||
|
});
|
||||||
|
};
|
||||||
// send test message
|
// send test message
|
||||||
winston.error('Slack "error" logging is online.');
|
winston.error('Slack "error" logging is online.');
|
||||||
winston.warn('Slack "warning" logging is online.');
|
|
||||||
winston.info('Slack "info" logging is online.');
|
winston.info('Slack "info" logging is online.');
|
||||||
} else {
|
} 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.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,253 +1,90 @@
|
||||||
const lbryApi = require('../helpers/lbryApi.js');
|
|
||||||
const db = require('../models');
|
const db = require('../models');
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const { serveFile, showFile, showFileLite } = require('../helpers/serveHelpers.js');
|
const { returnPaginatedChannelViewData } = require('../helpers/channelPagination.js');
|
||||||
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.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_CHANNEL = 'NO_CHANNEL';
|
||||||
const NO_CLAIM = 'NO_CLAIM';
|
const NO_CLAIM = 'NO_CLAIM';
|
||||||
|
const NO_FILE = 'NO_FILE';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getAssetByClaim (claimName, claimId) {
|
getClaimId (channelName, channelClaimId, name, claimId) {
|
||||||
logger.debug(`getAssetByClaim(${claimName}, ${claimId})`);
|
if (channelName) {
|
||||||
return new Promise((resolve, reject) => {
|
return module.exports.getClaimIdByChannel(channelName, channelClaimId, name);
|
||||||
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';
|
|
||||||
} else {
|
} else {
|
||||||
fileInfo['fileExt'] = fileInfo.fileName.substring(fileInfo.fileName.lastIndexOf('.') + 1);
|
return module.exports.getClaimIdByClaim(name, claimId);
|
||||||
}
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,25 +70,23 @@ module.exports = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getTrendingClaims (startDate) {
|
getTrendingClaims (startDate) {
|
||||||
logger.debug('retrieving trending requests');
|
logger.debug('retrieving trending');
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// get the raw requests data
|
// get the raw requests data
|
||||||
db.getTrendingClaims(startDate)
|
db.getTrendingFiles(startDate)
|
||||||
.then(results => {
|
.then(fileArray => {
|
||||||
if (results) {
|
let claimsPromiseArray = [];
|
||||||
results.forEach(element => {
|
if (fileArray) {
|
||||||
const fileExtenstion = element.fileType.substring(element.fileType.lastIndexOf('/') + 1);
|
fileArray.forEach(file => {
|
||||||
element['showUrlLong'] = `/${element.claimId}/${element.name}`;
|
claimsPromiseArray.push(db.Claim.resolveClaim(file.name, file.claimId));
|
||||||
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';
|
|
||||||
});
|
});
|
||||||
|
return Promise.all(claimsPromiseArray);
|
||||||
}
|
}
|
||||||
resolve(results);
|
})
|
||||||
|
.then(claimsArray => {
|
||||||
|
resolve(claimsArray);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('sequelize error >>', error);
|
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
71
helpers/channelPagination.js
Normal file
71
helpers/channelPagination.js
Normal file
|
@ -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;
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,5 +1,4 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const { postToStats } = require('../controllers/statsController.js');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
returnErrorMessageAndStatus: function (error) {
|
returnErrorMessageAndStatus: function (error) {
|
||||||
|
@ -33,17 +32,15 @@ module.exports = {
|
||||||
}
|
}
|
||||||
return [status, message];
|
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));
|
logger.error(`Request Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error));
|
||||||
postToStats(action, originalUrl, ip, null, null, error);
|
|
||||||
const [status, message] = module.exports.returnErrorMessageAndStatus(error);
|
const [status, message] = module.exports.returnErrorMessageAndStatus(error);
|
||||||
res
|
res
|
||||||
.status(status)
|
.status(status)
|
||||||
.render('requestError', module.exports.createErrorResponsePayload(status, message));
|
.render('requestError', module.exports.createErrorResponsePayload(status, message));
|
||||||
},
|
},
|
||||||
handleApiError: function (action, originalUrl, ip, error, res) {
|
handleApiError: function (originalUrl, ip, error, res) {
|
||||||
logger.error(`Api ${action} Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error));
|
logger.error(`Api Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error));
|
||||||
postToStats(action, originalUrl, ip, null, null, error);
|
|
||||||
const [status, message] = module.exports.returnErrorMessageAndStatus(error);
|
const [status, message] = module.exports.returnErrorMessageAndStatus(error);
|
||||||
res
|
res
|
||||||
.status(status)
|
.status(status)
|
||||||
|
|
|
@ -2,6 +2,10 @@ const Handlebars = require('handlebars');
|
||||||
const config = require('../config/speechConfig.js');
|
const config = require('../config/speechConfig.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
placeCommonHeaderTags () {
|
||||||
|
const headerBoilerplate = `<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Spee.ch</title><link rel="stylesheet" href="/assets/css/reset.css" type="text/css"><link rel="stylesheet" href="/assets/css/general.css" type="text/css"><link rel="stylesheet" href="/assets/css/mediaQueries.css" type="text/css">`;
|
||||||
|
return new Handlebars.SafeString(headerBoilerplate);
|
||||||
|
},
|
||||||
googleAnalytics () {
|
googleAnalytics () {
|
||||||
const googleApiKey = config.analytics.googleId;
|
const googleApiKey = config.analytics.googleId;
|
||||||
const gaCode = `<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
const gaCode = `<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
@ -12,41 +16,35 @@ module.exports = {
|
||||||
ga('send', 'pageview');</script>`;
|
ga('send', 'pageview');</script>`;
|
||||||
return new Handlebars.SafeString(gaCode);
|
return new Handlebars.SafeString(gaCode);
|
||||||
},
|
},
|
||||||
addOpenGraph (title, mimeType, showUrl, source, description, thumbnail) {
|
addOpenGraph ({ ogTitle, contentType, ogDescription, thumbnail, showUrl, source, ogThumbnailContentType }) {
|
||||||
if (title === null || title.trim() === '') {
|
const ogTitleTag = `<meta property="og:title" content="${ogTitle}" >`;
|
||||||
title = 'Spee.ch';
|
const ogUrlTag = `<meta property="og:url" content="${showUrl}" >`;
|
||||||
}
|
const ogSiteNameTag = `<meta property="og:site_name" content="Spee.ch" >`;
|
||||||
if (description === null || description.trim() === '') {
|
const ogDescriptionTag = `<meta property="og:description" content="${ogDescription}" >`;
|
||||||
description = 'Open-source, decentralized image and video sharing.';
|
const ogImageWidthTag = '<meta property="og:image:width" content="600" >';
|
||||||
}
|
const ogImageHeightTag = '<meta property="og:image:height" content="315" >';
|
||||||
const ogTitle = `<meta property="og:title" content="${title}" >`;
|
const basicTags = `${ogTitleTag} ${ogUrlTag} ${ogSiteNameTag} ${ogDescriptionTag} ${ogImageWidthTag} ${ogImageHeightTag}`;
|
||||||
const ogUrl = `<meta property="og:url" content="${showUrl}" >`;
|
let ogImageTag = `<meta property="og:image" content="${source}" >`;
|
||||||
const ogSiteName = `<meta property="og:site_name" content="Spee.ch" >`;
|
let ogImageTypeTag = `<meta property="og:image:type" content="${contentType}" >`;
|
||||||
const ogDescription = `<meta property="og:description" content="${description}" >`;
|
let ogTypeTag = `<meta property="og:type" content="article" >`;
|
||||||
const ogImageWidth = '<meta property="og:image:width" content="600" >';
|
if (contentType === 'video/mp4') {
|
||||||
const ogImageHeight = '<meta property="og:image:height" content="315" >';
|
const ogVideoTag = `<meta property="og:video" content="${source}" >`;
|
||||||
const basicTags = `${ogTitle} ${ogUrl} ${ogSiteName} ${ogDescription} ${ogImageWidth} ${ogImageHeight}`;
|
const ogVideoSecureUrlTag = `<meta property="og:video:secure_url" content="${source}" >`;
|
||||||
let ogImage = `<meta property="og:image" content="${source}" >`;
|
const ogVideoTypeTag = `<meta property="og:video:type" content="${contentType}" >`;
|
||||||
let ogImageType = `<meta property="og:image:type" content="${mimeType}" >`;
|
ogImageTag = `<meta property="og:image" content="${thumbnail}" >`;
|
||||||
let ogType = `<meta property="og:type" content="article" >`;
|
ogImageTypeTag = `<meta property="og:image:type" content="${ogThumbnailContentType}" >`;
|
||||||
if (mimeType === 'video/mp4') {
|
ogTypeTag = `<meta property="og:type" content="video" >`;
|
||||||
const ogVideo = `<meta property="og:video" content="${source}" >`;
|
return new Handlebars.SafeString(`${basicTags} ${ogImageTag} ${ogImageTypeTag} ${ogTypeTag} ${ogVideoTag} ${ogVideoSecureUrlTag} ${ogVideoTypeTag}`);
|
||||||
const ogVideoSecureUrl = `<meta property="og:video:secure_url" content="${source}" >`;
|
|
||||||
const ogVideoType = `<meta property="og:video:type" content="${mimeType}" >`;
|
|
||||||
ogImage = `<meta property="og:image" content="${thumbnail}" >`;
|
|
||||||
ogImageType = `<meta property="og:image:type" content="image/png" >`;
|
|
||||||
ogType = `<meta property="og:type" content="video" >`;
|
|
||||||
return new Handlebars.SafeString(`${basicTags} ${ogImage} ${ogImageType} ${ogType} ${ogVideo} ${ogVideoSecureUrl} ${ogVideoType}`);
|
|
||||||
} else {
|
} else {
|
||||||
if (mimeType === 'image/gif') {
|
if (contentType === 'image/gif') {
|
||||||
ogType = `<meta property="og:type" content="video.other" >`;
|
ogTypeTag = `<meta property="og:type" content="video.other" >`;
|
||||||
};
|
};
|
||||||
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 = `<meta name="twitter:site" content="@spee_ch" >`;
|
const basicTwitterTags = `<meta name="twitter:site" content="@spee_ch" >`;
|
||||||
if (mimeType === 'video/mp4') {
|
if (contentType === 'video/mp4') {
|
||||||
const twitterName = '<meta name="twitter:card" content="player" >';
|
const twitterName = '<meta name="twitter:card" content="player" >';
|
||||||
const twitterPlayer = `<meta name="twitter:player" content="${embedUrl}" >`;
|
const twitterPlayer = `<meta name="twitter:player" content="${embedUrl}" >`;
|
||||||
const twitterPlayerWidth = '<meta name="twitter:player:width" content="600" >';
|
const twitterPlayerWidth = '<meta name="twitter:player:width" content="600" >';
|
||||||
|
|
|
@ -10,7 +10,6 @@ function handleLbrynetResponse ({ data }, resolve, reject) {
|
||||||
reject(data.result.error);
|
reject(data.result.error);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// logger.debug('data.result', data.result);
|
|
||||||
resolve(data.result);
|
resolve(data.result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
90
helpers/lbryUri.js
Normal file
90
helpers/lbryUri.js
Normal file
|
@ -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,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,23 +1,23 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
returnShortId: function (result, longId) {
|
returnShortId: function (claimsArray, longId) {
|
||||||
let claimIndex;
|
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;
|
let shortIdLength = 0;
|
||||||
// find the index of this claim id
|
// find the index of this claim id
|
||||||
claimIndex = result.findIndex(element => {
|
claimIndex = claimsArray.findIndex(element => {
|
||||||
return element.claimId === longId;
|
return element.claimId === longId;
|
||||||
});
|
});
|
||||||
if (claimIndex < 0) {
|
if (claimIndex < 0) {
|
||||||
throw new Error('claim id not found in claims list');
|
throw new Error('claim id not found in claims list');
|
||||||
}
|
}
|
||||||
// get an array of all claims with lower height
|
// 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.
|
// remove certificates with the same prefixes until none are left.
|
||||||
while (possibleMatches.length > 0) {
|
while (possibleMatches.length > 0) {
|
||||||
shortIdLength += 1;
|
shortIdLength += 1;
|
||||||
shortId = longId.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 && (element.claimId.substring(0, shortIdLength) === shortId));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return shortId;
|
return shortId;
|
||||||
|
|
|
@ -1,45 +1,23 @@
|
||||||
const logger = require('winston');
|
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 = {
|
module.exports = {
|
||||||
serveFile ({ fileName, fileType, filePath }, res) {
|
serveFile ({ filePath, fileType }, claimId, name, res) {
|
||||||
logger.verbose(`serving file ${fileName}`);
|
logger.verbose(`serving ${name}#${claimId}`);
|
||||||
// set default options
|
// set response options
|
||||||
let options = {
|
const headerContentType = fileType || 'image/jpeg';
|
||||||
|
const options = {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'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
|
// send the file
|
||||||
res.status(200).sendFile(filePath, options);
|
res.status(200).sendFile(filePath, options);
|
||||||
},
|
},
|
||||||
showFile (fileInfo, res) {
|
showFile (claimInfo, shortId, res) {
|
||||||
const openGraphInfo = createOpenGraphInfo(fileInfo);
|
res.status(200).render('show', { layout: 'show', claimInfo, shortId });
|
||||||
res.status(200).render('show', { layout: 'show', fileInfo, openGraphInfo });
|
|
||||||
},
|
},
|
||||||
showFileLite (fileInfo, res) {
|
showFileLite (claimInfo, shortId, res) {
|
||||||
const openGraphInfo = createOpenGraphInfo(fileInfo);
|
res.status(200).render('showLite', { layout: 'showlite', claimInfo, shortId });
|
||||||
res.status(200).render('showLite', { layout: 'showlite', fileInfo, openGraphInfo });
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const { returnShortId } = require('../helpers/sequelizeHelpers.js');
|
const { returnShortId } = require('../helpers/sequelizeHelpers.js');
|
||||||
const NO_CHANNEL = 'NO_CHANNEL';
|
|
||||||
|
|
||||||
module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
const Certificate = sequelize.define(
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
this
|
this
|
||||||
.findAll({
|
.findAll({
|
||||||
where: {
|
where: {
|
||||||
name : channelName,
|
name : channelName,
|
||||||
claimId: {
|
claimId: {
|
||||||
$like: `${channelId}%`,
|
$like: `${channelClaimId}%`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
order: [['height', 'ASC']],
|
order: [['height', 'ASC']],
|
||||||
|
@ -138,7 +137,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
.then(result => {
|
.then(result => {
|
||||||
switch (result.length) {
|
switch (result.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return resolve(NO_CHANNEL);
|
return resolve(null);
|
||||||
default: // note results must be sorted
|
default: // note results must be sorted
|
||||||
return resolve(result[0].claimId);
|
return resolve(result[0].claimId);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +159,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
.then(result => {
|
.then(result => {
|
||||||
switch (result.length) {
|
switch (result.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return resolve(NO_CHANNEL);
|
return resolve(null);
|
||||||
default:
|
default:
|
||||||
return resolve(result[0].claimId);
|
return resolve(result[0].claimId);
|
||||||
}
|
}
|
||||||
|
@ -171,12 +170,29 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Certificate.getLongChannelId = function (channelName, channelId) {
|
Certificate.validateLongChannelId = function (name, claimId) {
|
||||||
logger.debug(`getLongChannelId(${channelName}, ${channelId})`);
|
return new Promise((resolve, reject) => {
|
||||||
if (channelId && (channelId.length === 40)) { // if a full channel id is provided
|
this.findOne({
|
||||||
return new Promise((resolve, reject) => resolve(channelId));
|
where: {name, claimId},
|
||||||
} else if (channelId && channelId.length < 40) { // if a short channel id is provided
|
})
|
||||||
return this.getLongChannelIdFromShortChannelId(channelName, channelId);
|
.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 {
|
} else {
|
||||||
return this.getLongChannelIdFromChannelName(channelName); // if no channel id provided
|
return this.getLongChannelIdFromChannelName(channelName); // if no channel id provided
|
||||||
}
|
}
|
||||||
|
|
152
models/claim.js
152
models/claim.js
|
@ -1,6 +1,89 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const { returnShortId } = require('../helpers/sequelizeHelpers.js');
|
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<ch';
|
||||||
|
const DEFAULT_DESCRIPTION = 'Decentralized video and content hosting.';
|
||||||
|
|
||||||
|
function determineFileExtensionFromContentType (contentType) {
|
||||||
|
switch (contentType) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
case 'image/jpg':
|
||||||
|
return 'jpg';
|
||||||
|
case 'image/png':
|
||||||
|
return 'png';
|
||||||
|
case 'image/gif':
|
||||||
|
return 'gif';
|
||||||
|
case 'video/mp4':
|
||||||
|
return 'mp4';
|
||||||
|
default:
|
||||||
|
logger.info('setting unknown file type as file extension jpg');
|
||||||
|
return 'jpg';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function determineContentTypeFromFileExtension (fileExtension) {
|
||||||
|
switch (fileExtension) {
|
||||||
|
case 'jpeg':
|
||||||
|
case 'jpg':
|
||||||
|
return 'image/jpg';
|
||||||
|
case 'png':
|
||||||
|
return 'image/png';
|
||||||
|
case 'gif':
|
||||||
|
return 'image/gif';
|
||||||
|
case 'mp4':
|
||||||
|
return 'video/mp4';
|
||||||
|
default:
|
||||||
|
logger.info('setting unknown file type as type image/jpg');
|
||||||
|
return 'image/jpg';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function ifEmptyReturnOther (value, replacement) {
|
||||||
|
if (value === '') {
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineThumbnail (storedThumbnail, defaultThumbnail) {
|
||||||
|
return ifEmptyReturnOther(storedThumbnail, defaultThumbnail);
|
||||||
|
};
|
||||||
|
|
||||||
|
function determineOgTitle (storedTitle, defaultTitle) {
|
||||||
|
return ifEmptyReturnOther(storedTitle, defaultTitle);
|
||||||
|
};
|
||||||
|
|
||||||
|
function determineOgDescription (storedDescription, defaultDescription) {
|
||||||
|
return ifEmptyReturnOther(storedDescription, defaultDescription);
|
||||||
|
};
|
||||||
|
|
||||||
|
function determineOgThumbnailContentType (thumbnail) {
|
||||||
|
if (thumbnail) {
|
||||||
|
if (thumbnail.lastIndexOf('.') !== -1) {
|
||||||
|
return determineContentTypeFromFileExtension(thumbnail.substring(thumbnail.lastIndexOf('.')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function addOpengraphDataToClaim (claim) {
|
||||||
|
claim['embedUrl'] = `https://spee.ch/embed/${claim.claimId}/${claim.name}`;
|
||||||
|
claim['showUrl'] = `https://spee.ch/${claim.claimId}/${claim.name}`;
|
||||||
|
claim['source'] = `https://spee.ch/${claim.claimId}/${claim.name}.${claim.fileExt}`;
|
||||||
|
claim['directFileUrl'] = `https://spee.ch/${claim.claimId}/${claim.name}.${claim.fileExt}`;
|
||||||
|
claim['ogTitle'] = determineOgTitle(claim.title, DEFAULT_TITLE);
|
||||||
|
claim['ogDescription'] = determineOgDescription(claim.description, DEFAULT_DESCRIPTION);
|
||||||
|
claim['ogThumbnailContentType'] = determineOgThumbnailContentType(claim.thumbnail);
|
||||||
|
return claim;
|
||||||
|
};
|
||||||
|
|
||||||
|
function prepareClaimData (claim) {
|
||||||
|
// logger.debug('preparing claim data based on resolved data:', claim);
|
||||||
|
claim['thumbnail'] = determineThumbnail(claim.thumbnail, DEFAULT_THUMBNAIL);
|
||||||
|
claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType);
|
||||||
|
claim = addOpengraphDataToClaim(claim);
|
||||||
|
return claim;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
const Claim = sequelize.define(
|
const Claim = sequelize.define(
|
||||||
|
@ -159,7 +242,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
Claim.getShortClaimIdFromLongClaimId = function (claimId, claimName) {
|
Claim.getShortClaimIdFromLongClaimId = function (claimId, claimName) {
|
||||||
logger.debug(`Claim.getShortClaimIdFromLongClaimId for ${claimId}#${claimId}`);
|
logger.debug(`Claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this
|
this
|
||||||
.findAll({
|
.findAll({
|
||||||
|
@ -180,20 +263,27 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Claim.getAllChannelClaims = function (channelId) {
|
Claim.getAllChannelClaims = function (channelClaimId) {
|
||||||
logger.debug(`Claim.getAllChannelClaims for ${channelId}`);
|
logger.debug(`Claim.getAllChannelClaims for ${channelClaimId}`);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this
|
this
|
||||||
.findAll({
|
.findAll({
|
||||||
where: { certificateId: channelId },
|
where: { certificateId: channelClaimId },
|
||||||
order: [['height', 'ASC']],
|
order: [['height', 'ASC']],
|
||||||
|
raw : true, // returns an array of only data, not an array of instances
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(channelClaimsArray => {
|
||||||
switch (result.length) {
|
// logger.debug('channelclaimsarray length:', channelClaimsArray.length);
|
||||||
|
switch (channelClaimsArray.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
default:
|
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 => {
|
.catch(error => {
|
||||||
|
@ -202,18 +292,18 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Claim.getClaimIdByLongChannelId = function (channelId, claimName) {
|
Claim.getClaimIdByLongChannelId = function (channelClaimId, claimName) {
|
||||||
logger.debug(`finding claim id for claim ${claimName} from channel ${channelId}`);
|
logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this
|
this
|
||||||
.findAll({
|
.findAll({
|
||||||
where: { name: claimName, certificateId: channelId },
|
where: { name: claimName, certificateId: channelClaimId },
|
||||||
order: [['id', 'ASC']],
|
order: [['id', 'ASC']],
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
switch (result.length) {
|
switch (result.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return resolve(NO_CLAIM);
|
return resolve(null);
|
||||||
case 1:
|
case 1:
|
||||||
return resolve(result[0].claimId);
|
return resolve(result[0].claimId);
|
||||||
default:
|
default:
|
||||||
|
@ -241,7 +331,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
.then(result => {
|
.then(result => {
|
||||||
switch (result.length) {
|
switch (result.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return resolve(NO_CLAIM);
|
return resolve(null);
|
||||||
default: // note results must be sorted
|
default: // note results must be sorted
|
||||||
return resolve(result[0].claimId);
|
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?
|
order: [['effectiveAmount', 'DESC'], ['height', 'ASC']], // note: maybe height and effective amount need to switch?
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
|
logger.debug('length of result', result.length);
|
||||||
switch (result.length) {
|
switch (result.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return resolve(NO_CLAIM);
|
return resolve(null);
|
||||||
default:
|
default:
|
||||||
logger.debug('getTopFreeClaimIdByClaimName result:', result.dataValues);
|
return resolve(result[0].dataValues.claimId);
|
||||||
return resolve(result[0].claimId);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.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) {
|
Claim.getLongClaimId = function (claimName, claimId) {
|
||||||
logger.debug(`getLongClaimId(${claimName}, ${claimId})`);
|
logger.debug(`getLongClaimId(${claimName}, ${claimId})`);
|
||||||
if (claimId && (claimId.length === 40)) { // if a full claim id is provided
|
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) {
|
} else if (claimId && claimId.length < 40) {
|
||||||
return this.getLongClaimIdFromShortClaimId(claimName, claimId); // if a short claim id is provided
|
return this.getLongClaimIdFromShortClaimId(claimName, claimId); // if a short claim id is provided
|
||||||
} else {
|
} else {
|
||||||
|
@ -291,15 +398,16 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
.findAll({
|
.findAll({
|
||||||
where: { name, claimId },
|
where: { name, claimId },
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(claimArray => {
|
||||||
switch (result.length) {
|
logger.debug('claims found on resolve:', claimArray.length);
|
||||||
|
switch (claimArray.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
case 1:
|
case 1:
|
||||||
return resolve(result[0]);
|
return resolve(prepareClaimData(claimArray[0].dataValues));
|
||||||
default:
|
default:
|
||||||
logger.warn(`more than one entry matches that name (${name}) and claimID (${claimId})`);
|
logger.error(`more than one entry matches that name (${name}) and claimID (${claimId})`);
|
||||||
return resolve(result[0]);
|
return resolve(prepareClaimData(claimArray[0].dataValues));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|
|
@ -69,11 +69,12 @@ db.upsert = (Model, values, condition, tableName) => {
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
logger.error(`${tableName}.upsert error`, error);
|
logger.error(`${tableName}.upsert error`, error);
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// add a 'getTrendingClaims' method to the db object
|
// add a 'getTrendingFiles' method to the db object. note: change this to get claims directly. might need new association between Request and Claim
|
||||||
db.getTrendingClaims = (startDate) => {
|
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 });
|
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 });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,10 @@ h3, p {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#show-body > .fine-print {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.blue {
|
.blue {
|
||||||
color: #4156C5;
|
color: #4156C5;
|
||||||
}
|
}
|
||||||
|
@ -153,9 +157,7 @@ a, a:visited {
|
||||||
.link--primary, .link--primary:visited {
|
.link--primary, .link--primary:visited {
|
||||||
color: #4156C5;
|
color: #4156C5;
|
||||||
}
|
}
|
||||||
.link--primary.fine-print {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.link--nav {
|
.link--nav {
|
||||||
color: black;
|
color: black;
|
||||||
border-bottom: 2px solid white;
|
border-bottom: 2px solid white;
|
||||||
|
@ -296,10 +298,6 @@ a, a:visited {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-message-placeholder {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* INPUT FIELDS */
|
/* INPUT FIELDS */
|
||||||
|
|
||||||
/* blocks */
|
/* blocks */
|
||||||
|
@ -347,14 +345,6 @@ option {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#claim-name-input {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#input-success-claim-name {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.span--relative {
|
.span--relative {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -511,27 +501,34 @@ table {
|
||||||
width: calc(100% - 1rem);
|
width: calc(100% - 1rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show page */
|
/* Assets */
|
||||||
|
|
||||||
.video-show, .gifv-show, .image-show {
|
.asset {
|
||||||
display: block;
|
max-width: 100%;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
#video-player, .showlite-asset {
|
|
||||||
display: block;
|
#show-body #asset-boilerpate {
|
||||||
margin: 0 auto;
|
display: none;
|
||||||
background-color: #fff;
|
}
|
||||||
|
|
||||||
|
#showlite-body #asset-display-component {
|
||||||
|
max-width: 50%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* video */
|
||||||
|
|
||||||
|
#video-asset {
|
||||||
|
background-color: #000000;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
#showlite-body #video-player {
|
#showlite-body #video-asset {
|
||||||
margin-top: 2%;
|
background-color: #ffffff;
|
||||||
padding: 6px;
|
width: calc(100% - 12px - 12px - 2px);
|
||||||
max-width: 50%;
|
margin: 6px;
|
||||||
|
padding: 6px;
|
||||||
border: 1px solid #d0d0d0;
|
border: 1px solid #d0d0d0;
|
||||||
}
|
}
|
||||||
.showlite-asset {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* item lists */
|
/* item lists */
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,14 @@
|
||||||
padding-right: 1.5em;
|
padding-right: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.showlite-asset {
|
#showlite-body #asset-display-component {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#showlite-body #asset-status {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
|
|
139
public/assets/js/assetConstructor.js
Normal file
139
public/assets/js/assetConstructor.js
Normal file
|
@ -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();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
};
|
45
public/assets/js/progressBarConstructor.js
Normal file
45
public/assets/js/progressBarConstructor.js
Normal file
|
@ -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);
|
||||||
|
};
|
||||||
|
};
|
|
@ -102,7 +102,7 @@ const publishFileFunctions = {
|
||||||
return fd;
|
return fd;
|
||||||
},
|
},
|
||||||
publishFile: function (file, metadata) {
|
publishFile: function (file, metadata) {
|
||||||
var uri = "/api/publish";
|
var uri = "/api/claim-publish";
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
var fd = this.appendDataToFormData(file, metadata);
|
var fd = this.appendDataToFormData(file, metadata);
|
||||||
var that = this;
|
var that = this;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -119,13 +119,13 @@ const validationFunctions = {
|
||||||
checkClaimName: function (name) {
|
checkClaimName: function (name) {
|
||||||
const successDisplayElement = document.getElementById('input-success-claim-name');
|
const successDisplayElement = document.getElementById('input-success-claim-name');
|
||||||
const errorDisplayElement = document.getElementById('input-error-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) {
|
checkChannelName: function (name) {
|
||||||
const successDisplayElement = document.getElementById('input-success-channel-name');
|
const successDisplayElement = document.getElementById('input-success-channel-name');
|
||||||
const errorDisplayElement = document.getElementById('input-error-channel-name');
|
const errorDisplayElement = document.getElementById('input-error-channel-name');
|
||||||
name = `@${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
|
// validation function which checks all aspects of the publish submission
|
||||||
validateFilePublishSubmission: function (stagedFiles, metadata) {
|
validateFilePublishSubmission: function (stagedFiles, metadata) {
|
||||||
|
@ -162,7 +162,7 @@ const validationFunctions = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if all validation passes, check availability of the name (note: do we need to re-validate channel name vs. credentials as well?)
|
// 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 => {
|
.then(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -193,7 +193,7 @@ const validationFunctions = {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
// 3. if all validation passes, check availability of the name
|
// 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) {
|
.then(function(result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
resolve();
|
resolve();
|
||||||
|
|
|
@ -4,36 +4,93 @@ const config = require('../config/speechConfig.js');
|
||||||
const multipartMiddleware = multipart({uploadDir: config.files.uploadDirectory});
|
const multipartMiddleware = multipart({uploadDir: config.files.uploadDirectory});
|
||||||
const db = require('../models');
|
const db = require('../models');
|
||||||
const { publish } = require('../controllers/publishController.js');
|
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 { createPublishParams, validateApiPublishRequest, validatePublishSubmission, cleanseChannelName, checkClaimNameAvailability, checkChannelAvailability } = require('../helpers/publishHelpers.js');
|
||||||
const errorHandlers = require('../helpers/errorHandlers.js');
|
const errorHandlers = require('../helpers/errorHandlers.js');
|
||||||
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
|
|
||||||
const { authenticateOrSkip } = require('../auth/authentication.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) => {
|
module.exports = (app) => {
|
||||||
// route to run a claim_list request on the daemon
|
// route to run a claim_list request on the daemon
|
||||||
app.get('/api/claim_list/:name', ({ headers, ip, originalUrl, params }, res) => {
|
app.get('/api/claim-list/:name', ({ ip, originalUrl, params }, res) => {
|
||||||
// google analytics
|
|
||||||
sendGoogleAnalytics('SERVE', headers, ip, originalUrl);
|
|
||||||
// serve the content
|
|
||||||
getClaimList(params.name)
|
getClaimList(params.name)
|
||||||
.then(claimsList => {
|
.then(claimsList => {
|
||||||
postToStats('serve', originalUrl, ip, null, null, 'success');
|
|
||||||
res.status(200).json(claimsList);
|
res.status(200).json(claimsList);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.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
|
// route to check whether spee.ch has published to a claim
|
||||||
app.get('/api/isClaimAvailable/:name', ({ params }, res) => {
|
app.get('/api/claim-is-available/:name', ({ params }, res) => {
|
||||||
// send response
|
|
||||||
checkClaimNameAvailability(params.name)
|
checkClaimNameAvailability(params.name)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
res.status(200).json(true);
|
res.status(200).json(true);
|
||||||
} else {
|
} 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);
|
res.status(200).json(false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -42,37 +99,32 @@ module.exports = (app) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to check whether spee.ch has published to a channel
|
// 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)
|
checkChannelAvailability(params.name)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
res.status(200).json(true);
|
res.status(200).json(true);
|
||||||
} else {
|
} 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);
|
res.status(200).json(false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.debug('api/isChannelAvailable/ error', error);
|
|
||||||
res.status(500).json(error);
|
res.status(500).json(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to run a resolve request on the daemon
|
// route to run a resolve request on the daemon
|
||||||
app.get('/api/resolve/:uri', ({ headers, ip, originalUrl, params }, res) => {
|
app.get('/api/claim-resolve/:uri', ({ headers, ip, originalUrl, params }, res) => {
|
||||||
// google analytics
|
|
||||||
sendGoogleAnalytics('SERVE', headers, ip, originalUrl);
|
|
||||||
// serve content
|
|
||||||
resolveUri(params.uri)
|
resolveUri(params.uri)
|
||||||
.then(resolvedUri => {
|
.then(resolvedUri => {
|
||||||
postToStats('serve', originalUrl, ip, null, null, 'success');
|
|
||||||
res.status(200).json(resolvedUri);
|
res.status(200).json(resolvedUri);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
errorHandlers.handleApiError('resolve', originalUrl, ip, error, res);
|
errorHandlers.handleApiError(originalUrl, ip, error, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to run a publish request on the daemon
|
// 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;
|
let file, fileName, filePath, fileType, name, nsfw, license, title, description, thumbnail, anonymous, skipAuth, channelName, channelPassword;
|
||||||
// validate that mandatory parts of the request are present
|
// validate that mandatory parts of the request are present
|
||||||
try {
|
try {
|
||||||
|
@ -123,7 +175,7 @@ module.exports = (app) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
channelName = cleanseChannelName(channelName);
|
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
|
// check channel authorization
|
||||||
authenticateOrSkip(skipAuth, channelName, channelPassword)
|
authenticateOrSkip(skipAuth, channelName, channelPassword)
|
||||||
.then(authenticated => {
|
.then(authenticated => {
|
||||||
|
@ -156,13 +208,11 @@ module.exports = (app) => {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.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
|
// route to get a short claim id from long claim Id
|
||||||
app.get('/api/shortClaimId/:longId/:name', ({ originalUrl, ip, params }, res) => {
|
app.get('/api/claim-shorten-id/:longId/:name', ({ params }, res) => {
|
||||||
// serve content
|
|
||||||
db.Claim.getShortClaimIdFromLongClaimId(params.longId, params.name)
|
db.Claim.getShortClaimIdFromLongClaimId(params.longId, params.name)
|
||||||
.then(shortId => {
|
.then(shortId => {
|
||||||
res.status(200).json(shortId);
|
res.status(200).json(shortId);
|
||||||
|
@ -173,8 +223,7 @@ module.exports = (app) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to get a short channel id from long channel Id
|
// route to get a short channel id from long channel Id
|
||||||
app.get('/api/shortChannelId/:longId/:name', ({ ip, originalUrl, params }, res) => {
|
app.get('/api/channel-shorten-id/:longId/:name', ({ ip, originalUrl, params }, res) => {
|
||||||
// serve content
|
|
||||||
db.Certificate.getShortChannelIdFromLongChannelId(params.longId, params.name)
|
db.Certificate.getShortChannelIdFromLongChannelId(params.longId, params.name)
|
||||||
.then(shortId => {
|
.then(shortId => {
|
||||||
logger.debug('sending back short channel id', shortId);
|
logger.debug('sending back short channel id', shortId);
|
||||||
|
@ -182,7 +231,7 @@ module.exports = (app) => {
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('api error getting short channel id', 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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const { postToStats } = require('../controllers/statsController.js');
|
|
||||||
|
|
||||||
module.exports = app => {
|
module.exports = app => {
|
||||||
// route for the home page
|
// route for the home page
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
|
@ -7,8 +5,6 @@ module.exports = app => {
|
||||||
});
|
});
|
||||||
// a catch-all route if someone visits a page that does not exist
|
// a catch-all route if someone visits a page that does not exist
|
||||||
app.use('*', ({ originalUrl, ip }, res) => {
|
app.use('*', ({ originalUrl, ip }, res) => {
|
||||||
// post to stats
|
|
||||||
postToStats('show', originalUrl, ip, null, null, 'Error: 404');
|
|
||||||
// send response
|
// send response
|
||||||
res.status(404).render('fourOhFour');
|
res.status(404).render('fourOhFour');
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,24 +30,22 @@ module.exports = (app) => {
|
||||||
const dateTime = startDate.toISOString().slice(0, 19).replace('T', ' ');
|
const dateTime = startDate.toISOString().slice(0, 19).replace('T', ' ');
|
||||||
getTrendingClaims(dateTime)
|
getTrendingClaims(dateTime)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
// logger.debug(result);
|
|
||||||
res.status(200).render('popular', {
|
res.status(200).render('popular', {
|
||||||
trendingAssets: result,
|
trendingAssets: result,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
errorHandlers.handleRequestError('popular', originalUrl, ip, error, res);
|
errorHandlers.handleRequestError(originalUrl, ip, error, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to display a list of the trending images
|
// route to display a list of the trending images
|
||||||
app.get('/new', ({ ip, originalUrl }, res) => {
|
app.get('/new', ({ ip, originalUrl }, res) => {
|
||||||
getRecentClaims()
|
getRecentClaims()
|
||||||
.then(result => {
|
.then(result => {
|
||||||
// logger.debug(result);
|
|
||||||
res.status(200).render('new', { newClaims: result });
|
res.status(200).render('new', { newClaims: result });
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
errorHandlers.handleRequestError('new', originalUrl, ip, error, res);
|
errorHandlers.handleRequestError(originalUrl, ip, error, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to send embedable video player (for twitter)
|
// route to send embedable video player (for twitter)
|
||||||
|
|
|
@ -1,258 +1,205 @@
|
||||||
const logger = require('winston');
|
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 { 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 SERVE = 'SERVE';
|
||||||
const SHOW = 'SHOW';
|
const SHOW = 'SHOW';
|
||||||
const SHOWLITE = 'SHOWLITE';
|
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_CHANNEL = 'NO_CHANNEL';
|
||||||
const NO_CLAIM = 'NO_CLAIM';
|
const NO_CLAIM = 'NO_CLAIM';
|
||||||
|
const NO_FILE = 'NO_FILE';
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidShortId (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) {
|
function isValidShortIdOrClaimId (input) {
|
||||||
return (isValidClaimId(input) || isValidShortId(input));
|
return (isValidClaimId(input) || isValidShortId(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAsset (claimType, channelName, channelId, name, claimId) {
|
function sendChannelInfoAndContentToClient (channelPageData, res) {
|
||||||
switch (claimType) {
|
if (channelPageData === NO_CHANNEL) {
|
||||||
case CHANNEL:
|
res.status(200).render('noChannel');
|
||||||
return getAssetByChannel(channelName, channelId, name);
|
} else {
|
||||||
case CLAIM:
|
res.status(200).render('channel', channelPageData);
|
||||||
return getAssetByClaim(name, claimId);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
default:
|
||||||
return new Error('that claim type was not found');
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPage (query) {
|
function flipClaimNameAndIdForBackwardsCompatibility (identifier, name) {
|
||||||
if (query.p) {
|
// this is a patch for backwards compatability with 'spee.ch/name/claim_id' url format
|
||||||
return parseInt(query.p);
|
if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) {
|
||||||
|
const tempName = name;
|
||||||
|
name = identifier;
|
||||||
|
identifier = tempName;
|
||||||
}
|
}
|
||||||
return 1;
|
return [identifier, name];
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPageFromClaims (claims, pageNumber) {
|
function logRequestData (responseType, claimName, channelName, claimId) {
|
||||||
logger.debug('claims is array?', Array.isArray(claims));
|
logger.debug('responseType ===', responseType);
|
||||||
logger.debug(`pageNumber ${pageNumber} is number?`, Number.isInteger(pageNumber));
|
logger.debug('claim name === ', claimName);
|
||||||
const claimStartIndex = (pageNumber - 1) * CLAIMS_PER_PAGE;
|
logger.debug('channel name ===', channelName);
|
||||||
const claimEndIndex = claimStartIndex + 10;
|
logger.debug('claim id ===', claimId);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = (app) => {
|
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) => {
|
app.get('/:identifier/:name', ({ headers, ip, originalUrl, params }, res) => {
|
||||||
let identifier = params.identifier;
|
let isChannel, channelName, channelClaimId, claimId, claimName, isServeRequest;
|
||||||
let name = params.name;
|
try {
|
||||||
let claimOrChannel;
|
({ isChannel, channelName, channelClaimId, claimId } = lbryUri.parseIdentifier(params.identifier));
|
||||||
let channelName = null;
|
({ claimName, isServeRequest } = lbryUri.parseName(params.name));
|
||||||
let claimId = null;
|
} catch (error) {
|
||||||
let channelId = null;
|
return handleRequestError(originalUrl, ip, error, res);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/* patch for backwards compatability with spee.ch/name/claim_id */
|
if (!isChannel) {
|
||||||
if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) {
|
[claimId, claimName] = flipClaimNameAndIdForBackwardsCompatibility(claimId, claimName);
|
||||||
let tempName = name;
|
|
||||||
name = identifier;
|
|
||||||
identifier = tempName;
|
|
||||||
}
|
}
|
||||||
/* end patch */
|
let responseType = determineResponseType(isServeRequest, headers);
|
||||||
logger.debug('claim name =', name);
|
// log the request data for debugging
|
||||||
logger.debug('method =', method);
|
logRequestData(responseType, claimName, channelName, claimId);
|
||||||
// parse identifier for whether it is a channel, short url, or claim_id
|
// get the claim Id and then serve/show the asset
|
||||||
if (identifier.charAt(0) === '@') {
|
getClaimId(channelName, channelClaimId, claimName, claimId)
|
||||||
channelName = identifier;
|
.then(fullClaimId => {
|
||||||
claimOrChannel = CHANNEL;
|
if (fullClaimId === NO_CLAIM) {
|
||||||
const channelIdIndex = channelName.indexOf(CLAIM_ID_CHAR);
|
return res.status(200).render('noClaim');
|
||||||
if (channelIdIndex !== -1) {
|
} else if (fullClaimId === NO_CHANNEL) {
|
||||||
channelId = channelName.substring(channelIdIndex + 1);
|
return res.status(200).render('noChannel');
|
||||||
channelName = channelName.substring(0, channelIdIndex);
|
|
||||||
}
|
}
|
||||||
logger.debug('channel name =', channelName);
|
showOrServeAsset(responseType, fullClaimId, claimName, res);
|
||||||
} else {
|
postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'success');
|
||||||
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
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
handleRequestError('serve', originalUrl, ip, error, res);
|
handleRequestError(originalUrl, ip, error, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// route to serve the winning asset at a claim
|
// route to serve the winning asset at a claim or a channel page
|
||||||
app.get('/:name', ({ headers, ip, originalUrl, params, query }, res) => {
|
app.get('/:identifier', ({ headers, ip, originalUrl, params, query }, res) => {
|
||||||
// parse name param
|
let isChannel, channelName, channelClaimId;
|
||||||
let name = params.name;
|
try {
|
||||||
let method;
|
({ isChannel, channelName, channelClaimId } = lbryUri.parseIdentifier(params.identifier));
|
||||||
let fileExtension;
|
} catch (error) {
|
||||||
let channelName = null;
|
return handleRequestError(originalUrl, ip, error, res);
|
||||||
let channelId = null;
|
}
|
||||||
// (a) handle channel requests
|
if (isChannel) {
|
||||||
if (name.charAt(0) === CHANNEL_CHAR) {
|
// log the request data for debugging
|
||||||
channelName = name;
|
logRequestData(null, null, channelName, null);
|
||||||
const paginationPage = getPage(query);
|
// handle showing the channel page
|
||||||
const channelIdIndex = channelName.indexOf(CLAIM_ID_CHAR);
|
showChannelPageToClient(channelName, channelClaimId, originalUrl, ip, query, res);
|
||||||
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
|
|
||||||
} else {
|
} else {
|
||||||
if (name.indexOf('.') !== -1) {
|
let claimName, isServeRequest;
|
||||||
method = SERVE;
|
try {
|
||||||
if (headers['accept'] && headers['accept'].split(',').includes('text/html')) {
|
({claimName, isServeRequest} = lbryUri.parseName(params.identifier));
|
||||||
method = SHOWLITE;
|
} catch (error) {
|
||||||
}
|
return handleRequestError(originalUrl, ip, error, res);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logger.debug('claim name = ', name);
|
let responseType = determineResponseType(isServeRequest, headers);
|
||||||
logger.debug('method =', method);
|
// log the request data for debugging
|
||||||
// 1. retrieve the asset and information
|
logRequestData(responseType, claimName, null, null);
|
||||||
getAsset(CLAIM, null, null, name, null)
|
// get the claim Id and then serve/show the asset
|
||||||
// 2. respond to the request
|
getClaimId(null, null, claimName, null)
|
||||||
.then(result => {
|
.then(fullClaimId => {
|
||||||
logger.debug('getAsset result', result);
|
if (fullClaimId === NO_CLAIM) {
|
||||||
if (result === NO_CLAIM) {
|
return res.status(200).render('noClaim');
|
||||||
res.status(200).render('noClaim');
|
}
|
||||||
} else {
|
showOrServeAsset(responseType, fullClaimId, claimName, res);
|
||||||
return serveOrShowAsset(result, fileExtension, method, headers, originalUrl, ip, res);
|
postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'success');
|
||||||
}
|
})
|
||||||
})
|
.catch(error => {
|
||||||
// 3. update the database
|
handleRequestError(originalUrl, ip, error, res);
|
||||||
.then(fileInfoForUpdate => {
|
});
|
||||||
// if needed, this is where we would update the file
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
handleRequestError('serve', originalUrl, ip, error, res);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
17
testpage.html
Normal file
17
testpage.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Test Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="https://staging.spee.ch/zackmath"/>
|
||||||
|
<img src="https://staging.spee.ch/8/zackmath"/>
|
||||||
|
<img src="https://staging.spee.ch/zackmath.ext"/>
|
||||||
|
<img src="https://staging.spee.ch/8/zackmath.ext"/>
|
||||||
|
<video width="50%" controls poster="https://spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/LBRY-Hype"></video>
|
||||||
|
<video width="50%" controls poster="https://spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/a/LBRY-Hype"></video>
|
||||||
|
<video width="50%" controls poster="https://spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/LBRY-Hype.test"></video>
|
||||||
|
<video width="50%" controls poster="https://spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/a/LBRY-Hype.test"></video>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="row row--padded">
|
<div class="row row--padded">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{{#ifConditional this.totalPages '===' 0}}
|
{{#ifConditional this.totalPages '===' 0}}
|
||||||
<p>There is no content in {{this.channelName}}:{{this.longChannelId}} yet. Upload some!</p>
|
<p>There is no content in {{this.channelName}}:{{this.longChannelClaimId}} yet. Upload some!</p>
|
||||||
{{/ifConditional}}
|
{{/ifConditional}}
|
||||||
{{#ifConditional this.totalPages '>=' 1}}
|
{{#ifConditional this.totalPages '>=' 1}}
|
||||||
<p>Below are the contents for {{this.channelName}}:{{this.longChannelId}}</p>
|
<p>Below are the contents for {{this.channelName}}:{{this.longChannelClaimId}}</p>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{{#each this.claims}}
|
{{#each this.claims}}
|
||||||
{{> gridItem}}
|
{{> gridItem}}
|
||||||
|
@ -14,21 +14,21 @@
|
||||||
{{#ifConditional this.totalPages '>' 1}}
|
{{#ifConditional this.totalPages '>' 1}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="column column--3 align-content--left">
|
<div class="column column--3 align-content--left">
|
||||||
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelId}}?p=1">First [1]</a>
|
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelClaimId}}?p=1">First [1]</a>
|
||||||
</div><div class="column column--4 align-content-center">
|
</div><div class="column column--4 align-content-center">
|
||||||
{{#if this.previousPage}}
|
{{#if this.previousPage}}
|
||||||
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelId}}?p={{this.previousPage}}">Previous</a>
|
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelClaimId}}?p={{this.previousPage}}">Previous</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a disabled>Previous</a>
|
<a disabled>Previous</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
|
||||||
{{#if this.nextPage}}
|
{{#if this.nextPage}}
|
||||||
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelId}}?p={{this.nextPage}}">Next</a>
|
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelClaimId}}?p={{this.nextPage}}">Next</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a disabled>Next</a>
|
<a disabled>Next</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div><div class="column column--3 align-content-right">
|
</div><div class="column column--3 align-content-right">
|
||||||
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelId}}?p={{this.totalPages}}">Last [{{this.totalPages}}]</a>
|
<a class="link--primary" href="/{{this.channelName}}:{{this.longChannelClaimId}}?p={{this.totalPages}}">Last [{{this.totalPages}}]</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/ifConditional}}
|
{{/ifConditional}}
|
||||||
|
@ -51,4 +51,4 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
|
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
{{ placeCommonHeaderTags }}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Spee.ch</title>
|
|
||||||
<link rel="stylesheet" href="/assets/css/reset.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/general.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/mediaQueries.css" type="text/css">
|
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:site" content="@spee_ch" />
|
<meta name="twitter:site" content="@spee_ch" />
|
||||||
<meta property="og:title" content="{{this.channelName}} on Spee.ch">
|
<meta property="og:title" content="{{this.channelName}} on Spee.ch">
|
||||||
|
@ -27,4 +21,4 @@
|
||||||
{{> navBar}}
|
{{> navBar}}
|
||||||
{{{ body }}}
|
{{{ body }}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
|
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
{{ placeCommonHeaderTags }}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Spee.ch</title>
|
|
||||||
<link rel="stylesheet" href="/assets/css/reset.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/general.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/mediaQueries.css" type="text/css">
|
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:site" content="@spee_ch" />
|
<meta name="twitter:site" content="@spee_ch" />
|
||||||
<meta property="og:title" content="Spee.ch">
|
<meta property="og:title" content="Spee.ch">
|
||||||
|
@ -33,4 +27,4 @@
|
||||||
{{> navBar}}
|
{{> navBar}}
|
||||||
{{{ body }}}
|
{{{ body }}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
|
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
{{ placeCommonHeaderTags }}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Spee.ch</title>
|
|
||||||
<link rel="stylesheet" href="/assets/css/reset.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/general.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/mediaQueries.css" type="text/css">
|
|
||||||
<meta property="fb:app_id" content="1371961932852223">
|
<meta property="fb:app_id" content="1371961932852223">
|
||||||
{{#unless fileInfo.nsfw}}
|
{{#unless claimInfo.nsfw}}
|
||||||
{{{addTwitterCard fileInfo.fileType openGraphInfo.source openGraphInfo.embedUrl openGraphInfo.directFileUrl}}}
|
{{{addTwitterCard claimInfo }}}
|
||||||
{{{addOpenGraph fileInfo.title fileInfo.fileType openGraphInfo.showUrl openGraphInfo.source fileInfo.description fileInfo.thumbnail}}}
|
{{{addOpenGraph claimInfo }}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
<!--google font-->
|
<!--google font-->
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet">
|
||||||
|
@ -21,9 +15,9 @@
|
||||||
<body id="show-body">
|
<body id="show-body">
|
||||||
<script src="/assets/js/generalFunctions.js"></script>
|
<script src="/assets/js/generalFunctions.js"></script>
|
||||||
<script src="/assets/js/navBarFunctions.js"></script>
|
<script src="/assets/js/navBarFunctions.js"></script>
|
||||||
|
<script src="/assets/js/assetConstructor.js"></script>
|
||||||
{{> navBar}}
|
{{> navBar}}
|
||||||
{{{ body }}}
|
{{{ body }}}
|
||||||
<script src="/assets/js/showFunctions.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,18 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
|
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
{{ placeCommonHeaderTags }}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Spee.ch</title>
|
|
||||||
<link rel="stylesheet" href="/assets/css/reset.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/general.css" type="text/css">
|
|
||||||
<link rel="stylesheet" href="/assets/css/mediaQueries.css" type="text/css">
|
|
||||||
<meta property="fb:app_id" content="1371961932852223">
|
<meta property="fb:app_id" content="1371961932852223">
|
||||||
{{#unless fileInfo.nsfw}}
|
{{#unless claimInfo.nsfw}}
|
||||||
{{{addTwitterCard fileInfo.fileType openGraphInfo.source openGraphInfo.embedUrl openGraphInfo.directFileUrl}}}
|
{{{addTwitterCard claimInfo }}}
|
||||||
{{{addOpenGraph fileInfo.title fileInfo.fileType openGraphInfo.showUrl openGraphInfo.source fileInfo.description fileInfo.thumbnail}}}
|
{{{addOpenGraph claimInfo }}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
<!-- google analytics -->
|
<!-- google analytics -->
|
||||||
{{ googleAnalytics }}
|
{{ googleAnalytics }}
|
||||||
</head>
|
</head>
|
||||||
<body id="showlite-body">
|
<body id="showlite-body">
|
||||||
|
<script src="/assets/js/assetConstructor.js"></script>
|
||||||
{{{ body }}}
|
{{{ body }}}
|
||||||
<script src="/assets/js/showFunctions.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
{{#ifConditional fileInfo.fileType '===' 'video/mp4'}}
|
<div id="asset-display-component">
|
||||||
{{#ifConditional fileInfo.fileExt '===' 'gifv'}}
|
<div id="asset-status">
|
||||||
<video class="gifv-show" autoplay loop muted>
|
<div id="searching-message" hidden="true">
|
||||||
<source src="/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}">
|
<p>Sit tight, we're searching the LBRY blockchain for your asset!</p>
|
||||||
{{!--fallback--}}
|
{{> progressBar}}
|
||||||
Your browser does not support the <code>video</code> element.
|
<p>Curious what magic is happening here? <a class="link--primary" target="blank" href="https://lbry.io/faq/what-is-lbry">Learn more.</a></p>
|
||||||
</video>
|
</div>
|
||||||
{{else}}
|
<div id="failure-message" hidden="true">
|
||||||
|
<p>Unfortunately, we couldn't download your asset from LBRY. You can help us out by sharing the below error message in the <a class="link--primary" href="https://discord.gg/YjYbwhS" target="_blank">LBRY discord</a>.</p>
|
||||||
|
<i><p id="error-message"></p></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="asset-holder" hidden="true">
|
||||||
|
{{#ifConditional claimInfo.contentType '===' 'video/mp4'}}
|
||||||
|
{{> video}}
|
||||||
|
{{else}}
|
||||||
|
{{> image}}
|
||||||
|
{{/ifConditional}}
|
||||||
|
<div>
|
||||||
|
<a id="asset-boilerpate" class="link--primary fine-print" href="/{{claimInfo.claimId}}/{{claimInfo.name}}">hosted via Spee<h</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<video id="video-player" class="video-show video" controls poster="{{fileInfo.thumbnail}}">
|
</div>
|
||||||
<source src="/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}">
|
</div>
|
||||||
{{!--fallback--}}
|
|
||||||
Your browser does not support the <code>video</code> element.
|
<script type="text/javascript">
|
||||||
</video>
|
|
||||||
<script type="text/javascript">
|
const asset = new Asset();
|
||||||
document.addEventListener('DOMContentLoaded', resizeVideoPlayer)
|
asset.data['src'] = '/{{claimInfo.claimId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}';
|
||||||
window.addEventListener("resize", resizeVideoPlayer);
|
asset.data['claimName'] = '{{claimInfo.name}}';
|
||||||
function resizeVideoPlayer() {
|
asset.data['claimId'] = '{{claimInfo.claimId}}';
|
||||||
const div = document.getElementById('video-player');
|
asset.data['fileExt'] = '{{claimInfo.fileExt}}';
|
||||||
const width = div.offsetWidth;
|
asset.data['contentType'] = '{{claimInfo.contentType}}';
|
||||||
div.height = (width * 9 / 16);
|
console.log('asset data:', asset.data);
|
||||||
}
|
asset.checkFileAndRenderAsset();
|
||||||
</script>
|
|
||||||
{{/ifConditional}}
|
</script>
|
||||||
{{else}}
|
|
||||||
<a href="/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}">
|
|
||||||
<img class="image-show" src="/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}" />
|
|
||||||
</a>
|
|
||||||
{{/ifConditional}}
|
|
|
@ -1,28 +1,28 @@
|
||||||
{{#if fileInfo.channelName}}
|
{{#if claimInfo.channelName}}
|
||||||
<div class="row row--padded row--wide row--no-top">
|
<div class="row row--padded row--wide row--no-top">
|
||||||
<div class="column column--2 column--med-10">
|
<div class="column column--2 column--med-10">
|
||||||
<span class="text">Channel:</span>
|
<span class="text">Channel:</span>
|
||||||
</div><div class="column column--8 column--med-10">
|
</div><div class="column column--8 column--med-10">
|
||||||
<span class="text"><a href="/{{fileInfo.channelName}}:{{fileInfo.certificateId}}">{{fileInfo.channelName}}</a></span>
|
<span class="text"><a href="/{{claimInfo.channelName}}:{{claimInfo.certificateId}}">{{claimInfo.channelName}}</a></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if fileInfo.description}}
|
{{#if claimInfo.description}}
|
||||||
<div class="row row--padded row--wide row--no-top">
|
<div class="row row--padded row--wide row--no-top">
|
||||||
<span class="text">{{fileInfo.description}}</span>
|
<span class="text">{{claimInfo.description}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="row row--wide">
|
<div class="row row--wide">
|
||||||
<div id="show-short-link">
|
<div id="show-short-link">
|
||||||
<div class="column column--2 column--med-10">
|
<div class="column column--2 column--med-10">
|
||||||
<a class="link--primary" href="/{{fileInfo.shortId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}"><span class="text">Link:</span></a>
|
<a class="link--primary" href="/{{shortId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}"><span class="text">Link:</span></a>
|
||||||
</div><div class="column column--8 column--med-10">
|
</div><div class="column column--8 column--med-10">
|
||||||
<div class="row row--short row--wide">
|
<div class="row row--short row--wide">
|
||||||
<div class="column column--7">
|
<div class="column column--7">
|
||||||
<div class="input-error" id="input-error-copy-short-link" hidden="true"></div>
|
<div class="input-error" id="input-error-copy-short-link" hidden="true"></div>
|
||||||
<input type="text" id="short-link" class="input-disabled input-text--full-width" readonly spellcheck="false" value="https://spee.ch/{{fileInfo.shortId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}" onclick="select()"/>
|
<input type="text" id="short-link" class="input-disabled input-text--full-width" readonly spellcheck="false" value="https://spee.ch/{{shortId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}" onclick="select()"/>
|
||||||
</div><div class="column column--1"></div><div class="column column--2">
|
</div><div class="column column--1"></div><div class="column column--2">
|
||||||
<button class="button--primary" data-elementtocopy="short-link" onclick="copyToClipboard(event)">copy</button>
|
<button class="button--primary" data-elementtocopy="short-link" onclick="copyToClipboard(event)">copy</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
<div class="row row--short row--wide">
|
<div class="row row--short row--wide">
|
||||||
<div class="column column--7">
|
<div class="column column--7">
|
||||||
<div class="input-error" id="input-error-copy-embed-text" hidden="true"></div>
|
<div class="input-error" id="input-error-copy-embed-text" hidden="true"></div>
|
||||||
{{#ifConditional fileInfo.fileType '===' 'video/mp4'}}
|
{{#ifConditional claimInfo.contentType '===' 'video/mp4'}}
|
||||||
<input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='<video width="100%" controls poster="{{fileInfo.thumbnail}}" src="https://spee.ch/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}"/></video>'/>
|
<input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='<video width="100%" controls poster="{{claimInfo.thumbnail}}" src="https://spee.ch/{{claimInfo.claimId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}"/></video>'/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='<img src="https://spee.ch/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}"/>'/>
|
<input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='<img src="https://spee.ch/{{claimInfo.claimId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}"/>'/>
|
||||||
{{/ifConditional}}
|
{{/ifConditional}}
|
||||||
</div><div class="column column--1"></div><div class="column column--2">
|
</div><div class="column column--1"></div><div class="column column--2">
|
||||||
<button class="button--primary" data-elementtocopy="embed-text" onclick="copyToClipboard(event)">copy</button>
|
<button class="button--primary" data-elementtocopy="embed-text" onclick="copyToClipboard(event)">copy</button>
|
||||||
|
@ -55,10 +55,10 @@
|
||||||
<span class="text">Share:</span>
|
<span class="text">Share:</span>
|
||||||
</div><div class="column column--7 column--med-10">
|
</div><div class="column column--7 column--med-10">
|
||||||
<div class="row row--short row--wide flex-container--row flex-container--space-between-bottom flex-container--wrap">
|
<div class="row row--short row--wide flex-container--row flex-container--space-between-bottom flex-container--wrap">
|
||||||
<a class="link--primary" target="_blank" href="https://twitter.com/intent/tweet?text=https://spee.ch/{{fileInfo.shortId}}/{{fileInfo.name}}">twitter</a>
|
<a class="link--primary" target="_blank" href="https://twitter.com/intent/tweet?text=https://spee.ch/{{shortId}}/{{claimInfo.name}}">twitter</a>
|
||||||
<a class="link--primary" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://spee.ch/{{fileInfo.shortId}}/{{fileInfo.name}}">facebook</a>
|
<a class="link--primary" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://spee.ch/{{shortId}}/{{claimInfo.name}}">facebook</a>
|
||||||
<a class="link--primary" target="_blank" href="http://tumblr.com/widgets/share/tool?canonicalUrl=https://spee.ch/{{fileInfo.shortId}}/{{fileInfo.name}}">tumblr</a>
|
<a class="link--primary" target="_blank" href="http://tumblr.com/widgets/share/tool?canonicalUrl=https://spee.ch/{{shortId}}/{{claimInfo.name}}">tumblr</a>
|
||||||
<a class="link--primary" target="_blank" href="https://www.reddit.com/submit?url=https://spee.ch/{{fileInfo.shortId}}/{{fileInfo.name}}&title={{fileInfo.name}}">reddit</a>
|
<a class="link--primary" target="_blank" href="https://www.reddit.com/submit?url=https://spee.ch/{{shortId}}/{{claimInfo.name}}&title={{claimInfo.name}}">reddit</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,29 +73,29 @@
|
||||||
<div class="column column--2 column--med-10">
|
<div class="column column--2 column--med-10">
|
||||||
<span class="text">Name:</span>
|
<span class="text">Name:</span>
|
||||||
</div><div class="column column--8 column--med-10">
|
</div><div class="column column--8 column--med-10">
|
||||||
{{fileInfo.name}}
|
{{claimInfo.name}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="show-claim-id">
|
<div id="show-claim-id">
|
||||||
<div class="column column--2 column--med-10">
|
<div class="column column--2 column--med-10">
|
||||||
<span class="text">Claim Id:</span>
|
<span class="text">Claim Id:</span>
|
||||||
</div><div class="column column--8 column--med-10">
|
</div><div class="column column--8 column--med-10">
|
||||||
{{fileInfo.claimId}}
|
{{claimInfo.claimId}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="show-claim-id">
|
<div id="show-claim-id">
|
||||||
<div class="column column--2 column--med-10">
|
<div class="column column--2 column--med-10">
|
||||||
<span class="text">File Name:</span>
|
<span class="text">File Name:</span>
|
||||||
</div><div class="column column--8 column--med-10">
|
</div><div class="column column--8 column--med-10">
|
||||||
{{fileInfo.fileName}}
|
{{claimInfo.fileName}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="show-claim-id">
|
<div id="show-claim-id">
|
||||||
<div class="column column--2 column--med-10">
|
<div class="column column--2 column--med-10">
|
||||||
<span class="text">File Type:</span>
|
<span class="text">File Type:</span>
|
||||||
</div><div class="column column--8 column--med-10">
|
</div><div class="column column--8 column--med-10">
|
||||||
{{#if fileInfo.fileType}}
|
{{#if claimInfo.contentType}}
|
||||||
{{fileInfo.fileType}}
|
{{claimInfo.contentType}}
|
||||||
{{else}}
|
{{else}}
|
||||||
unknown
|
unknown
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
{{#ifConditional this.contentType '===' 'video/mp4'}}
|
{{#ifConditional this.contentType '===' 'video/mp4'}}
|
||||||
<img class="grid-item-image" src="{{this.thumbnail}}"/>
|
<img class="grid-item-image" src="{{this.thumbnail}}"/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<img class="grid-item-image" src="{{this.directUrlLong}}" />
|
<img class="grid-item-image" src="{{this.claimId}}/{{this.name}}.{{this.fileExt}}" />
|
||||||
{{/ifConditional}}
|
{{/ifConditional}}
|
||||||
|
|
||||||
<div class="hidden" onclick="window.location='{{this.showUrlLong}}'">
|
<div class="hidden" onclick="window.location='{{this.claimId}}/{{this.name}}'">
|
||||||
<p class="grid-item-details-text">{{this.name}}</p>
|
<p class="grid-item-details-text">{{this.name}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
|
1
views/partials/image.handlebars
Normal file
1
views/partials/image.handlebars
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<img id="image-asset" class="asset"/>
|
8
views/partials/progressBar.handlebars
Normal file
8
views/partials/progressBar.handlebars
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<p id="bar-holder"></p>
|
||||||
|
|
||||||
|
<script src="/assets/js/progressBarConstructor.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
const progressBar = new ProgressBar();
|
||||||
|
progressBar.createProgressBar(10);
|
||||||
|
progressBar.startProgressBar();
|
||||||
|
</script>
|
10
views/partials/video.handlebars
Normal file
10
views/partials/video.handlebars
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
<video id="video-asset" class="asset" controls poster="{{claimInfo.thumbnail}}">
|
||||||
|
<source>
|
||||||
|
<!--fallback-->
|
||||||
|
Your browser does not support the <code>video</code> element.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
showFunctions.addPlayPauseToVideo();
|
||||||
|
</script>
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="row row--tall row--padded">
|
<div class="row row--tall row--padded">
|
||||||
<div class="column column--10">
|
<div class="column column--10">
|
||||||
<!-- title -->
|
<!-- title -->
|
||||||
<span class="text--large">{{fileInfo.title}}</span>
|
<span class="text--large">{{claimInfo.title}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column column--5 column--sml-10 align-content-top">
|
<div class="column column--5 column--sml-10 align-content-top">
|
||||||
<!-- asset -->
|
<!-- asset -->
|
||||||
|
|
|
@ -1,21 +1,3 @@
|
||||||
{{#ifConditional fileInfo.fileType '===' 'video/mp4'}}
|
<div class="row row--tall flex-container--column flex-container--center-center">
|
||||||
{{#ifConditional fileInfo.fileExt '===' '.gifv'}}
|
{{> asset }}
|
||||||
<video class="showlite-asset" autoplay loop muted>
|
</div>
|
||||||
<source src="/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}">
|
|
||||||
{{!--fallback--}}
|
|
||||||
Your browser does not support the <code>video</code> element.
|
|
||||||
</video>
|
|
||||||
{{else}}
|
|
||||||
<video class="showlite-asset" controls id="video-player">
|
|
||||||
<source src="/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}">
|
|
||||||
{{!--fallback--}}
|
|
||||||
Your browser does not support the <code>video</code> element.
|
|
||||||
</video>
|
|
||||||
{{/ifConditional}}
|
|
||||||
<br/>
|
|
||||||
<a class="link--primary fine-print" href="/{{fileInfo.claimId}}/{{fileInfo.name}}">hosted via spee<h</a>
|
|
||||||
{{else}}
|
|
||||||
<a href="/{{fileInfo.claimId}}/{{fileInfo.name}}">
|
|
||||||
<img class="showlite-asset" src="/{{fileInfo.claimId}}/{{fileInfo.name}}.{{fileInfo.fileExt}}" alt="{{fileInfo.fileName}}"/>
|
|
||||||
</a>
|
|
||||||
{{/ifConditional}}
|
|
Loading…
Reference in a new issue