store height and width in File table #543
20 changed files with 473 additions and 186 deletions
|
@ -2,4 +2,7 @@ const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'config': path.resolve('devConfig', 'sequelizeCliConfig.js'),
|
'config': path.resolve('devConfig', 'sequelizeCliConfig.js'),
|
||||||
|
'models-path': path.resolve('server', 'models'),
|
||||||
|
'seeders-path': path.resolve('server', 'seeders'),
|
||||||
|
'migrations-path': path.resolve('server', 'migrations')
|
||||||
}
|
}
|
||||||
|
|
40
README.md
40
README.md
|
@ -9,6 +9,11 @@ To get started running your own version of spee.ch, visit [lbryio/www.spee.ch](h
|
||||||
npm install spee.ch --save
|
npm install spee.ch --save
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dependenceis
|
||||||
|
Make sure the following are installed
|
||||||
|
* [imagemagick](https://www.imagemagick.org/script/download.php)
|
||||||
|
* [ffmpeg](https://www.ffmpeg.org/download.html)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
* the `server/` folder contains all of the server code
|
* the `server/` folder contains all of the server code
|
||||||
* `index.js` is the entry point for the server. It creates the [express app](https://expressjs.com/), requires the routes, syncs the database, and starts the server listening on the `PORT` designated in the config file.
|
* `index.js` is the entry point for the server. It creates the [express app](https://expressjs.com/), requires the routes, syncs the database, and starts the server listening on the `PORT` designated in the config file.
|
||||||
|
@ -23,21 +28,7 @@ npm install spee.ch --save
|
||||||
* To run only tests that do not require LBC, run `npm run test:no-lbc`
|
* To run only tests that do not require LBC, run `npm run test:no-lbc`
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
* _(post)_ /api/claim/publish
|
||||||
#### GET
|
|
||||||
* /api/claim/resolve/:name/:claimId
|
|
||||||
* example: `curl https://spee.ch/api/claim/resolve/doitlive/xyz`
|
|
||||||
* /api/claim/list/:name
|
|
||||||
* example: `curl https://spee.ch/api/claim/list/doitlive`
|
|
||||||
* /api/claim/availability/:name
|
|
||||||
* returns the name if it is available
|
|
||||||
* example: `curl https://spee.ch/api/claim/availability/doitlive`
|
|
||||||
* /api/channel/availability/:name
|
|
||||||
* returns the name if it is available
|
|
||||||
* example: `curl https://spee.ch/api/channel/availability/@CoolChannel`
|
|
||||||
|
|
||||||
#### POST
|
|
||||||
* /api/claim/publish
|
|
||||||
* example: `curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish`
|
* example: `curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish`
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* `name` (required)
|
* `name` (required)
|
||||||
|
@ -49,6 +40,16 @@ npm install spee.ch --save
|
||||||
* `thumbnail` url to thumbnail image, for .mp4 uploads only (optional)
|
* `thumbnail` url to thumbnail image, for .mp4 uploads only (optional)
|
||||||
* `channelName`(optional)
|
* `channelName`(optional)
|
||||||
* `channelPassword` (optional,; required if `channelName` is provided)
|
* `channelPassword` (optional,; required if `channelName` is provided)
|
||||||
|
* _(get)_ /api/claim/resolve/:name/:claimId
|
||||||
|
* example: `curl https://spee.ch/api/claim/resolve/doitlive/xyz`
|
||||||
|
* _(get)_ /api/claim/list/:name
|
||||||
|
* example: `curl https://spee.ch/api/claim/list/doitlive`
|
||||||
|
* _(get)_ /api/claim/availability/:name
|
||||||
|
* returns the name if it is available
|
||||||
|
* example: `curl https://spee.ch/api/claim/availability/doitlive`
|
||||||
|
* _(get)_ /api/channel/availability/:name
|
||||||
|
* returns the name if it is available
|
||||||
|
* example: `curl https://spee.ch/api/channel/availability/@CoolChannel`
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry discord!
|
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry discord!
|
||||||
|
@ -69,4 +70,13 @@ Issues with spee.ch strong familiarity with the spee.ch code base and how the lb
|
||||||
Issues with lbry (e.g. the spee.ch wallet, lbrynet configuration, etc.) that require strong familiarity with the lbry daemon and/or network to fix. Generally these issues are best suited for the lbry protocol team but are placed in this repo because of they are part of the spee.ch implementation
|
Issues with lbry (e.g. the spee.ch wallet, lbrynet configuration, etc.) that require strong familiarity with the lbry daemon and/or network to fix. Generally these issues are best suited for the lbry protocol team but are placed in this repo because of they are part of the spee.ch implementation
|
||||||
|
|
||||||
### Stack
|
### Stack
|
||||||
|
* server
|
||||||
|
* [mysql](https://www.mysql.com/)
|
||||||
|
* [express](https://www.npmjs.com/package/express)
|
||||||
|
* [node](https://nodejs.org/)
|
||||||
|
* [lbry](https://github.com/lbryio/lbry)
|
||||||
|
* [imagemagick](https://www.imagemagick.org/)
|
||||||
|
* [ffmpeg](https://www.ffmpeg.org/)
|
||||||
|
* client
|
||||||
|
* [react](https://reactjs.org/)
|
||||||
|
|
||||||
|
|
54
package-lock.json
generated
54
package-lock.json
generated
|
@ -953,6 +953,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz",
|
||||||
"integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8="
|
"integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8="
|
||||||
},
|
},
|
||||||
|
"any-promise": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
|
||||||
|
},
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
|
||||||
|
@ -3984,6 +3989,14 @@
|
||||||
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
|
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"get-video-dimensions": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-video-dimensions/-/get-video-dimensions-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-/H5ayBw5JEH1uG1Q3XeDiptTFHo=",
|
||||||
|
"requires": {
|
||||||
|
"mz": "1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"getpass": {
|
"getpass": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||||
|
@ -4377,6 +4390,16 @@
|
||||||
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
|
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"image-size": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA=="
|
||||||
|
},
|
||||||
|
"imagemagick": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/imagemagick/-/imagemagick-0.1.3.tgz",
|
||||||
|
"integrity": "sha1-dIPOoJO02fLi85aFetyIIbU3xWo="
|
||||||
|
},
|
||||||
"import-lazy": {
|
"import-lazy": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
|
||||||
|
@ -5371,6 +5394,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mz": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mz/-/mz-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-BvCT/dmVagbTfhsegTROJ0eMQvA=",
|
||||||
|
"requires": {
|
||||||
|
"native-or-bluebird": "1.2.0",
|
||||||
|
"thenify": "3.3.0",
|
||||||
|
"thenify-all": "1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"named-placeholders": {
|
"named-placeholders": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.1.tgz",
|
||||||
|
@ -5431,6 +5464,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"native-or-bluebird": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck="
|
||||||
|
},
|
||||||
"natural-compare": {
|
"natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
|
@ -7745,6 +7783,22 @@
|
||||||
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
|
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"thenify": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
|
||||||
|
"integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
|
||||||
|
"requires": {
|
||||||
|
"any-promise": "1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thenify-all": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
|
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
|
||||||
|
"requires": {
|
||||||
|
"thenify": "3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"through": {
|
"through": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||||
|
|
|
@ -37,7 +37,10 @@
|
||||||
"cookie-session": "^2.0.0-beta.3",
|
"cookie-session": "^2.0.0-beta.3",
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
"express-handlebars": "^3.0.0",
|
"express-handlebars": "^3.0.0",
|
||||||
|
"get-video-dimensions": "^1.0.0",
|
||||||
"helmet": "^3.8.1",
|
"helmet": "^3.8.1",
|
||||||
|
"image-size": "^0.6.3",
|
||||||
|
"imagemagick": "^0.1.3",
|
||||||
"module-alias": "^2.0.6",
|
"module-alias": "^2.0.6",
|
||||||
"mysql2": "^1.3.5",
|
"mysql2": "^1.3.5",
|
||||||
"passport": "^0.4.0",
|
"passport": "^0.4.0",
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
module.exports = (fileInfo, getResult) => {
|
|
||||||
fileInfo.fileName = getResult.file_name;
|
|
||||||
fileInfo.filePath = getResult.download_path;
|
|
||||||
return fileInfo;
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
module.exports = ({ name, claimId, outpoint, height, address, nsfw, contentType }) => {
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
claimId,
|
|
||||||
outpoint,
|
|
||||||
height,
|
|
||||||
address,
|
|
||||||
fileName: '',
|
|
||||||
filePath: '',
|
|
||||||
fileType: contentType,
|
|
||||||
nsfw,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,6 +1,5 @@
|
||||||
const { getClaim } = require('../../../../lbrynet');
|
const { getClaim } = require('../../../../lbrynet');
|
||||||
const addGetResultsToFileData = require('./addGetResultsToFileData.js');
|
const { createFileRecordDataAfterGet } = require('../../../../models/utils/createFileRecordData.js');
|
||||||
const createFileData = require('./createFileData.js');
|
|
||||||
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
|
const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
|
||||||
const db = require('../../../../models');
|
const db = require('../../../../models');
|
||||||
|
|
||||||
|
@ -13,23 +12,32 @@ const db = require('../../../../models');
|
||||||
const claimGet = ({ ip, originalUrl, params }, res) => {
|
const claimGet = ({ ip, originalUrl, params }, res) => {
|
||||||
const name = params.name;
|
const name = params.name;
|
||||||
const claimId = params.claimId;
|
const claimId = params.claimId;
|
||||||
|
let resolveResult;
|
||||||
|
let getResult;
|
||||||
// resolve the claim
|
// resolve the claim
|
||||||
db.Claim.resolveClaim(name, claimId)
|
db.Claim.resolveClaim(name, claimId)
|
||||||
.then(resolveResult => {
|
.then(result => {
|
||||||
// make sure a claim actually exists at that uri
|
if (!result) {
|
||||||
if (!resolveResult) {
|
|
||||||
throw new Error('No matching uri found in Claim table');
|
throw new Error('No matching uri found in Claim table');
|
||||||
}
|
}
|
||||||
let fileData = createFileData(resolveResult);
|
resolveResult = result;
|
||||||
// get the claim
|
return getClaim(`${name}#${claimId}`);
|
||||||
return Promise.all([fileData, getClaim(`${name}#${claimId}`)]);
|
|
||||||
})
|
})
|
||||||
.then(([ fileData, getResult ]) => {
|
.then(result => {
|
||||||
fileData = addGetResultsToFileData(fileData, getResult);
|
getResult = result;
|
||||||
return Promise.all([db.upsert(db.File, fileData, {name, claimId}, 'File'), getResult]);
|
|
||||||
})
|
})
|
||||||
.then(([ fileRecord, {message, completed} ]) => {
|
.then(() => {
|
||||||
res.status(200).json({ success: true, message, completed });
|
const fileData = createFileRecordDataAfterGet(resolveResult, getResult);
|
||||||
|
const upsertCriteria = { name, claimId};
|
||||||
|
return db.upsert(db.File, fileData, upsertCriteria, 'File')
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const { message, completed } = getResult;
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message,
|
||||||
|
completed,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
handleErrorResponse(originalUrl, ip, error, res);
|
handleErrorResponse(originalUrl, ip, error, res);
|
||||||
|
|
|
@ -47,29 +47,38 @@ const authenticateChannelCredentials = (channelName, channelId, userPassword) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const authenticateUser = (channelName, channelId, channelPassword, user) => {
|
const authenticateUser = (channelName, channelId, channelPassword, user) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
// case: no channelName or channel Id are provided (anonymous), regardless of whether user token is provided
|
// case: no channelName or channel Id are provided (anonymous), regardless of whether user token is provided
|
||||||
if (!channelName && !channelId) {
|
if (!channelName && !channelId) {
|
||||||
return {
|
resolve({
|
||||||
channelName : null,
|
channelName : null,
|
||||||
channelClaimId: null,
|
channelClaimId: null,
|
||||||
};
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// case: channelName or channel Id are provided with user token
|
// case: channelName or channel Id are provided with user token
|
||||||
if (user) {
|
if (user) {
|
||||||
if (channelName && channelName !== user.channelName) {
|
if (channelName && channelName !== user.channelName) {
|
||||||
throw new Error('the provided channel name does not match user credentials');
|
reject(new Error('the provided channel name does not match user credentials'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (channelId && channelId !== user.channelClaimId) {
|
if (channelId && channelId !== user.channelClaimId) {
|
||||||
throw new Error('the provided channel id does not match user credentials');
|
reject(new Error('the provided channel id does not match user credentials'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return {
|
resolve({
|
||||||
channelName : user.channelName,
|
channelName : user.channelName,
|
||||||
channelClaimId: user.channelClaimId,
|
channelClaimId: user.channelClaimId,
|
||||||
};
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// case: channelName or channel Id are provided with password instead of user token
|
// case: channelName or channel Id are provided with password instead of user token
|
||||||
if (!channelPassword) throw new Error('no channel password provided');
|
if (!channelPassword) {
|
||||||
return authenticateChannelCredentials(channelName, channelId, channelPassword);
|
reject(new Error('no channel password provided'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(authenticateChannelCredentials(channelName, channelId, channelPassword));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = authenticateUser;
|
module.exports = authenticateUser;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const { details, publishing } = require('@config/siteConfig');
|
const { details, publishing } = require('@config/siteConfig');
|
||||||
|
|
||||||
const createBasicPublishParams = (filePath, name, title, description, license, nsfw, thumbnail) => {
|
const createPublishParams = (filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId) => {
|
||||||
logger.debug(`Creating Publish Parameters`);
|
|
||||||
// provide defaults for title
|
// provide defaults for title
|
||||||
if (title === null || title.trim() === '') {
|
if (title === null || title.trim() === '') {
|
||||||
title = name;
|
title = name;
|
||||||
|
@ -15,7 +14,7 @@ const createBasicPublishParams = (filePath, name, title, description, license, n
|
||||||
if (license === null || license.trim() === '') {
|
if (license === null || license.trim() === '') {
|
||||||
license = ' '; // default to empty string
|
license = ' '; // default to empty string
|
||||||
}
|
}
|
||||||
// create the publish params
|
// create the basic publish params
|
||||||
const publishParams = {
|
const publishParams = {
|
||||||
name,
|
name,
|
||||||
file_path: filePath,
|
file_path: filePath,
|
||||||
|
@ -34,8 +33,15 @@ const createBasicPublishParams = (filePath, name, title, description, license, n
|
||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
publishParams['metadata']['thumbnail'] = thumbnail;
|
publishParams['metadata']['thumbnail'] = thumbnail;
|
||||||
}
|
}
|
||||||
|
// add channel details if publishing to a channel
|
||||||
|
if (channelName && channelClaimId) {
|
||||||
|
publishParams['channel_name'] = channelName;
|
||||||
|
publishParams['channel_id'] = channelClaimId;
|
||||||
|
}
|
||||||
|
// log params
|
||||||
logger.debug('publish params:', publishParams);
|
logger.debug('publish params:', publishParams);
|
||||||
|
// return
|
||||||
return publishParams;
|
return publishParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = createBasicPublishParams;
|
module.exports = createPublishParams;
|
|
@ -9,12 +9,14 @@ const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
|
||||||
const checkClaimAvailability = require('../availability/checkClaimAvailability.js');
|
const checkClaimAvailability = require('../availability/checkClaimAvailability.js');
|
||||||
|
|
||||||
const publish = require('./publish.js');
|
const publish = require('./publish.js');
|
||||||
const createBasicPublishParams = require('./createBasicPublishParams.js');
|
const createPublishParams = require('./createPublishParams.js');
|
||||||
const createThumbnailPublishParams = require('./createThumbnailPublishParams.js');
|
const createThumbnailPublishParams = require('./createThumbnailPublishParams.js');
|
||||||
const parsePublishApiRequestBody = require('./parsePublishApiRequestBody.js');
|
const parsePublishApiRequestBody = require('./parsePublishApiRequestBody.js');
|
||||||
const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
|
const parsePublishApiRequestFiles = require('./parsePublishApiRequestFiles.js');
|
||||||
const authenticateUser = require('./authentication.js');
|
const authenticateUser = require('./authentication.js');
|
||||||
|
|
||||||
|
const CLAIM_TAKEN = 'CLAIM_TAKEN';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
route to publish a claim through the daemon
|
route to publish a claim through the daemon
|
||||||
|
@ -50,18 +52,20 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
||||||
return res.status(400).json({success: false, message: error.message});
|
return res.status(400).json({success: false, message: error.message});
|
||||||
}
|
}
|
||||||
// check channel authorization
|
// check channel authorization
|
||||||
Promise
|
authenticateUser(channelName, channelId, channelPassword, user)
|
||||||
.all([
|
.then(({ channelName, channelClaimId }) => {
|
||||||
authenticateUser(channelName, channelId, channelPassword, user),
|
return Promise.all([
|
||||||
checkClaimAvailability(name),
|
checkClaimAvailability(name),
|
||||||
createBasicPublishParams(filePath, name, title, description, license, nsfw, thumbnail),
|
createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName, channelClaimId),
|
||||||
createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw),
|
createThumbnailPublishParams(thumbnailFilePath, name, license, nsfw),
|
||||||
])
|
])
|
||||||
.then(([{channelName, channelClaimId}, validatedClaimName, publishParams, thumbnailPublishParams]) => {
|
})
|
||||||
// add channel details to the publish params
|
.then(([ claimAvailable, publishParams, thumbnailPublishParams ]) => {
|
||||||
if (channelName && channelClaimId) {
|
if (!claimAvailable) {
|
||||||
publishParams['channel_name'] = channelName;
|
throw {
|
||||||
publishParams['channel_id'] = channelClaimId;
|
name: CLAIM_TAKEN,
|
||||||
|
message: 'That claim name is already taken'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// publish the thumbnail, if one exists
|
// publish the thumbnail, if one exists
|
||||||
if (thumbnailPublishParams) {
|
if (thumbnailPublishParams) {
|
||||||
|
@ -87,6 +91,12 @@ const claimPublish = ({ body, files, headers, ip, originalUrl, user, tor }, res)
|
||||||
sendGATimingEvent('end-to-end', 'publish', fileType, gaStartTime, Date.now());
|
sendGATimingEvent('end-to-end', 'publish', fileType, gaStartTime, Date.now());
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
if (error.name = CLAIM_TAKEN) {
|
||||||
|
res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
handleErrorResponse(originalUrl, ip, error, res);
|
handleErrorResponse(originalUrl, ip, error, res);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const db = require('../../../../models');
|
|
||||||
const { publishClaim } = require('../../../../lbrynet');
|
const { publishClaim } = require('../../../../lbrynet');
|
||||||
|
const db = require('../../../../models');
|
||||||
|
const { createFileRecordDataAfterPublish } = require('../../../../models/utils/createFileRecordData.js');
|
||||||
|
const { createClaimRecordDataAfterPublish } = require('../../../../models/utils/createClaimRecordData.js');
|
||||||
const deleteFile = require('./deleteFile.js');
|
const deleteFile = require('./deleteFile.js');
|
||||||
|
|
||||||
const publish = (publishParams, fileName, fileType) => {
|
const publish = (publishParams, fileName, fileType) => {
|
||||||
|
@ -35,51 +37,34 @@ const publish = (publishParams, fileName, fileType) => {
|
||||||
logger.debug(`certificateId: ${certificateId}`);
|
logger.debug(`certificateId: ${certificateId}`);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// create the File record
|
return Promise.all([
|
||||||
const fileRecord = {
|
createFileRecordDataAfterPublish(fileName, fileType, publishParams, publishResults),
|
||||||
name : publishParams.name,
|
createClaimRecordDataAfterPublish(certificateId, channelName, fileName, fileType, publishParams, publishResults),
|
||||||
claimId : publishResults.claim_id,
|
]);
|
||||||
title : publishParams.metadata.title,
|
})
|
||||||
description: publishParams.metadata.description,
|
.then(([fileRecord, claimRecord]) => {
|
||||||
address : publishParams.claim_address,
|
|
||||||
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
|
||||||
height : 0,
|
|
||||||
fileName,
|
|
||||||
filePath : publishParams.file_path,
|
|
||||||
fileType,
|
|
||||||
nsfw : publishParams.metadata.nsfw,
|
|
||||||
};
|
|
||||||
// create the Claim record
|
|
||||||
const claimRecord = {
|
|
||||||
name : publishParams.name,
|
|
||||||
claimId : publishResults.claim_id,
|
|
||||||
title : publishParams.metadata.title,
|
|
||||||
description: publishParams.metadata.description,
|
|
||||||
address : publishParams.claim_address,
|
|
||||||
thumbnail : publishParams.metadata.thumbnail,
|
|
||||||
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
|
||||||
height : 0,
|
|
||||||
contentType: fileType,
|
|
||||||
nsfw : publishParams.metadata.nsfw,
|
|
||||||
amount : publishParams.bid,
|
|
||||||
certificateId,
|
|
||||||
channelName,
|
|
||||||
};
|
|
||||||
// upsert criteria
|
|
||||||
const upsertCriteria = {
|
|
||||||
name : publishParams.name,
|
|
||||||
claimId: publishResults.claim_id,
|
|
||||||
};
|
|
||||||
// upsert the records
|
// upsert the records
|
||||||
return Promise.all([db.upsert(db.File, fileRecord, upsertCriteria, 'File'), db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim')]);
|
const {name, claim_id: claimId} = publishParams;
|
||||||
|
const upsertCriteria = {
|
||||||
|
name,
|
||||||
|
claimId,
|
||||||
|
};
|
||||||
|
return Promise.all([
|
||||||
|
db.upsert(db.File, fileRecord, upsertCriteria, 'File'),
|
||||||
|
db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim'),
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
.then(([file, claim]) => {
|
.then(([file, claim]) => {
|
||||||
logger.debug('File and Claim records successfully created');
|
logger.debug('File and Claim records successfully created');
|
||||||
return Promise.all([file.setClaim(claim), claim.setFile(file)]);
|
return Promise.all([
|
||||||
|
file.setClaim(claim),
|
||||||
|
claim.setFile(file),
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.debug('File and Claim records successfully associated');
|
logger.debug('File and Claim records successfully associated');
|
||||||
resolve(publishResults); // resolve the promise with the result from lbryApi publishClaim;
|
// resolve the promise with the result from lbryApi publishClaim;
|
||||||
|
resolve(publishResults);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('PUBLISH ERROR', error);
|
logger.error('PUBLISH ERROR', error);
|
||||||
|
|
88
server/migrations/File_AddHeightAndWidthColumn.js
Normal file
88
server/migrations/File_AddHeightAndWidthColumn.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface, { INTEGER }) => {
|
||||||
|
// logic for transforming into the new state
|
||||||
|
return Promise.all([
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'File',
|
||||||
|
'fileHeight',
|
||||||
|
{
|
||||||
|
type : INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
default : 0,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'File',
|
||||||
|
'fileWidth',
|
||||||
|
{
|
||||||
|
type : INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
default : 0,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
queryInterface.removeColumn(
|
||||||
|
'File',
|
||||||
|
'address',
|
||||||
|
),
|
||||||
|
queryInterface.removeColumn(
|
||||||
|
'File',
|
||||||
|
'height',
|
||||||
|
),
|
||||||
|
queryInterface.removeColumn(
|
||||||
|
'File',
|
||||||
|
'nsfw',
|
||||||
|
),
|
||||||
|
queryInterface.removeColumn(
|
||||||
|
'File',
|
||||||
|
'trendingEligible',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
down: (queryInterface, { BOOLEAN, INTEGER, STRING }) => {
|
||||||
|
return Promise.all([
|
||||||
|
queryInterface.removeColumn(
|
||||||
|
'File',
|
||||||
|
'fileHeight',
|
||||||
|
),
|
||||||
|
queryInterface.removeColumn(
|
||||||
|
'File',
|
||||||
|
'fileWidth',
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'File',
|
||||||
|
'address',
|
||||||
|
{
|
||||||
|
type : STRING,
|
||||||
|
allowNull: false,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'File',
|
||||||
|
'height',
|
||||||
|
{
|
||||||
|
type : INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
default : 0,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'File',
|
||||||
|
'nsfw',
|
||||||
|
{
|
||||||
|
type : BOOLEAN,
|
||||||
|
allowNull : false,
|
||||||
|
defaultValue: false,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'File',
|
||||||
|
'trendingEligible',
|
||||||
|
{
|
||||||
|
type : BOOLEAN,
|
||||||
|
allowNull : false,
|
||||||
|
defaultValue: true,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
|
@ -10,15 +10,16 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER }) => {
|
||||||
type : STRING,
|
type : STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
address: {
|
|
||||||
type : STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
outpoint: {
|
outpoint: {
|
||||||
type : STRING,
|
type : STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
height: {
|
fileHeight: {
|
||||||
|
type : INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
default : 0,
|
||||||
|
},
|
||||||
|
fileWidth: {
|
||||||
type : INTEGER,
|
type : INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
default : 0,
|
default : 0,
|
||||||
|
@ -34,16 +35,6 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER }) => {
|
||||||
fileType: {
|
fileType: {
|
||||||
type: STRING,
|
type: STRING,
|
||||||
},
|
},
|
||||||
nsfw: {
|
|
||||||
type : BOOLEAN,
|
|
||||||
allowNull : false,
|
|
||||||
defaultValue: false,
|
|
||||||
},
|
|
||||||
trendingEligible: {
|
|
||||||
type : BOOLEAN,
|
|
||||||
allowNull : false,
|
|
||||||
defaultValue: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
freezeTableName: true,
|
freezeTableName: true,
|
||||||
|
@ -51,17 +42,8 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
File.associate = db => {
|
File.associate = db => {
|
||||||
File.hasMany(db.Request);
|
|
||||||
File.hasOne(db.Claim);
|
File.hasOne(db.Claim);
|
||||||
};
|
};
|
||||||
|
|
||||||
File.getRecentClaims = function () {
|
|
||||||
return this.findAll({
|
|
||||||
where: { nsfw: false, trendingEligible: true },
|
|
||||||
order: [['createdAt', 'DESC']],
|
|
||||||
limit: 25,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return File;
|
return File;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,6 @@ const Certificate = require('./certificate.js');
|
||||||
const Channel = require('./channel.js');
|
const Channel = require('./channel.js');
|
||||||
const Claim = require('./claim.js');
|
const Claim = require('./claim.js');
|
||||||
const File = require('./file.js');
|
const File = require('./file.js');
|
||||||
const Request = require('./request.js');
|
|
||||||
const User = require('./user.js');
|
const User = require('./user.js');
|
||||||
const Blocked = require('./blocked.js');
|
const Blocked = require('./blocked.js');
|
||||||
const Tor = require('./tor.js');
|
const Tor = require('./tor.js');
|
||||||
|
@ -48,7 +47,6 @@ db['Certificate'] = sequelize.import('Certificate', Certificate);
|
||||||
db['Channel'] = sequelize.import('Channel', Channel);
|
db['Channel'] = sequelize.import('Channel', Channel);
|
||||||
db['Claim'] = sequelize.import('Claim', Claim);
|
db['Claim'] = sequelize.import('Claim', Claim);
|
||||||
db['File'] = sequelize.import('File', File);
|
db['File'] = sequelize.import('File', File);
|
||||||
db['Request'] = sequelize.import('Request', Request);
|
|
||||||
db['User'] = sequelize.import('User', User);
|
db['User'] = sequelize.import('User', User);
|
||||||
db['Blocked'] = sequelize.import('Blocked', Blocked);
|
db['Blocked'] = sequelize.import('Blocked', Blocked);
|
||||||
db['Tor'] = sequelize.import('Tor', Tor);
|
db['Tor'] = sequelize.import('Tor', Tor);
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
module.exports = (sequelize, { STRING, BOOLEAN, TEXT }) => {
|
|
||||||
const Request = sequelize.define(
|
|
||||||
'Request',
|
|
||||||
{
|
|
||||||
action: {
|
|
||||||
type : STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
type : STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
ipAddress: {
|
|
||||||
type : STRING,
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
result: {
|
|
||||||
type : TEXT('long'),
|
|
||||||
allowNull: true,
|
|
||||||
default : null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
freezeTableName: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Request.associate = db => {
|
|
||||||
Request.belongsTo(db.File, {
|
|
||||||
foreignKey: {
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return Request;
|
|
||||||
};
|
|
39
server/models/utils/createClaimRecordData.js
Normal file
39
server/models/utils/createClaimRecordData.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
const createClaimRecordDataAfterPublish = (certificateId, channelName, fileName, fileType, publishParams, publishResults) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
metadata: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
thumbnail,
|
||||||
|
nsfw,
|
||||||
|
},
|
||||||
|
claim_address: address,
|
||||||
|
bid: amount,
|
||||||
|
} = publishParams;
|
||||||
|
|
||||||
|
const {
|
||||||
|
claim_id: claimId,
|
||||||
|
txid,
|
||||||
|
nout,
|
||||||
|
} = publishResults;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
claimId,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
address,
|
||||||
|
thumbnail,
|
||||||
|
outpoint : `${txid}:${nout}`,
|
||||||
|
height : 0,
|
||||||
|
contentType: fileType,
|
||||||
|
nsfw,
|
||||||
|
amount,
|
||||||
|
certificateId,
|
||||||
|
channelName,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createClaimRecordDataAfterPublish,
|
||||||
|
};
|
65
server/models/utils/createFileRecordData.js
Normal file
65
server/models/utils/createFileRecordData.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
const getMediaDimensions = require('../../utils/getMediaDimensions.js');
|
||||||
|
|
||||||
|
async function createFileRecordDataAfterGet (resolveResult, getResult) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
claimId,
|
||||||
|
outpoint,
|
||||||
|
contentType: fileType,
|
||||||
|
} = resolveResult;
|
||||||
|
|
||||||
|
const {
|
||||||
|
file_name: fileName,
|
||||||
|
download_path: filePath,
|
||||||
|
} = getResult;
|
||||||
|
|
||||||
|
const {
|
||||||
|
height: fileHeight,
|
||||||
|
width: fileWidth,
|
||||||
|
} = await getMediaDimensions(fileType, filePath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
claimId,
|
||||||
|
outpoint,
|
||||||
|
fileHeight,
|
||||||
|
fileWidth,
|
||||||
|
fileName,
|
||||||
|
filePath,
|
||||||
|
fileType,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createFileRecordDataAfterPublish (fileName, fileType, publishParams, publishResults) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
file_path: filePath,
|
||||||
|
} = publishParams;
|
||||||
|
|
||||||
|
const {
|
||||||
|
claim_id: claimId,
|
||||||
|
txid,
|
||||||
|
nout,
|
||||||
|
} = publishResults;
|
||||||
|
|
||||||
|
const {
|
||||||
|
height: fileHeight,
|
||||||
|
width: fileWidth,
|
||||||
|
} = await getMediaDimensions(fileType, filePath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
claimId,
|
||||||
|
outpoint: `${txid}:${nout}`,
|
||||||
|
fileHeight,
|
||||||
|
fileWidth,
|
||||||
|
fileName,
|
||||||
|
filePath,
|
||||||
|
fileType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createFileRecordDataAfterGet,
|
||||||
|
createFileRecordDataAfterPublish,
|
||||||
|
};
|
30
server/utils/getMediaDimensions.js
Normal file
30
server/utils/getMediaDimensions.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
const logger = require('winston');
|
||||||
|
const { getImageHeightAndWidth } = require('./imageProcessing');
|
||||||
|
const { getVideoHeightAndWidth } = require('./videoProcessing');
|
||||||
|
|
||||||
|
async function getMediaDimensions (fileType, filePath) {
|
||||||
|
let height = 0;
|
||||||
|
let width = 0;
|
||||||
|
switch (fileType) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
case 'image/jpg':
|
||||||
|
case 'image/png':
|
||||||
|
case 'image/gif':
|
||||||
|
logger.debug('creating File data for an image');
|
||||||
|
[ height, width ] = await getImageHeightAndWidth(filePath);
|
||||||
|
break;
|
||||||
|
case 'video/mp4':
|
||||||
|
logger.debug('creating File data for a video');
|
||||||
|
[ height, width ] = await getVideoHeightAndWidth(filePath);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.error('unable to create File data for unspported file type:', fileType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getMediaDimensions;
|
41
server/utils/imageProcessing.js
Normal file
41
server/utils/imageProcessing.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
const imageMagick = require('imagemagick');
|
||||||
|
const sizeOf = require('image-size');
|
||||||
|
|
||||||
|
const getImageMetadata = (filePath) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
imageMagick.readMetadata(filePath, (err, metadata) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
resolve(metadata);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageDetails = (filePath) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
imageMagick.identify(filePath, (err, details) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
resolve(details);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageHeightAndWidth = (filePath) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { height, width } = sizeOf(filePath);
|
||||||
|
resolve([height, width]);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getImageMetadata,
|
||||||
|
getImageDetails,
|
||||||
|
getImageHeightAndWidth,
|
||||||
|
};
|
11
server/utils/videoProcessing.js
Normal file
11
server/utils/videoProcessing.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const getVideoDimensions = require('get-video-dimensions');
|
||||||
|
|
||||||
|
async function getVideoHeightAndWidth (filePath) {
|
||||||
|
const videoDimensions = await getVideoDimensions(filePath);
|
||||||
|
const { height, width } = videoDimensions;
|
||||||
|
return [ height, width ];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getVideoHeightAndWidth,
|
||||||
|
};
|
Loading…
Reference in a new issue