From 8a39bc02fa433fec4ac44b2d8b0c6c2c92d60d58 Mon Sep 17 00:00:00 2001 From: Ty Clifford Date: Thu, 25 Jun 2026 11:58:57 -0400 Subject: [PATCH] =?UTF-8?q?-=20Added=20Upkeeping=20=E2=86=92=20Correct=20c?= =?UTF-8?q?atalogue=20order.=20It=20now=20recalculates=20video=20sort=5For?= =?UTF-8?q?der=20from=20created=5Fat=20newest-first,=20so=20videos=20with?= =?UTF-8?q?=20corrected/original=20publish=20dates=20appear=20first=20in?= =?UTF-8?q?=20the=20homepage=20and=20public=20catalogue.=20I=20also=20upda?= =?UTF-8?q?ted=20catalogue/admin/latest-video=20ordering=20to=20use=20publ?= =?UTF-8?q?ish=20date=20as=20a=20tie-breaker=20after=20stored=20sort=20ord?= =?UTF-8?q?er.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- admin/index.php | 24 +++++++++++++++++++++++- changelog.md | 2 ++ includes/db.php | 39 ++++++++++++++++++++++++++++++++++++++- index.php | 2 +- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 936622f..9921592 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ The homepage can show a **Videos / Live** switch above the main player. Configur Use **Admin → YouTube Importer** to paste a YouTube channel URL, `@handle`, or `UC...` channel ID. The importer scrapes the channel's `/videos` page, extracts video data from `ytInitialData`, previews the results, and imports selected videos as remote YouTube embed sources. Duplicate YouTube watch URLs are skipped. -Imported YouTube videos use the video's original publish date for the database `created_at` timestamp when YouTube exposes it. The admin dashboard also includes **Upkeeping → Correct YouTube timestamps** to backfill existing YouTube entries in batches. +Imported YouTube videos use the video's original publish date for the database `created_at` timestamp when YouTube exposes it. The admin dashboard also includes **Upkeeping → Correct YouTube timestamps** to backfill existing YouTube entries in batches. Use **Upkeeping → Correct catalogue order** to rebuild catalogue sorting from those publish dates so newer dated videos appear first. ### View Counts and Public Statistics @@ -131,7 +131,7 @@ Only published videos appear on the public-facing pages. ### Sorting -Use the **Sort Order** field — lower numbers appear first. Default is 0. +Use the **Sort Order** field — lower numbers appear first. Default is 0. The **Upkeeping → Correct catalogue order** action can recalculate sort order from video publish dates, newest first. --- diff --git a/admin/index.php b/admin/index.php index 4309753..11f6d76 100644 --- a/admin/index.php +++ b/admin/index.php @@ -59,6 +59,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($result['not_found']) $msg .= ', ' . $result['not_found'] . ' without a readable external count'; $msg .= '.'; } + + if ($action === 'correct_catalogue_order') { + $result = media_correct_catalogue_sort_order(); + $msg = 'Upkeeping checked ' . $result['checked'] . ' video' . ($result['checked'] === 1 ? '' : 's') . ': '; + $msg .= $result['updated'] . ' video' . ($result['updated'] === 1 ? '' : 's') . ' reordered newest-first by publish date.'; + } } // Search + pagination @@ -85,7 +91,7 @@ $list_stmt = $db->prepare(" LEFT JOIN video_sources vs ON vs.video_id = v.id $where_sql GROUP BY v.id - ORDER BY v.sort_order ASC, v.id DESC + ORDER BY v.sort_order ASC, datetime(v.created_at) DESC, v.created_at DESC, v.id DESC LIMIT :limit OFFSET :offset "); foreach ($bind as $k => $val) $list_stmt->bindValue($k, $val); @@ -95,6 +101,7 @@ $list_stmt->execute(); $videos = $list_stmt->fetchAll(); $youtube_timestamp_candidates = media_youtube_timestamp_candidate_count(); $external_view_candidates = media_external_view_count_candidate_count(); +$catalogue_order_candidates = media_catalogue_order_candidate_count(); $admin_total_views = total_video_views(false); $qs = fn($p) => '?' . http_build_query(array_filter(['q' => $search, 'page' => $p])); @@ -236,6 +243,21 @@ render_head('Admin — TyClifford.com', ' +
+
+

Correct catalogue order

+

