mirror of
https://github.com/mjrode/WhatToWatch.git
synced 2026-05-02 18:49:11 -05:00
Merge branch 'master' of github.com:mjrode/WhatToWatch
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": true,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 70,
|
||||
}
|
||||
@@ -10,6 +10,30 @@ _This is still in development. If you would like to help contribute feel free to
|
||||
|
||||
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system
|
||||
|
||||
## Installing
|
||||
Clone the repository
|
||||
|
||||
`git clone git@github.com:mjrode/WhatToWatch.git`
|
||||
|
||||
cd into the new directory
|
||||
|
||||
`cd WhatToWatch`
|
||||
|
||||
## Server (run from the root directory)
|
||||
|
||||
Install dependencies
|
||||
|
||||
`npm install`
|
||||
|
||||
Install npx to easily run migrations
|
||||
`npm install -g npx`
|
||||
|
||||
|
||||
Create database, test database, and run the migrations
|
||||
`npx sequelize db:create`
|
||||
`NODE_ENV=test npx sequelize db:create`
|
||||
`npm run db:reset`
|
||||
|
||||
## Prerequisites
|
||||
* **Required Keys**
|
||||
#### Rename `example.env` to `.env` and update the test tokens with your tokens
|
||||
@@ -31,32 +55,18 @@ These instructions will get you a copy of the project up and running on your loc
|
||||
[TasteDive](https://tastedive.com/read/api)
|
||||
* `TDAW_API_TOKEN`
|
||||
|
||||
## Running the tests
|
||||
`npm test`
|
||||
|
||||
## Installing
|
||||
Clone the repository
|
||||
|
||||
`git clone git@github.com:mjrode/WhatToWatch.git`
|
||||
|
||||
cd into the new directory
|
||||
|
||||
`cd WhatToWatch`
|
||||
|
||||
Install dependencies
|
||||
|
||||
`npm install`
|
||||
|
||||
Create database and run the migrations
|
||||
|
||||
`node server/db/scripts/setup.js`
|
||||
# Client (run from WhatToWatch/client)
|
||||
## install dependencies
|
||||
`npm install`
|
||||
|
||||
## Run application
|
||||
Concurrently run the frontend and backend servers
|
||||
|
||||
`npm run dev`
|
||||
|
||||
## Running the tests
|
||||
`npm test`
|
||||
|
||||
## Built With
|
||||
|
||||
* [Node](www.example.com)
|
||||
* [React](www.example.com)
|
||||
@@ -3,6 +3,7 @@ import axios from 'axios';
|
||||
export const types = {
|
||||
SET_LOADING: 'set_loading',
|
||||
FETCH_USER: 'fetch_user',
|
||||
FETCH_USERS: 'fetch_users',
|
||||
FETCH_MEDIA_RESPONSE: 'fetch_media_response',
|
||||
GET_MOST_WATCHED: 'get_most_watched',
|
||||
ADD_SERIES: 'add_series',
|
||||
@@ -20,6 +21,11 @@ export const fetchUser = () => async dispatch => {
|
||||
dispatch({type: types.FETCH_USER, payload: res.data});
|
||||
};
|
||||
|
||||
export const fetchUsers = () => async dispatch => {
|
||||
const res = await axios.get('/api/admin/users');
|
||||
dispatch({ type: types.FETCH_USERS, payload: res.data });
|
||||
};
|
||||
|
||||
export const fetchPin = () => async dispatch => {
|
||||
const res = await axios.get('/api/plex/plex-pin');
|
||||
dispatch({type: types.FETCH_PIN, payload: res.data});
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import HeroSimple from './HeroSimple';
|
||||
import * as actions from './../actions';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
class Admin extends Component {
|
||||
async componentDidMount() {
|
||||
await this.props.fetchUsers();
|
||||
console.log('mike--', this.props.auth)
|
||||
}
|
||||
|
||||
loginAsUser = user => {
|
||||
console.log('I was clicked and got a user', user)
|
||||
}
|
||||
|
||||
renderUsers() {
|
||||
if (this.props.auth.users) {
|
||||
const usersList = this.props.auth.users.map(user => {
|
||||
return (
|
||||
<Typography
|
||||
variant="h2"
|
||||
key={user.email}
|
||||
align="center"
|
||||
color="textSecondary"
|
||||
paragraph
|
||||
className="z-depth-2 code"
|
||||
>
|
||||
<button
|
||||
onClick={() => this.loginAsUser(user)}
|
||||
className=""
|
||||
>
|
||||
{user.email}
|
||||
</button>
|
||||
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
return <div>{usersList}</div>
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (!this.props) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.props.auth.user || !this.props.auth.user.admin) {
|
||||
console.log('where are my props', this.props.auth)
|
||||
return <div><p>Checking for admin rights..</p></div>
|
||||
}
|
||||
console.log(this.props.auth);
|
||||
return (
|
||||
<div>
|
||||
<HeroSimple />
|
||||
|
||||
<div className="row flex-center">
|
||||
{this.renderUsers()}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps({ auth }) {
|
||||
console.log('plex props', auth)
|
||||
return { auth };
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actions,
|
||||
)(Admin)
|
||||
@@ -6,6 +6,7 @@ import * as actions from '../actions';
|
||||
import Header from './Header';
|
||||
import Hero from './Hero';
|
||||
import Plex from './plex/Plex';
|
||||
import Admin from './Admin';
|
||||
import PlexPin from './plex/PlexPin';
|
||||
import SimilarList from './SimilarList';
|
||||
import PopularList from './PopularList';
|
||||
@@ -29,6 +30,7 @@ class App extends Component {
|
||||
<Route exact path="/sonarr" component={PlexTokenForm} />
|
||||
<Route exact path="/sign-up" component={SignUp} />
|
||||
<Route exact path="/plex-pin" component={PlexPin} />
|
||||
<Route path="/admin" component={Admin} />
|
||||
<Route path="/most-watched" component={Plex} />
|
||||
<Route
|
||||
path="/similar/:show"
|
||||
|
||||
@@ -35,6 +35,7 @@ class Plex extends Component {
|
||||
}
|
||||
|
||||
function mapStateToProps({auth, plex}) {
|
||||
console.log('plex props', auth)
|
||||
return {auth, mediaResponse: plex.mediaResponse};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {Link, Redirect} from 'react-router-dom';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import HeroSimple from '../HeroSimple';
|
||||
import * as actions from '../../actions';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
@@ -18,7 +18,7 @@ class PlexPin extends Component {
|
||||
return <Redirect to="/most-watched" />;
|
||||
}
|
||||
if (!this.props.auth.plexToken) {
|
||||
console.log(this.props.auth);
|
||||
console.log('mike--', this.props.auth);
|
||||
return (
|
||||
<div>
|
||||
<HeroSimple />
|
||||
@@ -65,9 +65,9 @@ class PlexPin extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps({auth}) {
|
||||
function mapStateToProps({ auth }) {
|
||||
console.log('auth state to prop', auth);
|
||||
return {auth};
|
||||
return { auth };
|
||||
}
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import {types} from '../actions/index';
|
||||
import { types } from '../actions/index';
|
||||
|
||||
export const initialState = {
|
||||
loading: false,
|
||||
plexPin: '',
|
||||
user: '',
|
||||
users: '',
|
||||
};
|
||||
|
||||
export default function(state = {}, action) {
|
||||
console.log('action - payload', action.payload);
|
||||
export default function (state = {}, action) {
|
||||
console.log('action - payload', action.type);
|
||||
switch (action.type) {
|
||||
case types.FETCH_USER:
|
||||
return action.payload || false;
|
||||
case types.FETCH_USERS:
|
||||
return { ...state, users: action.payload };
|
||||
case types.FETCH_PIN:
|
||||
return {...state, plexPin: action.payload};
|
||||
return { ...state, plexPin: action.payload };
|
||||
case types.CHECK_PLEX_PIN:
|
||||
return {...state, plexToken: action.payload};
|
||||
return { ...state, plexToken: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
DATABASE=recommend
|
||||
DATABASE_USER=postgres
|
||||
DATABASE_PASSWORD=postgres
|
||||
PLEX_API_TOKEN=testPlexApiToken
|
||||
TDAW_API_TOKEN=testToken
|
||||
GOOGLE_CLIENT_ID=testToken
|
||||
GOOGLE_CLIENT_SECRET=testToken
|
||||
COOKIE_KEY=testToken
|
||||
MOVIE_API_KEY=testToken
|
||||
Generated
+2576
-2348
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -10,7 +10,7 @@
|
||||
"scripts": {
|
||||
"test": "cross-env NODE_ENV=test PLEX_API_TOKEN=testPlexApiToken TDAW_API_TOKEN=testTdawToken nyc --reporter=html --reporter=text mocha --require babel-core/register 'test/**/*.test.js' --exit",
|
||||
"debug:test": "cross-env NODE_ENV=test PLEX_API_TOKEN=testPlexApiToken TDAW_API_TOKEN=testTdawToken nyc --reporter=html --reporter=text mocha --inspect-brk --require babel-core/register 'test/**/*.test.js' --exit",
|
||||
"db:clean": "npm run db:drop && npm run db:create && npm run db:migrate",
|
||||
"db:create": "npm run db:create && npm run db:migrate && NODE_ENV=test npm run db:create && npm run db:migrate",
|
||||
"db:create:test": "NODE_ENV=test npx sequelize db:create",
|
||||
"db:create-migration": "babel-node ./scripts/db/createMigration",
|
||||
"db:drop": "babel-node ./scripts/db/drop",
|
||||
@@ -19,6 +19,7 @@
|
||||
"dev": "concurrently \"npm run server\" \"npm run client\"",
|
||||
"debug": "nodemon --exec babel-node --inspect index.js",
|
||||
"db:reset": "NODE_ENV=test npx sequelize db:migrate:undo:all && NODE_ENV=test npx sequelize db:migrate && npx sequelize db:migrate:undo:all && npx sequelize db:migrate",
|
||||
"prod:dump": "heroku pg:backups:capture && heroku pg:backups:download",
|
||||
"start": "babel-node index.js",
|
||||
"server": "nodemon --exec babel-node index.js",
|
||||
"client": "npm run start --prefix client",
|
||||
@@ -61,6 +62,7 @@
|
||||
"onchange": "^6.0.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-http": "^0.3.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^7.12.1",
|
||||
"pg-hstore": "^2.3.3",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pg_restore --verbose --clean --no-acl --no-owner -h localhost -U postgres -d recommend_development latest.dump
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Router } from 'express';
|
||||
import models from '../db/models'
|
||||
const router = Router();
|
||||
|
||||
router.get('/users', async (req, res) => {
|
||||
const users = await models.User.findAll()
|
||||
|
||||
res.send(users);
|
||||
});
|
||||
|
||||
router.get('/login-as-user', async (req, res) => {
|
||||
|
||||
res.send(users);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
up: function (queryInterface, Sequelize) {
|
||||
// logic for transforming into the new state
|
||||
return queryInterface.addColumn(
|
||||
'Users',
|
||||
'admin',
|
||||
Sequelize.BOOLEAN
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
down: function (queryInterface, Sequelize) {
|
||||
// logic for reverting the changes
|
||||
return queryInterface.removeColumn(
|
||||
'Users',
|
||||
'admin'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
plexToken: DataTypes.STRING,
|
||||
sonarrUrl: DataTypes.STRING,
|
||||
sonarrApiKey: DataTypes.STRING,
|
||||
admin: DataTypes.BOOLEAN,
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ import tdaw from './routes/tdaw.route';
|
||||
import movieDb from './routes/movieDb.route';
|
||||
import sonarr from './routes/sonarr.route';
|
||||
import auth from './routes/auth.route';
|
||||
import admin from './routes/admin.route';
|
||||
import recommend from './routes/recommend.route';
|
||||
require('./services/auth/passport');
|
||||
|
||||
@@ -46,6 +47,7 @@ export default () => {
|
||||
server.use('/api/recommend', recommend);
|
||||
server.use('/auth', auth);
|
||||
server.use('/api/auth', auth);
|
||||
server.use('/api/admin', admin);
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
server.use(express.static('client/build'));
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import adminController from '../controllers/admin.controller';
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(adminController);
|
||||
|
||||
export default router;
|
||||
@@ -3,42 +3,42 @@ import plexApi from './plexApi';
|
||||
import models from '../../db/models';
|
||||
import config from '../../../config';
|
||||
import MovieDb from 'moviedb-promise';
|
||||
import {Op} from 'sequelize';
|
||||
import { Op } from 'sequelize';
|
||||
const mdb = new MovieDb(config.server.movieApiKey);
|
||||
|
||||
const updateOrCreate = async (model, where, newItem, beforeCreate) => {
|
||||
const item = await model.findOne({where});
|
||||
const item = await model.findOne({ where });
|
||||
if (!item) {
|
||||
const createItem = await model.create(newItem, {
|
||||
returning: true,
|
||||
plain: true,
|
||||
raw: true,
|
||||
});
|
||||
return {createItem, created: true};
|
||||
return { createItem, created: true };
|
||||
} else {
|
||||
const updatedItem = await model.update(
|
||||
newItem,
|
||||
{where: where},
|
||||
{returning: true, plain: true, raw: true},
|
||||
{ where: where },
|
||||
{ returning: true, plain: true, raw: true },
|
||||
);
|
||||
return {item, created: false};
|
||||
return { item, created: false };
|
||||
}
|
||||
};
|
||||
|
||||
const importTvPosters = async user => {
|
||||
try {
|
||||
const mostWatched = await models.PlexLibrary.findAll({
|
||||
where: {UserId: user.id, type: 'show', views: {[Op.gt]: 0}},
|
||||
where: { UserId: user.id, type: 'show', views: { [Op.gt]: 0 } },
|
||||
});
|
||||
|
||||
const imageUrls = await mostWatched.map(async show => {
|
||||
const res = await mdb.searchTv({query: show.title});
|
||||
const res = await mdb.searchTv({ query: show.title });
|
||||
return models.PlexLibrary.update(
|
||||
{
|
||||
poster_path: res.results[0].poster_path,
|
||||
},
|
||||
{
|
||||
where: {UserId: user.id, title: show.title},
|
||||
where: { UserId: user.id, title: show.title },
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -132,7 +132,7 @@ const importMostWatched = async user => {
|
||||
};
|
||||
|
||||
const importMostWatchedData = async (sectionKey, user) => {
|
||||
const mostWatchedData = await plexApi.getMostWatched({sectionKey}, user);
|
||||
const mostWatchedData = await plexApi.getMostWatched({ sectionKey }, user);
|
||||
const mostWatchedDbData = await updateLibrary(mostWatchedData, user);
|
||||
return mostWatchedDbData;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import config from '../../../config';
|
||||
import helpers from '../helpers';
|
||||
|
||||
const getUsersUrlParams = function(user) {
|
||||
const getUsersUrlParams = function (user) {
|
||||
return {
|
||||
host: user.plexUrl,
|
||||
path: '/users',
|
||||
@@ -11,7 +11,7 @@ const getUsersUrlParams = function(user) {
|
||||
};
|
||||
};
|
||||
|
||||
const getSectionsUrlParams = function(user) {
|
||||
const getSectionsUrlParams = function (user) {
|
||||
return {
|
||||
host: user.plexUrl,
|
||||
path: '/library/sections',
|
||||
@@ -21,20 +21,20 @@ const getSectionsUrlParams = function(user) {
|
||||
};
|
||||
};
|
||||
|
||||
const mostWatchedUrlParams = function(accountId, sectionKey, limit = 10, user) {
|
||||
const mostWatchedUrlParams = function (accountId, sectionKey, limit = 10, user) {
|
||||
return {
|
||||
host: user.plexUrl,
|
||||
path: '/library/all/top',
|
||||
queryParams: {
|
||||
...(accountId && {accountId}),
|
||||
...(sectionKey && {type: sectionKey}),
|
||||
...(limit && {limit}),
|
||||
...(accountId && { accountId }),
|
||||
...(sectionKey && { type: sectionKey }),
|
||||
...(limit && { limit }),
|
||||
'X-Plex-Token': user.plexToken,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const getLibraryDataBySectionUrlParams = function(sectionId, user) {
|
||||
const getLibraryDataBySectionUrlParams = function (sectionId, user) {
|
||||
return {
|
||||
host: user.plexUrl,
|
||||
path: `/library/sections/${sectionId}/all`,
|
||||
@@ -44,7 +44,7 @@ const getLibraryDataBySectionUrlParams = function(sectionId, user) {
|
||||
};
|
||||
};
|
||||
|
||||
const getUsers = async function(user) {
|
||||
const getUsers = async function (user) {
|
||||
try {
|
||||
const urlParams = getUsersUrlParams(user);
|
||||
const getUsersUrl = helpers.buildUrl(urlParams);
|
||||
@@ -55,14 +55,15 @@ const getUsers = async function(user) {
|
||||
}
|
||||
};
|
||||
|
||||
const getMostWatched = async function(
|
||||
{accountId, sectionKey, limit = 10},
|
||||
const getMostWatched = async function (
|
||||
{ accountId, sectionKey, limit = 10 },
|
||||
user,
|
||||
) {
|
||||
try {
|
||||
const urlParams = mostWatchedUrlParams(accountId, sectionKey, limit, user);
|
||||
const mostWatchedUrl = helpers.buildUrl(urlParams);
|
||||
const response = await helpers.request(mostWatchedUrl);
|
||||
console.log('most-watched-raw-response---', response)
|
||||
return response.MediaContainer.Metadata;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -74,7 +75,7 @@ const getMostWatched = async function(
|
||||
}
|
||||
};
|
||||
|
||||
const getSections = async function(user) {
|
||||
const getSections = async function (user) {
|
||||
try {
|
||||
const urlParams = getSectionsUrlParams(user);
|
||||
const getSectionsUrl = helpers.buildUrl(urlParams);
|
||||
@@ -88,7 +89,7 @@ const getSections = async function(user) {
|
||||
}
|
||||
};
|
||||
|
||||
const getLibraryDataBySection = async function({sectionKey}, user) {
|
||||
const getLibraryDataBySection = async function ({ sectionKey }, user) {
|
||||
try {
|
||||
console.log('sectionId--', sectionKey);
|
||||
const urlParams = getLibraryDataBySectionUrlParams(sectionKey, user);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import models from '../../db/models';
|
||||
import helpers from '../helpers';
|
||||
import {Op} from 'sequelize';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
const getMostWatched = async (req, res) => {
|
||||
try {
|
||||
const mostWatched = await models.PlexLibrary.findAll({
|
||||
where: {UserId: req.user.id, type: 'show', views: {[Op.gt]: 0}},
|
||||
where: { UserId: req.user.id, type: 'show', views: { [Op.gt]: 0 } },
|
||||
});
|
||||
console.log('TCL: getMostWatched -> mostWatched', mostWatched);
|
||||
res.json(mostWatched);
|
||||
|
||||
@@ -4,7 +4,7 @@ import helpers from '../helpers';
|
||||
|
||||
const similarMedia = async (req, res) => {
|
||||
try {
|
||||
const {showName} = req.query;
|
||||
const { showName } = req.query;
|
||||
console.log('Show name', showName);
|
||||
const media = 'show';
|
||||
const response = await tdawApi.similarMedia(
|
||||
@@ -20,7 +20,7 @@ const similarMedia = async (req, res) => {
|
||||
};
|
||||
|
||||
const mostWatched = async (req, res) => {
|
||||
console.log('was i called');
|
||||
console.log('express-request-object---', req);
|
||||
const response = await tdawApi.mostWatched();
|
||||
console.log(response);
|
||||
res.json(response);
|
||||
@@ -28,7 +28,7 @@ const mostWatched = async (req, res) => {
|
||||
|
||||
const qlooMedia = async (req, res) => {
|
||||
try {
|
||||
const {mediaName, mediaType} = req.query;
|
||||
const { mediaName, mediaType } = req.query;
|
||||
const mediaId = await tdawApi.qlooMediaId(mediaName, mediaType);
|
||||
const response = await tdawApi.qlooMedia(mediaId, mediaType);
|
||||
res.json(response);
|
||||
|
||||
Reference in New Issue
Block a user