From 8e769996e13ace583d484358959502122eb7395c Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Sun, 23 Jun 2024 12:54:57 -0700 Subject: [PATCH 1/4] working changes for new auth --- Caddyfile | 1 + etydFrontend/_static/etyd.js | 58 ++++++++++++++++++++------ etydFrontend/_static/index.css | 7 +++- etydFrontend/index.html | 75 ++++++++++++---------------------- liberals/auth.js | 36 ++++++++++++++++ routes/etyd.js | 12 ++---- routes/mailjet.js | 7 ++-- 7 files changed, 121 insertions(+), 75 deletions(-) create mode 100644 liberals/auth.js diff --git a/Caddyfile b/Caddyfile index eb42a99..aea8b6c 100644 --- a/Caddyfile +++ b/Caddyfile @@ -10,6 +10,7 @@ } handle @staticpaths { + respond /favicon.ico 204 root ./etydFrontend file_server } diff --git a/etydFrontend/_static/etyd.js b/etydFrontend/_static/etyd.js index 5879b9d..79483e7 100644 --- a/etydFrontend/_static/etyd.js +++ b/etydFrontend/_static/etyd.js @@ -1,11 +1,22 @@ -//Firefox check window.onload = function() { - document.getElementById('resultfeed').value = "hii :3" if (navigator.userAgent.includes("Firefox")) { - document.getElementById('resultfeed').value += `\nClipboard functionality does not work on Firefox.` - document.getElementById('clipboard1').disabled = true - document.getElementById('clipboard2').disabled = true + document.getElementById('resultfeed').value += `\nClipboard buttons only work on Firefox >127.` } + + // Event listeners can only be added after the page is loaded + document.getElementById("actiondropdown").addEventListener("change", function() { + if (document.getElementById("actiondropdown").value === "POST") { + document.getElementById("randomizationtoggle").disabled = false + document.getElementById("valuefield").disabled = false + } else if (document.getElementById("actiondropdown").value === "DELETE") { + document.getElementById("randomizationtoggle").disabled = true + document.getElementById("randomizationtoggle").checked = false + randomUrlTick() + document.getElementById("valuefield").disabled = true + } else { + console.error("UI Code Error: Action dropdown event listener function reached impossible state") + } + }) } function makeRandomHex(amount) { @@ -19,6 +30,8 @@ function makeRandomHex(amount) { return result } + + function randomUrlTick() { if (document.getElementById("randomizationtoggle").checked == true) { document.getElementById("targetfield").disabled = true @@ -29,9 +42,9 @@ function randomUrlTick() { } } -function buttonCopyResult() { - navigator.clipboard.writeText(`${document.location.href}${document.getElementById("urlfield").value}`) -} +// function buttonCopyResult() { +// navigator.clipboard.writeText(`${document.location.href}${document.getElementById("urlfield").value}`) +// } function buttonFillFromClipboard() { navigator.clipboard.readText().then(res => { @@ -39,9 +52,29 @@ function buttonFillFromClipboard() { }) } -function postData() { - fetch("http://nrdesktop:8081/etydwrite", { - method: "POST", +// Changes the buttons text to OK for 500ms for action feedback +// "internal" in this context just means not called from the page +function internalButtonConfirmation(element) { + let normalValue = document.getElementById(element).innerHTML + document.getElementById(element).innerHTML = "Ok" + setTimeout(function() { + document.getElementById(element).innerHTML = normalValue + }, 500) +} + +function buttonCopyUrl() { + navigator.clipboard.writeText(`this doesn't work rn lol`) + internalButtonConfirmation("buttonCopyUrl") +} + +function buttonClearLog() { + document.getElementById("resultfeed").value = "" + internalButtonConfirmation("buttonClearLog") +} + +function submitData() { + fetch(`http://nrdesktop:8081/etyd${document.getElementById("targetfield").value}`, { + method: document.getElementById("actiondropdown").value, mode: "cors", headers: { "Authorization": document.getElementById("authfield").value @@ -57,5 +90,4 @@ function postData() { }).catch(error => { document.getElementById("resultfeed").value += `\nError: ${error}` }) -} - +} \ No newline at end of file diff --git a/etydFrontend/_static/index.css b/etydFrontend/_static/index.css index 66ddd02..b4b4698 100644 --- a/etydFrontend/_static/index.css +++ b/etydFrontend/_static/index.css @@ -11,6 +11,10 @@ body { margin-right: 1em; } +.marginbottom1em { + margin-bottom: 1em; +} + .resultfeed { height: 100%; } @@ -30,7 +34,8 @@ body { input, select, textarea, button { background: none; color: white; - border: 2px solid white; + border: 1px solid white; + padding: 1px 2px; } input:disabled, button:disabled { diff --git a/etydFrontend/index.html b/etydFrontend/index.html index 4b9026d..64e4571 100644 --- a/etydFrontend/index.html +++ b/etydFrontend/index.html @@ -1,3 +1,4 @@ + @@ -7,66 +8,44 @@ etyd.cc -

