From b82a4e1599a58b89557fc242776fa1f99bb50e7f Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 10 Jun 2026 23:32:52 -0400 Subject: [PATCH] change sorting for media files in folders (#1627) --- ...45dd4d8db55c20fd1aede2a4415cc1395cf3.json} | 8 +++---- ...e530e201a6c24afd29ad534cf7f3954c7316.json} | 8 +++---- .../media_files/list_folder_media_files.rs | 23 +++++++++++-------- .../list_folder_media_files_handler.rs | 2 +- 4 files changed, 22 insertions(+), 19 deletions(-) rename .sqlx/{query-88673127b54626dc38d28693e709dfaeced9a05300a26b3f3c52a17f28171c9a.json => query-8aca9bc976b980ea1a3b3052bc8a45dd4d8db55c20fd1aede2a4415cc1395cf3.json} (78%) rename .sqlx/{query-d2a406bcf08f656fd602aa6ba1124a77d04ad874b15abafca300e8b381c7b211.json => query-c0ba9f9aa3184b1cb3d714173b31e530e201a6c24afd29ad534cf7f3954c7316.json} (78%) diff --git a/.sqlx/query-88673127b54626dc38d28693e709dfaeced9a05300a26b3f3c52a17f28171c9a.json b/.sqlx/query-8aca9bc976b980ea1a3b3052bc8a45dd4d8db55c20fd1aede2a4415cc1395cf3.json similarity index 78% rename from .sqlx/query-88673127b54626dc38d28693e709dfaeced9a05300a26b3f3c52a17f28171c9a.json rename to .sqlx/query-8aca9bc976b980ea1a3b3052bc8a45dd4d8db55c20fd1aede2a4415cc1395cf3.json index ec55f7dbc1..06d0ed2afc 100644 --- a/.sqlx/query-88673127b54626dc38d28693e709dfaeced9a05300a26b3f3c52a17f28171c9a.json +++ b/.sqlx/query-8aca9bc976b980ea1a3b3052bc8a45dd4d8db55c20fd1aede2a4415cc1395cf3.json @@ -1,14 +1,14 @@ { "db_name": "MySQL", - "query": "\nSELECT\n fmf.id as `membership_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\nORDER BY fmf.id DESC\nLIMIT ?\n ", + "query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\nORDER BY mf.id DESC\nLIMIT ?\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "membership_id: u64", + "name": "media_file_id: u64", "type_info": { "type": "LongLong", - "flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT", + "flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT", "char_set": 63, "max_size": 20 } @@ -263,5 +263,5 @@ false ] }, - "hash": "88673127b54626dc38d28693e709dfaeced9a05300a26b3f3c52a17f28171c9a" + "hash": "8aca9bc976b980ea1a3b3052bc8a45dd4d8db55c20fd1aede2a4415cc1395cf3" } diff --git a/.sqlx/query-d2a406bcf08f656fd602aa6ba1124a77d04ad874b15abafca300e8b381c7b211.json b/.sqlx/query-c0ba9f9aa3184b1cb3d714173b31e530e201a6c24afd29ad534cf7f3954c7316.json similarity index 78% rename from .sqlx/query-d2a406bcf08f656fd602aa6ba1124a77d04ad874b15abafca300e8b381c7b211.json rename to .sqlx/query-c0ba9f9aa3184b1cb3d714173b31e530e201a6c24afd29ad534cf7f3954c7316.json index 08fca70a35..d5d3942a87 100644 --- a/.sqlx/query-d2a406bcf08f656fd602aa6ba1124a77d04ad874b15abafca300e8b381c7b211.json +++ b/.sqlx/query-c0ba9f9aa3184b1cb3d714173b31e530e201a6c24afd29ad534cf7f3954c7316.json @@ -1,14 +1,14 @@ { "db_name": "MySQL", - "query": "\nSELECT\n fmf.id as `membership_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND fmf.id < ?\nORDER BY fmf.id DESC\nLIMIT ?\n ", + "query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "membership_id: u64", + "name": "media_file_id: u64", "type_info": { "type": "LongLong", - "flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT", + "flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT", "char_set": 63, "max_size": 20 } @@ -263,5 +263,5 @@ false ] }, - "hash": "d2a406bcf08f656fd602aa6ba1124a77d04ad874b15abafca300e8b381c7b211" + "hash": "c0ba9f9aa3184b1cb3d714173b31e530e201a6c24afd29ad534cf7f3954c7316" } diff --git a/crates/schema/database/mysql_queries/src/queries/folders/media_files/list_folder_media_files.rs b/crates/schema/database/mysql_queries/src/queries/folders/media_files/list_folder_media_files.rs index b04946524c..0ef4d9eea6 100644 --- a/crates/schema/database/mysql_queries/src/queries/folders/media_files/list_folder_media_files.rs +++ b/crates/schema/database/mysql_queries/src/queries/folders/media_files/list_folder_media_files.rs @@ -16,8 +16,10 @@ use tokens::tokens::prompts::PromptToken; /// and the rest of the typed response shape. #[derive(Debug, Clone)] pub struct FolderMediaFileRow { - /// `folder_media_files.id` — used as the pagination cursor. - pub membership_id: u64, + /// `media_files.id` — used as the pagination cursor. The list is sorted + /// by this (descending) rather than by folder-membership order so the + /// frontend's group-by-date view doesn't interleave across pages. + pub media_file_id: u64, /// When the media file was added to the folder. pub added_to_folder_at: DateTime, @@ -71,7 +73,8 @@ where /// for the file's own metadata and LEFT JOINs `media_files` again on /// `maybe_cover_image_media_file_token` so the cover-image bucket fields /// come back in a single round-trip. Filters out media files that are -/// soft-deleted on the media-file side. Most-recently-added first. +/// soft-deleted on the media-file side. Newest media files first +/// (`media_files.id` descending — NOT folder-membership order). /// /// The caller is expected to have already authorized the folder access. pub async fn list_folder_media_files<'e, 'c: 'e, E>( @@ -87,7 +90,7 @@ where sqlx::query!( r#" SELECT - fmf.id as `membership_id: u64`, + mf.id as `media_file_id: u64`, fmf.created_at as `added_to_folder_at: DateTime`, mf.token as `media_file_token: MediaFileToken`, @@ -126,8 +129,8 @@ LEFT JOIN media_files cover WHERE fmf.folder_token = ? AND mf.user_deleted_at IS NULL AND mf.mod_deleted_at IS NULL - AND fmf.id < ? -ORDER BY fmf.id DESC + AND mf.id < ? +ORDER BY mf.id DESC LIMIT ? "#, args.folder_token.as_str(), @@ -138,7 +141,7 @@ LIMIT ? .await? .into_iter() .map(|r| FolderMediaFileRow { - membership_id: r.membership_id, + media_file_id: r.media_file_id, added_to_folder_at: r.added_to_folder_at, media_file_token: r.media_file_token, media_class: r.media_class, @@ -168,7 +171,7 @@ LIMIT ? sqlx::query!( r#" SELECT - fmf.id as `membership_id: u64`, + mf.id as `media_file_id: u64`, fmf.created_at as `added_to_folder_at: DateTime`, mf.token as `media_file_token: MediaFileToken`, @@ -207,7 +210,7 @@ LEFT JOIN media_files cover WHERE fmf.folder_token = ? AND mf.user_deleted_at IS NULL AND mf.mod_deleted_at IS NULL -ORDER BY fmf.id DESC +ORDER BY mf.id DESC LIMIT ? "#, args.folder_token.as_str(), @@ -217,7 +220,7 @@ LIMIT ? .await? .into_iter() .map(|r| FolderMediaFileRow { - membership_id: r.membership_id, + media_file_id: r.media_file_id, added_to_folder_at: r.added_to_folder_at, media_file_token: r.media_file_token, media_class: r.media_class, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs index 84bcde4871..c5ba5ef0a4 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/folders/media_files/list_folder_media_files_handler.rs @@ -172,7 +172,7 @@ pub async fn list_folder_media_files_handler( })?; let maybe_cursor = rows.last().map(|last| { - server_state.opaque_cursors.encode_last_id_cursor(CURSOR_NAME, last.membership_id) + server_state.opaque_cursors.encode_last_id_cursor(CURSOR_NAME, last.media_file_id) }).transpose().map_err(|err| { warn!("Failed to encode cursor: {:?}", err); CommonWebError::server_error_with_message("Failed to encode cursor")