+ Rebuilds video sort order from the database publish dates so newer dated videos appear first in the homepage and public catalogue. + video can be reordered. +

+
+
+ + +
+

Correct external view counts

diff --git a/changelog.md b/changelog.md index e8e298b..28162f1 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ All notable changes to this media player project. - Added an **Upkeeping** section to the admin dashboard for batch maintenance actions. - Added per-video local view counts, external provider view counts, total public view counts, and configurable public statistics display. - Added upkeep actions to correct external view counts in batches. +- Added an upkeep action to correct catalogue order from video publish dates so newer dated videos appear first. - Added public search for published videos. - Added configurable URL rewriting, defaulting to `/v/`, `/all`, and `/search/`. - Added a configurable homepage live section with a Videos/Live toggle and default Stream.Place embed support. @@ -28,6 +29,7 @@ All notable changes to this media player project. - Public display settings now control per-video counts, total counts, and page statistics. - Admin video creation and editing now attempt to fill blank metadata from external media information. - The main player now chooses between local media playback, direct remote media playback, and provider embeds based on stored source data. +- Catalogue ordering now uses publish date as a tie-breaker after stored sort order. - README documentation now reflects the current remote URL, provider embed, live section, view count, and pretty URL behavior. ### Fixed diff --git a/includes/db.php b/includes/db.php index 7d164f1..2dd8c7c 100644 --- a/includes/db.php +++ b/includes/db.php @@ -56,6 +56,7 @@ function get_db(): PDO { CREATE INDEX IF NOT EXISTS idx_videos_published ON videos(published); CREATE INDEX IF NOT EXISTS idx_videos_sort ON videos(sort_order, id); + CREATE INDEX IF NOT EXISTS idx_videos_created ON videos(created_at, id); CREATE INDEX IF NOT EXISTS idx_videos_title ON videos(title); CREATE INDEX IF NOT EXISTS idx_sources_video ON video_sources(video_id); "); @@ -768,6 +769,42 @@ function media_correct_external_view_counts(int $limit = 25): array { return media_restore_external_view_counts($limit); } +function media_catalogue_order_candidate_count(): int { + return (int)get_db()->query("SELECT COUNT(*) FROM videos")->fetchColumn(); +} + +function media_correct_catalogue_sort_order(): array { + $db = get_db(); + $videos = $db->query(" + SELECT id, sort_order + FROM videos + ORDER BY datetime(created_at) DESC, created_at DESC, id DESC + ")->fetchAll(); + + $result = [ + 'checked' => count($videos), + 'updated' => 0, + ]; + if (!$videos) return $result; + + $stmt = $db->prepare("UPDATE videos SET sort_order=?, updated_at=datetime('now') WHERE id=?"); + $db->beginTransaction(); + try { + foreach ($videos as $index => $video) { + $sort_order = $index * 10; + if ((int)$video['sort_order'] === $sort_order) continue; + $stmt->execute([$sort_order, (int)$video['id']]); + $result['updated']++; + } + $db->commit(); + } catch (Throwable $e) { + $db->rollBack(); + throw $e; + } + + return $result; +} + function media_youtube_source_url_for_video(int $video_id): string { $stmt = get_db()->prepare(" SELECT source_url @@ -1436,7 +1473,7 @@ function get_videos_paginated(int $page, int $per_page, bool $published_only = t LEFT JOIN video_sources vs ON vs.video_id = v.id $where GROUP BY v.id - ORDER BY v.sort_order ASC, v.id DESC + ORDER BY v.sort_order ASC, datetime(v.created_at) DESC, v.created_at DESC, v.id DESC LIMIT :limit OFFSET :offset "); foreach ($bind as $key => $value) $rows->bindValue($key, $value); diff --git a/index.php b/index.php index 16dea3b..2c6e175 100644 --- a/index.php +++ b/index.php @@ -16,7 +16,7 @@ $video = $slug ? get_video_by_slug($slug) : null; // Latest video as default if none specified if (!$video) { $db = get_db(); - $latest = $db->query("SELECT slug FROM videos WHERE published=1 ORDER BY sort_order ASC, id DESC LIMIT 1")->fetch(); + $latest = $db->query("SELECT slug FROM videos WHERE published=1 ORDER BY sort_order ASC, datetime(created_at) DESC, created_at DESC, id DESC LIMIT 1")->fetch(); if ($latest) $video = get_video_by_slug($latest['slug']); }