Merge branch 'develop' into 'master'

Develop

See merge request mjrode/recommend!1
This commit is contained in:
Michael Rode
2019-02-12 23:43:38 +00:00
25 changed files with 12257 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
{
"presets": ["node8"],
"env": {
"test": {
"plugins": ["istanbul"]
}
}
}
+24
View File
@@ -0,0 +1,24 @@
module.exports = {
extends: ['airbnb-base', 'prettier'],
env: {
node: true,
mocha: true,
},
plugins: ['prefer-arrow'],
rules: {
'no-console': 0,
'import/no-dynamic-require': 0,
'func-names': 0,
'prefer-arrow-callback': 0,
'no-unused-expressions': 0,
'no-shadow': 0,
'prefer-arrow/prefer-arrow-functions': [
0,
{
disallowPrototype: true,
singleReturnOnly: false,
classPropertiesAllowed: false,
},
],
},
};
+5
View File
@@ -0,0 +1,5 @@
node_modules
.vscode
.nyc_output
dist
npm-debug.log
+8
View File
@@ -0,0 +1,8 @@
node_modules
npm-debug.log
.DS_Store
.nyc_output
bab.cache
test
src
coverage
+1
View File
@@ -0,0 +1 @@
dist/
+21
View File
@@ -0,0 +1,21 @@
{
"arrowParens": "avoid",
"bracketSpacing": false,
"jsxBracketSameLine": false,
"printWidth": 80,
"proseWrap": "preserve",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"overrides": [
{
"files": "*.json",
"options": {
"printWidth": 200
}
}
]
}
+13
View File
@@ -0,0 +1,13 @@
/* eslint-disable no-undef */
const _ = require('lodash');
const env = process.env.NODE_ENV || 'local';
const envConfig = require(`./${env}`);
const plexConfig = require('./plex').default;
const defaultConfig = {
env,
};
export default {env: _.merge(defaultConfig, envConfig), plex: plexConfig};
+7
View File
@@ -0,0 +1,7 @@
const localConfig = {
hostname: 'localhost',
port: 8000,
// viewDir: './app/views',
};
export default localConfig;
+7
View File
@@ -0,0 +1,7 @@
const plexConfig = {
ip: 'http://192.168.0.44',
url: 'https://plex.mjrflix.com',
token: 'hhnKQYskVjepfkhixqJu',
};
export default plexConfig;
+9
View File
@@ -0,0 +1,9 @@
import config from './config';
const server = require('./server').default();
server.create(config.env);
server.start();
export default server.create(config.env);
+11536
View File
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
{
"name": "recommend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha --compilers js:babel-core/register ./test/**/**/**/*.test.js --exit",
"dev": "nodemon --exec babel-node index.js",
"start": "nodemon --exec babel-node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.18.0",
"body-parser": "^1.18.3",
"build-url": "^1.3.2",
"cors": "^2.8.5",
"express": "^4.16.4",
"lodash": "^4.17.11",
"morgan": "^1.9.1",
"onchange": "^5.2.0",
"plex-api": "^5.2.1",
"tdaw": "^1.3.0",
"xml2json": "^0.11.2"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-plugin-istanbul": "^5.1.0",
"babel-preset-node8": "^1.2.0",
"chai": "^4.2.0",
"chai-http": "^4.2.1",
"eslint": "5.13.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "4.0.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-prefer-arrow": "^1.1.4",
"eslint-plugin-prettier": "3.0.1",
"husky": "1.3.1",
"i": "^0.3.6",
"lint-staged": "8.1.3",
"mocha": "^5.2.0",
"nock": "^10.0.6",
"nodemon": "^1.18.9",
"npm": "^6.7.0",
"nyc": "^13.2.0",
"prettier": "1.16.4",
"supertest": "^3.4.2"
}
}
+9
View File
@@ -0,0 +1,9 @@
import {Router} from 'express';
import plexService from '../../../services/plex';
const router = Router();
router.get('/users', plexService.getUsers);
// router.get('/:id', plexService.getDogWithId);
export default router;
+35
View File
@@ -0,0 +1,35 @@
import express from 'express';
import {json, urlencoded} from 'body-parser';
const routes = require('./routes').default;
export default () => {
const server = express();
const create = config => {
// Server settings
server.set('env', config.env);
server.set('port', config.port);
server.set('hostname', config.hostname);
// Returns middleware that parses json
server.use(json());
server.use(urlencoded({extended: true}));
// Set up routes
routes.init(server);
return server;
};
const start = () => {
const hostname = server.get('hostname');
const port = server.get('port');
server.listen(port, () => {
console.log(`Express server listening on - http://${hostname}:${port}`);
});
};
return {create, start};
};
+8
View File
@@ -0,0 +1,8 @@
import {Router} from 'express';
import v1ApiController from './v1';
const router = Router();
router.use('/v1', v1ApiController);
export default router;
+8
View File
@@ -0,0 +1,8 @@
import {Router} from 'express';
import plexController from '../../../controllers/apis/plex';
const router = Router();
router.use('/plex', plexController);
export default router;
+14
View File
@@ -0,0 +1,14 @@
import apiRoute from './apis';
const init = server => {
server.get('*', (req, res, next) => {
console.log(`Request was made to: ${req.originalUrl}`);
return next();
});
server.use('/api', apiRoute);
};
export default {
init,
};
+67
View File
@@ -0,0 +1,67 @@
import axios from 'axios';
import config from '../../../config';
const url = require('url');
const buildUrl = require('build-url');
const plexUrl = buildUrl(config.plex.url, {
path: '/library/sections',
port: '32400',
queryParams: {
'X-Plex-Token': config.plex.token,
},
});
const getUsersUrlParams = () => ({
host: 'https://plex.tv',
path: '/api/users',
queryParams: {
'X-Plex-Token': config.plex.token,
},
});
const constructPlexUrl = urlParams => {
const params = urlParams();
const {host} = params;
delete params.host;
const urlHash = params;
return buildUrl(host, urlHash);
};
const request = options => {
const opts = options || {};
const plexUrl = typeof options === 'string' ? options : options.url;
const httpClient = opts.httpClient || axios;
httpClient
.get(plexUrl)
.then(response => {
return response.data;
})
.catch(error => {
// Error
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log('data', error.response.data);
console.log('status', error.response.status);
console.log('headers', error.response.headers);
} else if (error.request) {
// The request was made but no response was received
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
};
// console.log(
// url.parse('https://plex.tv/api/users?&X-Plex-Token=hhnKQYskVjepfkhixqJu'),
// );
const usersUrl = constructPlexUrl(getUsersUrlParams);
request(usersUrl);
// console.log(request(usersUrl));
+22
View File
@@ -0,0 +1,22 @@
const dogs = [
{
id: 1,
name: 'Corgi',
origin: 'Wales',
breeds: ['Pembroke', 'Cardigan'],
},
{
id: 2,
name: 'Husky',
breeds: ['Alaskan', 'Siberian', 'Labrador', 'Sakhalin'],
},
];
const getUsers = (req, res) => {
console.log(dogs);
res.json(dogs);
};
export default {
getUsers,
};
+3
View File
@@ -0,0 +1,3 @@
import plexApi from './plexApi';
export default plexApi;
+99
View File
@@ -0,0 +1,99 @@
import axios from 'axios';
import buildUrl from 'build-url';
import parser from 'xml2json';
import config from '../../../config';
// const url = require('url');
function PlexApiClient(options) {
this.setOptions(options);
}
PlexApiClient.prototype.setOptions = function(options) {
this.options = options || {};
};
PlexApiClient.prototype.getUsersUrlParams = function() {
return {
host: 'https://plex.tv',
path: '/api/users',
queryParams: {
'X-Plex-Token': this.options.token,
},
};
};
PlexApiClient.prototype.buildUrl = function(urlParams) {
const params = urlParams;
const {host} = params;
delete params.host;
const urlHash = params;
return buildUrl(host, urlHash);
};
PlexApiClient.prototype.request = async function(url) {
return new Promise((resolve, reject) => {
const httpClient = this.options.httpClient || axios;
httpClient
.get(url)
.then(response => {
return resolve(JSON.parse(parser.toJson(response.data)));
})
.catch(error => {
// Error
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log('data', error.response.data);
console.log('status', error.response.status);
console.log('headers', error.response.headers);
return reject(error.data);
}
if (error.request) {
// The request was made but no response was received
console.log('request', error.request);
} else {
console.log('Error', error.message);
}
return reject(error);
});
});
};
// console.log(PlexApiClient.prototype);
export default PlexApiClient;
// const options = {token: config.plex.token};
// const PlexApi = new PlexApiClient(options);
// const urlParams = PlexApi.getUsersUrlParams();
// const url = PlexApi.buildUrl(urlParams);
// const result = PlexApi.request(url);
// const plexUrl = buildUrl(config.plex.url, {
// path: '/library/sections',
// port: '32400',
// queryParams: {
// 'X-Plex-Token': config.plex.token,
// },
// });
// const constructPlexUrl = urlParams => {
// const params = urlParams();
// const {host} = params;
// delete params.host;
// const urlHash = params;
// return buildUrl(host, urlHash);
// };
// const request = options => {
// };
// // console.log(
// // url.parse('https://plex.tv/api/users?&X-Plex-Token=hhnKQYskVjepfkhixqJu'),
// // );
// const usersUrl = constructPlexUrl(getUsersUrlParams);
// request(usersUrl);
// // console.log(request(usersUrl));
+12
View File
@@ -0,0 +1,12 @@
{
"extends": "airbnb-base",
"env": {
"node": true,
"mocha": true
},
"rules": {
"no-unused-vars": 0,
"no-unused-expressions": 0,
"import/no-extraneous-dependencies": 0
}
}
+15
View File
@@ -0,0 +1,15 @@
import request from 'supertest';
import mocha from 'mocha';
import chai from 'chai';
import chaiHttp from 'chai-http';
import app from '../index';
describe('GET /api/v1/plex/users', () => {
it('responds with json', (done) => {
request(app)
.get('/api/v1/plex/users')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
});
});
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<MediaContainer friendlyName="myPlex" identifier="com.plexapp.plugins.myplex" machineIdentifier="154b5ad3f3fade27a1f35eb8f44c014f7ab0d2a0" totalSize="6" size="6">
<User id="22100504" title="e311connell@gmail.com" username="e311connell@gmail.com" email="e311connell@gmail.com" recommendationsPlaylistId="a3ab3b4756506985" thumb="https://plex.tv/users/1a12ace72ecebbff/avatar?c=1549164875" protected="0" home="0" allowSync="0" allowCameraUpload="0" allowChannels="0" allowTuners="0" allowSubtitleAdmin="0" filterAll="" filterMovies="" filterMusic="" filterPhotos="" filterTelevision="" restricted="0">
<Server id="11520061" serverId="15932286" machineIdentifier="f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a" name="mjrflix" lastSeenAt="1549854842" numLibraries="2" allLibraries="1" owned="1" pending="0"/>
</User>
<User id="22099864" title="mike.rode@malauzai.com" username="mike.rode@malauzai.com" email="mike.rode@malauzai.com" recommendationsPlaylistId="4f34e0b774a38c55" thumb="https://plex.tv/users/d269a00326accf58/avatar?c=1549783460" protected="0" home="0" allowSync="0" allowCameraUpload="0" allowChannels="0" allowTuners="0" allowSubtitleAdmin="0" filterAll="" filterMovies="" filterMusic="" filterPhotos="" filterTelevision="" restricted="0">
<Server id="11519703" serverId="15932286" machineIdentifier="f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a" name="mjrflix" lastSeenAt="1549854842" numLibraries="2" allLibraries="1" owned="1" pending="0"/>
</User>
<User id="22160998" title="mjrflix+carson@gmail.com" username="mjrflix+carson@gmail.com" email="mjrflix+carson@gmail.com" recommendationsPlaylistId="ffe32f53e19fad28" thumb="https://plex.tv/users/b70f48ce01d53a73/avatar?c=1549214882" protected="0" home="0" allowSync="0" allowCameraUpload="0" allowChannels="0" allowTuners="0" allowSubtitleAdmin="0" filterAll="" filterMovies="" filterMusic="" filterPhotos="" filterTelevision="" restricted="0">
<Server id="11562729" serverId="15932286" machineIdentifier="f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a" name="mjrflix" lastSeenAt="1549854842" numLibraries="2" allLibraries="1" owned="1" pending="0"/>
</User>
<User id="22160913" title="mjrflix+jazz@gmail.com" username="mjrflix+jazz@gmail.com" email="mjrflix+jazz@gmail.com" recommendationsPlaylistId="98a2337ca26c7420" thumb="https://plex.tv/users/df0eb846405fb1b6/avatar?c=1549214755" protected="0" home="0" allowSync="0" allowCameraUpload="0" allowChannels="0" allowTuners="0" allowSubtitleAdmin="0" filterAll="" filterMovies="" filterMusic="" filterPhotos="" filterTelevision="" restricted="0">
<Server id="11562688" serverId="15932286" machineIdentifier="f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a" name="mjrflix" lastSeenAt="1549854842" numLibraries="2" allLibraries="1" owned="1" pending="0"/>
</User>
<User id="22161020" title="mjrflix+wade@gmail.com" username="mjrflix+wade@gmail.com" email="mjrflix+wade@gmail.com" recommendationsPlaylistId="1d63c4534b40fd82" thumb="https://plex.tv/users/52e6cac0635b4f05/avatar?c=1549214823" protected="0" home="0" allowSync="0" allowCameraUpload="0" allowChannels="0" allowTuners="0" allowSubtitleAdmin="0" filterAll="" filterMovies="" filterMusic="" filterPhotos="" filterTelevision="" restricted="0">
<Server id="11562742" serverId="15932286" machineIdentifier="f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a" name="mjrflix" lastSeenAt="1549854842" numLibraries="2" allLibraries="1" owned="1" pending="0"/>
</User>
<User id="22110937" title="rode4@gmail.com" username="rode4@gmail.com" email="rode4@gmail.com" recommendationsPlaylistId="ccb87923782deff9" thumb="https://plex.tv/users/7c0d31791846bc6a/avatar?c=1548902067" protected="0" home="0" allowSync="0" allowCameraUpload="0" allowChannels="0" allowTuners="0" allowSubtitleAdmin="0" filterAll="" filterMovies="" filterMusic="" filterPhotos="" filterTelevision="" restricted="0">
<Server id="11527625" serverId="15932286" machineIdentifier="f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a" name="mjrflix" lastSeenAt="1549854842" numLibraries="2" allLibraries="1" owned="1" pending="0"/>
</User>
</MediaContainer>
@@ -0,0 +1,255 @@
import request from 'supertest';
import mocha from 'mocha';
import chai from 'chai';
import chaiHttp from 'chai-http';
import nock from 'nock';
import PlexApiClient from '../../../../server/services/plexApi/index';
nock.enableNetConnect;
const should = chai.should();
describe('plexApi', () => {
it('sets options when passed valid options object', () => {
const options = { token: 'plexToken' };
const result = new PlexApiClient(options).options;
result.should.deep.equal({
token: 'plexToken',
});
});
it('return url params object', () => {
const options = { token: 'plexToken' };
const result = new PlexApiClient(options).getUsersUrlParams();
result.should.deep.equal({
host: 'https://plex.tv',
path: '/api/users',
queryParams: {
'X-Plex-Token': 'plexToken',
},
});
});
it('returns url', () => {
const options = { token: 'plexToken' };
const PlexApi = new PlexApiClient(options);
const urlParams = PlexApi.getUsersUrlParams();
const url = PlexApi.buildUrl(urlParams);
url.should.equal('https://plex.tv/api/users?X-Plex-Token=plexToken');
});
it('returns users', async () => {
const usersResponse = `${__dirname}/../../../mocks/plexApi/getUsers.xml`;
nock('https://plex.tv')
.get('/api/users?X-Plex-Token=plexToken')
.replyWithFile(200, usersResponse, { 'Content-Type': 'text/xml' });
const options = { token: 'plexToken' };
const PlexApi = new PlexApiClient(options);
const urlParams = PlexApi.getUsersUrlParams();
const url = PlexApi.buildUrl(urlParams);
const result = await PlexApi.request(url);
result.should.deep.equal({
MediaContainer: {
friendlyName: 'myPlex',
identifier: 'com.plexapp.plugins.myplex',
machineIdentifier: '154b5ad3f3fade27a1f35eb8f44c014f7ab0d2a0',
totalSize: '6',
size: '6',
User: [
{
id: '22100504',
title: 'e311connell@gmail.com',
username: 'e311connell@gmail.com',
email: 'e311connell@gmail.com',
recommendationsPlaylistId: 'a3ab3b4756506985',
thumb: 'https://plex.tv/users/1a12ace72ecebbff/avatar?c=1549164875',
protected: '0',
home: '0',
allowSync: '0',
allowCameraUpload: '0',
allowChannels: '0',
allowTuners: '0',
allowSubtitleAdmin: '0',
filterAll: '',
filterMovies: '',
filterMusic: '',
filterPhotos: '',
filterTelevision: '',
restricted: '0',
Server: {
id: '11520061',
serverId: '15932286',
machineIdentifier: 'f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a',
name: 'mjrflix',
lastSeenAt: '1549854842',
numLibraries: '2',
allLibraries: '1',
owned: '1',
pending: '0',
},
},
{
id: '22099864',
title: 'mike.rode@malauzai.com',
username: 'mike.rode@malauzai.com',
email: 'mike.rode@malauzai.com',
recommendationsPlaylistId: '4f34e0b774a38c55',
thumb: 'https://plex.tv/users/d269a00326accf58/avatar?c=1549783460',
protected: '0',
home: '0',
allowSync: '0',
allowCameraUpload: '0',
allowChannels: '0',
allowTuners: '0',
allowSubtitleAdmin: '0',
filterAll: '',
filterMovies: '',
filterMusic: '',
filterPhotos: '',
filterTelevision: '',
restricted: '0',
Server: {
id: '11519703',
serverId: '15932286',
machineIdentifier: 'f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a',
name: 'mjrflix',
lastSeenAt: '1549854842',
numLibraries: '2',
allLibraries: '1',
owned: '1',
pending: '0',
},
},
{
id: '22160998',
title: 'mjrflix+carson@gmail.com',
username: 'mjrflix+carson@gmail.com',
email: 'mjrflix+carson@gmail.com',
recommendationsPlaylistId: 'ffe32f53e19fad28',
thumb: 'https://plex.tv/users/b70f48ce01d53a73/avatar?c=1549214882',
protected: '0',
home: '0',
allowSync: '0',
allowCameraUpload: '0',
allowChannels: '0',
allowTuners: '0',
allowSubtitleAdmin: '0',
filterAll: '',
filterMovies: '',
filterMusic: '',
filterPhotos: '',
filterTelevision: '',
restricted: '0',
Server: {
id: '11562729',
serverId: '15932286',
machineIdentifier: 'f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a',
name: 'mjrflix',
lastSeenAt: '1549854842',
numLibraries: '2',
allLibraries: '1',
owned: '1',
pending: '0',
},
},
{
id: '22160913',
title: 'mjrflix+jazz@gmail.com',
username: 'mjrflix+jazz@gmail.com',
email: 'mjrflix+jazz@gmail.com',
recommendationsPlaylistId: '98a2337ca26c7420',
thumb: 'https://plex.tv/users/df0eb846405fb1b6/avatar?c=1549214755',
protected: '0',
home: '0',
allowSync: '0',
allowCameraUpload: '0',
allowChannels: '0',
allowTuners: '0',
allowSubtitleAdmin: '0',
filterAll: '',
filterMovies: '',
filterMusic: '',
filterPhotos: '',
filterTelevision: '',
restricted: '0',
Server: {
id: '11562688',
serverId: '15932286',
machineIdentifier: 'f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a',
name: 'mjrflix',
lastSeenAt: '1549854842',
numLibraries: '2',
allLibraries: '1',
owned: '1',
pending: '0',
},
},
{
id: '22161020',
title: 'mjrflix+wade@gmail.com',
username: 'mjrflix+wade@gmail.com',
email: 'mjrflix+wade@gmail.com',
recommendationsPlaylistId: '1d63c4534b40fd82',
thumb: 'https://plex.tv/users/52e6cac0635b4f05/avatar?c=1549214823',
protected: '0',
home: '0',
allowSync: '0',
allowCameraUpload: '0',
allowChannels: '0',
allowTuners: '0',
allowSubtitleAdmin: '0',
filterAll: '',
filterMovies: '',
filterMusic: '',
filterPhotos: '',
filterTelevision: '',
restricted: '0',
Server: {
id: '11562742',
serverId: '15932286',
machineIdentifier: 'f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a',
name: 'mjrflix',
lastSeenAt: '1549854842',
numLibraries: '2',
allLibraries: '1',
owned: '1',
pending: '0',
},
},
{
id: '22110937',
title: 'rode4@gmail.com',
username: 'rode4@gmail.com',
email: 'rode4@gmail.com',
recommendationsPlaylistId: 'ccb87923782deff9',
thumb: 'https://plex.tv/users/7c0d31791846bc6a/avatar?c=1548902067',
protected: '0',
home: '0',
allowSync: '0',
allowCameraUpload: '0',
allowChannels: '0',
allowTuners: '0',
allowSubtitleAdmin: '0',
filterAll: '',
filterMovies: '',
filterMusic: '',
filterPhotos: '',
filterTelevision: '',
restricted: '0',
Server: {
id: '11527625',
serverId: '15932286',
machineIdentifier: 'f1adbb16fead548bd0fd8dc723166dfa9ae6cd0a',
name: 'mjrflix',
lastSeenAt: '1549854842',
numLibraries: '2',
allLibraries: '1',
owned: '1',
pending: '0',
},
},
],
},
});
});
});