Add video title and author to watch page

This commit is contained in:
Reimar 2024-02-12 16:02:00 +01:00
parent 52e07b54dc
commit 8852a6f0c3
Signed by: Reimar
GPG Key ID: 93549FA07F0AE268
6 changed files with 92 additions and 41 deletions

View File

@ -14,15 +14,19 @@ let sessions = [];
const db = new sqlite3.Database("database.sqlite3"); const db = new sqlite3.Database("database.sqlite3");
function dbGet(query, ...parameters) { function dbGet(query, ...parameters) {
return new Promise((resolve, reject) => db.get(query, parameters, (err, data) => err ? reject(err) : resolve(data))); return new Promise((resolve, reject) => db.get(query, parameters, (err, data) => err ? reject(err) : resolve(data)));
} }
function dbAll(query, ...parameters) { function dbAll(query, ...parameters) {
return new Promise((resolve, reject) => db.all(query, parameters, (err, data) => err ? reject(err) : resolve(data))); return new Promise((resolve, reject) => db.all(query, parameters, (err, data) => err ? reject(err) : resolve(data)));
} }
function dbRun(query, ...parameters) { function dbRun(query, ...parameters) {
return new Promise((resolve, reject) => db.run(query, parameters, (err, data) => err ? reject(err) : resolve(data))); return new Promise((resolve, reject) => db.run(query, parameters, (err, data) => err ? reject(err) : resolve(data)));
}
function videoPath(id) {
return `videos/${id}.webm`;
} }
function randomString(length) { function randomString(length) {
@ -54,7 +58,7 @@ app.post("/api/register", async (req, res) => {
return res.status(400).json({ ok: false, error: "bad request" }); return res.status(400).json({ ok: false, error: "bad request" });
} }
const existingUser = await dbGet("SELECT * FROM users WHERE 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" });
@ -62,7 +66,7 @@ app.post("/api/register", async (req, res) => {
const passwordHash = await bcrypt.hash(password, 10); const passwordHash = await bcrypt.hash(password, 10);
await dbGet("INSERT INTO users (username, password) VALUES (?, ?)", username, passwordHash); await dbGet("INSERT INTO users (username, password) VALUES (?, ?)", username, passwordHash);
return res.status(200).json({ ok: true }); return res.status(200).json({ ok: true });
}); });
@ -107,16 +111,16 @@ app.get("/api/search", async (req, res) => {
} }
const [start, end] = [20 * page, 20]; const [start, end] = [20 * page, 20];
const videos = await dbAll(` const videos = await dbAll(`
SELECT videos.*, users.username AS author SELECT videos.*, users.username AS author
FROM videos FROM videos
JOIN users ON users.id = videos.user_id JOIN users ON users.id = videos.user_id
WHERE title LIKE CONCAT('%', ?, '%') WHERE title LIKE CONCAT('%', ?, '%')
LIMIT ? LIMIT ?
OFFSET ? OFFSET ?
`, search, end, start); `, search, end, start);
const { total } = await dbGet("SELECT COUNT(*) AS total FROM videos"); const { total } = await dbGet("SELECT COUNT(*) AS total FROM videos");
return res.status(200).json({ ok: true, videos, total }); return res.status(200).json({ ok: true, videos, total });
}); });
@ -155,7 +159,7 @@ app.get("/api/logout", authorized(), (req, res) => {
return res.status(200).json({ ok: true }); return res.status(200).json({ ok: true });
}); });
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) {
@ -169,7 +173,7 @@ app.post("/api/upload_video", authorized(), fileUpload({ limits: { fileSize: 2 *
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(), videoPath(id));
const thumbnailPath = path.join(dirname(), "videos", `${id}.png`); const thumbnailPath = path.join(dirname(), "videos", `${id}.png`);
console.log(newPath); console.log(newPath);
@ -197,7 +201,27 @@ app.post("/api/upload_video", authorized(), fileUpload({ limits: { fileSize: 2 *
await dbRun("INSERT INTO videos (id, user_id, title) VALUES (?, ?, ?)", id, userId, title); await dbRun("INSERT INTO videos (id, user_id, title) VALUES (?, ?, ?)", id, userId, title);
return res.status(200).json({ ok: true }); return res.status(200).json({ ok: true });
}) });
app.get("/api/video-info", async (req, res) => {
const id = req.query["id"];
const video = await dbGet(`
SELECT videos.id, videos.title, users.username AS author
FROM videos
JOIN users ON users.id = videos.user_id
WHERE videos.id = ?
LIMIT 1
`, id);
if (!video) {
return res.status(404).json({ ok: false, error: "video not found" });
}
video.path = path.join("/", videoPath(id));
return res.status(200).json({ ok: true, video });
});
app.use((err, req, res, next) => { app.use((err, req, res, next) => {
console.error(err); console.error(err);

View File

@ -35,7 +35,7 @@ function displayHeader() {
${links} ${links}
- -
<form id="search-form" method="GET" target="_self" action="/search"> <form id="search-form" method="GET" target="_self" action="/search">
<input type="text" id="search" name="query" placeholder="Search"> <input type="text" id="search" name="query" placeholder="Search" accesskey="s">
<input type="submit" value="Search"> <input type="submit" value="Search">
</form> </form>
</nav> </nav>

View File

@ -72,7 +72,16 @@ input::file-selector-button {
} }
#video-player { #video-player {
max-height: 80vh; height: 60vh;
width: 100%;
background-color: black;
}
#video-result {
width: 106vh;
display: none;
text-align: left;
margin: auto;
} }
.video-item { .video-item {
@ -130,6 +139,3 @@ a {
color: var(--red); color: var(--red);
} }
a:visited {
color: #FF5722;
}

View File

@ -7,7 +7,7 @@
</head> </head>
<body> <body>
<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" autofocus> <input type="text" name="title" autofocus>
<label for="password"><p>Video</p></label> <label for="password"><p>Video</p></label>

View File

@ -7,19 +7,21 @@
</head> </head>
<body> <body>
<h1>MaoTube</h1> <h1>MaoTube</h1>
<form method="GET" target="_self" action="/search">
<label for="query"><p>Search</p></label>
<input type="text" id="query" name="query" placeholder="...">
<input type="submit" value="Search">
</form>
<br> <br>
<div id="result"> <div id="result">
<div id="video-result">
<video id="video-player"></video>
<h1 id="video-title"></h1>
<span id="video-author"></span>
</div>
<noscript> <noscript>
<div class="mao-error"> <div class="mao-error">
<p>javascript not enabled</p> <p>javascript not enabled</p>
<p>bottom text</p> <p>bottom text</p>
</div> </div>
</noscript> </noscript>
<div id="mao-error" class="mao-error hidden"> <div id="mao-error" class="mao-error hidden">
<p id="mao-error-message"></p> <p id="mao-error-message"></p>
<p>bottom text</p> <p>bottom text</p>

View File

@ -1,13 +1,14 @@
function error(message) { function error(message) {
const errorContainer = document.getElementById("mao-error"); const errorContainer = document.getElementById("mao-error");
const errorElement = document.getElementById("mao-error-message"); const errorElement = document.getElementById("mao-error-message");
errorElement.innerText = message; document.getElementById("video-result").style.display = "none";
errorElement.innerText = message || "unknown error";
errorContainer.classList.remove("hidden"); errorContainer.classList.remove("hidden");
} }
function main() { async function main() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const id = params.get("id"); const id = params.get("id");
if (!id) { if (!id) {
@ -15,17 +16,35 @@ function main() {
return; return;
} }
const result = document.getElementById("result"); const info = await fetch(`/api/video-info?id=${id}`);
if (!info.ok) {
const video = document.createElement("video"); error("error fetching video info");
video.controls = true; return;
video.id = "video-player";
video.src = `/videos/${id}.mp4`;
result.appendChild(video);
video.onerror = () => {
video.remove();
error("invalid id parameter");
} }
const json = await info.json();
if (!json.ok) {
error(json.error);
return;
}
const video = json.video;
const player = document.getElementById("video-player");
player.controls = true;
player.src = video.path;
player.onerror = () => {
error("unable to play video");
}
player.onload = () => {
player.style.height = player.clientWidth / 16 * 9 + "px";
}
document.getElementById("video-title").innerText = video.title;
document.getElementById("video-author").innerText = "by " + video.author;
document.getElementById("video-result").style.display = "block";
} }
main(); main();