From 5f1181476da20bcaf3aad2599995c638ec2358ac Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Sun, 22 Sep 2024 14:04:15 -0700 Subject: [PATCH] ""refactor"" basically everything --- .gitignore | 2 -- Caddyfile | 22 --------------- README.md | 28 ++++++++----------- {routes => disabled}/auth.js | 6 ++-- {routes => disabled}/etyd.js | 8 +++--- {routes => disabled}/mailjet.js | 8 +++--- {routes => disabled}/nowplaying.js | 6 ++-- index.js | 45 +++++++++++++++++------------- liberals/auth.js | 4 +-- liberals/libnowplaying.js | 17 +++++++++-- liberals/logging.js | 2 +- package.json | 1 + routes/frontpage.js | 14 +++++----- routes/ip.js | 5 ++-- 14 files changed, 78 insertions(+), 90 deletions(-) delete mode 100644 Caddyfile rename {routes => disabled}/auth.js (89%) rename {routes => disabled}/etyd.js (94%) rename {routes => disabled}/mailjet.js (89%) rename {routes => disabled}/nowplaying.js (85%) diff --git a/.gitignore b/.gitignore index c4978fe..b725d4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ node_modules/ -config.json bun.lockb -GITVERSION todo.txt proto.js \ No newline at end of file diff --git a/Caddyfile b/Caddyfile deleted file mode 100644 index aea8b6c..0000000 --- a/Caddyfile +++ /dev/null @@ -1,22 +0,0 @@ -:8082 { - reverse_proxy localhost:8081 -} - -:8083 { - @staticpaths { - path / - path /_static* - path /favicon.ico - } - - handle @staticpaths { - respond /favicon.ico 204 - root ./etydFrontend - file_server - } - - handle /* { - rewrite * /etyd{uri} - reverse_proxy localhost:8081 - } -} \ No newline at end of file diff --git a/README.md b/README.md index 040ab4f..f3b9c36 100644 --- a/README.md +++ b/README.md @@ -8,37 +8,32 @@ This file contains documentation relevant for development and deployment, but no If you would like to report a bug or security issue, please open a GitHub issue. If you are the operator of a service this application accesses, use the contact information provided during registration with your service to contact me directly. ## Configuration -On startup, this application will look for two files. If either cannot be read, it will exit with an error code. -1. `config.json` contains settings required for operation and API keys used for calling external services. -2. `GITVERSION` contains the commit that was cloned when the container was built. +The configuration is downloaded from CouchDB on startup, however two environment variables must be set to specify the URL of the CouchDB server and the credentials for accessing it: +| Variable | Required? | Purpose | +|--------------|----------------------|-----------------------------------------------------------------------------------------------------| +| `API_PORT` | No, defaults to 8081 | Sets the port the server will listen on | +| `API_DBHOST` | Yes | Complete URL of the CouchDB instance, including port and protocol | +| `API_DBCRED` | Yes | Credentials to access the CouchDB instance, in Basic Authentication format e.g. `username:password` |
Configuration Example -* `couchdbHhost`: URL of CouchDB server. +* `frontpage.directory`: Directory of frontpage, will be served at root with modifications. * `mailjet.apiKey`: Mailjet API Key. * `mailjet.senderAddress`: Email address that emails will be received from, must be verified in Mailjet admin panel. -* `frontpage.frontpageDir`: Directory of frontpage, will be served at root with modifications. * `nowplaying.*.apiKey`: API key of respective service. * `nowplaying.*.target`: User that should be queried to retrieve playback information. ```json { - "startup": { - "apiPort": 8081, - "routesDir": "./routes" + "frontpage": { + "directory": "" }, - "couchdbHost": "", - "mailjet": { "apiKey": "", "senderAddress": "" }, - "frontpage": { - "frontpageDir": "" - }, - "nowplaying": { "lastfm": { "apiKey": "", @@ -67,14 +62,13 @@ In production, this application is designed to be run in Docker, and the contain > Please review the Configuration section of this document for important information. By default, the `config.json` file is expected to be mounted into the container at `/app/config.json`. ```dockerfile -FROM node:20 +FROM node:22 WORKDIR /app RUN git clone https://github.com/enstrayed/enstrayedapi . -RUN git config --global --add safe.directory /app -RUN git show --oneline -s >> GITVERSION RUN npm install +USER node ENTRYPOINT [ "node", "index.js" ] ``` diff --git a/routes/auth.js b/disabled/auth.js similarity index 89% rename from routes/auth.js rename to disabled/auth.js index 7be2973..ab57783 100644 --- a/routes/auth.js +++ b/disabled/auth.js @@ -1,5 +1,5 @@ -const { globalConfig, app } = require("../index.js") -const { logRequest } = require("../liberals/logging.js") +import { globalConfig, app } from "../index.js" +import { logRequest } from "../liberals/logging.js" app.delete("/api/token", (rreq,rres) => { fetch(`${globalConfig.couchdbHost}/auth/sessions`).then(res => res.json()).then(fetchRes => { @@ -31,4 +31,4 @@ app.delete("/api/token", (rreq,rres) => { }) }) -module.exports = {app} \ No newline at end of file +export default {app} \ No newline at end of file diff --git a/routes/etyd.js b/disabled/etyd.js similarity index 94% rename from routes/etyd.js rename to disabled/etyd.js index e389447..17e2834 100644 --- a/routes/etyd.js +++ b/disabled/etyd.js @@ -1,6 +1,6 @@ -const { app, globalConfig } = require("../index.js") // Get globals from index -const { checkToken } = require("../liberals/auth.js") -const { logRequest } = require("../liberals/logging.js") +import { app, globalConfig } from "../index.js" // Get globals from index +import { checkToken } from "../liberals/auth.js" +import { logRequest } from "../liberals/logging.js" app.get("/api/etyd*", (rreq,rres) => { fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/api/etyd","")}`).then(dbRes => { @@ -119,4 +119,4 @@ app.post("/api/etyd*", (rreq,rres) => { }) -module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file +export {app} // export routes to be imported by index for execution \ No newline at end of file diff --git a/routes/mailjet.js b/disabled/mailjet.js similarity index 89% rename from routes/mailjet.js rename to disabled/mailjet.js index 00a6e8a..7e3ab92 100644 --- a/routes/mailjet.js +++ b/disabled/mailjet.js @@ -1,6 +1,6 @@ -const { app, globalConfig } = require("../index.js") // Get globals from index -const { checkToken } = require("../liberals/auth.js") -const { logRequest } = require("../liberals/logging.js") +import { app, globalConfig } from "../index.js" // Get globals from index +import { checkToken } from "../liberals/auth.js" +import { logRequest } from "../liberals/logging.js" app.post("/api/sendemail", (rreq,rres) => { checkToken(rreq.get("Authorization"),"mailjet").then(authRes => { @@ -40,4 +40,4 @@ app.post("/api/sendemail", (rreq,rres) => { }) }) -module.exports = {app} \ No newline at end of file +export {app} \ No newline at end of file diff --git a/routes/nowplaying.js b/disabled/nowplaying.js similarity index 85% rename from routes/nowplaying.js rename to disabled/nowplaying.js index 0b4eb32..7acd304 100644 --- a/routes/nowplaying.js +++ b/disabled/nowplaying.js @@ -1,5 +1,5 @@ -const { app, globalConfig } = require("../index.js") -const { queryLastfm } = require("../liberals/libnowplaying.js") +import { app, globalConfig } from "../index.js" +import { queryLastfm } from "../liberals/libnowplaying.js" var timeSinceLastLastfmQuery = Date.now()-5000 var cachedLastfmResult = {} @@ -30,4 +30,4 @@ app.get("/api/nowplaying", (rreq,rres) => { }) -module.exports = {app} \ No newline at end of file +export {app} \ No newline at end of file diff --git a/index.js b/index.js index fbb7efb..082c881 100644 --- a/index.js +++ b/index.js @@ -1,22 +1,28 @@ -const fs = require('fs'); // Filesystem Access -const express = require('express'); -const app = express(); // Init Express +import * as fs from 'fs' +import { execSync } from 'child_process' +import express, { json } from 'express' +const app = express() -function criticalFileLoader(file) { - try { - return fs.readFileSync(file, 'utf-8') - } catch { - console.error(`FATAL: Failed to load ${file}`) - process.exit(1) - } +if (!process.env.API_DBHOST || !process.env.API_DBCRED) { + console.log("FATAL: API_DBHOST and API_DBCRED must be set") + process.exit(1) } -const globalConfig = JSON.parse(criticalFileLoader('config.json')) -const globalVersion = criticalFileLoader('GITVERSION').split(" ")[0] +const globalConfig = await fetch(`${process.env.API_DBHOST}/config/${process.env.API_DBCRED.split(":")[0]}`,{ + headers: { "Authorization": `Basic ${btoa(process.env.API_DBCRED)}`} +}).then(response => { + if (response.status !== 200) { + console.log(`FATAL: Failed to download configuration: ${response.status} ${response.statusText}`) + process.exit(1) + } else { + return response.json() + } +}) +const globalVersion = execSync(`git show --oneline -s`).toString().split(" ")[0] -module.exports = { app, globalConfig, fs, globalVersion } // Export express app and fs objects and globalconfig +export { app, fs, globalConfig, globalVersion} -app.use(express.json()) // Allows receiving JSON bodies +app.use(json()) // Allows receiving JSON bodies // see important note: https://expressjs.com/en/api.html#express.json process.on('SIGTERM', function() { @@ -24,20 +30,19 @@ process.on('SIGTERM', function() { process.exit(0) }) -// Import Routes -fs.readdir(globalConfig.startup.routesDir, (err, files) => { +fs.readdir("./routes", (err, files) => { if (err) { - console.log(`FATAL: Unable to read ${globalConfig.startup.routesDir}`) + console.log(`FATAL: Unable to import routes: ${err}`) process.exit(1) } else { let importedRoutes = [] files.forEach(file => { - require(`${globalConfig.startup.routesDir}/${file}`) + import(`./routes/${file}`) importedRoutes.push(file.slice(0,-3)) }) console.log(`Imported Routes: ${importedRoutes}`) } }) -console.log(`Enstrayed API | Version: ${globalVersion} | Port: ${globalConfig.startup.apiPort}`) -app.listen(globalConfig.startup.apiPort) \ No newline at end of file +console.log(`Enstrayed API | Version: ${globalVersion} | Port: ${process.env.API_PORT ?? 8081}`) +app.listen(process.env.API_PORT ?? 8081) \ No newline at end of file diff --git a/liberals/auth.js b/liberals/auth.js index 28b9c2b..e297c2e 100644 --- a/liberals/auth.js +++ b/liberals/auth.js @@ -1,4 +1,4 @@ -const { globalConfig } = require("../index.js") +import { globalConfig } from "../index.js" /** * Checks if a token exists in the sessions file (authentication) and if it has the correct permissions (authorization) @@ -27,4 +27,4 @@ async function checkToken(token,scope) { }) } -module.exports = {checkToken} \ No newline at end of file +export {checkToken} \ No newline at end of file diff --git a/liberals/libnowplaying.js b/liberals/libnowplaying.js index 76fd8aa..a0609e5 100644 --- a/liberals/libnowplaying.js +++ b/liberals/libnowplaying.js @@ -1,4 +1,4 @@ -const { globalConfig } = require("../index.js") +import { globalConfig } from "../index.js" /** * Queries LastFM for user set in config file and returns formatted result @@ -30,4 +30,17 @@ async function queryLastfm() { }) } -module.exports = { queryLastfm } \ No newline at end of file +// async function queryJellyfin() { +// return await fetch(`${globalConfig.nowplaying.jellyfin.host}/Sessions`, { +// headers: { +// "Authorization": `MediaBrowser Token=${globalConfig.nowplaying.jellyfin.apiKey}` +// } +// }).then(response => response.json()).then(response => { +// for (x in response) { +// if (response[x].UserName !== globalConfig.nowplaying.jellyfin.target) { break } +// if (response[x].) +// } +// }) +// } + +export { queryLastfm } \ No newline at end of file diff --git a/liberals/logging.js b/liberals/logging.js index ded7685..01d2606 100644 --- a/liberals/logging.js +++ b/liberals/logging.js @@ -23,4 +23,4 @@ function logRequest(response,request,code,extra) { console.log(`${request.get("cf-connecting-ip") ?? request.ip}${actualAuth}${request.method} ${request.path} returned ${code}${actualExtra}`) } -module.exports = { logRequest } \ No newline at end of file +export { logRequest } \ No newline at end of file diff --git a/package.json b/package.json index 6794ec1..e3e66ca 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bugs": { "url": "https://github.com/enstrayed/enstrayedapi/issues" }, + "type": "module", "devDependencies": { "@types/bun": "^1.0.12", "@types/node": "^20.12.3" diff --git a/routes/frontpage.js b/routes/frontpage.js index 75ff5fe..9a19d34 100644 --- a/routes/frontpage.js +++ b/routes/frontpage.js @@ -1,31 +1,31 @@ -const { app, globalConfig, fs, globalVersion } = require("../index.js") // Get globals from index +import { app, globalConfig, fs, globalVersion } from "../index.js" // Get globals from index var timeSinceLastQuery = Date.now()-10000 var cachedResult = "" app.get("/static/*", (rreq,rres) => { - rres.sendFile(globalConfig.frontpage.frontpageDir+"/static/"+rreq.url.replace("/static/","")) + rres.sendFile(globalConfig.frontpage.directory+"static/"+rreq.url.replace("/static/","")) }) app.get("/posts/*", (rreq,rres) => { - rres.sendFile(globalConfig.frontpage.frontpageDir+"/posts/"+rreq.url.replace("/posts/","")) + rres.sendFile(globalConfig.frontpage.directory+"posts/"+rreq.url.replace("/posts/","")) }) app.get("/", (rreq, rres) => { if (Date.now() < timeSinceLastQuery+10000) { rres.send(cachedResult) } else { - let indexFile = fs.readFileSync(globalConfig.frontpage.frontpageDir+"/index.html","utf-8") + let indexFile = fs.readFileSync(globalConfig.frontpage.directory+"index.html","utf-8") cachedResult = indexFile.replace("",parseFiles()).replace("",`API Version ${globalVersion}`) rres.send(cachedResult) } }) function parseFiles() { - let files = fs.readdirSync(globalConfig.frontpage.frontpageDir+"/posts/") + let files = fs.readdirSync(globalConfig.frontpage.directory+"posts/") let result = "" - for (x in files) { + for (let x in files) { if (files[x].endsWith(".html") === false) { break } // If file/dir is not .html then ignore let date = files[x].split("-")[0] @@ -41,4 +41,4 @@ function parseFiles() { return result } -module.exports = {app} \ No newline at end of file +export {app} \ No newline at end of file diff --git a/routes/ip.js b/routes/ip.js index a547514..8b48df4 100644 --- a/routes/ip.js +++ b/routes/ip.js @@ -1,4 +1,4 @@ -const { app } = require("../index.js") +import { app } from "../index.js" app.get("/api/ip", (rreq,rres) => { let jsonResponse = { @@ -6,7 +6,6 @@ app.get("/api/ip", (rreq,rres) => { "Country": rreq.get("cf-ipcountry") || "not_cloudflare", "CfRay": rreq.get("cf-ray") || "not_cloudflare" } - rres.send(jsonResponse) }) @@ -14,4 +13,4 @@ app.get("/api/headers", (rreq,rres) => { rres.send(rreq.headers) }) -module.exports = {app} \ No newline at end of file +export { app } \ No newline at end of file