From b021882013f027230acbe74d2a29714f61bb85e4 Mon Sep 17 00:00:00 2001 From: Kevin Midboe Date: Wed, 12 Jan 2022 22:20:12 +0100 Subject: [PATCH] Refactored user store & moved popup logic from App to store Cleaned up bits of all the components that use these stores. User store now focuses around keeping track of the authorization token and the response from /login. When a sucessfull login request is made we save our new token and username & admin data to the with login(). Since cookies aren't implemented yet we keep track of the auth_token to make authroized requests back to the api later. The username and admin data from within the body of the token is saved and only cleared on logout(). Since we haven't implemented cookies we persist storage with localStorage. Whenever we successfully decode and save a token body we also save the token to localStorage. This is later used by initFromLocalStorage() to hydrate the store on first page load. Popup module is for opening and closing the popup, and now moved away from a inline plugin in App entry. Now handles loading from & updating query parameters type=movie | show. The route listens checks if open every navigation and closes popup if it is. --- src/App.vue | 55 +-- src/api.js | 532 +++++++++++++++--------------- src/components/404.vue | 21 +- src/components/MoviePopup.vue | 70 ++-- src/components/MoviesListItem.vue | 11 +- src/components/Profile.vue | 33 +- src/components/Settings.vue | 242 +++++++------- src/components/Signin.vue | 96 +++--- src/main.js | 35 +- src/modules/popup.js | 59 ++++ src/modules/user.js | 103 ++++++ src/modules/userModule.js | 112 ------- src/routes.js | 17 +- src/store.js | 22 +- 14 files changed, 723 insertions(+), 685 deletions(-) create mode 100644 src/modules/popup.js create mode 100644 src/modules/user.js delete mode 100644 src/modules/userModule.js diff --git a/src/App.vue b/src/App.vue index b3ee099..e005bb0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -46,18 +46,13 @@ - + @@ -126,7 +90,8 @@ body { transition: background-color 0.5s ease, color 0.5s ease; * { - transition: background-color 0.5s ease, color 0.5s ease; + transition: background-color 0.5s ease, color 0.5s ease, + border-color 0.5s ease; } &.hidden { @@ -158,20 +123,6 @@ img { .wrapper { position: relative; } -// .header { -// position: fixed; -// z-index: 15; -// display: flex; -// flex-direction: column; - -// @include tablet-min { -// width: calc(100% - 170px); -// margin-left: 95px; -// border-top: 0; -// border-bottom: 0; -// top: 0; -// } -// } // router view transition .fade-enter-active, diff --git a/src/api.js b/src/api.js index d36b6c5..da86d0c 100644 --- a/src/api.js +++ b/src/api.js @@ -1,24 +1,34 @@ -import axios from 'axios' -import storage from '@/storage' -import config from '@/config.json' -import path from 'path' -import store from '@/store' +import axios from "axios"; +import storage from "@/storage"; +import config from "@/config.json"; +import path from "path"; +import store from "@/store"; -const SEASONED_URL = config.SEASONED_URL -const ELASTIC_URL = config.ELASTIC_URL -const ELASTIC_INDEX = config.ELASTIC_INDEX +const token = () => store.getters["user/token"]; +const plexId = () => store.getters["user/plexId"]; + +const AUTHORIZATION_HEADERS = () => { + return { + Authorization: token(), + "Content-Type": "application/json" + }; +}; + +const SEASONED_URL = config.SEASONED_URL; +const ELASTIC_URL = config.ELASTIC_URL; +const ELASTIC_INDEX = config.ELASTIC_INDEX; // TODO // - Move autorization token and errors here? -const checkStatusAndReturnJson = (response) => { +const checkStatusAndReturnJson = response => { if (!response.ok) { - throw resp + throw resp; } - return response.json() -} + return response.json(); +}; -// - - - TMDB - - - +// - - - TMDB - - - /** * Fetches tmdb movie by id. Can optionally include cast credits in result object. @@ -26,23 +36,31 @@ const checkStatusAndReturnJson = (response) => { * @param {boolean} [credits=false] Include credits * @returns {object} Tmdb response */ -const getMovie = (id, checkExistance=false, credits=false, release_dates=false) => { - const url = new URL('v2/movie', SEASONED_URL) - url.pathname = path.join(url.pathname, id.toString()) +const getMovie = ( + id, + checkExistance = false, + credits = false, + release_dates = false +) => { + const url = new URL("v2/movie", SEASONED_URL); + url.pathname = path.join(url.pathname, id.toString()); if (checkExistance) { - url.searchParams.append('check_existance', true) + url.searchParams.append("check_existance", true); } if (credits) { - url.searchParams.append('credits', true) + url.searchParams.append("credits", true); } - if(release_dates) { - url.searchParams.append('release_dates', true) + if (release_dates) { + url.searchParams.append("release_dates", true); } - return fetch(url.href) + return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }) .then(resp => resp.json()) - .catch(error => { console.error(`api error getting movie: ${id}`); throw error }) -} + .catch(error => { + console.error(`api error getting movie: ${id}`); + throw error; + }); +}; /** * Fetches tmdb show by id. Can optionally include cast credits in result object. @@ -50,20 +68,23 @@ const getMovie = (id, checkExistance=false, credits=false, release_dates=false) * @param {boolean} [credits=false] Include credits * @returns {object} Tmdb response */ -const getShow = (id, checkExistance=false, credits=false) => { - const url = new URL('v2/show', SEASONED_URL) - url.pathname = path.join(url.pathname, id.toString()) +const getShow = (id, checkExistance = false, credits = false) => { + const url = new URL("v2/show", SEASONED_URL); + url.pathname = path.join(url.pathname, id.toString()); if (checkExistance) { - url.searchParams.append('check_existance', true) + url.searchParams.append("check_existance", true); } if (credits) { - url.searchParams.append('credits', true) + url.searchParams.append("credits", true); } - return fetch(url.href) + return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }) .then(resp => resp.json()) - .catch(error => { console.error(`api error getting show: ${id}`); throw error }) -} + .catch(error => { + console.error(`api error getting show: ${id}`); + throw error; + }); +}; /** * Fetches tmdb person by id. Can optionally include cast credits in result object. @@ -71,17 +92,20 @@ const getShow = (id, checkExistance=false, credits=false) => { * @param {boolean} [credits=false] Include credits * @returns {object} Tmdb response */ -const getPerson = (id, credits=false) => { - const url = new URL('v2/person', SEASONED_URL) - url.pathname = path.join(url.pathname, id.toString()) +const getPerson = (id, credits = false) => { + const url = new URL("v2/person", SEASONED_URL); + url.pathname = path.join(url.pathname, id.toString()); if (credits) { - url.searchParams.append('credits', true) + url.searchParams.append("credits", true); } return fetch(url.href) .then(resp => resp.json()) - .catch(error => { console.error(`api error getting person: ${id}`); throw error }) -} + .catch(error => { + console.error(`api error getting person: ${id}`); + throw error; + }); +}; /** * Fetches tmdb list by name. @@ -89,41 +113,39 @@ const getPerson = (id, credits=false) => { * @param {number} [page=1] * @returns {object} Tmdb list response */ -const getTmdbMovieListByName = (name, page=1) => { - const url = new URL('v2/movie/' + name, SEASONED_URL) - url.searchParams.append('page', page) - const headers = { authorization: storage.token } +const getTmdbMovieListByName = (name, page = 1) => { + const url = new URL("v2/movie/" + name, SEASONED_URL); + url.searchParams.append("page", page); - return fetch(url.href, { headers: headers }) - .then(resp => resp.json()) - // .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error }) -} + return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }).then(resp => + resp.json() + ); + // .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error }) +}; /** * Fetches requested items. * @param {number} [page=1] * @returns {object} Request response */ -const getRequests = (page=1) => { - const url = new URL('v2/request', SEASONED_URL) - url.searchParams.append('page', page) - const headers = { authorization: storage.token } +const getRequests = (page = 1) => { + const url = new URL("v2/request", SEASONED_URL); + url.searchParams.append("page", page); - return fetch(url.href, { headers: headers }) - .then(resp => resp.json()) - // .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error }) -} + return fetch(url.href, { + headers: AUTHORIZATION_HEADERS() + }).then(resp => resp.json()); + // .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error }) +}; +const getUserRequests = (page = 1) => { + const url = new URL("v1/user/requests", SEASONED_URL); + url.searchParams.append("page", page); -const getUserRequests = (page=1) => { - const url = new URL('v1/user/requests', SEASONED_URL) - url.searchParams.append('page', page) - - const headers = { authorization: localStorage.getItem('token') } - - return fetch(url.href, { headers }) - .then(resp => resp.json()) -} + return fetch(url.href, { + headers: AUTHORIZATION_HEADERS() + }).then(resp => resp.json()); +}; /** * Fetches tmdb movies and shows by query. @@ -131,24 +153,27 @@ const getUserRequests = (page=1) => { * @param {number} [page=1] * @returns {object} Tmdb response */ -const searchTmdb = (query, page=1, adult=false, mediaType=null) => { - const url = new URL('v2/search', SEASONED_URL) - if (mediaType != null && ['movie', 'show', 'person'].includes(mediaType)) { - url.pathname += `/${mediaType}` +const searchTmdb = (query, page = 1, adult = false, mediaType = null) => { + const url = new URL("v2/search", SEASONED_URL); + if (mediaType != null && ["movie", "show", "person"].includes(mediaType)) { + url.pathname += `/${mediaType}`; } - url.searchParams.append('query', query) - url.searchParams.append('page', page) - url.searchParams.append('adult', adult) + url.searchParams.append("query", query); + url.searchParams.append("page", page); + url.searchParams.append("adult", adult); - const headers = { authorization: localStorage.getItem('token') } - - return fetch(url.href, { headers }) + return fetch(url.href, { + headers: AUTHORIZATION_HEADERS() + }) .then(resp => resp.json()) - .catch(error => { console.error(`api error searching: ${query}, page: ${page}`); throw error }) -} + .catch(error => { + console.error(`api error searching: ${query}, page: ${page}`); + throw error; + }); +}; -// - - - Torrents - - - +// - - - Torrents - - - /** * Search for torrents by query @@ -157,15 +182,18 @@ const searchTmdb = (query, page=1, adult=false, mediaType=null) => { * @returns {object} Torrent response */ const searchTorrents = (query, authorization_token) => { - const url = new URL('/api/v1/pirate/search', SEASONED_URL) - url.searchParams.append('query', query) + const url = new URL("/api/v1/pirate/search", SEASONED_URL); + url.searchParams.append("query", query); - const headers = { authorization: storage.token } - - return fetch(url.href, { headers: headers }) + return fetch(url.href, { + headers: AUTHORIZATION_HEADERS() + }) .then(resp => resp.json()) - .catch(error => { console.error(`api error searching torrents: ${query}`); throw error }) -} + .catch(error => { + console.error(`api error searching torrents: ${query}`); + throw error; + }); +}; /** * Add magnet to download queue. @@ -175,28 +203,27 @@ const searchTorrents = (query, authorization_token) => { * @returns {object} Success/Failure response */ const addMagnet = (magnet, name, tmdb_id) => { - const url = new URL('v1/pirate/add', SEASONED_URL) + const url = new URL("v1/pirate/add", SEASONED_URL); const body = JSON.stringify({ magnet: magnet, name: name, tmdb_id: tmdb_id - }) - const headers = { - 'Content-Type': 'application/json', - authorization: storage.token - } + }); return fetch(url.href, { - method: 'POST', - headers, - body - }) + method: "POST", + headers: AUTHORIZATION_HEADERS(), + body + }) .then(resp => resp.json()) - .catch(error => { console.error(`api error adding magnet: ${name} ${error}`); throw error }) -} + .catch(error => { + console.error(`api error adding magnet: ${name} ${error}`); + throw error; + }); +}; -// - - - Plex/Request - - - +// - - - Plex/Request - - - /** * Request a movie or show from id. If authorization token is included the user will be linked @@ -206,28 +233,19 @@ const addMagnet = (magnet, name, tmdb_id) => { * @param {string} [authorization_token] To identify the requesting user * @returns {object} Success/Failure response */ -const request = (id, type, authorization_token=undefined) => { - const url = new URL('v2/request', SEASONED_URL) -// url.pathname = path.join(url.pathname, id.toString()) -// url.searchParams.append('type', type) - - const headers = { - 'Authorization': authorization_token, - 'Content-Type': 'application/json' - } - const body = { - id: id, - type: type - } - +const request = (id, type) => { + const url = new URL("v2/request", SEASONED_URL); return fetch(url.href, { - method: 'POST', - headers: headers, - body: JSON.stringify(body) + method: "POST", + headers: AUTHORIZATION_HEADERS(), + body: JSON.stringify({ id, type }) }) - .then(resp => resp.json()) - .catch(error => { console.error(`api error requesting: ${id}, type: ${type}`); throw error }) -} + .then(resp => resp.json()) + .catch(error => { + console.error(`api error requesting: ${id}, type: ${type}`); + throw error; + }); +}; /** * Check request status by tmdb id and type @@ -235,186 +253,182 @@ const request = (id, type, authorization_token=undefined) => { * @param {string} type * @returns {object} Success/Failure response */ -const getRequestStatus = (id, type, authorization_token=undefined) => { - const url = new URL('v2/request', SEASONED_URL) - url.pathname = path.join(url.pathname, id.toString()) - url.searchParams.append('type', type) +const getRequestStatus = (id, type, authorization_token = undefined) => { + const url = new URL("v2/request", SEASONED_URL); + url.pathname = path.join(url.pathname, id.toString()); + url.searchParams.append("type", type); - return fetch(url.href) + return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }) .then(resp => { const status = resp.status; - if (status === 200) { return true } - else if (status === 404) { return false } - else { - console.error(`api error getting request status for id ${id} and type ${type}`) + if (status === 200) { + return true; + } else if (status === 404) { + return false; + } else { + console.error( + `api error getting request status for id ${id} and type ${type}` + ); } }) - .catch(err => Promise.reject(err)) -} + .catch(err => Promise.reject(err)); +}; -const watchLink = (title, year, authorization_token=undefined) => { - const url = new URL('v1/plex/watch-link', SEASONED_URL) - url.searchParams.append('title', title) - url.searchParams.append('year', year) +const watchLink = (title, year) => { + const url = new URL("v1/plex/watch-link", SEASONED_URL); + url.searchParams.append("title", title); + url.searchParams.append("year", year); + + return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }) + .then(resp => resp.json()) + .then(response => response.link); +}; + +const movieImages = id => { + const url = new URL(`v2/movie/${id}/images`, SEASONED_URL); const headers = { - 'Authorization': authorization_token, - 'Content-Type': 'application/json' - } + "Content-Type": "application/json" + }; - return fetch(url.href, { headers }) - .then(resp => resp.json()) - .then(response => response.link) -} + return fetch(url.href, { headers }).then(resp => resp.json()); +}; // - - - Seasoned user endpoints - - - const register = (username, password) => { - const url = new URL('v1/user', SEASONED_URL) + const url = new URL("v1/user", SEASONED_URL); const options = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: AUTHORIZATION_HEADERS(), body: JSON.stringify({ username, password }) - } + }; return fetch(url.href, options) .then(resp => resp.json()) .catch(error => { - console.error('Unexpected error occured before receiving response. Error:', error) + console.error( + "Unexpected error occured before receiving response. Error:", + error + ); // TODO log to sentry the issue here - throw error - }) -} + throw error; + }); +}; -const login = (username, password, throwError=false) => { - const url = new URL('v1/user/login', SEASONED_URL) +const login = (username, password, throwError = false) => { + const url = new URL("v1/user/login", SEASONED_URL); const options = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }) - } + }; - return fetch(url.href, options) - .then(resp => { - if (resp.status == 200) - return resp.json(); + return fetch(url.href, options).then(resp => { + if (resp.status == 200) return resp.json(); - if (throwError) - throw resp; - else - console.error("Error occured when trying to sign in.\nError:", resp); - }) -} + if (throwError) throw resp; + else console.error("Error occured when trying to sign in.\nError:", resp); + }); +}; const getSettings = () => { - const settingsExists = (value) => { - if (value instanceof Object && value.hasOwnProperty('settings')) + const settingsExists = value => { + if (value instanceof Object && value.hasOwnProperty("settings")) return value; throw "Settings does not exist in response object."; - } - const commitSettingsToStore = (response) => { - store.dispatch('userModule/setSettings', response.settings) - return response - } + }; + const commitSettingsToStore = response => { + store.dispatch("user/setSettings", response.settings); + return response; + }; - const url = new URL('v1/user/settings', SEASONED_URL) + const url = new URL("v1/user/settings", SEASONED_URL); - const authorization_token = localStorage.getItem('token') - const headers = authorization_token ? { - 'Authorization': authorization_token, - 'Content-Type': 'application/json' - } : {} - - return fetch(url.href, { headers }) + return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }) .then(resp => resp.json()) .then(settingsExists) .then(commitSettingsToStore) .then(response => response.settings) - .catch(error => { console.log('api error getting user settings'); throw error }) -} - -const updateSettings = (settings) => { - const url = new URL('v1/user/settings', SEASONED_URL) - - const authorization_token = localStorage.getItem('token') - const headers = authorization_token ? { - 'Authorization': authorization_token, - 'Content-Type': 'application/json' - } : {} + .catch(error => { + console.log("api error getting user settings"); + throw error; + }); +}; +const updateSettings = settings => { + const url = new URL("v1/user/settings", SEASONED_URL); return fetch(url.href, { - method: 'PUT', - headers, - body: JSON.stringify(settings) - }) + method: "PUT", + headers: AUTHORIZATION_HEADERS(), + body: JSON.stringify(settings) + }) .then(resp => resp.json()) - .catch(error => { console.log('api error updating user settings'); throw error }) -} + .catch(error => { + console.log("api error updating user settings"); + throw error; + }); +}; // - - - Authenticate with plex - - - const linkPlexAccount = (username, password) => { - const url = new URL('v1/user/link_plex', SEASONED_URL) - const body = { username, password } - const headers = { - 'Content-Type': 'application/json', - authorization: storage.token - } + const url = new URL("v1/user/link_plex", SEASONED_URL); + const body = { username, password }; return fetch(url.href, { - method: 'POST', - headers, + method: "POST", + headers: AUTHORIZATION_HEADERS(), body: JSON.stringify(body) }) - .then(resp => resp.json()) - .catch(error => { console.error(`api error linking plex account: ${username}`); throw error }) -} + .then(resp => resp.json()) + .catch(error => { + console.error(`api error linking plex account: ${username}`); + throw error; + }); +}; const unlinkPlexAccount = (username, password) => { - const url = new URL('v1/user/unlink_plex', SEASONED_URL) - const headers = { - 'Content-Type': 'application/json', - authorization: storage.token - } + const url = new URL("v1/user/unlink_plex", SEASONED_URL); return fetch(url.href, { - method: 'POST', - headers + method: "POST", + headers: AUTHORIZATION_HEADERS() }) - .then(resp => resp.json()) - .catch(error => { console.error(`api error unlinking plex account: ${username}`); throw error }) -} - + .then(resp => resp.json()) + .catch(error => { + console.error(`api error unlinking plex account: ${username}`); + throw error; + }); +}; // - - - User graphs - - - const fetchChart = (urlPath, days, chartType) => { - const url = new URL('v1/user' + urlPath, SEASONED_URL) - url.searchParams.append('days', days) - url.searchParams.append('y_axis', chartType) + const url = new URL("v1/user" + urlPath, SEASONED_URL); + url.searchParams.append("days", days); + url.searchParams.append("y_axis", chartType); - const authorization_token = localStorage.getItem('token') - const headers = authorization_token ? { - 'Authorization': authorization_token, - 'Content-Type': 'application/json' - } : {} - - return fetch(url.href, { headers }) + return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }) .then(resp => resp.json()) - .catch(error => { console.log('api error fetching chart'); throw error }) -} - + .catch(error => { + console.log("api error fetching chart"); + throw error; + }); +}; // - - - Random emoji - - - const getEmoji = () => { - const url = new URL('v1/emoji', SEASONED_URL) + const url = new URL("v1/emoji", SEASONED_URL); return fetch(url.href) .then(resp => resp.json()) - .catch(error => { console.log('api error getting emoji'); throw error }) -} - + .catch(error => { + console.log("api error getting emoji"); + throw error; + }); +}; // - - - ELASTIC SEARCH - - - // This elastic index contains titles mapped to ids. Lightning search @@ -426,44 +440,41 @@ const getEmoji = () => { * @param {string} query * @returns {object} List of movies and shows matching query */ -const elasticSearchMoviesAndShows = (query) => { - const url = new URL(path.join(ELASTIC_INDEX, '/_search'), ELASTIC_URL) - const headers = { - 'Content-Type': 'application/json' - } +const elasticSearchMoviesAndShows = query => { + const url = new URL(path.join(ELASTIC_INDEX, "/_search"), ELASTIC_URL); const body = { - "sort" : [ - { "popularity" : {"order" : "desc"}}, - "_score" - ], - "query": { - "bool": { - "should": [{ - "match_phrase_prefix": { - "original_name": query + sort: [{ popularity: { order: "desc" } }, "_score"], + query: { + bool: { + should: [ + { + match_phrase_prefix: { + original_name: query + } + }, + { + match_phrase_prefix: { + original_title: query + } } - }, - { - "match_phrase_prefix": { - "original_title": query - } - }] + ] } }, - "size": 6 - } + size: 22 + }; return fetch(url.href, { - method: 'POST', - headers: headers, + method: "POST", + headers: AUTHORIZATION_HEADERS(), body: JSON.stringify(body) }) .then(resp => resp.json()) - .catch(error => { console.log(`api error searching elasticsearch: ${query}`); throw error }) -} - - + .catch(error => { + console.log(`api error searching elasticsearch: ${query}`); + throw error; + }); +}; export { getMovie, @@ -477,6 +488,7 @@ export { addMagnet, request, watchLink, + movieImages, getRequestStatus, linkPlexAccount, unlinkPlexAccount, @@ -487,4 +499,4 @@ export { fetchChart, getEmoji, elasticSearchMoviesAndShows -} +}; diff --git a/src/components/404.vue b/src/components/404.vue index 14cd231..68eca47 100644 --- a/src/components/404.vue +++ b/src/components/404.vue @@ -3,26 +3,31 @@

Page Not Found

- go back to previous page + go back to previous page diff --git a/src/components/Signin.vue b/src/components/Signin.vue index 8baea18..1d056d9 100644 --- a/src/components/Signin.vue +++ b/src/components/Signin.vue @@ -2,88 +2,94 @@

Sign in

- - + + sign in - Don't have a user? Register here + Don't have a user? Register here
- -