diff --git a/index.js b/index.js
index 498265a..13c247c 100644
--- a/index.js
+++ b/index.js
@@ -4,6 +4,7 @@ import bcrypt from "bcrypt";
import fileUpload from "express-fileupload";
import path from "path";
import childProcess from "child_process";
+import levenshtein from "js-levenshtein";
const users = [];
let sessions = [];
@@ -32,12 +33,12 @@ app.post("/api/register", async (req, res) => {
return res.status(400).json({ ok: false, error: "bad request" });
}
const existingUser = users.find(user => user.username === username);
- if (existingUser === undefined) {
+ if (existingUser !== undefined) {
return res.status(400).json({ ok: false, error: "username taken" });
}
const passwordHash = await bcrypt.hash(password, 10);
const id = users.length;
- const user = { id, username, passwordHash };
+ const user = { id, username, password: passwordHash };
users.push(user);
return res.status(200).json({ ok: true, user });
});
@@ -63,6 +64,28 @@ app.post("/api/login", async (req, res) => {
return res.status(200).json({ ok: true, session });
});
+
+app.get("/api/search", async (req, res) => {
+ const page = +req.query.page || 0;
+ const search = req.query.query;
+ if (!search) {
+ return res.status(400).json({ ok: false, error: "bad request" });
+ }
+ const [start, end] = [20 * page, 20 * (page + 1)];
+ const returnedVideos = videos
+ .map(video => ({dist: levenshtein(search, video.title), ...video}))
+ .toSorted((a, b) => a.dist - b.dist)
+ .slice(start, end)
+ .map(video => {
+ const user = users.find(user => user.id === v.userId);
+ if (!user) {
+ return {...video, author: "[Liberal]"};
+ }
+ return {...video, author: user.username};
+ });
+ return res.status(200).json({ ok: true, videos: returnedVideos, total: videos.length });
+});
+
function authorized() {
return (req, res, next) => {
const token = (() => {
diff --git a/package-lock.json b/package-lock.json
index 0d304d4..88e4b0d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,8 @@
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"express": "^4.18.2",
- "express-fileupload": "^1.4.3"
+ "express-fileupload": "^1.4.3",
+ "js-levenshtein": "^1.1.6"
}
},
"node_modules/@mapbox/node-pre-gyp": {
@@ -668,6 +669,14 @@
"node": ">=8"
}
},
+ "node_modules/js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
diff --git a/package.json b/package.json
index 75792e9..e114af8 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"express": "^4.18.2",
- "express-fileupload": "^1.4.3"
+ "express-fileupload": "^1.4.3",
+ "js-levenshtein": "^1.1.6"
}
}
diff --git a/public/index.html b/public/index.html
index 24c799b..438ec15 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,11 +4,10 @@
MaoTube
-
MaoTube
-
diff --git a/public/login/index.html b/public/login/index.html
new file mode 100644
index 0000000..a1fd655
--- /dev/null
+++ b/public/login/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+ MaoTube
+
+
+
+ MaoTube
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/login/script.js b/public/login/script.js
new file mode 100644
index 0000000..f689adc
--- /dev/null
+++ b/public/login/script.js
@@ -0,0 +1,46 @@
+function error(message) {
+ const errorContainer = document.getElementById("mao-error");
+ const errorElement = document.getElementById("mao-error-message");
+
+ errorElement.innerText = message;
+ errorContainer.classList.remove("hidden");
+}
+
+function displayResponse(response) {
+ if (!response.ok) {
+ error(response.error);
+ return;
+ }
+ window.location.href = "/";
+}
+
+function click() {
+ const username = document.getElementById("username").value;
+ const password = document.getElementById("password").value;
+
+ if (username.trim().length === 0) {
+ error("username cannot be empty");
+ return;
+ } else if (password.trim().length === 0) {
+ error("password cannot be empty");
+ return;
+ }
+ const method = "POST";
+ const body = JSON.stringify({ username, password });
+ const headers = new Headers({ "Content-Type": "application/json" });
+ return fetch("/api/login", {
+ method,
+ body,
+ headers,
+ })
+ .then(v => v.json())
+ .then(displayResponse);
+
+}
+
+function main() {
+ const submit = document.getElementById("submit");
+ submit.addEventListener("click", () => click())
+}
+
+main();
diff --git a/public/register/index.html b/public/register/index.html
new file mode 100644
index 0000000..1a311d5
--- /dev/null
+++ b/public/register/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+ MaoTube
+
+
+
+ MaoTube
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/register/script.js b/public/register/script.js
new file mode 100644
index 0000000..ca6b8a1
--- /dev/null
+++ b/public/register/script.js
@@ -0,0 +1,46 @@
+function error(message) {
+ const errorContainer = document.getElementById("mao-error");
+ const errorElement = document.getElementById("mao-error-message");
+
+ errorElement.innerText = message;
+ errorContainer.classList.remove("hidden");
+}
+
+function displayResponse(response) {
+ if (!response.ok) {
+ error(response.error);
+ return;
+ }
+ window.location.href = "/login";
+}
+
+function click() {
+ const username = document.getElementById("username").value;
+ const password = document.getElementById("password").value;
+
+ if (username.trim().length === 0) {
+ error("username cannot be empty");
+ return;
+ } else if (password.trim().length === 0) {
+ error("password cannot be empty");
+ return;
+ }
+ const method = "POST";
+ const body = JSON.stringify({ username, password });
+ const headers = new Headers({ "Content-Type": "application/json" });
+ return fetch("/api/register", {
+ method,
+ body,
+ headers,
+ })
+ .then(v => v.json())
+ .then(displayResponse);
+
+}
+
+function main() {
+ const submit = document.getElementById("submit");
+ submit.addEventListener("click", () => click())
+}
+
+main();
diff --git a/public/script.js b/public/script.js
deleted file mode 100644
index b28b04f..0000000
--- a/public/script.js
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/public/search/index.html b/public/search/index.html
new file mode 100644
index 0000000..a8b63e4
--- /dev/null
+++ b/public/search/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+ MaoTube
+
+
+
+ MaoTube
+
+
+
+
+
+
+
+
+
+
diff --git a/public/search/script.js b/public/search/script.js
new file mode 100644
index 0000000..e154ab0
--- /dev/null
+++ b/public/search/script.js
@@ -0,0 +1,38 @@
+
+function error(message) {
+ const errorContainer = document.getElementById("mao-error");
+ const errorElement = document.getElementById("mao-error-message");
+
+ errorElement.innerText = message;
+ errorContainer.classList.remove("hidden");
+}
+
+function displayResponse(response) {
+ if (!response.ok) {
+ error(response.error);
+ return;
+ }
+ const { videos, total } = response;
+ if (videos.length === 0) {
+ error("search returned no results");
+ return;
+ }
+ const resultElement = document.getElementById("result");
+ resultElement.innerHTML = `Showing ${videos.length}/${total} results.
`
+ + ""
+ + videos
+ .map(v => {
+ return `${v.title} - ${v.author}
`
+ })
+ .join("")
+ + "
";
+}
+
+function main() {
+ const params = new URLSearchParams(window.location.search);
+ return fetch(`/api/search?${params.toString()}`)
+ .then(v => v.json())
+ .then(displayResponse);
+}
+
+main();
diff --git a/public/style.css b/public/style.css
index 4751a62..a4d0111 100644
--- a/public/style.css
+++ b/public/style.css
@@ -12,3 +12,44 @@ body {
padding: 2rem;
text-align: center;
}
+
+.mao-error {
+ display: flex;
+ flex-direction: column;
+ background: url("/chairman_1.jpg");
+ background-size: auto 600px;
+ background-repeat: no-repeat;
+ background-position: center center;
+ height: 600px;
+}
+
+.hidden {
+ display: none;
+}
+
+.mao-error p {
+ color: #fff;
+ text-transform: uppercase;
+ font-weight: bold;
+ font-family: "Impact", "Bebas", "League Gothic", "Oswald", "Coluna", "Ubuntu Condensed", system-ui, sans-serif;
+ text-shadow:
+ 3px 3px 0 black,
+ -3px -3px 0 black,
+ 3px -3px 0 black,
+ -3px 3px 0 black,
+
+ -3px 0px 0 black,
+ 3px 0px 0 black,
+ 0px 3px 0 black,
+ 0px -3px 0 black;
+ font-size: 2em;
+}
+
+.mao-error p:last-child {
+ margin-top: auto;
+}
+
+ul#video-list {
+ padding: 0;
+ list-style: none;
+}