""refactor"" basically everything
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
config.json
|
|
||||||
bun.lockb
|
bun.lockb
|
||||||
GITVERSION
|
|
||||||
todo.txt
|
todo.txt
|
||||||
proto.js
|
proto.js
|
||||||
22
Caddyfile
22
Caddyfile
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
README.md
28
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.
|
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
|
## Configuration
|
||||||
On startup, this application will look for two files. If either cannot be read, it will exit with an error code.
|
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:
|
||||||
1. `config.json` contains settings required for operation and API keys used for calling external services.
|
| Variable | Required? | Purpose |
|
||||||
2. `GITVERSION` contains the commit that was cloned when the container was built.
|
|--------------|----------------------|-----------------------------------------------------------------------------------------------------|
|
||||||
|
| `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>
|
<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.apiKey`: Mailjet API Key.
|
||||||
* `mailjet.senderAddress`: Email address that emails will be received from, must be verified in Mailjet admin panel.
|
* `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.*.apiKey`: API key of respective service.
|
||||||
* `nowplaying.*.target`: User that should be queried to retrieve playback information.
|
* `nowplaying.*.target`: User that should be queried to retrieve playback information.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"startup": {
|
"frontpage": {
|
||||||
"apiPort": 8081,
|
"directory": ""
|
||||||
"routesDir": "./routes"
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"couchdbHost": "",
|
|
||||||
|
|
||||||
"mailjet": {
|
"mailjet": {
|
||||||
"apiKey": "",
|
"apiKey": "",
|
||||||
"senderAddress": ""
|
"senderAddress": ""
|
||||||
},
|
},
|
||||||
|
|
||||||
"frontpage": {
|
|
||||||
"frontpageDir": ""
|
|
||||||
},
|
|
||||||
|
|
||||||
"nowplaying": {
|
"nowplaying": {
|
||||||
"lastfm": {
|
"lastfm": {
|
||||||
"apiKey": "",
|
"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`.
|
> 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
|
```dockerfile
|
||||||
FROM node:20
|
FROM node:22
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN git clone https://github.com/enstrayed/enstrayedapi .
|
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
|
RUN npm install
|
||||||
|
|
||||||
|
USER node
|
||||||
ENTRYPOINT [ "node", "index.js" ]
|
ENTRYPOINT [ "node", "index.js" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const { globalConfig, app } = require("../index.js")
|
import { globalConfig, app } from "../index.js"
|
||||||
const { logRequest } = require("../liberals/logging.js")
|
import { logRequest } from "../liberals/logging.js"
|
||||||
|
|
||||||
app.delete("/api/token", (rreq,rres) => {
|
app.delete("/api/token", (rreq,rres) => {
|
||||||
fetch(`${globalConfig.couchdbHost}/auth/sessions`).then(res => res.json()).then(fetchRes => {
|
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}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
const { app, globalConfig } = require("../index.js") // Get globals from index
|
import { app, globalConfig } from "../index.js" // Get globals from index
|
||||||
const { checkToken } = require("../liberals/auth.js")
|
import { checkToken } from "../liberals/auth.js"
|
||||||
const { logRequest } = require("../liberals/logging.js")
|
import { logRequest } from "../liberals/logging.js"
|
||||||
|
|
||||||
app.get("/api/etyd*", (rreq,rres) => {
|
app.get("/api/etyd*", (rreq,rres) => {
|
||||||
fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/api/etyd","")}`).then(dbRes => {
|
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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
const { app, globalConfig } = require("../index.js") // Get globals from index
|
import { app, globalConfig } from "../index.js" // Get globals from index
|
||||||
const { checkToken } = require("../liberals/auth.js")
|
import { checkToken } from "../liberals/auth.js"
|
||||||
const { logRequest } = require("../liberals/logging.js")
|
import { logRequest } from "../liberals/logging.js"
|
||||||
|
|
||||||
app.post("/api/sendemail", (rreq,rres) => {
|
app.post("/api/sendemail", (rreq,rres) => {
|
||||||
checkToken(rreq.get("Authorization"),"mailjet").then(authRes => {
|
checkToken(rreq.get("Authorization"),"mailjet").then(authRes => {
|
||||||
@@ -40,4 +40,4 @@ app.post("/api/sendemail", (rreq,rres) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {app}
|
export {app}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
const { app, globalConfig } = require("../index.js")
|
import { app, globalConfig } from "../index.js"
|
||||||
const { queryLastfm } = require("../liberals/libnowplaying.js")
|
import { queryLastfm } from "../liberals/libnowplaying.js"
|
||||||
|
|
||||||
var timeSinceLastLastfmQuery = Date.now()-5000
|
var timeSinceLastLastfmQuery = Date.now()-5000
|
||||||
var cachedLastfmResult = {}
|
var cachedLastfmResult = {}
|
||||||
@@ -30,4 +30,4 @@ app.get("/api/nowplaying", (rreq,rres) => {
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {app}
|
export {app}
|
||||||
45
index.js
45
index.js
@@ -1,22 +1,28 @@
|
|||||||
const fs = require('fs'); // Filesystem Access
|
import * as fs from 'fs'
|
||||||
const express = require('express');
|
import { execSync } from 'child_process'
|
||||||
const app = express(); // Init Express
|
import express, { json } from 'express'
|
||||||
|
const app = express()
|
||||||
|
|
||||||
function criticalFileLoader(file) {
|
if (!process.env.API_DBHOST || !process.env.API_DBCRED) {
|
||||||
try {
|
console.log("FATAL: API_DBHOST and API_DBCRED must be set")
|
||||||
return fs.readFileSync(file, 'utf-8')
|
process.exit(1)
|
||||||
} catch {
|
|
||||||
console.error(`FATAL: Failed to load ${file}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const globalConfig = JSON.parse(criticalFileLoader('config.json'))
|
const globalConfig = await fetch(`${process.env.API_DBHOST}/config/${process.env.API_DBCRED.split(":")[0]}`,{
|
||||||
const globalVersion = criticalFileLoader('GITVERSION').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
|
// see important note: https://expressjs.com/en/api.html#express.json
|
||||||
|
|
||||||
process.on('SIGTERM', function() {
|
process.on('SIGTERM', function() {
|
||||||
@@ -24,20 +30,19 @@ process.on('SIGTERM', function() {
|
|||||||
process.exit(0)
|
process.exit(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Import Routes
|
fs.readdir("./routes", (err, files) => {
|
||||||
fs.readdir(globalConfig.startup.routesDir, (err, files) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(`FATAL: Unable to read ${globalConfig.startup.routesDir}`)
|
console.log(`FATAL: Unable to import routes: ${err}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
} else {
|
} else {
|
||||||
let importedRoutes = []
|
let importedRoutes = []
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
require(`${globalConfig.startup.routesDir}/${file}`)
|
import(`./routes/${file}`)
|
||||||
importedRoutes.push(file.slice(0,-3))
|
importedRoutes.push(file.slice(0,-3))
|
||||||
})
|
})
|
||||||
console.log(`Imported Routes: ${importedRoutes}`)
|
console.log(`Imported Routes: ${importedRoutes}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`Enstrayed API | Version: ${globalVersion} | Port: ${globalConfig.startup.apiPort}`)
|
console.log(`Enstrayed API | Version: ${globalVersion} | Port: ${process.env.API_PORT ?? 8081}`)
|
||||||
app.listen(globalConfig.startup.apiPort)
|
app.listen(process.env.API_PORT ?? 8081)
|
||||||
@@ -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)
|
* 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}
|
||||||
@@ -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
|
* 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 }
|
||||||
@@ -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}`)
|
console.log(`${request.get("cf-connecting-ip") ?? request.ip}${actualAuth}${request.method} ${request.path} returned ${code}${actualExtra}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { logRequest }
|
export { logRequest }
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/enstrayed/enstrayedapi/issues"
|
"url": "https://github.com/enstrayed/enstrayedapi/issues"
|
||||||
},
|
},
|
||||||
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.0.12",
|
"@types/bun": "^1.0.12",
|
||||||
"@types/node": "^20.12.3"
|
"@types/node": "^20.12.3"
|
||||||
|
|||||||
@@ -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 timeSinceLastQuery = Date.now()-10000
|
||||||
var cachedResult = ""
|
var cachedResult = ""
|
||||||
|
|
||||||
app.get("/static/*", (rreq,rres) => {
|
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) => {
|
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) => {
|
app.get("/", (rreq, rres) => {
|
||||||
if (Date.now() < timeSinceLastQuery+10000) {
|
if (Date.now() < timeSinceLastQuery+10000) {
|
||||||
rres.send(cachedResult)
|
rres.send(cachedResult)
|
||||||
} else {
|
} 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>`)
|
cachedResult = indexFile.replace("<!--SSR_BLOGPOSTS-->",parseFiles()).replace("<!--SSR_APIVERSION-->",`<sup>API Version ${globalVersion}</sup>`)
|
||||||
rres.send(cachedResult)
|
rres.send(cachedResult)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function parseFiles() {
|
function parseFiles() {
|
||||||
let files = fs.readdirSync(globalConfig.frontpage.frontpageDir+"/posts/")
|
let files = fs.readdirSync(globalConfig.frontpage.directory+"posts/")
|
||||||
let result = ""
|
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
|
if (files[x].endsWith(".html") === false) { break } // If file/dir is not .html then ignore
|
||||||
|
|
||||||
let date = files[x].split("-")[0]
|
let date = files[x].split("-")[0]
|
||||||
@@ -41,4 +41,4 @@ function parseFiles() {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {app}
|
export {app}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const { app } = require("../index.js")
|
import { app } from "../index.js"
|
||||||
|
|
||||||
app.get("/api/ip", (rreq,rres) => {
|
app.get("/api/ip", (rreq,rres) => {
|
||||||
let jsonResponse = {
|
let jsonResponse = {
|
||||||
@@ -6,7 +6,6 @@ app.get("/api/ip", (rreq,rres) => {
|
|||||||
"Country": rreq.get("cf-ipcountry") || "not_cloudflare",
|
"Country": rreq.get("cf-ipcountry") || "not_cloudflare",
|
||||||
"CfRay": rreq.get("cf-ray") || "not_cloudflare"
|
"CfRay": rreq.get("cf-ray") || "not_cloudflare"
|
||||||
}
|
}
|
||||||
|
|
||||||
rres.send(jsonResponse)
|
rres.send(jsonResponse)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -14,4 +13,4 @@ app.get("/api/headers", (rreq,rres) => {
|
|||||||
rres.send(rreq.headers)
|
rres.send(rreq.headers)
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {app}
|
export { app }
|
||||||
Reference in New Issue
Block a user