etyd.cc URL Shortener

-
-
+
+
- -
-
+ + + +
+ + +
-
-
+
+ + +
-
- -
+
+ + + +
-
- - - - -

+
+ + + +
-
- -
- - - -
-
-

Instructions

-

- 1. Enter your API Key in the 'Authorization' field
- 2. Enter the shortened URL you want to act upon under the 'URL' field
- 3. Enter the URL that the user will be redirected to under the 'Value' field
- 4. Change 'Action' depending if you want to create or delete a URL
- 5. Press 'POST Data' to submit the form to the server -

-
- -
-

Status Code Reference

-

- 400: Bad Request - You will see this if you try and delete a non-existent URL
- 401: Unauthorized - Did you enter your API key?
- 409: Conflict - The entered URL already exists, tick 'Random' and try again
- 500: Internal Server Error - If this happens something has gone very wrong
- 502: Bad Gateway - If you see this the backend is down/unreachable by Caddy
-

-
diff --git a/liberals/auth.js b/liberals/auth.js new file mode 100644 index 0000000..115523c --- /dev/null +++ b/liberals/auth.js @@ -0,0 +1,36 @@ +const { globalConfig } = require("../index.js") + +async function checkToken(token,scope) { + return await fetch(`http://${globalConfig.couchdb.host}/auth/sessions`, { + headers: { + "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` + } + }).then(fetchRes => { + + // CouchDB should only ever return 200/304 for success so this should work + // https://docs.couchdb.org/en/stable/api/document/common.html#get--db-docid + if (fetchRes.status !== 200 || fetchRes.status !== 304) { + console.log(`ERROR: auth.js: Database request returned ${fetchRes.status}`) + return false + } else { + + return fetchRes.json().then(dbRes => { + + if (dbRes.sessions[token] == undefined) { // If the token is not on the sessions list then reject + return false + } else if (dbRes.sessions[token].scopes.includes(scope)) { // If the token is on the seesions list and includes the scope then accept + return true + } else { // Otherwise reject + return false + } + + }) + } + + }).catch(error => { + console.log("ERROR: auth.js: " + error) + return false + }) +} + +module.exports = {checkToken} \ No newline at end of file diff --git a/routes/etyd.js b/routes/etyd.js index ca39d10..87ed09a 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -1,5 +1,5 @@ const { app, globalConfig } = require("../index.js") // Get globals from index -const { checkAuthorization } = require("../liberals/authorization.js") +const { checkToken } = require("../liberals/auth.js") app.get("/etyd*", (rreq,rres) => { fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd","")}`, { @@ -30,9 +30,8 @@ app.delete("/etyd*", (rreq,rres) => { if (rreq.get("Authorization") === undefined) { rres.sendStatus(400) } else { - checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).then(authRes => { + checkToken(rreq.get("Authorization"),"etyd").then(authRes => { if (authRes === false) { - console.log(`${rreq.get("cf-connecting-ip")} DELETE ${rreq.path} returned 401`) // Log unauthorized requests rres.sendStatus(401) } else if (authRes === true) { // Authorization successful @@ -82,14 +81,12 @@ app.post("/etyd*", (rreq,rres) => { if (rreq.get("Authorization") === undefined) { rres.sendStatus(400) } else { - checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).then(authRes => { + checkToken(rreq.get("Authorization"),"etyd").then(authRes => { if (authRes === false) { - console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 401`) // Log unauthorized requests rres.sendStatus(401) } else if (authRes === true) { // Authorization successful if (rreq.body["url"] == undefined) { - console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 400 KEY: ${rreq.get("Authorization")}`) rres.sendStatus(400) } else { fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { @@ -106,12 +103,10 @@ app.post("/etyd*", (rreq,rres) => { switch(dbRes.status) { case 409: - console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 409 KEY: ${rreq.get("Authorization")}`) rres.sendStatus(409) break; case 201: - console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 200 KEY: ${rreq.get("Authorization")}`) rres.status(200).send(rreq.path.replace("/etyd", "")) break; @@ -132,5 +127,4 @@ app.post("/etyd*", (rreq,rres) => { }) - module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file diff --git a/routes/mailjet.js b/routes/mailjet.js index b26e9d5..19d5e35 100644 --- a/routes/mailjet.js +++ b/routes/mailjet.js @@ -1,9 +1,9 @@ const { app, globalConfig } = require("../index.js") // Get globals from index -const { checkAuthorization } = require("../liberals/authorization.js") +const { checkToken } = require("../liberals/auth.js") app.post("/sendemail", (rreq,rres) => { - checkAuthorization(globalConfig.mailjet.authKeysDoc,rreq.get("Authorization")).then(authRes => { + checkToken(rreq.get("Authorization"),"mailjet").then(authRes => { if (authRes === false) { // If the supplied authorization is invalid or an error occured console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 401`) // Log the request @@ -21,8 +21,7 @@ app.post("/sendemail", (rreq,rres) => { "Messages": [ { "From": { - "Email": globalConfig.mailjet.senderAddress, - "Name": globalConfig.mailjet.senderName, + "Email": globalConfig.mailjet.senderAddress }, "To": [ { -- 2.49.1 From 9a7a5b69ee356061834b4195aee1a94ab25c04b4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 26 Jun 2024 16:48:05 -0700 Subject: [PATCH 2/4] Repo maintenance & add nowplaying --- .gitignore | 4 ++- config.example.json | 27 +++++++++++--------- liberals/nowplaying.js | 26 ++++++++++++++++++++ package-lock.json | 56 ++++++++++++++++++++++++++++-------------- package.json | 2 +- routes/nowplaying.js | 28 ++++++++++++++++++--- 6 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 liberals/nowplaying.js diff --git a/.gitignore b/.gitignore index b068519..c4978fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ config.json bun.lockb -GITVERSION \ No newline at end of file +GITVERSION +todo.txt +proto.js \ No newline at end of file diff --git a/config.example.json b/config.example.json index 2192d6e..7725a1f 100644 --- a/config.example.json +++ b/config.example.json @@ -8,12 +8,6 @@ "host": "hazeldale:5984", "authorization": "" }, - - "cider": { - "targetHosts": ["localhost:10769"], - - "authKeysDoc": "cider" - }, "mailjet": { "apiKey": "", @@ -23,14 +17,25 @@ "authKeysDoc": "mailjet" }, - "etyd": { - "randomHexLength": 6, - "authKeyInDb": "apiAuthKeys.etyd" - }, - "blog": { "postsDirectory": "C:/Users/natha/Downloads/proto/posts", "postsDirUrl": "/posts" + }, + + "nowplaying": { + "lastfm": { + "apiKey": "", + "target": "enstrayed" + }, + "jellyfin": { + "apiKey": "", + "host": "", + "target": "" + }, + "cider": { + "apiKeys": [], + "hosts": [] + } } } \ No newline at end of file diff --git a/liberals/nowplaying.js b/liberals/nowplaying.js new file mode 100644 index 0000000..ab7ec30 --- /dev/null +++ b/liberals/nowplaying.js @@ -0,0 +1,26 @@ +const { globalConfig } = require("../index.js") + +async function queryLastfm() { + return await fetch(`https://ws.audioscrobbler.com/2.0/?format=json&method=user.getrecenttracks&limit=1&api_key=${globalConfig.nowplaying.lastfm.apiKey}&user=${globalConfig.nowplaying.lastfm.target}`).then(response => response.json()).then(response => { + if (response["recenttracks"] == undefined) { + return 1 + } else { + if (response.recenttracks.track[0]["@attr"] == undefined) { + return 1 + } else { + return { + "json": { + "songName": response.recenttracks.track[0].name, + "artistName": response.recenttracks.track[0].artist["#text"], + "albumName": response.recenttracks.track[0].album["#text"], + "artUrl": response.recenttracks.track[0].image[3]["#text"], + "link": response.recenttracks.track[0].url + }, + "html": `Album Art

I'm listening to

${response.recenttracks.track[0].name} by ${response.recenttracks.track[0].artist["#text"]}

from ${response.recenttracks.track[0].album["#text"]}

View on Last.fm
` + } + } + } + }) +} + +module.exports = { queryLastfm } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 43da3b4..0b770df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,22 +9,43 @@ "version": "1.0.0", "license": "UNLICENSED", "dependencies": { - "express": "^4.18.2", - "typescript": "^5.4.3" + "express": "^4.18.2" }, "devDependencies": { + "@types/bun": "^1.0.12", "@types/node": "^20.12.3" } }, - "node_modules/@types/node": { - "version": "20.12.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.3.tgz", - "integrity": "sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==", + "node_modules/@types/bun": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.5.tgz", + "integrity": "sha512-7RprVDMF+1o+EWSo7F1+iJpkfNz+Ikw9K//vwambcY+D1QHXfb9l7jWY1hSBfuFEkW9yFAhkMzP2uTi1pQXoqw==", "dev": true, + "license": "MIT", + "dependencies": { + "bun-types": "1.1.14" + } + }, + "node_modules/@types/node": { + "version": "20.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", + "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", + "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -65,6 +86,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/bun-types": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.14.tgz", + "integrity": "sha512-esfxOvECTkjEuUEHBOoOo590Qggf4b9cz5h29AOB2SKt3yZwG3LbAX4iIYwWZX7GnO7vaY5hIdcQygwN0xGdNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "~20.12.8", + "@types/ws": "~8.5.10" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -679,18 +711,6 @@ "node": ">= 0.6" } }, - "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index 01392d7..633d388 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "dependencies": { "express": "^4.18.2" }, - "name": "api", + "name": "enstrayedapi", "version": "1.0.0", "description": "api.enstrayed.com", "main": "index.js", diff --git a/routes/nowplaying.js b/routes/nowplaying.js index 2254ab3..ab7e75b 100644 --- a/routes/nowplaying.js +++ b/routes/nowplaying.js @@ -1,11 +1,33 @@ const { app, globalConfig } = require("../index.js") +const { queryLastfm } = require("../liberals/nowplaying.js") + +var timeSinceLastLastfmQuery = Date.now()-5000 +var cachedLastfmResult = {} + +const notPlayingAnythingPlaceholder = { + "json": { + "playing": false + }, + "html": `I'm not currently listening to anything.` +} app.get("/nowplaying", (rreq,rres) => { - if (rreq.query.format === "html") { - rres.send("The /nowplaying endpoint is currently under construction.") + + if (Date.now() < timeSinceLastLastfmQuery+5000) { + rres.send(cachedLastfmResult[rreq.query.format] ?? cachedLastfmResult.json) } else { - rres.send({"message":"The /nowplaying endpoint is currently under construction."}) + timeSinceLastLastfmQuery = Date.now() + queryLastfm().then(response => { + if (response == 1) { + cachedLastfmResult = notPlayingAnythingPlaceholder + } else { + cachedLastfmResult = response + } + + rres.send(cachedLastfmResult[rreq.query.format] ?? cachedLastfmResult.json) + }) } + }) module.exports = {app} \ No newline at end of file -- 2.49.1 From 6ea66c04070050c981e9fe1c5f172f0faf70f2ac Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Wed, 26 Jun 2024 20:16:01 -0700 Subject: [PATCH 3/4] More repo maintenance & add Readme --- Dockerfile | 9 ---- README.md | 105 ++++++++++++++++++++++++++++++++++++++++++++ config.example.json | 41 ----------------- deprecated/cider.js | 101 ------------------------------------------ docker-compose.yml | 10 ----- package.json | 6 +-- 6 files changed, 106 insertions(+), 166 deletions(-) delete mode 100644 Dockerfile create mode 100644 README.md delete mode 100644 config.example.json delete mode 100644 deprecated/cider.js delete mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index fbcad3c..0000000 --- a/Dockerfile +++ /dev/null @@ -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" ] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..de1fa55 --- /dev/null +++ b/README.md @@ -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. + +
Configuration Example + +* `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": [] + } + } + +} +``` + +
+ +## 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" ] +``` + +
Docker Compose File + +```yaml +--- +services: + enstrayedapi: + build: + context: . + image: enstrayedapi + container_name: enstrayedapi + restart: unless-stopped + volumes: + - ./config.json:/app/config.json +``` + +
+ +## 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. \ No newline at end of file diff --git a/config.example.json b/config.example.json deleted file mode 100644 index 7725a1f..0000000 --- a/config.example.json +++ /dev/null @@ -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": [] - } - } - -} \ No newline at end of file diff --git a/deprecated/cider.js b/deprecated/cider.js deleted file mode 100644 index 048566c..0000000 --- a/deprecated/cider.js +++ /dev/null @@ -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("Cider endpoint is temporarily unavailable.") -}) - -// 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 `Album Art

I'm listening to

${`${jsonRes.info.name} by ${jsonRes.info.artistName}`}

from ${jsonRes.info.albumName}

song.link
` -// } 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 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 4f58d54..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -services: - enstrayedapi: - build: - context: . - image: enstrayedapi - container_name: enstrayedapi - restart: unless-stopped - volumes: - - ./config.json:/app/config.json \ No newline at end of file diff --git a/package.json b/package.json index 633d388..6794ec1 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,8 @@ }, "name": "enstrayedapi", "version": "1.0.0", - "description": "api.enstrayed.com", + "description": "EnstrayedAPI", "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "repository": { "type": "git", "url": "git+https://github.com/enstrayed/enstrayedapi.git" @@ -18,7 +15,6 @@ "bugs": { "url": "https://github.com/enstrayed/enstrayedapi/issues" }, - "homepage": "https://api.enstrayed.com", "devDependencies": { "@types/bun": "^1.0.12", "@types/node": "^20.12.3" -- 2.49.1 From 49f9927ab94724ff266b2637947dac5d48e93b6c Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Sat, 6 Jul 2024 10:52:00 -0700 Subject: [PATCH 4/4] quick fix for couchdb --- README.md | 2 +- liberals/authorization.js | 6 +----- routes/etyd.js | 20 ++++---------------- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index de1fa55..8319009 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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). +This file contains documentation relevant for development and deployment, but not necessarily usage. Information for all endpoints is available [on my website](https://enstrayed.com/posts/20240409-API-Documentation.html). ## 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. diff --git a/liberals/authorization.js b/liberals/authorization.js index 4ca6348..49a14ed 100644 --- a/liberals/authorization.js +++ b/liberals/authorization.js @@ -1,11 +1,7 @@ const { globalConfig } = require("../index.js") async function checkAuthorization(documentToUse,keyToCheck) { - return await fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${documentToUse}`, { - headers: { - "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` - } - }).then(fetchRes => { + return await fetch(`${globalConfig.couchdbHost}/apiauthkeys/${documentToUse}`).then(fetchRes => { if (fetchRes.status === 404) { // If document doesnt exist fail gracefully diff --git a/routes/etyd.js b/routes/etyd.js index ca39d10..60b9cd8 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -2,11 +2,7 @@ const { app, globalConfig } = require("../index.js") // Get globals from index const { checkAuthorization } = require("../liberals/authorization.js") app.get("/etyd*", (rreq,rres) => { - fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd","")}`, { - headers: { - "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` - } - }).then(dbRes => { + fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd","")}`).then(dbRes => { if (dbRes.status == 404) { rres.sendStatus(404) } else { @@ -36,21 +32,16 @@ app.delete("/etyd*", (rreq,rres) => { rres.sendStatus(401) } else if (authRes === true) { // Authorization successful - fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { - headers: { - "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` - } - }).then(dbRes => { + fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd", "")}`).then(dbRes => { if (dbRes.status == 404) { rres.sendStatus(404) } else { dbRes.json().then(dbRes => { - fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { + fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd", "")}`, { method: "DELETE", headers: { - "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`, "If-Match": dbRes["_rev"] // Using the If-Match header is easiest for deleting entries in couchdb } }).then(fetchRes => { @@ -92,10 +83,7 @@ app.post("/etyd*", (rreq,rres) => { console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 400 KEY: ${rreq.get("Authorization")}`) rres.sendStatus(400) } else { - fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { - headers: { - "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` - }, + fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd", "")}`, { method: "PUT", body: JSON.stringify({ "content": { -- 2.49.1