add video versioning

This commit is contained in:
localhost 2026-02-17 14:33:18 +01:00
parent eb790eb8fc
commit e007c0ebd3
2 changed files with 65 additions and 15 deletions

View File

@ -26,17 +26,30 @@ app.get('/watch', async ({ query: { v }, set, redirect, error }) => {
return cached
}
if (!v.match(/[\w\-_]{11}/)) return error(404)
const idMatch = v.match(/^([\w\-_]{11})(?:-(\d+))?$/)
if (!idMatch) return error(404)
const json = await db.selectFrom('videos')
const baseId = idMatch[1];
const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const allowedVersionPattern = new RegExp(`^${escapeRegex(baseId)}(?:-\\d+)?$`);
const videoVersions = (await db.selectFrom('videos')
.selectAll()
.where('id', '=', v)
.executeTakeFirst()
.where('id', 'like', `${baseId}%`)
.execute())
.filter(video => allowedVersionPattern.test(video.id))
.sort((a, b) => {
const aVersion = Number(a.id.slice(baseId.length + 1) || 1);
const bVersion = Number(b.id.slice(baseId.length + 1) || 1);
return aVersion - bVersion;
});
const json = videoVersions.find(video => video.id === v) || videoVersions[0];
if (!json) {
const html = await m(eta.render('./watch', {
isMissing: true,
id: v,
id: baseId,
title: 'Video Not Found | PreserveTube',
manualAnalytics: true
}))
@ -45,13 +58,13 @@ app.get('/watch', async ({ query: { v }, set, redirect, error }) => {
set.headers['content-type'] = 'text/html; charset=utf-8'
return error(404, html)
}
if (json.disabled) return redirect(`/transparency/${v}`)
if (json.disabled) return redirect(`/transparency/${json.id}`)
let transparency: any[] = []
if (json.hasBeenReported) {
transparency = await db.selectFrom('reports')
.selectAll()
.where('target', '=', v)
.where('target', '=', json.id)
.execute()
}
@ -85,6 +98,11 @@ app.get('/watch', async ({ query: { v }, set, redirect, error }) => {
const html = await m(eta.render('./watch', {
transparency,
versions: videoVersions.map(video => ({
id: video.id,
archived: video.archived
})),
baseId,
...json,
description: DOMPurify.sanitize(json.description),
title: `${json.title} | PreserveTube`,

View File

@ -11,10 +11,28 @@
<div class="content-wrapper">
<div class="main-content">
<div class="report">
<% if (it.versions && it.versions.length > 1) { %>
<div class="version-picker">
<label for="version-select">Version:</label>
<select
id="version-select"
onchange="window.location.href = '/watch?v=' + this.value"
>
<% it.versions.forEach(function(version, index){ %>
<option value="<%= version.id %>" <%= version.id === it.id ? 'selected' : '' %>>
v<%= version.id.slice(it.baseId.length + 1) || '1' %> (archived <%= version.archived %>)
</option>
<% }) %>
</select>
</div>
<% } else { %>
<div></div>
<% } %>
<div class="report-links">
<a href="abuse">[report abuse]</a>
<div class="space"></div>
<a href="about">[faq]</a>
</div>
</div>
<% if (it.transparency.length != 0) { %>
<div class="reports">
@ -76,13 +94,16 @@
}
.report {
text-align: right;
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin-top: 5px;
}
.space {
display: inline-block;
width: 0.5%;
.report-links {
display: flex;
gap: 0.5rem;
}
video {
@ -154,6 +175,17 @@
font-weight: 600;
}
.version-picker {
display: flex;
align-items: center;
gap: 0.5rem;
}
.version-picker select {
font: inherit;
padding: 0.2rem 0.4rem;
}
.video-wrapper {
position: relative;
display: inline-block;