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.
This commit is contained in:
55
src/App.vue
55
src/App.vue
@@ -46,18 +46,13 @@
|
|||||||
<router-view class="content" :key="$route.fullPath"></router-view>
|
<router-view class="content" :key="$route.fullPath"></router-view>
|
||||||
|
|
||||||
<!-- Movie popup that will show above existing rendered content -->
|
<!-- Movie popup that will show above existing rendered content -->
|
||||||
<movie-popup
|
<movie-popup></movie-popup>
|
||||||
v-if="moviePopupIsVisible"
|
|
||||||
:id="popupID"
|
|
||||||
:type="popupType"
|
|
||||||
></movie-popup>
|
|
||||||
|
|
||||||
<darkmode-toggle />
|
<darkmode-toggle />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from "vue";
|
|
||||||
import NavigationHeader from "@/components/NavigationHeader";
|
import NavigationHeader from "@/components/NavigationHeader";
|
||||||
import NavigationIcons from "@/components/NavigationIcons";
|
import NavigationIcons from "@/components/NavigationIcons";
|
||||||
import MoviePopup from "@/components/MoviePopup";
|
import MoviePopup from "@/components/MoviePopup";
|
||||||
@@ -70,37 +65,6 @@ export default {
|
|||||||
NavigationIcons,
|
NavigationIcons,
|
||||||
MoviePopup,
|
MoviePopup,
|
||||||
DarkmodeToggle
|
DarkmodeToggle
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
query: "",
|
|
||||||
moviePopupIsVisible: false,
|
|
||||||
popupID: 0,
|
|
||||||
popupType: "movie"
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
let that = this;
|
|
||||||
Vue.prototype.$popup = {
|
|
||||||
get isOpen() {
|
|
||||||
return that.moviePopupIsVisible;
|
|
||||||
},
|
|
||||||
open: (id, type) => {
|
|
||||||
this.popupID = id || this.popupID;
|
|
||||||
this.popupType = type || this.popupType;
|
|
||||||
this.moviePopupIsVisible = true;
|
|
||||||
console.log("opened");
|
|
||||||
},
|
|
||||||
close: () => {
|
|
||||||
this.moviePopupIsVisible = false;
|
|
||||||
console.log("closed");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const movieId = new URLSearchParams(window.location.search).get("movie");
|
|
||||||
if (movieId) {
|
|
||||||
this.$popup.open(movieId, "movie");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -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;
|
transition: background-color 0.5s ease, color 0.5s ease,
|
||||||
|
border-color 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
@@ -158,20 +123,6 @@ img {
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
position: relative;
|
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
|
// router view transition
|
||||||
.fade-enter-active,
|
.fade-enter-active,
|
||||||
|
|||||||
532
src/api.js
532
src/api.js
@@ -1,24 +1,34 @@
|
|||||||
import axios from 'axios'
|
import axios from "axios";
|
||||||
import storage from '@/storage'
|
import storage from "@/storage";
|
||||||
import config from '@/config.json'
|
import config from "@/config.json";
|
||||||
import path from 'path'
|
import path from "path";
|
||||||
import store from '@/store'
|
import store from "@/store";
|
||||||
|
|
||||||
const SEASONED_URL = config.SEASONED_URL
|
const token = () => store.getters["user/token"];
|
||||||
const ELASTIC_URL = config.ELASTIC_URL
|
const plexId = () => store.getters["user/plexId"];
|
||||||
const ELASTIC_INDEX = config.ELASTIC_INDEX
|
|
||||||
|
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
|
// TODO
|
||||||
// - Move autorization token and errors here?
|
// - Move autorization token and errors here?
|
||||||
|
|
||||||
const checkStatusAndReturnJson = (response) => {
|
const checkStatusAndReturnJson = response => {
|
||||||
if (!response.ok) {
|
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.
|
* 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
|
* @param {boolean} [credits=false] Include credits
|
||||||
* @returns {object} Tmdb response
|
* @returns {object} Tmdb response
|
||||||
*/
|
*/
|
||||||
const getMovie = (id, checkExistance=false, credits=false, release_dates=false) => {
|
const getMovie = (
|
||||||
const url = new URL('v2/movie', SEASONED_URL)
|
id,
|
||||||
url.pathname = path.join(url.pathname, id.toString())
|
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) {
|
if (checkExistance) {
|
||||||
url.searchParams.append('check_existance', true)
|
url.searchParams.append("check_existance", true);
|
||||||
}
|
}
|
||||||
if (credits) {
|
if (credits) {
|
||||||
url.searchParams.append('credits', true)
|
url.searchParams.append("credits", true);
|
||||||
}
|
}
|
||||||
if(release_dates) {
|
if (release_dates) {
|
||||||
url.searchParams.append('release_dates', true)
|
url.searchParams.append("release_dates", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(url.href)
|
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
|
||||||
.then(resp => resp.json())
|
.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.
|
* 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
|
* @param {boolean} [credits=false] Include credits
|
||||||
* @returns {object} Tmdb response
|
* @returns {object} Tmdb response
|
||||||
*/
|
*/
|
||||||
const getShow = (id, checkExistance=false, credits=false) => {
|
const getShow = (id, checkExistance = false, credits = false) => {
|
||||||
const url = new URL('v2/show', SEASONED_URL)
|
const url = new URL("v2/show", SEASONED_URL);
|
||||||
url.pathname = path.join(url.pathname, id.toString())
|
url.pathname = path.join(url.pathname, id.toString());
|
||||||
if (checkExistance) {
|
if (checkExistance) {
|
||||||
url.searchParams.append('check_existance', true)
|
url.searchParams.append("check_existance", true);
|
||||||
}
|
}
|
||||||
if (credits) {
|
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())
|
.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.
|
* 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
|
* @param {boolean} [credits=false] Include credits
|
||||||
* @returns {object} Tmdb response
|
* @returns {object} Tmdb response
|
||||||
*/
|
*/
|
||||||
const getPerson = (id, credits=false) => {
|
const getPerson = (id, credits = false) => {
|
||||||
const url = new URL('v2/person', SEASONED_URL)
|
const url = new URL("v2/person", SEASONED_URL);
|
||||||
url.pathname = path.join(url.pathname, id.toString())
|
url.pathname = path.join(url.pathname, id.toString());
|
||||||
if (credits) {
|
if (credits) {
|
||||||
url.searchParams.append('credits', true)
|
url.searchParams.append("credits", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(url.href)
|
return fetch(url.href)
|
||||||
.then(resp => resp.json())
|
.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.
|
* Fetches tmdb list by name.
|
||||||
@@ -89,41 +113,39 @@ const getPerson = (id, credits=false) => {
|
|||||||
* @param {number} [page=1]
|
* @param {number} [page=1]
|
||||||
* @returns {object} Tmdb list response
|
* @returns {object} Tmdb list response
|
||||||
*/
|
*/
|
||||||
const getTmdbMovieListByName = (name, page=1) => {
|
const getTmdbMovieListByName = (name, page = 1) => {
|
||||||
const url = new URL('v2/movie/' + name, SEASONED_URL)
|
const url = new URL("v2/movie/" + name, SEASONED_URL);
|
||||||
url.searchParams.append('page', page)
|
url.searchParams.append("page", page);
|
||||||
const headers = { authorization: storage.token }
|
|
||||||
|
|
||||||
return fetch(url.href, { headers: headers })
|
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }).then(resp =>
|
||||||
.then(resp => resp.json())
|
resp.json()
|
||||||
// .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error })
|
);
|
||||||
}
|
// .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error })
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches requested items.
|
* Fetches requested items.
|
||||||
* @param {number} [page=1]
|
* @param {number} [page=1]
|
||||||
* @returns {object} Request response
|
* @returns {object} Request response
|
||||||
*/
|
*/
|
||||||
const getRequests = (page=1) => {
|
const getRequests = (page = 1) => {
|
||||||
const url = new URL('v2/request', SEASONED_URL)
|
const url = new URL("v2/request", SEASONED_URL);
|
||||||
url.searchParams.append('page', page)
|
url.searchParams.append("page", page);
|
||||||
const headers = { authorization: storage.token }
|
|
||||||
|
|
||||||
return fetch(url.href, { headers: headers })
|
return fetch(url.href, {
|
||||||
.then(resp => resp.json())
|
headers: AUTHORIZATION_HEADERS()
|
||||||
// .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error })
|
}).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) => {
|
return fetch(url.href, {
|
||||||
const url = new URL('v1/user/requests', SEASONED_URL)
|
headers: AUTHORIZATION_HEADERS()
|
||||||
url.searchParams.append('page', page)
|
}).then(resp => resp.json());
|
||||||
|
};
|
||||||
const headers = { authorization: localStorage.getItem('token') }
|
|
||||||
|
|
||||||
return fetch(url.href, { headers })
|
|
||||||
.then(resp => resp.json())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches tmdb movies and shows by query.
|
* Fetches tmdb movies and shows by query.
|
||||||
@@ -131,24 +153,27 @@ const getUserRequests = (page=1) => {
|
|||||||
* @param {number} [page=1]
|
* @param {number} [page=1]
|
||||||
* @returns {object} Tmdb response
|
* @returns {object} Tmdb response
|
||||||
*/
|
*/
|
||||||
const searchTmdb = (query, page=1, adult=false, mediaType=null) => {
|
const searchTmdb = (query, page = 1, adult = false, mediaType = null) => {
|
||||||
const url = new URL('v2/search', SEASONED_URL)
|
const url = new URL("v2/search", SEASONED_URL);
|
||||||
if (mediaType != null && ['movie', 'show', 'person'].includes(mediaType)) {
|
if (mediaType != null && ["movie", "show", "person"].includes(mediaType)) {
|
||||||
url.pathname += `/${mediaType}`
|
url.pathname += `/${mediaType}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
url.searchParams.append('query', query)
|
url.searchParams.append("query", query);
|
||||||
url.searchParams.append('page', page)
|
url.searchParams.append("page", page);
|
||||||
url.searchParams.append('adult', adult)
|
url.searchParams.append("adult", adult);
|
||||||
|
|
||||||
const headers = { authorization: localStorage.getItem('token') }
|
return fetch(url.href, {
|
||||||
|
headers: AUTHORIZATION_HEADERS()
|
||||||
return fetch(url.href, { headers })
|
})
|
||||||
.then(resp => resp.json())
|
.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
|
* Search for torrents by query
|
||||||
@@ -157,15 +182,18 @@ const searchTmdb = (query, page=1, adult=false, mediaType=null) => {
|
|||||||
* @returns {object} Torrent response
|
* @returns {object} Torrent response
|
||||||
*/
|
*/
|
||||||
const searchTorrents = (query, authorization_token) => {
|
const searchTorrents = (query, authorization_token) => {
|
||||||
const url = new URL('/api/v1/pirate/search', SEASONED_URL)
|
const url = new URL("/api/v1/pirate/search", SEASONED_URL);
|
||||||
url.searchParams.append('query', query)
|
url.searchParams.append("query", query);
|
||||||
|
|
||||||
const headers = { authorization: storage.token }
|
return fetch(url.href, {
|
||||||
|
headers: AUTHORIZATION_HEADERS()
|
||||||
return fetch(url.href, { headers: headers })
|
})
|
||||||
.then(resp => resp.json())
|
.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.
|
* Add magnet to download queue.
|
||||||
@@ -175,28 +203,27 @@ const searchTorrents = (query, authorization_token) => {
|
|||||||
* @returns {object} Success/Failure response
|
* @returns {object} Success/Failure response
|
||||||
*/
|
*/
|
||||||
const addMagnet = (magnet, name, tmdb_id) => {
|
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({
|
const body = JSON.stringify({
|
||||||
magnet: magnet,
|
magnet: magnet,
|
||||||
name: name,
|
name: name,
|
||||||
tmdb_id: tmdb_id
|
tmdb_id: tmdb_id
|
||||||
})
|
});
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
authorization: storage.token
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url.href, {
|
return fetch(url.href, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers,
|
headers: AUTHORIZATION_HEADERS(),
|
||||||
body
|
body
|
||||||
})
|
})
|
||||||
.then(resp => resp.json())
|
.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
|
* 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
|
* @param {string} [authorization_token] To identify the requesting user
|
||||||
* @returns {object} Success/Failure response
|
* @returns {object} Success/Failure response
|
||||||
*/
|
*/
|
||||||
const request = (id, type, authorization_token=undefined) => {
|
const request = (id, type) => {
|
||||||
const url = new URL('v2/request', SEASONED_URL)
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url.href, {
|
return fetch(url.href, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: headers,
|
headers: AUTHORIZATION_HEADERS(),
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify({ id, type })
|
||||||
})
|
})
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.catch(error => { console.error(`api error requesting: ${id}, type: ${type}`); throw error })
|
.catch(error => {
|
||||||
}
|
console.error(`api error requesting: ${id}, type: ${type}`);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check request status by tmdb id and type
|
* Check request status by tmdb id and type
|
||||||
@@ -235,186 +253,182 @@ const request = (id, type, authorization_token=undefined) => {
|
|||||||
* @param {string} type
|
* @param {string} type
|
||||||
* @returns {object} Success/Failure response
|
* @returns {object} Success/Failure response
|
||||||
*/
|
*/
|
||||||
const getRequestStatus = (id, type, authorization_token=undefined) => {
|
const getRequestStatus = (id, type, authorization_token = undefined) => {
|
||||||
const url = new URL('v2/request', SEASONED_URL)
|
const url = new URL("v2/request", SEASONED_URL);
|
||||||
url.pathname = path.join(url.pathname, id.toString())
|
url.pathname = path.join(url.pathname, id.toString());
|
||||||
url.searchParams.append('type', type)
|
url.searchParams.append("type", type);
|
||||||
|
|
||||||
return fetch(url.href)
|
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
const status = resp.status;
|
const status = resp.status;
|
||||||
if (status === 200) { return true }
|
if (status === 200) {
|
||||||
else if (status === 404) { return false }
|
return true;
|
||||||
else {
|
} else if (status === 404) {
|
||||||
console.error(`api error getting request status for id ${id} and type ${type}`)
|
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 watchLink = (title, year) => {
|
||||||
const url = new URL('v1/plex/watch-link', SEASONED_URL)
|
const url = new URL("v1/plex/watch-link", SEASONED_URL);
|
||||||
url.searchParams.append('title', title)
|
url.searchParams.append("title", title);
|
||||||
url.searchParams.append('year', year)
|
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 = {
|
const headers = {
|
||||||
'Authorization': authorization_token,
|
"Content-Type": "application/json"
|
||||||
'Content-Type': 'application/json'
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url.href, { headers })
|
return fetch(url.href, { headers }).then(resp => resp.json());
|
||||||
.then(resp => resp.json())
|
};
|
||||||
.then(response => response.link)
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - Seasoned user endpoints - - -
|
// - - - Seasoned user endpoints - - -
|
||||||
|
|
||||||
const register = (username, password) => {
|
const register = (username, password) => {
|
||||||
const url = new URL('v1/user', SEASONED_URL)
|
const url = new URL("v1/user", SEASONED_URL);
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: AUTHORIZATION_HEADERS(),
|
||||||
body: JSON.stringify({ username, password })
|
body: JSON.stringify({ username, password })
|
||||||
}
|
};
|
||||||
|
|
||||||
return fetch(url.href, options)
|
return fetch(url.href, options)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.catch(error => {
|
.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
|
// TODO log to sentry the issue here
|
||||||
throw error
|
throw error;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const login = (username, password, throwError=false) => {
|
const login = (username, password, throwError = false) => {
|
||||||
const url = new URL('v1/user/login', SEASONED_URL)
|
const url = new URL("v1/user/login", SEASONED_URL);
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username, password })
|
body: JSON.stringify({ username, password })
|
||||||
}
|
};
|
||||||
|
|
||||||
return fetch(url.href, options)
|
return fetch(url.href, options).then(resp => {
|
||||||
.then(resp => {
|
if (resp.status == 200) return resp.json();
|
||||||
if (resp.status == 200)
|
|
||||||
return resp.json();
|
|
||||||
|
|
||||||
if (throwError)
|
if (throwError) throw resp;
|
||||||
throw resp;
|
else console.error("Error occured when trying to sign in.\nError:", resp);
|
||||||
else
|
});
|
||||||
console.error("Error occured when trying to sign in.\nError:", resp);
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSettings = () => {
|
const getSettings = () => {
|
||||||
const settingsExists = (value) => {
|
const settingsExists = value => {
|
||||||
if (value instanceof Object && value.hasOwnProperty('settings'))
|
if (value instanceof Object && value.hasOwnProperty("settings"))
|
||||||
return value;
|
return value;
|
||||||
throw "Settings does not exist in response object.";
|
throw "Settings does not exist in response object.";
|
||||||
}
|
};
|
||||||
const commitSettingsToStore = (response) => {
|
const commitSettingsToStore = response => {
|
||||||
store.dispatch('userModule/setSettings', response.settings)
|
store.dispatch("user/setSettings", response.settings);
|
||||||
return response
|
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')
|
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
|
||||||
const headers = authorization_token ? {
|
|
||||||
'Authorization': authorization_token,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
} : {}
|
|
||||||
|
|
||||||
return fetch(url.href, { headers })
|
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then(settingsExists)
|
.then(settingsExists)
|
||||||
.then(commitSettingsToStore)
|
.then(commitSettingsToStore)
|
||||||
.then(response => response.settings)
|
.then(response => response.settings)
|
||||||
.catch(error => { console.log('api error getting user settings'); throw error })
|
.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'
|
|
||||||
} : {}
|
|
||||||
|
|
||||||
|
const updateSettings = settings => {
|
||||||
|
const url = new URL("v1/user/settings", SEASONED_URL);
|
||||||
return fetch(url.href, {
|
return fetch(url.href, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers,
|
headers: AUTHORIZATION_HEADERS(),
|
||||||
body: JSON.stringify(settings)
|
body: JSON.stringify(settings)
|
||||||
})
|
})
|
||||||
.then(resp => resp.json())
|
.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 - - -
|
// - - - Authenticate with plex - - -
|
||||||
|
|
||||||
const linkPlexAccount = (username, password) => {
|
const linkPlexAccount = (username, password) => {
|
||||||
const url = new URL('v1/user/link_plex', SEASONED_URL)
|
const url = new URL("v1/user/link_plex", SEASONED_URL);
|
||||||
const body = { username, password }
|
const body = { username, password };
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
authorization: storage.token
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url.href, {
|
return fetch(url.href, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers,
|
headers: AUTHORIZATION_HEADERS(),
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
})
|
})
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.catch(error => { console.error(`api error linking plex account: ${username}`); throw error })
|
.catch(error => {
|
||||||
}
|
console.error(`api error linking plex account: ${username}`);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const unlinkPlexAccount = (username, password) => {
|
const unlinkPlexAccount = (username, password) => {
|
||||||
const url = new URL('v1/user/unlink_plex', SEASONED_URL)
|
const url = new URL("v1/user/unlink_plex", SEASONED_URL);
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
authorization: storage.token
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(url.href, {
|
return fetch(url.href, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers
|
headers: AUTHORIZATION_HEADERS()
|
||||||
})
|
})
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.catch(error => { console.error(`api error unlinking plex account: ${username}`); throw error })
|
.catch(error => {
|
||||||
}
|
console.error(`api error unlinking plex account: ${username}`);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// - - - User graphs - - -
|
// - - - User graphs - - -
|
||||||
|
|
||||||
const fetchChart = (urlPath, days, chartType) => {
|
const fetchChart = (urlPath, days, chartType) => {
|
||||||
const url = new URL('v1/user' + urlPath, SEASONED_URL)
|
const url = new URL("v1/user" + urlPath, SEASONED_URL);
|
||||||
url.searchParams.append('days', days)
|
url.searchParams.append("days", days);
|
||||||
url.searchParams.append('y_axis', chartType)
|
url.searchParams.append("y_axis", chartType);
|
||||||
|
|
||||||
const authorization_token = localStorage.getItem('token')
|
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
|
||||||
const headers = authorization_token ? {
|
|
||||||
'Authorization': authorization_token,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
} : {}
|
|
||||||
|
|
||||||
return fetch(url.href, { headers })
|
|
||||||
.then(resp => resp.json())
|
.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 - - -
|
// - - - Random emoji - - -
|
||||||
|
|
||||||
const getEmoji = () => {
|
const getEmoji = () => {
|
||||||
const url = new URL('v1/emoji', SEASONED_URL)
|
const url = new URL("v1/emoji", SEASONED_URL);
|
||||||
|
|
||||||
return fetch(url.href)
|
return fetch(url.href)
|
||||||
.then(resp => resp.json())
|
.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 - - -
|
// - - - ELASTIC SEARCH - - -
|
||||||
// This elastic index contains titles mapped to ids. Lightning search
|
// This elastic index contains titles mapped to ids. Lightning search
|
||||||
@@ -426,44 +440,41 @@ const getEmoji = () => {
|
|||||||
* @param {string} query
|
* @param {string} query
|
||||||
* @returns {object} List of movies and shows matching query
|
* @returns {object} List of movies and shows matching query
|
||||||
*/
|
*/
|
||||||
const elasticSearchMoviesAndShows = (query) => {
|
const elasticSearchMoviesAndShows = query => {
|
||||||
const url = new URL(path.join(ELASTIC_INDEX, '/_search'), ELASTIC_URL)
|
const url = new URL(path.join(ELASTIC_INDEX, "/_search"), ELASTIC_URL);
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
"sort" : [
|
sort: [{ popularity: { order: "desc" } }, "_score"],
|
||||||
{ "popularity" : {"order" : "desc"}},
|
query: {
|
||||||
"_score"
|
bool: {
|
||||||
],
|
should: [
|
||||||
"query": {
|
{
|
||||||
"bool": {
|
match_phrase_prefix: {
|
||||||
"should": [{
|
original_name: query
|
||||||
"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, {
|
return fetch(url.href, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: headers,
|
headers: AUTHORIZATION_HEADERS(),
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
})
|
})
|
||||||
.then(resp => resp.json())
|
.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 {
|
export {
|
||||||
getMovie,
|
getMovie,
|
||||||
@@ -477,6 +488,7 @@ export {
|
|||||||
addMagnet,
|
addMagnet,
|
||||||
request,
|
request,
|
||||||
watchLink,
|
watchLink,
|
||||||
|
movieImages,
|
||||||
getRequestStatus,
|
getRequestStatus,
|
||||||
linkPlexAccount,
|
linkPlexAccount,
|
||||||
unlinkPlexAccount,
|
unlinkPlexAccount,
|
||||||
@@ -487,4 +499,4 @@ export {
|
|||||||
fetchChart,
|
fetchChart,
|
||||||
getEmoji,
|
getEmoji,
|
||||||
elasticSearchMoviesAndShows
|
elasticSearchMoviesAndShows
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -3,26 +3,31 @@
|
|||||||
<section class="not-found">
|
<section class="not-found">
|
||||||
<h1 class="not-found__title">Page Not Found</h1>
|
<h1 class="not-found__title">Page Not Found</h1>
|
||||||
</section>
|
</section>
|
||||||
<seasoned-button class="button" @click="goBack">go back to previous page</seasoned-button>
|
<seasoned-button class="button" @click="goBack"
|
||||||
|
>go back to previous page</seasoned-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import store from '@/store'
|
import { mapActions, mapGetters } from "vuex";
|
||||||
import SeasonedButton from '@/components/ui/SeasonedButton'
|
import SeasonedButton from "@/components/ui/SeasonedButton";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { SeasonedButton },
|
components: { SeasonedButton },
|
||||||
|
computed: {
|
||||||
|
...mapGetters("popup", ["isOpen"])
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions("popup", ["close"]),
|
||||||
goBack() {
|
goBack() {
|
||||||
this.$router.go(-1)
|
this.$router.go(-1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.$popup.isOpen == true)
|
if (this.isOpen) this.close();
|
||||||
this.$popup.close()
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -47,7 +52,7 @@ export default {
|
|||||||
.not-found {
|
.not-found {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - var(--header-size));
|
height: calc(100vh - var(--header-size));
|
||||||
background: url('~assets/pulp-fiction.jpg') no-repeat 50% 50%;
|
background: url("~assets/pulp-fiction.jpg") no-repeat 50% 50%;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,64 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="movie-popup" @click="$popup.close()">
|
<div v-if="isOpen" class="movie-popup" @click="close">
|
||||||
<div class="movie-popup__box" @click.stop>
|
<div class="movie-popup__box" @click.stop>
|
||||||
<movie :id="id" :type="type"></movie>
|
<movie :id="id" :type="type"></movie>
|
||||||
<button class="movie-popup__close" @click="$popup.close()"></button>
|
<button class="movie-popup__close" @click="close"></button>
|
||||||
</div>
|
</div>
|
||||||
<i class="loader"></i>
|
<i class="loader"></i>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions, mapGetters } from "vuex";
|
||||||
import Movie from "./Movie";
|
import Movie from "./Movie";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
components: { Movie },
|
||||||
id: {
|
computed: {
|
||||||
type: Number,
|
...mapGetters("popup", ["isOpen", "id", "type"])
|
||||||
required: true
|
},
|
||||||
},
|
watch: {
|
||||||
type: {
|
isOpen(value) {
|
||||||
type: String,
|
value
|
||||||
required: true
|
? document.getElementsByTagName("body")[0].classList.add("no-scroll")
|
||||||
|
: document
|
||||||
|
.getElementsByTagName("body")[0]
|
||||||
|
.classList.remove("no-scroll");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Movie },
|
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions("popup", ["close", "open"]),
|
||||||
checkEventForEscapeKey(event) {
|
checkEventForEscapeKey(event) {
|
||||||
if (event.keyCode == 27) {
|
if (event.keyCode == 27) this.close();
|
||||||
this.$popup.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateQueryParams(id = false) {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
if (params.has("movie")) {
|
|
||||||
params.delete("movie");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id) {
|
|
||||||
params.append("movie", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.history.replaceState(
|
|
||||||
{},
|
|
||||||
"search",
|
|
||||||
`${window.location.protocol}//${window.location.hostname}${
|
|
||||||
window.location.port ? `:${window.location.port}` : ""
|
|
||||||
}${window.location.pathname}${
|
|
||||||
params.toString().length ? `?${params}` : ""
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.updateQueryParams(this.id);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
let id = null;
|
||||||
|
let type = null;
|
||||||
|
|
||||||
|
if (params.has("movie")) {
|
||||||
|
id = Number(params.get("movie"));
|
||||||
|
type = "movie";
|
||||||
|
} else if (params.has("show")) {
|
||||||
|
id = Number(params.get("show"));
|
||||||
|
type = "show";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id && type) {
|
||||||
|
this.open({ id, type });
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("keyup", this.checkEventForEscapeKey);
|
window.addEventListener("keyup", this.checkEventForEscapeKey);
|
||||||
document.getElementsByTagName("body")[0].classList.add("no-scroll");
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.updateQueryParams();
|
|
||||||
window.removeEventListener("keyup", this.checkEventForEscapeKey);
|
window.removeEventListener("keyup", this.checkEventForEscapeKey);
|
||||||
document.getElementsByTagName("body")[0].classList.remove("no-scroll");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<img
|
<img
|
||||||
class="movie-item__img"
|
class="movie-item__img"
|
||||||
ref="poster-image"
|
ref="poster-image"
|
||||||
@click="openMoviePopup(movie.id, movie.type)"
|
@click="openMoviePopup"
|
||||||
:alt="posterAltText"
|
:alt="posterAltText"
|
||||||
:data-src="poster"
|
:data-src="poster"
|
||||||
src="~assets/placeholder.png"
|
src="~assets/placeholder.png"
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions } from "vuex";
|
||||||
import img from "../directives/v-image";
|
import img from "../directives/v-image";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -101,8 +102,12 @@ export default {
|
|||||||
imageObserver.observe(poster);
|
imageObserver.observe(poster);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openMoviePopup(id, type) {
|
...mapActions("popup", ["open"]),
|
||||||
this.$popup.open(id, type);
|
openMoviePopup() {
|
||||||
|
this.open({
|
||||||
|
id: this.movie.id,
|
||||||
|
type: this.movie.type
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="profile">
|
<section class="profile">
|
||||||
<div class="profile__content" v-if="userLoggedIn">
|
<div class="profile__content" v-if="loggedIn">
|
||||||
<header class="profile__header">
|
<header class="profile__header">
|
||||||
<h2 class="profile__title">{{ emoji }} Welcome {{ username }}</h2>
|
<h2 class="profile__title">{{ emoji }} Welcome {{ username }}</h2>
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
showActivity ? "hide activity" : "show activity"
|
showActivity ? "hide activity" : "show activity"
|
||||||
}}</seasoned-button>
|
}}</seasoned-button>
|
||||||
|
|
||||||
<seasoned-button @click="logOut">Log out</seasoned-button>
|
<seasoned-button @click="_logout">Log out</seasoned-button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<results-list v-if="results" :results="results" />
|
<results-list v-if="results" :results="results" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="not-found" v-if="!userLoggedIn">
|
<section class="not-found" v-if="!loggedIn">
|
||||||
<div class="not-found__content">
|
<div class="not-found__content">
|
||||||
<h2 class="not-found__title">Authentication Request Failed</h2>
|
<h2 class="not-found__title">Authentication Request Failed</h2>
|
||||||
<router-link :to="{ name: 'signin' }" exact title="Sign in here">
|
<router-link :to="{ name: 'signin' }" exact title="Sign in here">
|
||||||
@@ -36,21 +36,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import storage from "@/storage";
|
import { mapGetters, mapActions } from "vuex";
|
||||||
import store from "@/store";
|
|
||||||
import ListHeader from "@/components/ListHeader";
|
import ListHeader from "@/components/ListHeader";
|
||||||
import ResultsList from "@/components/ResultsList";
|
import ResultsList from "@/components/ResultsList";
|
||||||
import Settings from "@/components/Settings";
|
import Settings from "@/components/Settings";
|
||||||
import Activity from "@/components/ActivityPage";
|
import Activity from "@/components/ActivityPage";
|
||||||
import SeasonedButton from "@/components/ui/SeasonedButton";
|
import SeasonedButton from "@/components/ui/SeasonedButton";
|
||||||
|
|
||||||
import { getEmoji, getUserRequests } from "@/api";
|
import { getEmoji, getUserRequests, getSettings } from "@/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ListHeader, ResultsList, Settings, Activity, SeasonedButton },
|
components: { ListHeader, ResultsList, Settings, Activity, SeasonedButton },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
userLoggedIn: "",
|
|
||||||
emoji: "",
|
emoji: "",
|
||||||
results: undefined,
|
results: undefined,
|
||||||
totalResults: undefined,
|
totalResults: undefined,
|
||||||
@@ -59,16 +57,17 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters("user", ["loggedIn", "username", "settings"]),
|
||||||
resultCount() {
|
resultCount() {
|
||||||
if (this.results === undefined) return;
|
if (this.results === undefined) return;
|
||||||
|
|
||||||
const loadedResults = this.results.length;
|
const loadedResults = this.results.length;
|
||||||
const totalResults = this.totalResults < 10000 ? this.totalResults : "∞";
|
const totalResults = this.totalResults < 10000 ? this.totalResults : "∞";
|
||||||
return `${loadedResults} of ${totalResults} results`;
|
return `${loadedResults} of ${totalResults} results`;
|
||||||
},
|
}
|
||||||
username: () => store.getters["userModule/username"]
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions("user", ["logout", "updateSettings"]),
|
||||||
toggleSettings() {
|
toggleSettings() {
|
||||||
this.showSettings = this.showSettings ? false : true;
|
this.showSettings = this.showSettings ? false : true;
|
||||||
|
|
||||||
@@ -98,16 +97,20 @@ export default {
|
|||||||
this.showActivity = this.showActivity == true ? false : true;
|
this.showActivity = this.showActivity == true ? false : true;
|
||||||
this.updateQueryParams("activity", this.showActivity);
|
this.updateQueryParams("activity", this.showActivity);
|
||||||
},
|
},
|
||||||
logOut() {
|
_logout() {
|
||||||
this.$router.push("logout");
|
this.logout();
|
||||||
|
this.$router.push("home");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (!localStorage.getItem("token")) {
|
if (!this.settings) {
|
||||||
this.userLoggedIn = false;
|
getSettings().then(resp => {
|
||||||
} else {
|
const { settings } = resp;
|
||||||
this.userLoggedIn = true;
|
if (settings) updateSettings(settings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.loggedIn) {
|
||||||
this.showSettings = window.location.toString().includes("settings=true");
|
this.showSettings = window.location.toString().includes("settings=true");
|
||||||
this.showActivity = window.location.toString().includes("activity=true");
|
this.showActivity = window.location.toString().includes("activity=true");
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,77 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="profile">
|
<section class="profile">
|
||||||
<div class="profile__content" v-if="userLoggedIn">
|
<div class="profile__content" v-if="loggedIn">
|
||||||
<section class='settings'>
|
<section class="settings">
|
||||||
<h3 class='settings__header'>Plex account</h3>
|
<h3 class="settings__header">Plex account</h3>
|
||||||
|
|
||||||
<div v-if="!hasPlexUser">
|
<div v-if="!plexId">
|
||||||
<span class="settings__info">Sign in to your plex account to get information about recently added movies and to see your watch history</span>
|
<span class="settings__info"
|
||||||
|
>Sign in to your plex account to get information about recently
|
||||||
|
added movies and to see your watch history</span
|
||||||
|
>
|
||||||
|
|
||||||
<form class="form">
|
<form class="form">
|
||||||
<seasoned-input placeholder="plex username" icon="Email" :value.sync="plexUsername"/>
|
<seasoned-input
|
||||||
<seasoned-input placeholder="plex password" icon="Keyhole" type="password"
|
placeholder="plex username"
|
||||||
:value.sync="plexPassword" @submit="authenticatePlex" />
|
icon="Email"
|
||||||
|
:value.sync="plexUsername"
|
||||||
|
/>
|
||||||
|
<seasoned-input
|
||||||
|
placeholder="plex password"
|
||||||
|
icon="Keyhole"
|
||||||
|
type="password"
|
||||||
|
:value.sync="plexPassword"
|
||||||
|
@submit="authenticatePlex"
|
||||||
|
/>
|
||||||
|
|
||||||
<seasoned-button @click="authenticatePlex">link plex account</seasoned-button>
|
<seasoned-button @click="authenticatePlex"
|
||||||
|
>link plex account</seasoned-button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<span class="settings__info">Awesome, your account is already authenticated with plex! Enjoy viewing your seasoned search history, plex watch history and real-time torrent download progress.</span>
|
<span class="settings__info"
|
||||||
<seasoned-button @click="unauthenticatePlex">un-link plex account</seasoned-button>
|
>Awesome, your account is already authenticated with plex! Enjoy
|
||||||
|
viewing your seasoned search history, plex watch history and
|
||||||
|
real-time torrent download progress.</span
|
||||||
|
>
|
||||||
|
<seasoned-button @click="unauthenticatePlex"
|
||||||
|
>un-link plex account</seasoned-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<seasoned-messages :messages.sync="messages" />
|
<seasoned-messages :messages.sync="messages" />
|
||||||
|
|
||||||
<hr class='setting__divider'>
|
<hr class="setting__divider" />
|
||||||
|
|
||||||
<h3 class='settings__header'>Change password</h3>
|
<h3 class="settings__header">Change password</h3>
|
||||||
<form class="form">
|
<form class="form">
|
||||||
<seasoned-input placeholder="new password" icon="Keyhole" type="password"
|
<seasoned-input
|
||||||
:value.sync="newPassword" />
|
placeholder="new password"
|
||||||
|
icon="Keyhole"
|
||||||
|
type="password"
|
||||||
|
:value.sync="newPassword"
|
||||||
|
/>
|
||||||
|
|
||||||
<seasoned-input placeholder="repeat new password" icon="Keyhole" type="password"
|
<seasoned-input
|
||||||
:value.sync="newPasswordRepeat" />
|
placeholder="repeat new password"
|
||||||
|
icon="Keyhole"
|
||||||
|
type="password"
|
||||||
|
:value.sync="newPasswordRepeat"
|
||||||
|
/>
|
||||||
|
|
||||||
<seasoned-button @click="changePassword">change password</seasoned-button>
|
<seasoned-button @click="changePassword"
|
||||||
|
>change password</seasoned-button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr class='setting__divider'>
|
<hr class="setting__divider" />
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="not-found" v-else>
|
<section class="not-found" v-else>
|
||||||
<div class="not-found__content">
|
<div class="not-found__content">
|
||||||
<h2 class="not-found__title">Authentication Request Failed</h2>
|
<h2 class="not-found__title">Authentication Request Failed</h2>
|
||||||
<router-link :to="{name: 'signin'}" exact title="Sign in here">
|
<router-link :to="{ name: 'signin' }" exact title="Sign in here">
|
||||||
<button class="not-found__button button">Sign In</button>
|
<button class="not-found__button button">Sign In</button>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,82 +80,68 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import store from '@/store'
|
import { mapGetters, mapState, mapActions } from "vuex";
|
||||||
import storage from '@/storage'
|
import storage from "@/storage";
|
||||||
import SeasonedInput from '@/components/ui/SeasonedInput'
|
import SeasonedInput from "@/components/ui/SeasonedInput";
|
||||||
import SeasonedButton from '@/components/ui/SeasonedButton'
|
import SeasonedButton from "@/components/ui/SeasonedButton";
|
||||||
import SeasonedMessages from '@/components/ui/SeasonedMessages'
|
import SeasonedMessages from "@/components/ui/SeasonedMessages";
|
||||||
|
|
||||||
import { getSettings, updateSettings, linkPlexAccount, unlinkPlexAccount } from '@/api'
|
import { linkPlexAccount, unlinkPlexAccount, getSettings } from "@/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { SeasonedInput, SeasonedButton, SeasonedMessages },
|
components: { SeasonedInput, SeasonedButton, SeasonedMessages },
|
||||||
data(){
|
data() {
|
||||||
return{
|
return {
|
||||||
userLoggedIn: '',
|
|
||||||
messages: [],
|
messages: [],
|
||||||
plexUsername: null,
|
plexUsername: null,
|
||||||
plexPassword: null,
|
plexPassword: null,
|
||||||
newPassword: null,
|
newPassword: null,
|
||||||
newPasswordRepeat: null,
|
newPasswordRepeat: null,
|
||||||
emoji: null
|
emoji: null
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasPlexUser: function() {
|
...mapGetters("user", ["loggedIn", "plexId", "settings"])
|
||||||
return this.settings && this.settings['plex_userid']
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
get: () => {
|
|
||||||
return store.getters['userModule/settings']
|
|
||||||
},
|
|
||||||
set: function(newSettings) {
|
|
||||||
store.dispatch('userModule/setSettings', newSettings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setValue(l, t) {
|
...mapActions("user", ["login", "updateSettings"]),
|
||||||
this[l] = t
|
|
||||||
},
|
|
||||||
changePassword() {
|
changePassword() {
|
||||||
return
|
return;
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.settings) {
|
||||||
|
console.log("settings does not exists.", this.settings);
|
||||||
|
getSettings().then(resp => {
|
||||||
|
const { settings } = resp;
|
||||||
|
if (settings) updateSettings(settings);
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async authenticatePlex() {
|
async authenticatePlex() {
|
||||||
let username = this.plexUsername
|
let username = this.plexUsername;
|
||||||
let password = this.plexPassword
|
let password = this.plexPassword;
|
||||||
|
|
||||||
const response = await linkPlexAccount(username, password)
|
const { success, message } = await linkPlexAccount(username, password);
|
||||||
|
|
||||||
this.messages.push({
|
this.messages.push({
|
||||||
type: response.success ? 'success' : 'error',
|
type: success ? "success" : "error",
|
||||||
title: response.success ? 'Authenticated with plex' : 'Something went wrong',
|
title: success ? "Authenticated with plex" : "Something went wrong",
|
||||||
message: response.message
|
message: message
|
||||||
})
|
});
|
||||||
|
|
||||||
if (response.success)
|
|
||||||
getSettings().then(settings => this.settings = settings)
|
|
||||||
},
|
},
|
||||||
async unauthenticatePlex() {
|
async unauthenticatePlex() {
|
||||||
const response = await unlinkPlexAccount()
|
const response = await unlinkPlexAccount();
|
||||||
|
|
||||||
this.messages.push({
|
this.messages.push({
|
||||||
type: response.success ? 'success' : 'error',
|
type: response.success ? "success" : "error",
|
||||||
title: response.success ? 'Unlinked plex account ' : 'Something went wrong',
|
title: response.success
|
||||||
|
? "Unlinked plex account "
|
||||||
|
: "Something went wrong",
|
||||||
message: response.message
|
message: response.message
|
||||||
})
|
});
|
||||||
|
|
||||||
if (response.success)
|
|
||||||
getSettings().then(settings => this.settings = settings)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created(){
|
|
||||||
const token = localStorage.getItem('token') || false;
|
|
||||||
if (token){
|
|
||||||
this.userLoggedIn = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -133,7 +149,7 @@ export default {
|
|||||||
@import "./src/scss/media-queries";
|
@import "./src/scss/media-queries";
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DUPLICATE CODE
|
// DUPLICATE CODE
|
||||||
@@ -142,22 +158,22 @@ a {
|
|||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__group{
|
&__group {
|
||||||
justify-content: unset;
|
justify-content: unset;
|
||||||
&__input-icon {
|
&__input-icon {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
}
|
}
|
||||||
&-input {
|
&-input {
|
||||||
padding: 10px 5px 10px 45px;
|
padding: 10px 5px 10px 45px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
@include desktop-min {
|
@include desktop-min {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.settings {
|
.settings {
|
||||||
@@ -167,32 +183,32 @@ a {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
&__info {
|
&__info {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
hr {
|
hr {
|
||||||
display: block;
|
display: block;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid $text-color-50;
|
border-bottom: 1px solid $text-color-50;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
margin-bottom: 70px;
|
margin-bottom: 70px;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
width: 96%;
|
width: 96%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
span {
|
span {
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
size: 16px;
|
size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,88 +2,94 @@
|
|||||||
<section>
|
<section>
|
||||||
<h1>Sign in</h1>
|
<h1>Sign in</h1>
|
||||||
|
|
||||||
<seasoned-input placeholder="username"
|
<seasoned-input
|
||||||
icon="Email"
|
placeholder="username"
|
||||||
type="email"
|
icon="Email"
|
||||||
@enter="submit"
|
type="email"
|
||||||
:value.sync="username" />
|
@enter="submit"
|
||||||
<seasoned-input placeholder="password" icon="Keyhole" type="password" :value.sync="password" @enter="submit"/>
|
:value.sync="username"
|
||||||
|
/>
|
||||||
|
<seasoned-input
|
||||||
|
placeholder="password"
|
||||||
|
icon="Keyhole"
|
||||||
|
type="password"
|
||||||
|
:value.sync="password"
|
||||||
|
@enter="submit"
|
||||||
|
/>
|
||||||
|
|
||||||
<seasoned-button @click="submit">sign in</seasoned-button>
|
<seasoned-button @click="submit">sign in</seasoned-button>
|
||||||
<router-link class="link" to="/register">Don't have a user? Register here</router-link>
|
<router-link class="link" to="/register"
|
||||||
|
>Don't have a user? Register here</router-link
|
||||||
|
>
|
||||||
|
|
||||||
<seasoned-messages :messages.sync="messages"></seasoned-messages>
|
<seasoned-messages :messages.sync="messages"></seasoned-messages>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { login } from '@/api'
|
import { mapActions } from "vuex";
|
||||||
import storage from '../storage'
|
import { login } from "@/api";
|
||||||
import SeasonedInput from '@/components/ui/SeasonedInput'
|
import storage from "../storage";
|
||||||
import SeasonedButton from '@/components/ui/SeasonedButton'
|
import SeasonedInput from "@/components/ui/SeasonedInput";
|
||||||
import SeasonedMessages from '@/components/ui/SeasonedMessages'
|
import SeasonedButton from "@/components/ui/SeasonedButton";
|
||||||
import { parseJwt } from '@/utils'
|
import SeasonedMessages from "@/components/ui/SeasonedMessages";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { SeasonedInput, SeasonedButton, SeasonedMessages },
|
components: { SeasonedInput, SeasonedButton, SeasonedMessages },
|
||||||
data(){
|
data() {
|
||||||
return{
|
return {
|
||||||
messages: [],
|
messages: [],
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setValue(l, t) {
|
...mapActions("user", ["login"]),
|
||||||
this[l] = t
|
|
||||||
},
|
|
||||||
submit() {
|
submit() {
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
let username = this.username;
|
let { username, password } = this;
|
||||||
let password = this.password;
|
|
||||||
|
|
||||||
if (username == null || username.length == 0) {
|
if (!username || username.length == 0) {
|
||||||
this.messages.push({ type: 'error', title: 'Missing username' })
|
this.messages.push({ type: "error", title: "Missing username" });
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password == null || password.length == 0) {
|
if (!password || password.length == 0) {
|
||||||
this.messages.push({ type: 'error', title: 'Missing password' })
|
this.messages.push({ type: "error", title: "Missing password" });
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.signin(username, password)
|
this.signin(username, password);
|
||||||
},
|
},
|
||||||
signin(username, password) {
|
signin(username, password) {
|
||||||
login(username, password, true)
|
login(username, password, true)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success){
|
if (data.success && this.login(data.token)) {
|
||||||
const jwtData = parseJwt(data.token)
|
this.$router.push({ name: "profile" });
|
||||||
localStorage.setItem('token', data.token);
|
|
||||||
localStorage.setItem('username', jwtData['username']);
|
|
||||||
localStorage.setItem('admin', jwtData['admin'] || false);
|
|
||||||
|
|
||||||
eventHub.$emit('setUserStatus');
|
|
||||||
this.$router.push({ name: 'profile' })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.status === 401) {
|
if (error.status === 401) {
|
||||||
this.messages.push({ type: 'error', title: 'Access denied', message: 'Incorrect username or password' })
|
this.messages.push({
|
||||||
}
|
type: "error",
|
||||||
else {
|
title: "Access denied",
|
||||||
this.messages.push({ type: 'error', title: 'Unexpected error', message: error.message })
|
message: "Incorrect username or password"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.messages.push({
|
||||||
|
type: "error",
|
||||||
|
title: "Unexpected error",
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created(){
|
created() {
|
||||||
document.title = 'Sign in' + storage.pageTitlePostfix;
|
document.title = "Sign in" + storage.pageTitlePostfix;
|
||||||
storage.backTitle = document.title;
|
storage.backTitle = document.title;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
35
src/main.js
35
src/main.js
@@ -1,28 +1,29 @@
|
|||||||
import Vue from 'vue'
|
import Vue from "vue";
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from "vue-router";
|
||||||
import axios from 'axios'
|
import axios from "axios";
|
||||||
import router from './routes'
|
import router from "./routes";
|
||||||
import store from './store'
|
import store from "./store";
|
||||||
|
|
||||||
import Toast from './plugins/Toast'
|
import Toast from "./plugins/Toast";
|
||||||
import DataTablee from 'vue-data-tablee'
|
import DataTablee from "vue-data-tablee";
|
||||||
import VModal from 'vue-js-modal'
|
import VModal from "vue-js-modal";
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
|
|
||||||
window.eventHub = new Vue();
|
window.eventHub = new Vue();
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter);
|
||||||
Vue.use(Toast)
|
Vue.use(Toast);
|
||||||
Vue.use(DataTablee)
|
Vue.use(DataTablee);
|
||||||
Vue.use(VModal, { dialog: true })
|
Vue.use(VModal, { dialog: true });
|
||||||
|
|
||||||
store.dispatch('darkmodeModule/findAndSetDarkmodeSupported')
|
store.dispatch("darkmodeModule/findAndSetDarkmodeSupported");
|
||||||
|
store.dispatch("user/initFromLocalStorage");
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: "#app",
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
components: { App },
|
components: { App },
|
||||||
template: '<App />'
|
template: "<App />"
|
||||||
})
|
});
|
||||||
|
|||||||
59
src/modules/popup.js
Normal file
59
src/modules/popup.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
const removeIncludedQueryParams = (params, key) => {
|
||||||
|
if (params.has(key)) params.delete(key);
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateQueryParams = (id = null, type = null) => {
|
||||||
|
let params = new URLSearchParams(window.location.search);
|
||||||
|
params = removeIncludedQueryParams(params, "movie");
|
||||||
|
params = removeIncludedQueryParams(params, "show");
|
||||||
|
|
||||||
|
if (id && type === "movie") {
|
||||||
|
params.append("movie", id);
|
||||||
|
}
|
||||||
|
if (id && type === "show") {
|
||||||
|
params.append("show", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = `${window.location.protocol}//${window.location.hostname}${
|
||||||
|
window.location.port ? `:${window.location.port}` : ""
|
||||||
|
}${window.location.pathname}${params.toString().length ? `?${params}` : ""}`;
|
||||||
|
|
||||||
|
window.history.replaceState({}, "search", url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
id: null,
|
||||||
|
type: null,
|
||||||
|
open: false
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
isOpen: state => state.open,
|
||||||
|
id: state => state.id,
|
||||||
|
type: state => state.type
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
SET_OPEN: (state, { id, type }) => {
|
||||||
|
state.id = id;
|
||||||
|
state.type = type;
|
||||||
|
state.open = true;
|
||||||
|
},
|
||||||
|
SET_CLOSE: state => {
|
||||||
|
state.id = null;
|
||||||
|
state.type = null;
|
||||||
|
state.open = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
open: ({ commit }, { id, type = "movie" }) => {
|
||||||
|
commit("SET_OPEN", { id, type });
|
||||||
|
updateQueryParams(id, type);
|
||||||
|
},
|
||||||
|
close: ({ commit }) => {
|
||||||
|
commit("SET_CLOSE");
|
||||||
|
updateQueryParams(); // reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
103
src/modules/user.js
Normal file
103
src/modules/user.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { refreshToken } from "@/api";
|
||||||
|
import { parseJwt } from "@/utils";
|
||||||
|
|
||||||
|
function setLocalStorageByKey(key, value) {
|
||||||
|
if (value instanceof Object || value instanceof Array) {
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
const buff = Buffer.from(value);
|
||||||
|
const encodedValue = buff.toString("base64");
|
||||||
|
localStorage.setItem(key, encodedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalStorageByKey(key) {
|
||||||
|
const encodedValue = localStorage.getItem(key);
|
||||||
|
if (encodedValue == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const buff = new Buffer(encodedValue, "base64");
|
||||||
|
const value = buff.toString("utf-8");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
token: null,
|
||||||
|
admin: false,
|
||||||
|
settings: null,
|
||||||
|
username: null
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
username: state => state.username,
|
||||||
|
settings: state => state.settings,
|
||||||
|
token: state => state.token,
|
||||||
|
loggedIn: state => state && state.username !== null,
|
||||||
|
admin: state => state && state.admin !== null,
|
||||||
|
plexId: state => {
|
||||||
|
if (state && state.settings && state.settings.plex_userid)
|
||||||
|
return state.settings.plex_userid;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
SET_TOKEN: (state, token) => (state.token = token),
|
||||||
|
SET_USERNAME: (state, username) => (state.username = username),
|
||||||
|
SET_SETTINGS: (state, settings) => (state.settings = settings),
|
||||||
|
SET_ADMIN: (state, admin) => (state.admin = admin),
|
||||||
|
LOGOUT: state => {
|
||||||
|
state.token = null;
|
||||||
|
state.username = null;
|
||||||
|
state.settings = null;
|
||||||
|
state.admin = false;
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
initFromLocalStorage: async ({ dispatch }) => {
|
||||||
|
const token = getLocalStorageByKey("token");
|
||||||
|
if (token) await dispatch("setupStateFromToken", token);
|
||||||
|
|
||||||
|
const settings = getLocalStorageByKey("settings");
|
||||||
|
if (settings) await dispatch("setSettings", settings);
|
||||||
|
},
|
||||||
|
setupStateFromToken: ({ commit }, token) => {
|
||||||
|
try {
|
||||||
|
const jwtData = parseJwt(token);
|
||||||
|
const { username, admin } = jwtData;
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
commit("SET_TOKEN", token);
|
||||||
|
commit("SET_USERNAME", username);
|
||||||
|
commit("SET_ADMIN", admin != undefined ? true : false);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Unable to parse JWT, failed with error:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSettings: ({ commit }, settings) => {
|
||||||
|
if (!(settings instanceof Object)) {
|
||||||
|
throw "Parameter is not a object.";
|
||||||
|
}
|
||||||
|
|
||||||
|
commit("SET_SETTINGS", settings);
|
||||||
|
setLocalStorageByKey("settings", settings);
|
||||||
|
},
|
||||||
|
logout: ({ commit }) => commit("LOGOUT"),
|
||||||
|
login: async ({ dispatch }, token) => {
|
||||||
|
const loggedIn = await dispatch("setupStateFromToken", token);
|
||||||
|
if (loggedIn) setLocalStorageByKey("token", token);
|
||||||
|
|
||||||
|
return loggedIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import { getSettings } from '@/api'
|
|
||||||
|
|
||||||
function setLocalStorageByKey(key, value) {
|
|
||||||
if (value instanceof Object || value instanceof Array) {
|
|
||||||
value = JSON.stringify(value)
|
|
||||||
}
|
|
||||||
const buff = Buffer.from(value)
|
|
||||||
const encodedValue = buff.toString('base64')
|
|
||||||
localStorage.setItem(key, encodedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocalStorageByKey(key) {
|
|
||||||
const encodedValue = localStorage.getItem(key)
|
|
||||||
if (encodedValue == null) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const buff = new Buffer(encodedValue, 'base64')
|
|
||||||
const value = buff.toString('utf-8')
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(value)
|
|
||||||
} catch {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ifMissingSettingsAndTokenExistsFetchSettings =
|
|
||||||
() => getLocalStorageByKey('token') ? getSettings() : null
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state: {
|
|
||||||
admin: false,
|
|
||||||
settings: undefined,
|
|
||||||
username: undefined,
|
|
||||||
plex_userid: undefined
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
admin: (state) => {
|
|
||||||
return state.admin
|
|
||||||
},
|
|
||||||
settings: (state, foo, bar) => {
|
|
||||||
console.log('is this called?')
|
|
||||||
const settings = state.settings || getLocalStorageByKey('settings')
|
|
||||||
if (settings instanceof Object) {
|
|
||||||
return settings
|
|
||||||
}
|
|
||||||
|
|
||||||
ifMissingSettingsAndTokenExistsFetchSettings()
|
|
||||||
return undefined
|
|
||||||
},
|
|
||||||
username: (state) => {
|
|
||||||
const settings = state.settings || getLocalStorageByKey('settings')
|
|
||||||
|
|
||||||
if (settings instanceof Object && settings.hasOwnProperty('user_name')) {
|
|
||||||
return settings.user_name
|
|
||||||
}
|
|
||||||
|
|
||||||
ifMissingSettingsAndTokenExistsFetchSettings()
|
|
||||||
return undefined
|
|
||||||
},
|
|
||||||
plex_userid: (state) => {
|
|
||||||
const settings = state.settings || getLocalStorageByKey('settings')
|
|
||||||
console.log('plex_userid from store', settings)
|
|
||||||
|
|
||||||
if (settings instanceof Object && settings.hasOwnProperty('plex_userid')) {
|
|
||||||
return settings.plex_userid
|
|
||||||
}
|
|
||||||
|
|
||||||
ifMissingSettingsAndTokenExistsFetchSettings()
|
|
||||||
return undefined
|
|
||||||
},
|
|
||||||
isPlexAuthenticated: (state) => {
|
|
||||||
const settings = state.settings || getLocalStorageByKey('settings')
|
|
||||||
if (settings == null)
|
|
||||||
return false
|
|
||||||
|
|
||||||
const hasPlexId = settings['plex_userid']
|
|
||||||
return hasPlexId != null ? true : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
SET_ADMIN: (state, isAdmin) => {
|
|
||||||
state.admin = isAdmin
|
|
||||||
},
|
|
||||||
SET_USERNAME: (state, username) => {
|
|
||||||
state.username = username
|
|
||||||
console.log('username')
|
|
||||||
setLocalStorageByKey('username', username)
|
|
||||||
},
|
|
||||||
SET_SETTINGS: (state, settings) => {
|
|
||||||
state.settings = settings
|
|
||||||
console.log('settings')
|
|
||||||
setLocalStorageByKey('settings', settings)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
setAdmin: ({commit}, isAdmin) => {
|
|
||||||
if (!(isAdmin instanceof Object)) {
|
|
||||||
throw "Parameter is not a boolean value."
|
|
||||||
}
|
|
||||||
commit('SET_ADMIN', isAdmin)
|
|
||||||
},
|
|
||||||
setSettings: ({commit}, settings) => {
|
|
||||||
console.log('settings input', settings)
|
|
||||||
if (!(settings instanceof Object)) {
|
|
||||||
throw "Parameter is not a object."
|
|
||||||
}
|
|
||||||
commit('SET_SETTINGS', settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -66,17 +66,6 @@ let routes = [
|
|||||||
path: "/404",
|
path: "/404",
|
||||||
component: resolve => require(["./components/404.vue"], resolve)
|
component: resolve => require(["./components/404.vue"], resolve)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "logout",
|
|
||||||
path: "/logout",
|
|
||||||
component: {
|
|
||||||
template: "<div></div>",
|
|
||||||
created() {
|
|
||||||
localStorage.clear();
|
|
||||||
this.$router.push({ name: "home" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "*",
|
path: "*",
|
||||||
redirect: "/"
|
redirect: "/"
|
||||||
@@ -94,8 +83,12 @@ const router = new VueRouter({
|
|||||||
linkActiveClass: "is-active"
|
linkActiveClass: "is-active"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loggedIn = () => store.getters["user/loggedIn"];
|
||||||
|
const isOpen = () => store.getters["user/isOpen"];
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
store.dispatch("documentTitle/updateTitle", to.name);
|
store.dispatch("documentTitle/updateTitle", to.name);
|
||||||
|
if (isOpen()) store.dispatch("popup/close");
|
||||||
|
|
||||||
// Toggle mobile nav
|
// Toggle mobile nav
|
||||||
if (document.querySelector(".nav__hamburger--active")) {
|
if (document.querySelector(".nav__hamburger--active")) {
|
||||||
@@ -106,7 +99,7 @@ router.beforeEach((to, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||||
if (localStorage.getItem("token") == null) {
|
if (!loggedIn) {
|
||||||
next({ path: "/signin" });
|
next({ path: "/signin" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/store.js
22
src/store.js
@@ -1,20 +1,22 @@
|
|||||||
import Vue from 'vue'
|
import Vue from "vue";
|
||||||
import Vuex from 'vuex'
|
import Vuex from "vuex";
|
||||||
|
|
||||||
import darkmodeModule from './modules/darkmodeModule'
|
import darkmodeModule from "./modules/darkmodeModule";
|
||||||
import documentTitle from './modules/documentTitle'
|
import documentTitle from "./modules/documentTitle";
|
||||||
import torrentModule from './modules/torrentModule'
|
import torrentModule from "./modules/torrentModule";
|
||||||
import userModule from './modules/userModule'
|
import user from "./modules/user";
|
||||||
|
import popup from "./modules/popup";
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex);
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
darkmodeModule,
|
darkmodeModule,
|
||||||
documentTitle,
|
documentTitle,
|
||||||
torrentModule,
|
torrentModule,
|
||||||
userModule
|
user,
|
||||||
|
popup
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
export default store
|
export default store;
|
||||||
|
|||||||
Reference in New Issue
Block a user