More repo maintenance & add Readme
This commit is contained in:
@@ -1,9 +0,0 @@
|
|||||||
FROM node:20
|
|
||||||
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
|
|
||||||
|
|
||||||
ENTRYPOINT [ "node", "index.js" ]
|
|
||||||
105
README.md
Normal file
105
README.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Enstrayed API
|
||||||
|
This repository contains the code for my personal web API written in JavaScript using the Express framework.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
This file contains documentation relevant for development and deployment, but not necessarily usage. Information for all endpoints is available [on my website](https://music.youtube.com/watch?v=n2LxBXS4jJM&si=ZpzGNBGvQp1cFicW).
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
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.
|
||||||
|
|
||||||
|
<details> <summary>Configuration Example</summary>
|
||||||
|
|
||||||
|
* `couchdb.host`: Hostname/IP address and port of a CouchDB server.
|
||||||
|
* `couchdb.authorization`: Username & password used to access the CouchDB server, in HTTP Basic authentication format, e.g. `username:password`.
|
||||||
|
* `blog.postsDirectory`: Directory that will be parsed when calling /blogposts. If running in Docker this directory will need to be mounted to the container.
|
||||||
|
* `blog.postsDirUrl`: Location of the posts directory on the web server.
|
||||||
|
* `nowplaying.*.target`: Set to the Last.fm/Jellyfin username to query for playback information.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"startup": {
|
||||||
|
"apiPort": 8081,
|
||||||
|
"routesDir": "./routes"
|
||||||
|
},
|
||||||
|
|
||||||
|
"couchdb": {
|
||||||
|
"host": "hazeldale:5984",
|
||||||
|
"authorization": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"mailjet": {
|
||||||
|
"apiKey": "",
|
||||||
|
"senderAddress": "apinotifications@enstrayed.com",
|
||||||
|
"senderName": "API Notifications",
|
||||||
|
|
||||||
|
"authKeysDoc": "mailjet"
|
||||||
|
},
|
||||||
|
|
||||||
|
"blog": {
|
||||||
|
"postsDirectory": "C:/Users/natha/Downloads/proto/posts",
|
||||||
|
"postsDirUrl": "/posts"
|
||||||
|
},
|
||||||
|
|
||||||
|
"nowplaying": {
|
||||||
|
"lastfm": {
|
||||||
|
"apiKey": "",
|
||||||
|
"target": "enstrayed"
|
||||||
|
},
|
||||||
|
"jellyfin": {
|
||||||
|
"apiKey": "",
|
||||||
|
"host": "",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
"cider": {
|
||||||
|
"apiKeys": [],
|
||||||
|
"hosts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
In production, this application is designed to be run in Docker, and the container built by pulling the latest commit from the main branch. As such, deploying this application is just a matter of creating a directory and copying the Dockerfile:
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> 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
|
||||||
|
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
|
||||||
|
|
||||||
|
ENTRYPOINT [ "node", "index.js" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
<details> <summary>Docker Compose File</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
enstrayedapi:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
image: enstrayedapi
|
||||||
|
container_name: enstrayedapi
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./config.json:/app/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## License
|
||||||
|
If for whatever reason you want to, you are free to adapt this code for your own projects or as reference. However, this software is provided as-is with no warranty or agreement to support it.
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"startup": {
|
|
||||||
"apiPort": 8081,
|
|
||||||
"routesDir": "./routes"
|
|
||||||
},
|
|
||||||
|
|
||||||
"couchdb": {
|
|
||||||
"host": "hazeldale:5984",
|
|
||||||
"authorization": ""
|
|
||||||
},
|
|
||||||
|
|
||||||
"mailjet": {
|
|
||||||
"apiKey": "",
|
|
||||||
"senderAddress": "apinotifications@enstrayed.com",
|
|
||||||
"senderName": "API Notifications",
|
|
||||||
|
|
||||||
"authKeysDoc": "mailjet"
|
|
||||||
},
|
|
||||||
|
|
||||||
"blog": {
|
|
||||||
"postsDirectory": "C:/Users/natha/Downloads/proto/posts",
|
|
||||||
"postsDirUrl": "/posts"
|
|
||||||
},
|
|
||||||
|
|
||||||
"nowplaying": {
|
|
||||||
"lastfm": {
|
|
||||||
"apiKey": "",
|
|
||||||
"target": "enstrayed"
|
|
||||||
},
|
|
||||||
"jellyfin": {
|
|
||||||
"apiKey": "",
|
|
||||||
"host": "",
|
|
||||||
"target": ""
|
|
||||||
},
|
|
||||||
"cider": {
|
|
||||||
"apiKeys": [],
|
|
||||||
"hosts": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
const { app, db, globalConfig } = require("../index.js") // Get globals from index
|
|
||||||
|
|
||||||
var timeSinceLastCiderQuery = Date.now()-2000;
|
|
||||||
var currentListening = {}
|
|
||||||
var currentListeningHtml = ""
|
|
||||||
|
|
||||||
app.get("/cider", (rreq,rres) => {
|
|
||||||
|
|
||||||
rres.send("<span>Cider endpoint is temporarily unavailable.</span>")
|
|
||||||
})
|
|
||||||
|
|
||||||
// app.get("/cider", (rreq,rres) => { // GET current listening from target
|
|
||||||
|
|
||||||
// if (Date.now() < timeSinceLastCiderQuery+2000) {
|
|
||||||
// rres.send(currentListening); // If it has been <2 seconds since the last request, return the cached result.
|
|
||||||
// } else {
|
|
||||||
// getCurrentListening(globalConfig.cider.targetHosts[0],"json").then(funcRes => {
|
|
||||||
// if (funcRes == 1) {
|
|
||||||
// rres.sendStatus(503) // If there was a problem getting the upstream JSON, return 503 Service Unavailable.
|
|
||||||
// } else {
|
|
||||||
// // Required (I think?) because of CORS.
|
|
||||||
// currentListening = funcRes
|
|
||||||
// rres.send(funcRes)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// })
|
|
||||||
|
|
||||||
// app.post("/cider", (rreq,rres) => { // POST stop listening on cider target
|
|
||||||
|
|
||||||
// fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.cider.authKeysDoc}`, {
|
|
||||||
// headers: {
|
|
||||||
// "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
|
|
||||||
// }
|
|
||||||
// }).then(dbRes => dbRes.json()).then(dbRes => {
|
|
||||||
|
|
||||||
// if (dbRes.status == 404) { // If document containing cider auth keys does not exist
|
|
||||||
// console.log(`ERROR: Could not find apiauthkeys/${globalConfig.mailjet.authKeysDoc}`)
|
|
||||||
// rres.sendStatus(500) // Refuse request
|
|
||||||
// } else {
|
|
||||||
// if (dbRes["content"][rreq.get("Authorization").split("_")[0]] === rreq.get("Authorization").split("_")[1]) {
|
|
||||||
|
|
||||||
// fetch(`http://${globalConfig.cider.targetHosts[0]}/stop`).then(fres => { // send GET /stop to cider target
|
|
||||||
// if (fres.status == 204) {
|
|
||||||
// console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 200 KEY:${rreq.get("Authorization")}`)
|
|
||||||
// rres.sendStatus(200) // if that works then 200
|
|
||||||
// } else {
|
|
||||||
// rres.sendStatus(500) // otherwise lol
|
|
||||||
// }
|
|
||||||
// }).catch(ferror => {
|
|
||||||
// rres.sendStatus(503) // and if a problem happens its probably cause cider target is unavailable
|
|
||||||
// })
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
// console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 401`) // log ip of unauthorized requests
|
|
||||||
// rres.sendStatus(401) // received auth key was not in database
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 2024-04-10: Retrieves currentPlayingSong JSON from specified Cider host and
|
|
||||||
// returns JSON/HTML containing the useful bits if successful, returning 1 if not.
|
|
||||||
|
|
||||||
// async function getCurrentListening(host,contentType) { // 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 {
|
|
||||||
// if (contentType === "json") {
|
|
||||||
// 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)
|
|
||||||
// }
|
|
||||||
// } else if (contentType === "html") {
|
|
||||||
// return `<img src="${jsonRes.info.artwork.url.replace("{w}", jsonRes.info.artwork.width).replace("{h}", jsonRes.info.artwork.height)}" alt="Album Art" id="nowplaying-albumart" style="width: 10em;"> <div class="textlist"><p>I'm listening to</p><h3>${`${jsonRes.info.name} by ${jsonRes.info.artistName}`}</h3><p>from ${jsonRes.info.albumName}</p><a href="${jsonRes.info.url.songLink}" class="noindent">song.link</a></div>`
|
|
||||||
// } else {
|
|
||||||
// return 1
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }).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
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
services:
|
|
||||||
enstrayedapi:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
image: enstrayedapi
|
|
||||||
container_name: enstrayedapi
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- ./config.json:/app/config.json
|
|
||||||
@@ -4,11 +4,8 @@
|
|||||||
},
|
},
|
||||||
"name": "enstrayedapi",
|
"name": "enstrayedapi",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "api.enstrayed.com",
|
"description": "EnstrayedAPI",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/enstrayed/enstrayedapi.git"
|
"url": "git+https://github.com/enstrayed/enstrayedapi.git"
|
||||||
@@ -18,7 +15,6 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/enstrayed/enstrayedapi/issues"
|
"url": "https://github.com/enstrayed/enstrayedapi/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://api.enstrayed.com",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.0.12",
|
"@types/bun": "^1.0.12",
|
||||||
"@types/node": "^20.12.3"
|
"@types/node": "^20.12.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user