Implement SQLite
This commit is contained in:
parent
ba3785df0c
commit
9fb560fc9a
BIN
database.sqlite3
Normal file
BIN
database.sqlite3
Normal file
Binary file not shown.
103
index.js
103
index.js
@ -6,11 +6,24 @@ import path from "path";
|
|||||||
import childProcess from "child_process";
|
import childProcess from "child_process";
|
||||||
import levenshtein from "js-levenshtein";
|
import levenshtein from "js-levenshtein";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from "url";
|
||||||
|
import sqlite3 from "sqlite3";
|
||||||
|
|
||||||
const users = [];
|
|
||||||
let sessions = [];
|
let sessions = [];
|
||||||
const videos = [];
|
|
||||||
|
const db = new sqlite3.Database("database.sqlite3");
|
||||||
|
|
||||||
|
function dbGet(query, ...parameters) {
|
||||||
|
return new Promise((resolve, reject) => db.get(query, parameters, (err, data) => err ? reject(err) : resolve(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbAll(query, ...parameters) {
|
||||||
|
return new Promise((resolve, reject) => db.all(query, parameters, (err, data) => err ? reject(err) : resolve(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbRun(query, ...parameters) {
|
||||||
|
return new Promise((resolve, reject) => db.run(query, parameters, (err, data) => err ? reject(err) : resolve(data)));
|
||||||
|
}
|
||||||
|
|
||||||
function randomString(length) {
|
function randomString(length) {
|
||||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
|
||||||
@ -36,38 +49,51 @@ app.use("/videos", express.static("videos/"));
|
|||||||
|
|
||||||
app.post("/api/register", async (req, res) => {
|
app.post("/api/register", async (req, res) => {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
if (typeof username !== "string" || typeof password !== "string") {
|
if (typeof username !== "string" || typeof password !== "string") {
|
||||||
return res.status(400).json({ ok: false, error: "bad request" });
|
return res.status(400).json({ ok: false, error: "bad request" });
|
||||||
}
|
}
|
||||||
const existingUser = users.find(user => user.username === username);
|
|
||||||
|
const existingUser = await dbGet("SELECT * FROM users WHERE username = ?", username);
|
||||||
|
|
||||||
if (existingUser !== undefined) {
|
if (existingUser !== undefined) {
|
||||||
return res.status(400).json({ ok: false, error: "username taken" });
|
return res.status(400).json({ ok: false, error: "username taken" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordHash = await bcrypt.hash(password, 10);
|
const passwordHash = await bcrypt.hash(password, 10);
|
||||||
const id = users.length;
|
|
||||||
const user = { id, username, passwordHash };
|
await dbGet("INSERT INTO users (username, password) VALUES (?, ?)", username, passwordHash);
|
||||||
users.push(user);
|
|
||||||
return res.status(200).json({ ok: true, user });
|
return res.status(200).json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/login", async (req, res) => {
|
app.post("/api/login", async (req, res) => {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
if (typeof username !== "string" || typeof password !== "string") {
|
if (typeof username !== "string" || typeof password !== "string") {
|
||||||
return res.status(400).json({ ok: false, error: "bad request" });
|
return res.status(400).json({ ok: false, error: "bad request" });
|
||||||
}
|
}
|
||||||
const user = users.find(user => user.username === username);
|
|
||||||
|
const user = await dbGet("SELECT * FROM users WHERE username = ?", username);
|
||||||
|
|
||||||
if (user === undefined) {
|
if (user === undefined) {
|
||||||
return res.status(400).json({ ok: false, error: "wrong username/password" });
|
return res.status(400).json({ ok: false, error: "wrong username/password" });
|
||||||
}
|
}
|
||||||
if (!await bcrypt.compare(password, user.passwordHash)) {
|
|
||||||
|
if (!await bcrypt.compare(password, user.password)) {
|
||||||
return res.status(400).json({ ok: false, error: "wrong username/password" });
|
return res.status(400).json({ ok: false, error: "wrong username/password" });
|
||||||
}
|
}
|
||||||
|
|
||||||
sessions = sessions.filter(session => session.userId !== user.id);
|
sessions = sessions.filter(session => session.userId !== user.id);
|
||||||
|
|
||||||
const token = randomString(64);
|
const token = randomString(64);
|
||||||
const session = { userId: user.id, token };
|
const session = { userId: user.id, token };
|
||||||
|
|
||||||
sessions.push(session);
|
sessions.push(session);
|
||||||
|
|
||||||
res.clearCookie("token");
|
res.clearCookie("token");
|
||||||
res.cookie("token", token);
|
res.cookie("token", token);
|
||||||
|
|
||||||
return res.status(200).json({ ok: true, session });
|
return res.status(200).json({ ok: true, session });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,27 +101,30 @@ app.post("/api/login", async (req, res) => {
|
|||||||
app.get("/api/search", async (req, res) => {
|
app.get("/api/search", async (req, res) => {
|
||||||
const page = +req.query.page || 0;
|
const page = +req.query.page || 0;
|
||||||
const search = req.query.query;
|
const search = req.query.query;
|
||||||
|
|
||||||
if (!search) {
|
if (!search) {
|
||||||
return res.status(400).json({ ok: false, error: "bad request" });
|
return res.status(400).json({ ok: false, error: "bad request" });
|
||||||
}
|
}
|
||||||
const [start, end] = [20 * page, 20 * (page + 1)];
|
|
||||||
const withDistance = videos
|
const [start, end] = [20 * page, 20];
|
||||||
.map(video => ({ dist: levenshtein(search, video.title), ...video }));
|
const videos = await dbAll(`
|
||||||
withDistance.sort((a, b) => a.dist - b.dist);
|
SELECT videos.*, users.username AS author
|
||||||
const returnedVideos = withDistance
|
FROM videos
|
||||||
.slice(start, end)
|
JOIN users ON users.id = videos.user_id
|
||||||
.map(video => {
|
WHERE title LIKE CONCAT('%', ?, '%')
|
||||||
const user = users.find(user => user.id === video.userId);
|
LIMIT ?
|
||||||
if (!user) {
|
OFFSET ?
|
||||||
return { ...video, author: "[Liberal]" };
|
`, search, end, start);
|
||||||
}
|
|
||||||
return { ...video, author: user.username };
|
console.log(videos);
|
||||||
});
|
|
||||||
return res.status(200).json({ ok: true, videos: returnedVideos, total: videos.length });
|
const { total } = await dbGet("SELECT COUNT(*) AS total FROM videos");
|
||||||
|
|
||||||
|
return res.status(200).json({ ok: true, videos, total });
|
||||||
});
|
});
|
||||||
|
|
||||||
function authorized() {
|
function authorized() {
|
||||||
return (req, res, next) => {
|
return async (req, res, next) => {
|
||||||
const token = (() => {
|
const token = (() => {
|
||||||
if (req.cookies && "token" in req.cookies) {
|
if (req.cookies && "token" in req.cookies) {
|
||||||
return req.cookies["token"];
|
return req.cookies["token"];
|
||||||
@ -114,7 +143,7 @@ function authorized() {
|
|||||||
if (session === undefined) {
|
if (session === undefined) {
|
||||||
return res.status(400).json({ ok: false, error: "unauthorized" });
|
return res.status(400).json({ ok: false, error: "unauthorized" });
|
||||||
}
|
}
|
||||||
const user = users.find(user => user.id === session.userId);
|
const user = await dbGet("SELECT * FROM users WHERE id = ?", session.userId);
|
||||||
if (user === undefined) {
|
if (user === undefined) {
|
||||||
throw new Error("error: session with invalid userId");
|
throw new Error("error: session with invalid userId");
|
||||||
}
|
}
|
||||||
@ -130,39 +159,39 @@ app.get("/api/logout", authorized(), (req, res) => {
|
|||||||
|
|
||||||
app.post("/api/upload_video", authorized(), fileUpload({ limits: { fileSize: 2 ** 26 }, useTempFiles: true }), async (req, res) => {
|
app.post("/api/upload_video", authorized(), fileUpload({ limits: { fileSize: 2 ** 26 }, useTempFiles: true }), async (req, res) => {
|
||||||
const { title } = req.body;
|
const { title } = req.body;
|
||||||
|
|
||||||
if (!req.files || !req.files.video) {
|
if (!req.files || !req.files.video) {
|
||||||
return res.status(400).json({ ok: false, error: "bad request" });
|
return res.status(400).json({ ok: false, error: "bad request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.files.video.mimetype !== "video/mp4") {
|
if (req.files.video.mimetype !== "video/mp4") {
|
||||||
return res.status(400).json({ ok: false, error: "bad mimetype" });
|
return res.status(400).json({ ok: false, error: "bad mimetype" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const id = randomString(4);
|
const id = randomString(4);
|
||||||
const tempPath = req.files.video.tempFilePath;
|
const tempPath = req.files.video.tempFilePath;
|
||||||
const newPath = path.join(dirname(), "videos", `${id}.mp4`);
|
const newPath = path.join(dirname(), "videos", `${id}.mp4`);
|
||||||
|
|
||||||
console.log(newPath);
|
console.log(newPath);
|
||||||
|
|
||||||
const exitCode = await new Promise((resolve, _reject) => {
|
const exitCode = await new Promise((resolve, _reject) => {
|
||||||
const process = childProcess.spawn("HandBrakeCLI", ["-i", tempPath, "-o", newPath, "-Z", "Social 25 MB 5 Minutes 360p60"]);
|
const process = childProcess.spawn("ffmpeg", ["-i", tempPath, "-b:v", "1M", "-b:a", "192k", newPath]);
|
||||||
process.stderr.on("data", (data) => {
|
process.stderr.on("data", (data) => {
|
||||||
console.error(data.toString());
|
console.error(data.toString());
|
||||||
});
|
});
|
||||||
process.on("close", (code) => {
|
process.on("close", (code) => {
|
||||||
resolve(code);
|
resolve(code);
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
|
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
throw new Error("handbrake failed");
|
throw new Error("ffmpeg failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const video = {
|
await dbRun("INSERT INTO videos (id, user_id, title, path) VALUES (?, ?, ?, ?)", id, userId, title, newPath);
|
||||||
id,
|
|
||||||
userId,
|
return res.status(200).json({ ok: true });
|
||||||
title,
|
|
||||||
path: newPath,
|
|
||||||
};
|
|
||||||
videos.push(video);
|
|
||||||
return res.status(200).json({ ok: true, video });
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
|
14
migration.sql
Normal file
14
migration.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE videos (
|
||||||
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
user_id INTEGER,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
998
package-lock.json
generated
998
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-fileupload": "^1.4.3",
|
"express-fileupload": "^1.4.3",
|
||||||
"js-levenshtein": "^1.1.6"
|
"js-levenshtein": "^1.1.6",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<h1>MaoTube</h1>
|
<h1>MaoTube</h1>
|
||||||
<div>
|
<div>
|
||||||
<label for="username"><p>Username</p></label>
|
<label for="username"><p>Username</p></label>
|
||||||
<input type="text" name="username" id="username">
|
<input type="text" name="username" id="username" autofocus>
|
||||||
<label for="password"><p>Password</p></label>
|
<label for="password"><p>Password</p></label>
|
||||||
<input type="password" name="password" id="password">
|
<input type="password" name="password" id="password">
|
||||||
<br>
|
<br>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<h1>MaoTube</h1>
|
<h1>MaoTube</h1>
|
||||||
<div>
|
<div>
|
||||||
<label for="username"><p>Username</p></label>
|
<label for="username"><p>Username</p></label>
|
||||||
<input type="text" name="username" id="username">
|
<input type="text" name="username" id="username" autofocus>
|
||||||
<label for="password"><p>Password</p></label>
|
<label for="password"><p>Password</p></label>
|
||||||
<input type="password" name="password" id="password">
|
<input type="password" name="password" id="password">
|
||||||
<br>
|
<br>
|
||||||
|
@ -17,11 +17,13 @@ function displayResponse(response) {
|
|||||||
error("search returned no results");
|
error("search returned no results");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log(videos);
|
||||||
const resultElement = document.getElementById("result");
|
const resultElement = document.getElementById("result");
|
||||||
resultElement.innerHTML = `<p>Showing ${videos.length}/${total} results.</p>`
|
resultElement.innerHTML = `<p>Showing ${videos.length}/${total} results.</p>`
|
||||||
+ "<ul id='video-list'>"
|
+ "<ul id='video-list'>"
|
||||||
+ videos
|
+ videos
|
||||||
.map(v => {
|
.map(v => {
|
||||||
|
console.log(v);
|
||||||
return `<li><p><a href="/watch?id=${v.id}">${v.title}</a> - ${v.author}</p></li>`
|
return `<li><p><a href="/watch?id=${v.id}">${v.title}</a> - ${v.author}</p></li>`
|
||||||
})
|
})
|
||||||
.join("")
|
.join("")
|
||||||
|
@ -83,7 +83,7 @@ header {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
header a {
|
||||||
color: #fcf4c8;
|
color: #fcf4c8;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<h1>MaoTube</h1>
|
<h1>MaoTube</h1>
|
||||||
<form action="/api/upload_video" method="POST" enctype="multipart/form-data">
|
<form action="/api/upload_video" method="POST" enctype="multipart/form-data">
|
||||||
<label for="username"><p>Title</p></label>
|
<label for="username"><p>Title</p></label>
|
||||||
<input type="text" name="title">
|
<input type="text" name="title" autofocus>
|
||||||
<label for="password"><p>Video</p></label>
|
<label for="password"><p>Video</p></label>
|
||||||
<input type="file" name="video">
|
<input type="file" name="video">
|
||||||
<br>
|
<br>
|
||||||
|
Loading…
Reference in New Issue
Block a user