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 levenshtein from "js-levenshtein";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { fileURLToPath } from 'url';
|
||||
import { fileURLToPath } from "url";
|
||||
import sqlite3 from "sqlite3";
|
||||
|
||||
const users = [];
|
||||
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) {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
|
||||
@ -36,38 +49,51 @@ app.use("/videos", express.static("videos/"));
|
||||
|
||||
app.post("/api/register", async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (typeof username !== "string" || typeof password !== "string") {
|
||||
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) {
|
||||
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 };
|
||||
users.push(user);
|
||||
return res.status(200).json({ ok: true, user });
|
||||
|
||||
await dbGet("INSERT INTO users (username, password) VALUES (?, ?)", username, passwordHash);
|
||||
|
||||
return res.status(200).json({ ok: true });
|
||||
});
|
||||
|
||||
app.post("/api/login", async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (typeof username !== "string" || typeof password !== "string") {
|
||||
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) {
|
||||
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" });
|
||||
}
|
||||
|
||||
sessions = sessions.filter(session => session.userId !== user.id);
|
||||
|
||||
const token = randomString(64);
|
||||
const session = { userId: user.id, token };
|
||||
|
||||
sessions.push(session);
|
||||
|
||||
res.clearCookie("token");
|
||||
res.cookie("token", token);
|
||||
|
||||
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) => {
|
||||
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 withDistance = videos
|
||||
.map(video => ({ dist: levenshtein(search, video.title), ...video }));
|
||||
withDistance.sort((a, b) => a.dist - b.dist);
|
||||
const returnedVideos = withDistance
|
||||
.slice(start, end)
|
||||
.map(video => {
|
||||
const user = users.find(user => user.id === video.userId);
|
||||
if (!user) {
|
||||
return { ...video, author: "[Liberal]" };
|
||||
}
|
||||
return { ...video, author: user.username };
|
||||
});
|
||||
return res.status(200).json({ ok: true, videos: returnedVideos, total: videos.length });
|
||||
|
||||
const [start, end] = [20 * page, 20];
|
||||
const videos = await dbAll(`
|
||||
SELECT videos.*, users.username AS author
|
||||
FROM videos
|
||||
JOIN users ON users.id = videos.user_id
|
||||
WHERE title LIKE CONCAT('%', ?, '%')
|
||||
LIMIT ?
|
||||
OFFSET ?
|
||||
`, search, end, start);
|
||||
|
||||
console.log(videos);
|
||||
|
||||
const { total } = await dbGet("SELECT COUNT(*) AS total FROM videos");
|
||||
|
||||
return res.status(200).json({ ok: true, videos, total });
|
||||
});
|
||||
|
||||
function authorized() {
|
||||
return (req, res, next) => {
|
||||
return async (req, res, next) => {
|
||||
const token = (() => {
|
||||
if (req.cookies && "token" in req.cookies) {
|
||||
return req.cookies["token"];
|
||||
@ -114,7 +143,7 @@ function authorized() {
|
||||
if (session === undefined) {
|
||||
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) {
|
||||
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) => {
|
||||
const { title } = req.body;
|
||||
|
||||
if (!req.files || !req.files.video) {
|
||||
return res.status(400).json({ ok: false, error: "bad request" });
|
||||
}
|
||||
|
||||
if (req.files.video.mimetype !== "video/mp4") {
|
||||
return res.status(400).json({ ok: false, error: "bad mimetype" });
|
||||
}
|
||||
|
||||
const userId = req.user.id;
|
||||
const id = randomString(4);
|
||||
const tempPath = req.files.video.tempFilePath;
|
||||
const newPath = path.join(dirname(), "videos", `${id}.mp4`);
|
||||
|
||||
console.log(newPath);
|
||||
|
||||
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) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
process.on("close", (code) => {
|
||||
resolve(code);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("handbrake failed");
|
||||
throw new Error("ffmpeg failed");
|
||||
}
|
||||
|
||||
const video = {
|
||||
id,
|
||||
userId,
|
||||
title,
|
||||
path: newPath,
|
||||
};
|
||||
videos.push(video);
|
||||
return res.status(200).json({ ok: true, video });
|
||||
await dbRun("INSERT INTO videos (id, user_id, title, path) VALUES (?, ?, ?, ?)", id, userId, title, newPath);
|
||||
|
||||
return res.status(200).json({ ok: true });
|
||||
})
|
||||
|
||||
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",
|
||||
"express": "^4.18.2",
|
||||
"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>
|
||||
<div>
|
||||
<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>
|
||||
<input type="password" name="password" id="password">
|
||||
<br>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<h1>MaoTube</h1>
|
||||
<div>
|
||||
<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>
|
||||
<input type="password" name="password" id="password">
|
||||
<br>
|
||||
|
@ -17,11 +17,13 @@ function displayResponse(response) {
|
||||
error("search returned no results");
|
||||
return;
|
||||
}
|
||||
console.log(videos);
|
||||
const resultElement = document.getElementById("result");
|
||||
resultElement.innerHTML = `<p>Showing ${videos.length}/${total} results.</p>`
|
||||
+ "<ul id='video-list'>"
|
||||
+ videos
|
||||
.map(v => {
|
||||
console.log(v);
|
||||
return `<li><p><a href="/watch?id=${v.id}">${v.title}</a> - ${v.author}</p></li>`
|
||||
})
|
||||
.join("")
|
||||
|
@ -83,7 +83,7 @@ header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
header a {
|
||||
color: #fcf4c8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<h1>MaoTube</h1>
|
||||
<form action="/api/upload_video" method="POST" enctype="multipart/form-data">
|
||||
<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>
|
||||
<input type="file" name="video">
|
||||
<br>
|
||||
|
Loading…
Reference in New Issue
Block a user