diff --git a/.gitignore b/.gitignore
index 664c5e7..b068519 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
node_modules/
config.json
+bun.lockb
GITVERSION
\ No newline at end of file
diff --git a/Caddyfile b/Caddyfile
new file mode 100644
index 0000000..eb42a99
--- /dev/null
+++ b/Caddyfile
@@ -0,0 +1,21 @@
+:8082 {
+ reverse_proxy localhost:8081
+}
+
+:8083 {
+ @staticpaths {
+ path /
+ path /_static*
+ path /favicon.ico
+ }
+
+ handle @staticpaths {
+ root ./etydFrontend
+ file_server
+ }
+
+ handle /* {
+ rewrite * /etyd{uri}
+ reverse_proxy localhost:8081
+ }
+}
\ No newline at end of file
diff --git a/bun.lockb b/bun.lockb
deleted file mode 100644
index 9c2504a..0000000
Binary files a/bun.lockb and /dev/null differ
diff --git a/etydFrontend/_static/etyd.js b/etydFrontend/_static/etyd.js
new file mode 100644
index 0000000..5879b9d
--- /dev/null
+++ b/etydFrontend/_static/etyd.js
@@ -0,0 +1,61 @@
+//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
+ }
+}
+
+function makeRandomHex(amount) {
+ const characters = "1234567890abcdef"
+ let counter = 0
+ let result = ""
+ while (counter < amount) {
+ result += characters.charAt(Math.floor(Math.random() * characters.length))
+ counter += 1
+ }
+ return result
+}
+
+function randomUrlTick() {
+ if (document.getElementById("randomizationtoggle").checked == true) {
+ document.getElementById("targetfield").disabled = true
+ document.getElementById("targetfield").value = makeRandomHex(6)
+ } else {
+ document.getElementById("targetfield").disabled = false
+ document.getElementById("targetfield").value = null
+ }
+}
+
+function buttonCopyResult() {
+ navigator.clipboard.writeText(`${document.location.href}${document.getElementById("urlfield").value}`)
+}
+
+function buttonFillFromClipboard() {
+ navigator.clipboard.readText().then(res => {
+ document.getElementById("valuefield").value = res;
+ })
+}
+
+function postData() {
+ fetch("http://nrdesktop:8081/etydwrite", {
+ method: "POST",
+ mode: "cors",
+ headers: {
+ "Authorization": document.getElementById("authfield").value
+ },
+ body: JSON.stringify({
+ "target": document.getElementById("targetfield").value,
+ "value": document.getElementById("valuefield").value,
+ "action": document.getElementById("actiondropdown").value,
+ "random": document.getElementById("randomizationtoggle").checked
+ })
+ }).then(response => {
+ document.getElementById("resultfeed").value += `\n${response.status} ${response.body}`
+ }).catch(error => {
+ document.getElementById("resultfeed").value += `\nError: ${error}`
+ })
+}
+
diff --git a/etydFrontend/_static/index.css b/etydFrontend/_static/index.css
new file mode 100644
index 0000000..66ddd02
--- /dev/null
+++ b/etydFrontend/_static/index.css
@@ -0,0 +1,40 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+.flexbox {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.marginright1em {
+ margin-right: 1em;
+}
+
+.resultfeed {
+ height: 100%;
+}
+
+@media (max-width: 700px) {
+ .resultfeed {
+ min-height: 20vh;
+ }
+}
+
+@media (prefers-color-scheme: dark) { /* Dark mode support */
+ body {
+ background-color: black;
+ color: white;
+ }
+
+ input, select, textarea, button {
+ background: none;
+ color: white;
+ border: 2px solid white;
+ }
+
+ input:disabled, button:disabled {
+ opacity: 0.8;
+ cursor: not-allowed;
+ }
+}
\ No newline at end of file
diff --git a/etydFrontend/index.html b/etydFrontend/index.html
new file mode 100644
index 0000000..4b9026d
--- /dev/null
+++ b/etydFrontend/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 21f47ad..43da3b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,20 @@
"version": "1.0.0",
"license": "UNLICENSED",
"dependencies": {
- "express": "^4.18.2"
+ "express": "^4.18.2",
+ "typescript": "^5.4.3"
+ },
+ "devDependencies": {
+ "@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==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~5.26.4"
}
},
"node_modules/accepts": {
@@ -666,6 +679,24 @@
"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",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
diff --git a/package.json b/package.json
index 9b134b7..01392d7 100644
--- a/package.json
+++ b/package.json
@@ -18,5 +18,9 @@
"bugs": {
"url": "https://github.com/enstrayed/enstrayedapi/issues"
},
- "homepage": "https://api.enstrayed.com"
+ "homepage": "https://api.enstrayed.com",
+ "devDependencies": {
+ "@types/bun": "^1.0.12",
+ "@types/node": "^20.12.3"
+ }
}
diff --git a/routes/etyd.js b/routes/etyd.js
index 24abbc7..ca39d10 100644
--- a/routes/etyd.js
+++ b/routes/etyd.js
@@ -1,4 +1,5 @@
-const { app, db, globalConfig } = require("../index.js") // Get globals from index
+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","")}`, {
@@ -24,4 +25,112 @@ app.get("/etyd*", (rreq,rres) => {
})
})
+app.delete("/etyd*", (rreq,rres) => {
+
+ if (rreq.get("Authorization") === undefined) {
+ rres.sendStatus(400)
+ } else {
+ checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).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
+
+ fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, {
+ headers: {
+ "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
+ }
+ }).then(dbRes => {
+
+ if (dbRes.status == 404) {
+ rres.sendStatus(404)
+ } else {
+ dbRes.json().then(dbRes => {
+
+ fetch(`http://${globalConfig.couchdb.host}/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 => {
+ if (fetchRes.status == 200) {
+ console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 200 KEY: ${rreq.get("Authorization")}`)
+ rres.sendStatus(200)
+ }
+ }).catch(fetchError => {
+ console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`)
+ rres.sendStatus(500)
+ })
+
+ })
+ }
+
+ }).catch(fetchError => {
+ console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`)
+ rres.sendStatus(500)
+ })
+
+ }
+ })
+ }
+
+})
+
+app.post("/etyd*", (rreq,rres) => {
+
+ if (rreq.get("Authorization") === undefined) {
+ rres.sendStatus(400)
+ } else {
+ checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).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", "")}`, {
+ headers: {
+ "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
+ },
+ method: "PUT",
+ body: JSON.stringify({
+ "content": {
+ "url": rreq.body["url"]
+ }
+ })
+ }).then(dbRes => {
+
+ 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;
+
+ default:
+ console.log(`ERROR: CouchDB PUT did not return expected code: ${dbRes.status}`)
+ break;
+ }
+
+ }).catch(fetchError => {
+ console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`)
+ rres.sendStatus(500)
+ })
+ }
+
+ }
+ })
+ }
+
+})
+
+
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 1ef3cc1..b26e9d5 100644
--- a/routes/mailjet.js
+++ b/routes/mailjet.js
@@ -7,7 +7,7 @@ app.post("/sendemail", (rreq,rres) => {
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
- rres.sendStatus(401) // Return 401 Unauthorized
+ rres.sendStatus(401)
} else if (authRes === true) { // If the authorization was valid, continue function