diff --git a/.gitignore b/.gitignore index 29b0255..b068519 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ config.json -bun.lockb \ No newline at end of file +bun.lockb +GITVERSION \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5f9f46d..fbcad3c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ FROM node:20 WORKDIR /app -EXPOSE 8127 -CMD [ "bash", "init.sh" ] -# This is just copied from urlshortener cause both apps have similar architecture +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 + +ENTRYPOINT [ "node", "index.js" ] \ No newline at end of file diff --git a/config.example.json b/config.example.json index af5faa9..2192d6e 100644 --- a/config.example.json +++ b/config.example.json @@ -1,8 +1,7 @@ { "startup": { "apiPort": 8081, - "routesDir": "./routes", - "documentationUrl": "https://api.enstrayed.com" + "routesDir": "./routes" }, "couchdb": { diff --git a/docker-compose.yml b/docker-compose.yml index 4e25782..4f58d54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,4 @@ -version: '3.0' - +--- services: enstrayedapi: build: @@ -8,4 +7,4 @@ services: container_name: enstrayedapi restart: unless-stopped volumes: - - .:/app \ No newline at end of file + - ./config.json:/app/config.json \ No newline at end of file diff --git a/index.js b/index.js index 0e2b2e4..831794d 100644 --- a/index.js +++ b/index.js @@ -2,13 +2,28 @@ const fs = require('fs'); // Filesystem Access const express = require('express'); const app = express(); // Init Express -const globalConfig = JSON.parse(fs.readFileSync('config.json', 'utf-8')) // Read config file +function criticalFileLoader(file) { + try { + return fs.readFileSync(file, 'utf-8') + } catch { + console.error(`FATAL: Failed to load ${file}`) + process.exit(1) + } +} + +const globalConfig = JSON.parse(criticalFileLoader('config.json')) +const globalVersion = criticalFileLoader('GITVERSION').split(" ")[0] module.exports = { app, globalConfig, fs } // Export express app and fs objects and globalconfig app.use(express.json()) // Allows receiving JSON bodies // see important note: https://expressjs.com/en/api.html#express.json +process.on('SIGTERM', function() { + console.log("Received SIGTERM, exiting...") + process.exit(0) +}) + // Import Routes fs.readdir(globalConfig.startup.routesDir, (err, files) => { if (err) { @@ -25,8 +40,8 @@ fs.readdir(globalConfig.startup.routesDir, (err, files) => { }) app.get("/", (rreq,rres) => { - rres.redirect(globalConfig.startup.documentationUrl) + rres.send(`Enstrayed API | Version: ${globalVersion} | Documentation: etyd.cc/apidocs`) }) -console.log(`Started on ${globalConfig.startup.apiPort}`) +console.log(`Enstrayed API | Version: ${globalVersion} | Port: ${globalConfig.startup.apiPort}`) app.listen(globalConfig.startup.apiPort) \ No newline at end of file diff --git a/init.sh b/init.sh deleted file mode 100644 index c6a65d2..0000000 --- a/init.sh +++ /dev/null @@ -1,4 +0,0 @@ -#! /bin/bash - -npm install -node index.js \ No newline at end of file diff --git a/routes/cider.js b/routes/cider.js index 546cdd3..b309411 100644 --- a/routes/cider.js +++ b/routes/cider.js @@ -1,42 +1,27 @@ const { app, db, globalConfig } = require("../index.js") // Get globals from index var timeSinceLastCiderQuery = Date.now()-2000; -var currentListening = {} // GET cache storage +var currentListening = {} app.get("/cider", (rreq,rres) => { // GET current listening from target - if (Date.now() < timeSinceLastCiderQuery+2000) { // if it has been <2 seconds since last request - rres.send(currentListening); // send cached json - // console.log(`Sent cached response`); + if (Date.now() < timeSinceLastCiderQuery+2000) { + rres.send(currentListening); // If it has been <2 seconds since the last request, return the cached result. } else { - getCurrentListening().then(res => { // fetch JSON from target - if (res == "unreachable") { // if fetch returned unreachable - rres.sendStatus(503) // send service unavailable to requestee + getCurrentListening(globalConfig.cider.targetHosts[0]).then(funcRes => { + if (funcRes == 1) { + rres.sendStatus(503) // If there was a problem getting the upstream JSON, return 503 Service Unavailable. } else { - currentListening = { // format source JSON and store to cache - "songName": res.info.name, - "artistName": res.info.artistName, - "albumName": res.info.albumName, - "songLinkUrl": res.info.url.songLink, - "endtimeEpochInMs": res.info.endTime - }; - - // Formats info.artwork.url from upstream Cider Endpoint - let workingArtworkUrl = res.info.artwork.url - workingArtworkUrl = workingArtworkUrl.replace("{w}",res.info.artwork.width) - workingArtworkUrl = workingArtworkUrl.replace("{h}",res.info.artwork.height) - currentListening.artworkUrl = workingArtworkUrl - - rres.set("Access-Control-Allow-Origin","*") - rres.send(currentListening) // send freshly cached json + rres.set("Access-Control-Allow-Origin","*") // Required (I think?) because of CORS. + currentListening = funcRes + rres.send(funcRes) } - }) - // console.log(`Sent uncached response`); } }) + app.post("/cider", (rreq,rres) => { // POST stop listening on cider target fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.cider.authKeysDoc}`, { @@ -71,12 +56,33 @@ app.post("/cider", (rreq,rres) => { // POST stop listening on cider target }) -async function getCurrentListening() { // async function to actually get and return the json (this is just adapted from the original gist) - timeSinceLastCiderQuery = Date.now(); // update last query time - return await fetch(`http://${globalConfig.cider.targetHosts[0]}/currentPlayingSong`).then(res => res.json()).catch(err => { // fetch, format and return JSON - return "unreachable" +// 2024-04-10: Retrieves currentPlayingSong JSON from specified Cider host and +// returns JSON containing the useful bits if successful, returning 1 if not. +async function getCurrentListening(host) { // Host should be hostname/ip & port only. + timeSinceLastCiderQuery = Date.now(); // Save last time function was run, used to indicate when the cache needs refreshed. + return await fetch(`http://${host}/currentPlayingSong`).then(fetchRes => { + if (fetchRes.status == 502) { + return 1 // If the upstream server returns 502 (Bad Gateway) then internally return 1, indicating error. + } else { + return fetchRes.json().then(jsonRes => { + if (jsonRes.info.name == undefined) { + return 1 // If Cider is running but not playing a song this check prevents an undefined variable error. + } else { + return { + "songName": jsonRes.info.name, + "artistName": jsonRes.info.artistName, + "albumName": jsonRes.info.albumName, + "songLinkUrl": jsonRes.info.url.songLink, + "endtimeEpochInMs": jsonRes.info.endTime, + "artworkUrl": jsonRes.info.artwork.url.replace("{w}", jsonRes.info.artwork.width).replace("{h}", jsonRes.info.artwork.height) + } + } + }) + } + }).catch(fetchError => { + console.error("Error fetch()ing upstream Cider host: "+fetchError) + return 1 // If something else happens then log it and return 1, indicating error. }) - } module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file diff --git a/routes/etyd.js b/routes/etyd.js index 140eb16..699fd0e 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -1,24 +1,5 @@ const { app, db, globalConfig } = require("../index.js") // Get globals from index -// 2024-04-05: Unused because trying to put randomization server side just made no sense -// function makeRandomHex() { -// const characters = "1234567890abcdef" -// let counter = 0 -// let result = "" -// while (counter < globalConfig.etyd.randomHexLength) { -// result += characters.charAt(Math.floor(Math.random() * characters.length)) -// counter += 1 -// } -// return result -// } - -// 2024-04-05: Defining OPTIONS for browser prefetch is no longer necessary as CORS is not going to be used -// app.options("/etydwrite", (rreq,rres) => { -// rres.set("Access-Control-Allow-Headers","Authorization") -// rres.set("Access-Control-Allow-Origin","*") -// rres.sendStatus(204) -// }) - app.get("/etyd*", (rreq,rres) => { fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd","")}`, { headers: {