From 3b98faedddad93ef8046ce463c8ca057926e28f0 Mon Sep 17 00:00:00 2001 From: Kevin Midboe Date: Sun, 14 Aug 2022 19:49:48 +0200 Subject: [PATCH] Replaced config w/ dotenv. Hydrate docker nginx using env. Updated readme --- .env.example | 4 ++ .gitignore | 1 + Dockerfile | 5 +- README.md | 75 ++++++++++++++++++--------- docker-entrypoint.sh | 9 ++++ nginx.conf | 4 +- package.json | 1 + src/api.ts | 59 ++++++++++----------- src/components/header/SearchInput.vue | 4 +- src/config.ts | 9 ---- src/interfaces/IConfig.ts | 5 -- webpack.config.js | 21 ++++++-- yarn.lock | 5 ++ 13 files changed, 123 insertions(+), 79 deletions(-) create mode 100644 .env.example create mode 100644 docker-entrypoint.sh delete mode 100644 src/config.ts delete mode 100644 src/interfaces/IConfig.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..78b073f --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +SEASONED_API= +ELASTIC= +ELASTIC_INDEX=shows,movies +SEASONED_DOMAIN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3a0ead1..93bd3be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # config file - copy config.json.example src/config.json +.env # Build directory dist/ diff --git a/Dockerfile b/Dockerfile index 1516c54..9cf5b56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,10 @@ FROM nginx:1.23.1 COPY public /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d/default.conf.template +COPY docker-entrypoint.sh /docker-entrypoint.d/05-docker-entrypoint.sh + +RUN chmod +x /docker-entrypoint.d/05-docker-entrypoint.sh EXPOSE 5000 diff --git a/README.md b/README.md index 80af768..2dd2828 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,72 @@ -# The Movie Database App +# Seasoned Request -A Vue.js project. +Seasoned request is frontend vue application for searching, requesting and viewing account watch activity. ![](https://github.com/dmtrbrl/tmdb-app/blob/master/docs/demo.gif) -## Demo - -[TMDB Vue App](https://tmdb-vue-app.herokuapp.com/) - ## Config setup -Set seasonedShows api endpoint and/or elastic. - - SeasonedShows [can be found here](https://github.com/kevinmidboe/seasonedshows) and is the matching backend to fetch tmdb search results, tmdb lists, request new content, check plex status and lets owner search and add torrents to download. - - Elastic is optional and can be used for a instant search feature for all movies and shows registered in tmdb. -```json -{ - "SEASONED_URL": "http://localhost:31459/api", - "ELASTIC_URL": "http://localhost:9200" -} +```bash +# make copy of example environment file +cp .env.example .env ``` -*Set ELASTIC_URL to undefined or false to disable* -## Build Setup +```bash +# .env sane default values +SEASONED_API= +ELASTIC= +ELASTIC_INDEX=shows,movies +SEASONED_DOMAIN= +``` -``` bash +- Leave SEASONED_API empty to request `/api` from same origin and proxy passed by nginx, set if hosting [seasonedShows backend api](https://github.com/KevinMidboe/seasonedShows) locally. +- Elastic is optional and can be used for a instant search feature for all movies and shows registered in tmdb, leave empty to disable. + +```bash +# .env example values +SEASONED_API=http://localhost:31459 +ELASTIC=http://localhost:9200 +ELASTIC_INDEX=shows,movies +SEASONED_DOMAIN=request.movie +``` + +## Build Steps + +```bash # install dependencies -npm install +yarn -# serve with hot reload at localhost:8080 -npm run dev +# build vue project using webpack +yarn build -# build for production with minification -npm run build +# test or host built files using docker, might require sudo: +docker build -t seasoned . +docker run -d -p 5000:5000 --name seasoned-request --env-file .env seasoned ``` -For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). -This app uses [history mode](https://router.vuejs.org/en/essentials/history-mode.html) +## Development Steps + +```bash +# serve project with hot reloading at localhost:8080 +yarn dev +``` + +To proxy requests to `/api` either update `SEASONED_API` in `.env` or run set environment variable, e.g.: + +```bash +# export and run +export SEASONED_API=http://localhost:31459 +yarn dev + +# or run with environment variable inline +SEASONED_API=http://localhost:31459 yarn dev +``` ## Documentation + All api functions are documented in `/docs` and [found here](docs/api.md). [html version also available](http://htmlpreview.github.io/?https://github.com/KevinMidboe/seasoned/blob/release/v2/docs/api/index.html) ## License + [MIT](https://github.com/dmtrbrl/tmdb-app/blob/master/LICENSE) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..24e7b29 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +export SEASONED_API=${SEASONED_API:-http://localhost:31459} +export SEASONED_DOMAIN=${SEASONED_DOMAIN:-localhost} + +envsubst '$SEASONED_API,$SEASONED_DOMAIN' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf + +exec "$@" diff --git a/nginx.conf b/nginx.conf index e1274d7..8279da6 100644 --- a/nginx.conf +++ b/nginx.conf @@ -2,7 +2,7 @@ server { listen 5000 default_server; listen [::]:5000 default_server; -# server_name request.movie; + server_name $SEASONED_DOMAIN; root /usr/share/nginx/html; gzip on; @@ -20,7 +20,7 @@ server { } location /api { - proxy_pass http://request.movie; + proxy_pass $SEASONED_API; } location / { diff --git a/package.json b/package.json index db5abfb..d01d0be 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "chart.js": "3.9.1", "connect-history-api-fallback": "2.0.0", + "dotenv": "^16.0.1", "express": "4.18.1", "vue": "3.2.37", "vue-router": "4.1.3", diff --git a/src/api.ts b/src/api.ts index f1cf937..d1242de 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,10 +1,7 @@ -import config from "./config"; import { IList, IMediaCredits, IPersonCredits } from "./interfaces/IList"; -const { ELASTIC_URL, ELASTIC_INDEX } = config; - -let { SEASONED_URL } = config; -if (!SEASONED_URL) SEASONED_URL = window.location.origin; +const { ELASTIC, ELASTIC_INDEX } = process.env; +const API_HOSTNAME = window.location.origin; // - - - TMDB - - - @@ -21,7 +18,7 @@ const getMovie = ( releaseDates }: { checkExistance: boolean; credits: boolean; releaseDates?: boolean } ) => { - const url = new URL("/api/v2/movie", SEASONED_URL); + const url = new URL("/api/v2/movie", API_HOSTNAME); url.pathname = `${url.pathname}/${id.toString()}`; if (checkExistance) { url.searchParams.append("check_existance", "true"); @@ -55,7 +52,7 @@ const getShow = ( releaseDates }: { checkExistance: boolean; credits: boolean; releaseDates?: boolean } ) => { - const url = new URL("/api/v2/show", SEASONED_URL); + const url = new URL("/api/v2/show", API_HOSTNAME); url.pathname = `${url.pathname}/${id.toString()}`; if (checkExistance) { url.searchParams.append("check_existance", "true"); @@ -82,7 +79,7 @@ const getShow = ( * @returns {object} Tmdb response */ const getPerson = (id, credits = false) => { - const url = new URL("/api/v2/person", SEASONED_URL); + const url = new URL("/api/v2/person", API_HOSTNAME); url.pathname = `${url.pathname}/${id.toString()}`; if (credits) { url.searchParams.append("credits", "true"); @@ -102,7 +99,7 @@ const getPerson = (id, credits = false) => { * @returns {object} Tmdb response */ const getMovieCredits = (id: number): Promise => { - const url = new URL("/api/v2/movie", SEASONED_URL); + const url = new URL("/api/v2/movie", API_HOSTNAME); url.pathname = `${url.pathname}/${id.toString()}/credits`; return fetch(url.href) @@ -119,7 +116,7 @@ const getMovieCredits = (id: number): Promise => { * @returns {object} Tmdb response */ const getShowCredits = (id: number): Promise => { - const url = new URL("/api/v2/show", SEASONED_URL); + const url = new URL("/api/v2/show", API_HOSTNAME); url.pathname = `${url.pathname}/${id.toString()}/credits`; return fetch(url.href) @@ -136,7 +133,7 @@ const getShowCredits = (id: number): Promise => { * @returns {object} Tmdb response */ const getPersonCredits = (id: number): Promise => { - const url = new URL("/api/v2/person", SEASONED_URL); + const url = new URL("/api/v2/person", API_HOSTNAME); url.pathname = `${url.pathname}/${id.toString()}/credits`; return fetch(url.href) @@ -154,7 +151,7 @@ const getPersonCredits = (id: number): Promise => { * @returns {object} Tmdb list response */ const getTmdbMovieListByName = (name: string, page = 1): Promise => { - const url = new URL(`/api/v2/movie/${name}`, SEASONED_URL); + const url = new URL(`/api/v2/movie/${name}`, API_HOSTNAME); url.searchParams.append("page", page.toString()); return fetch(url.href).then(resp => resp.json()); @@ -167,7 +164,7 @@ const getTmdbMovieListByName = (name: string, page = 1): Promise => { * @returns {object} Request response */ const getRequests = (page = 1) => { - const url = new URL("/api/v2/request", SEASONED_URL); + const url = new URL("/api/v2/request", API_HOSTNAME); url.searchParams.append("page", page.toString()); return fetch(url.href).then(resp => resp.json()); @@ -175,7 +172,7 @@ const getRequests = (page = 1) => { }; const getUserRequests = (page = 1) => { - const url = new URL("/api/v1/user/requests", SEASONED_URL); + const url = new URL("/api/v1/user/requests", API_HOSTNAME); url.searchParams.append("page", page.toString()); return fetch(url.href).then(resp => resp.json()); @@ -188,7 +185,7 @@ const getUserRequests = (page = 1) => { * @returns {object} Tmdb response */ const searchTmdb = (query, page = 1, adult = false, mediaType = null) => { - const url = new URL("/api/v2/search", SEASONED_URL); + const url = new URL("/api/v2/search", API_HOSTNAME); if (mediaType != null && ["movie", "show", "person"].includes(mediaType)) { url.pathname += `/${mediaType}`; } @@ -214,7 +211,7 @@ const searchTmdb = (query, page = 1, adult = false, mediaType = null) => { * @returns {object} Torrent response */ const searchTorrents = query => { - const url = new URL("/api/v1/pirate/search", SEASONED_URL); + const url = new URL("/api/v1/pirate/search", API_HOSTNAME); url.searchParams.append("query", query); return fetch(url.href) @@ -233,7 +230,7 @@ const searchTorrents = query => { * @returns {object} Success/Failure response */ const addMagnet = (magnet: string, name: string, tmdbId: number | null) => { - const url = new URL("/api/v1/pirate/add", SEASONED_URL); + const url = new URL("/api/v1/pirate/add", API_HOSTNAME); const options = { method: "POST", @@ -263,7 +260,7 @@ const addMagnet = (magnet: string, name: string, tmdbId: number | null) => { * @returns {object} Success/Failure response */ const request = (id, type) => { - const url = new URL("/api/v2/request", SEASONED_URL); + const url = new URL("/api/v2/request", API_HOSTNAME); const options = { method: "POST", @@ -286,7 +283,7 @@ const request = (id, type) => { * @returns {object} Success/Failure response */ const getRequestStatus = (id, type = undefined) => { - const url = new URL("/api/v2/request", SEASONED_URL); + const url = new URL("/api/v2/request", API_HOSTNAME); url.pathname = `${url.pathname}/${id.toString()}`; url.searchParams.append("type", type); @@ -301,7 +298,7 @@ const getRequestStatus = (id, type = undefined) => { }; const watchLink = (title, year) => { - const url = new URL("/api/v1/plex/watch-link", SEASONED_URL); + const url = new URL("/api/v1/plex/watch-link", API_HOSTNAME); url.searchParams.append("title", title); url.searchParams.append("year", year); @@ -311,7 +308,7 @@ const watchLink = (title, year) => { }; const movieImages = id => { - const url = new URL(`v2/movie/${id}/images`, SEASONED_URL); + const url = new URL(`v2/movie/${id}/images`, API_HOSTNAME); return fetch(url.href).then(resp => resp.json()); }; @@ -319,7 +316,7 @@ const movieImages = id => { // - - - Seasoned user endpoints - - - const register = (username, password) => { - const url = new URL("/api/v1/user", SEASONED_URL); + const url = new URL("/api/v1/user", API_HOSTNAME); const options = { method: "POST", headers: { "Content-Type": "application/json" }, @@ -339,7 +336,7 @@ const register = (username, password) => { }; const login = (username, password, throwError = false) => { - const url = new URL("/api/v1/user/login", SEASONED_URL); + const url = new URL("/api/v1/user/login", API_HOSTNAME); const options = { method: "POST", headers: { "Content-Type": "application/json" }, @@ -356,7 +353,7 @@ const login = (username, password, throwError = false) => { }; const logout = (throwError = false) => { - const url = new URL("/api/v1/user/logout", SEASONED_URL); + const url = new URL("/api/v1/user/logout", API_HOSTNAME); const options = { method: "POST" }; return fetch(url.href, options).then(resp => { @@ -369,7 +366,7 @@ const logout = (throwError = false) => { }; const getSettings = () => { - const url = new URL("/api/v1/user/settings", SEASONED_URL); + const url = new URL("/api/v1/user/settings", API_HOSTNAME); return fetch(url.href) .then(resp => resp.json()) @@ -380,7 +377,7 @@ const getSettings = () => { }; const updateSettings = settings => { - const url = new URL("/api/v1/user/settings", SEASONED_URL); + const url = new URL("/api/v1/user/settings", API_HOSTNAME); const options = { method: "PUT", @@ -399,7 +396,7 @@ const updateSettings = settings => { // - - - Authenticate with plex - - - const linkPlexAccount = (username, password) => { - const url = new URL("/api/v1/user/link_plex", SEASONED_URL); + const url = new URL("/api/v1/user/link_plex", API_HOSTNAME); const body = { username, password }; const options = { @@ -417,7 +414,7 @@ const linkPlexAccount = (username, password) => { }; const unlinkPlexAccount = () => { - const url = new URL("/api/v1/user/unlink_plex", SEASONED_URL); + const url = new URL("/api/v1/user/unlink_plex", API_HOSTNAME); const options = { method: "POST", @@ -435,7 +432,7 @@ const unlinkPlexAccount = () => { // - - - User graphs - - - const fetchGraphData = (urlPath, days, chartType) => { - const url = new URL(`/api/v1/user/${urlPath}`, SEASONED_URL); + const url = new URL(`/api/v1/user/${urlPath}`, API_HOSTNAME); url.searchParams.append("days", days); url.searchParams.append("y_axis", chartType); @@ -452,7 +449,7 @@ const fetchGraphData = (urlPath, days, chartType) => { // - - - Random emoji - - - const getEmoji = () => { - const url = new URL("/api/v1/emoji", SEASONED_URL); + const url = new URL("/api/v1/emoji", API_HOSTNAME); return fetch(url.href) .then(resp => resp.json()) @@ -473,7 +470,7 @@ const getEmoji = () => { * @returns {object} List of movies and shows matching query */ const elasticSearchMoviesAndShows = (query, count = 22) => { - const url = new URL(`${ELASTIC_INDEX}/_search`, ELASTIC_URL); + const url = new URL(`${ELASTIC_INDEX}/_search`, ELASTIC); const body = { sort: [{ popularity: { order: "desc" } }, "_score"], diff --git a/src/components/header/SearchInput.vue b/src/components/header/SearchInput.vue index 950fa8a..1bb5bb6 100644 --- a/src/components/header/SearchInput.vue +++ b/src/components/header/SearchInput.vue @@ -86,8 +86,8 @@ query.value = decodeURIComponent(params.get("query")); } - const elasticUrl = config.ELASTIC_URL; - if (elasticUrl === undefined || elasticUrl === "") { + const { ELASTIC } = process.env; + if (ELASTIC === undefined || ELASTIC === "") { disabled.value = true; } diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index b9125ad..0000000 --- a/src/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type IConfig from "./interfaces/IConfig"; - -const config: IConfig = { - SEASONED_URL: "", - ELASTIC_URL: "https://elastic.kevinmidboe.com/", - ELASTIC_INDEX: "shows,movies" -}; - -export default config; diff --git a/src/interfaces/IConfig.ts b/src/interfaces/IConfig.ts deleted file mode 100644 index 7f95b8e..0000000 --- a/src/interfaces/IConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface IConfig { - SEASONED_URL: string; - ELASTIC_URL: string; - ELASTIC_INDEX: string; -} diff --git a/webpack.config.js b/webpack.config.js index 8564ba7..f96c1ae 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,7 @@ const sass = require("sass"); const HTMLWebpackPlugin = require("html-webpack-plugin"); const { VueLoaderPlugin } = require("vue-loader"); const TerserPlugin = require("terser-webpack-plugin"); +const dotenv = require("dotenv").config({ path: "./.env" }); const sourcePath = path.resolve(__dirname, "src"); const indexFile = path.join(sourcePath, "index.html"); @@ -11,6 +12,13 @@ const javascriptEntry = path.join(sourcePath, "main.ts"); const publicPath = path.resolve(__dirname, "public"); const isProd = process.env.NODE_ENV === "production"; +// Merge inn all process.env values that match dotenv keys +Object.keys(process.env).forEach(key => { + if (key in dotenv.parsed) { + dotenv.parsed[key] = process.env[key]; + } +}); + module.exports = { mode: process.env.NODE_ENV, context: publicPath, @@ -67,6 +75,9 @@ module.exports = { template: indexFile, filename: "index.html", minify: isProd + }), + new webpack.DefinePlugin({ + "process.env": JSON.stringify(dotenv.parsed) }) ], resolve: { @@ -131,13 +142,13 @@ if (isProd) { module.exports.performance.hints = "warning"; } -// enable proxy by running command e.g.: -// proxyhost=https://request.movie yarn dev -const { proxyhost } = process.env; -if (proxyhost) { +// enable proxy for anything that hits /Api +// View README or update src/config.ts:SEASONED_API_URL +const { SEASONED_API } = process.env; +if (SEASONED_API) { module.exports.devServer.proxy = { "/api": { - target: proxyhost, + target: SEASONED_API, changeOrigin: true } }; diff --git a/yarn.lock b/yarn.lock index 016c3d0..e78f918 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4203,6 +4203,11 @@ dotenv@^10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" + integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== + dotgitignore@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b"