""refactor"" basically everything

This commit is contained in:
Enstrayed
2024-09-22 14:04:15 -07:00
parent 2e527a8539
commit 5f1181476d
14 changed files with 78 additions and 90 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,4 @@
node_modules/
config.json
bun.lockb
GITVERSION
todo.txt
proto.js

View File

@@ -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
}
}

View File

@@ -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` |
<details> <summary>Configuration Example</summary>
* `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" ]
```

View File

@@ -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}
export default {app}

View File

@@ -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
export {app} // export routes to be imported by index for execution

View File

@@ -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}
export {app}

View File

@@ -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}
export {app}

View File

@@ -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)
console.log(`Enstrayed API | Version: ${globalVersion} | Port: ${process.env.API_PORT ?? 8081}`)
app.listen(process.env.API_PORT ?? 8081)

View File

@@ -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}
export {checkToken}

View File

@@ -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 }
// 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 }

View File

@@ -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 }
export { logRequest }

View File

@@ -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"

View File

@@ -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("<!--SSR_BLOGPOSTS-->",parseFiles()).replace("<!--SSR_APIVERSION-->",`<sup>API Version ${globalVersion}</sup>`)
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}
export {app}

View File

@@ -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}
export { app }