mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-01 07:15:54 -05:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c446185e |
-25
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT DISTINCT tag_token as `tag_token: TagToken`\nFROM media_file_tags\nWHERE media_file_token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "tag_token: TagToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "05033f7f4588cc2d68d9db22236f2be323731ee120217192fa29fe60af776e19"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n \n maybe_engine_category = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_generation_provider = ?,\n\n platform_type = ?,\n\n maybe_cover_image_media_file_token = ?,\n\n maybe_mod_user_token = ?,\n is_generated_on_prem = ?,\n generated_by_worker = ?,\n generated_by_cluster = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 41
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1c74e9c2dcc9205e2c9fe60a6de0828208e497e68560246e588092bafd03eb59"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "DELETE FROM media_file_tags WHERE tag_token = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3495f03338e7ee4c2ae9787204d521aefa787312463d392e45d02a693d9afb83"
|
||||
}
|
||||
-245
@@ -1,245 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\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\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 media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.media_class = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "media_file_id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "media_file_token: MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_class: MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "media_type: MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_batch_token: BatchGenerationToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_cover_public_bucket_directory_hash?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_cover_public_bucket_prefix?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_cover_public_bucket_extension?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "creator_set_visibility: Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "is_user_upload: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_prompt_token: PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_origin_filename",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_duration_millis",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "maybe_frame_width",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "maybe_frame_height",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3681e931cc87714d71c95da44d6dd92568ce58aea252164ab3d5f8e96491169c"
|
||||
}
|
||||
-245
@@ -1,245 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\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\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 media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1\n FROM folder_media_files fmf\n JOIN folders f\n ON f.token = fmf.folder_token\n AND f.maybe_deleted_at IS NULL\n WHERE fmf.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "media_file_id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "media_file_token: MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_class: MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "media_type: MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_batch_token: BatchGenerationToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_cover_public_bucket_directory_hash?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_cover_public_bucket_prefix?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_cover_public_bucket_extension?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "creator_set_visibility: Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "is_user_upload: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_prompt_token: PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_origin_filename",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_duration_millis",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "maybe_frame_width",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "maybe_frame_height",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6f077c5d35b19319aba4e9f4c59610822334fd304ba9133ba8e099056c426f2a"
|
||||
}
|
||||
-223
@@ -1,223 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id < ?\nORDER BY m.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "token: tokens::tokens::media_files::MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_class: enums::by_table::media_files::media_file_class::MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_creator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_creator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_creator_gravatar_hash",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_file_cover_image_public_bucket_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_file_cover_image_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_file_cover_image_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "creator_set_visibility: enums::common::visibility::Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "73540435892b7b4dbc11c6c63712abdb18fd8b3db2bcd56b8518c968b8169de7"
|
||||
}
|
||||
-245
@@ -1,245 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\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\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 media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "media_file_id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "media_file_token: MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_class: MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "media_type: MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_batch_token: BatchGenerationToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_cover_public_bucket_directory_hash?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_cover_public_bucket_prefix?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_cover_public_bucket_extension?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "creator_set_visibility: Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "is_user_upload: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_prompt_token: PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_origin_filename",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_duration_millis",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "maybe_frame_width",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "maybe_frame_height",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "76bcf698f40d852558f071a6909b05b1c960c1cf236831ae009f90de46d3aefb"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE tags\nSET tag_value = ?,\n tag_value_lowercase = ?\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NULL\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8938fc564dda214e383a8d30532db6ef964c03f7e1fd6d9561bca5e8b56834cf"
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n maybe_creator_user_token as `maybe_creator_user_token: UserToken`,\n creator_set_visibility as `creator_set_visibility: Visibility`\nFROM media_files\nWHERE token = ?\n AND user_deleted_at IS NULL\n AND mod_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "maybe_creator_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "creator_set_visibility: Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a114fab486689fd2b16197cbe3a3ebb41d3d10937542832e15dd652200b6d419"
|
||||
}
|
||||
-223
@@ -1,223 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n m.id as `id: u64`,\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n m.maybe_title,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nWHERE\n m.maybe_creator_user_token = ?\n AND m.media_class = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n AND NOT m.is_intermediate_system_file\n AND m.id > ?\nORDER BY m.id ASC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "token: tokens::tokens::media_files::MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_class: enums::by_table::media_files::media_file_class::MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_creator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_creator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_creator_gravatar_hash",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_file_cover_image_public_bucket_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_file_cover_image_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_file_cover_image_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "creator_set_visibility: enums::common::visibility::Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a52cc2d6e86607c430173553230b3794a4c1ada07274a975c04128e47d3b7f83"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE tags\nSET maybe_deleted_at = NOW(),\n use_count = 0\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NULL\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a58fc9050c1fd160e03d6e4932c1b777ea7ab8cbfddca5834faab3e2963d816d"
|
||||
}
|
||||
-245
@@ -1,245 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\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\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 media_files mf\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND NOT EXISTS (\n SELECT 1 FROM media_file_tags mft\n WHERE mft.media_file_token = mf.token\n )\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "media_file_id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "media_file_token: MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_class: MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "media_type: MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_batch_token: BatchGenerationToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_cover_public_bucket_directory_hash?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_cover_public_bucket_prefix?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_cover_public_bucket_extension?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "creator_set_visibility: Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "is_user_upload: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_prompt_token: PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_origin_filename",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_duration_millis",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "maybe_frame_width",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "maybe_frame_height",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "aa1e693babe6997e75935ec893e6eed46351ec72ce02a9144fd11d2fcfd37fdf"
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n t.id as `id: u64`,\n t.token as `token: TagToken`,\n t.tag_value,\n t.tag_value_lowercase,\n t.use_count as `use_count: u32`\nFROM media_file_tags mft\nJOIN tags t\n ON t.token = mft.tag_token\nWHERE mft.media_file_token = ?\n AND t.maybe_deleted_at IS NULL\nORDER BY t.tag_value_lowercase ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "token: TagToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "tag_value",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "tag_value_lowercase",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "use_count: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bbfed9e861b1a7cd0cdb2413e2a985aaa79aa9c885408bf5e104d1ea80bdaf49"
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT token as `token: TagToken`\nFROM tags\nWHERE creator_user_token = ?\n AND tag_value_lowercase = ?\n AND maybe_deleted_at IS NOT NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: TagToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "cbfe9172d6227175aa6dfd276a4d1a6676d1d1b6bdc3a35927a5d71be38219c0"
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n pci.media_token as `media_token: MediaFileToken`\nFROM prompt_context_items pci\nJOIN media_files m\n ON pci.media_token = m.token\nWHERE pci.prompt_token = ?\n AND m.media_class = 'image'\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\nORDER BY pci.id ASC\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "media_token: MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d6dfca49327b2b31078517c5926ae51cc2490b17d8c94a97fefd3decdf8e5664"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nDELETE FROM tags\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NOT NULL\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e50d11df0606a12059e13f36daa41fc5aa59d700168425c406e545b535d6d2ba"
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n id as `id: u64`,\n token as `token: TagToken`,\n tag_value,\n tag_value_lowercase,\n use_count as `use_count: u32`\nFROM tags\nWHERE creator_user_token = ?\n AND maybe_deleted_at IS NULL\n AND id < ?\nORDER BY id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "token: TagToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "tag_value",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "tag_value_lowercase",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "use_count: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "eafaf1a9ed2e27dec201ea66d23a65662492ca2dbdac6333629b82defc4b5111"
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n id as `id: u64`,\n token as `token: TagToken`,\n tag_value,\n tag_value_lowercase,\n use_count as `use_count: u32`\nFROM tags\nWHERE token = ?\n AND creator_user_token = ?\n AND maybe_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "token: TagToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "tag_value",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "tag_value_lowercase",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "use_count: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ecc3848cd186f1298a2a60c810c69b7a8715719a0c7622122d7e1c5747fab06a"
|
||||
}
|
||||
-245
@@ -1,245 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\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\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 media_file_tags mft\nJOIN media_files mf\n ON mf.token = mft.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE mft.tag_token = ?\n AND mft.user_token = ?\n AND mf.maybe_creator_user_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND NOT mf.is_intermediate_system_file\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "media_file_id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "media_file_token: MediaFileToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "media_class: MediaFileClass",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "media_type: MediaFileType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_batch_token: BatchGenerationToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "public_bucket_directory_hash",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_public_bucket_prefix",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_public_bucket_extension",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_cover_public_bucket_directory_hash?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_cover_public_bucket_prefix?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_cover_public_bucket_extension?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "creator_set_visibility: Visibility",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "is_user_upload: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_prompt_token: PromptToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "maybe_origin_filename",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_duration_millis",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "maybe_frame_width",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "maybe_frame_height",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f36c7238231353882b5b2ac39616dcdc6adebf7c7e1198d9c2af4674ba5d982c"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?,\n origin_product_category = ?,\n maybe_origin_model_type = ?,\n maybe_origin_model_token = ?,\n maybe_origin_filename = ?,\n\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?,\n maybe_video_encoding = ?,\n maybe_frame_width = ?,\n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n\n maybe_engine_category = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?,\n maybe_public_bucket_prefix = ?,\n maybe_public_bucket_extension = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n maybe_creator_file_synthetic_id = ?,\n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_generation_provider = ?,\n\n platform_type = ?,\n\n maybe_cover_image_media_file_token = ?,\n\n maybe_mod_user_token = ?,\n is_generated_on_prem = ?,\n generated_by_worker = ?,\n generated_by_cluster = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 41
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f5bc4a956a20f6c0998585092a0922301d53c95ae8ea9cb808ae2dbcf8d0bb32"
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE tag_uses (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- ========== TAG LINK ==========
|
||||
|
||||
-- Token of the tag
|
||||
tag_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== LINKED ENTITY ==========
|
||||
|
||||
-- Entity the tag is linked to (composite key)
|
||||
entity_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Entity the tag is linked to (composite key)
|
||||
entity_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
maybe_deleted_at TIMESTAMP NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (tag_token, entity_type, entity_token),
|
||||
|
||||
KEY index_tag_token (tag_token),
|
||||
KEY index_entity_type_and_token (entity_type, entity_token),
|
||||
KEY index_maybe_deleted_at (maybe_deleted_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE tag_uses;
|
||||
@@ -1,30 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE tags (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- ========== PRIMARY FUNCTIONALITY ==========
|
||||
|
||||
-- Effective "primary key" of the beta key.
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The actual value of the tag.
|
||||
-- The code will normalize this.
|
||||
tag_value VARCHAR(255) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
maybe_deleted_at TIMESTAMP NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (tag_value),
|
||||
|
||||
KEY index_maybe_deleted_at (maybe_deleted_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE tags;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS tags;
|
||||
@@ -1,38 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE tags (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key".
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The display value of the tag, as entered by the creator.
|
||||
tag_value VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Lowercased copy of `tag_value` for case-insensitive uniqueness and lookup.
|
||||
-- (The table collation is utf8mb4_bin, so the code must lowercase this.)
|
||||
tag_value_lowercase VARCHAR(255) NOT NULL,
|
||||
|
||||
-- The user that created the tag.
|
||||
creator_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Rollup statistic for usage counts.
|
||||
use_count INT(10) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
maybe_deleted_at TIMESTAMP NULL DEFAULT NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (tag_value_lowercase, creator_user_token),
|
||||
|
||||
KEY index_creator_user_token (creator_user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS media_file_tags;
|
||||
@@ -1,32 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE media_file_tags (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- The media file being tagged.
|
||||
media_file_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The tag applied to the media file.
|
||||
tag_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The user that owns this tagging.
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (media_file_token, tag_token, user_token),
|
||||
|
||||
KEY index_media_file_token_and_user_token (media_file_token, user_token),
|
||||
KEY index_tag_token_and_user_token (tag_token, user_token),
|
||||
KEY index_media_file_token (media_file_token),
|
||||
KEY index_tag_token (tag_token),
|
||||
KEY index_user_token (user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -1,36 +0,0 @@
|
||||
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
|
||||
-- with comments attached to the fields for centralized documentation.
|
||||
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- N:M mapping between `media_files` and `tags`, scoped to the owning user.
|
||||
CREATE TABLE media_file_tags (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- The media file being tagged.
|
||||
media_file_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The tag applied to the media file.
|
||||
tag_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The user that owns this tagging.
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (media_file_token, tag_token, user_token),
|
||||
|
||||
KEY index_media_file_token_and_user_token (media_file_token, user_token),
|
||||
KEY index_tag_token_and_user_token (tag_token, user_token),
|
||||
KEY index_media_file_token (media_file_token),
|
||||
KEY index_tag_token (tag_token),
|
||||
KEY index_user_token (user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -1,44 +0,0 @@
|
||||
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
|
||||
-- with comments attached to the fields for centralized documentation.
|
||||
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- User-created tags for media files. Tags are scoped per-user: two users can
|
||||
-- each have their own tag with the same value. Applied to media files via the
|
||||
-- `media_file_tags` join table.
|
||||
CREATE TABLE tags (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key".
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The display value of the tag, as entered by the creator.
|
||||
tag_value VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Lowercased copy of `tag_value` for case-insensitive uniqueness and lookup.
|
||||
-- (The table collation is utf8mb4_bin, so the code must lowercase this.)
|
||||
tag_value_lowercase VARCHAR(255) NOT NULL,
|
||||
|
||||
-- The user that created the tag.
|
||||
creator_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Rollup statistic for usage counts.
|
||||
use_count INT(10) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
maybe_deleted_at TIMESTAMP NULL DEFAULT NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (tag_value_lowercase, creator_user_token),
|
||||
|
||||
KEY index_creator_user_token (creator_user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -1,7 +1,6 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
@@ -23,28 +22,6 @@ pub struct ListFolderMediaFilesQueryParams {
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
// ── GET /v1/folders/media_files_without_folder ──
|
||||
//
|
||||
// NB: `ListMediaFilesWithoutFolderSuccessResponse` and its list item live in
|
||||
// `storyteller_web`'s handler because the wire shape embeds
|
||||
// `MediaFileCoverImageDetails` and `MediaLinks` constructors that depend
|
||||
// on the request's `MediaDomain` + `ServerEnvironment`.
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct ListMediaFilesWithoutFolderQueryParams {
|
||||
pub cursor: Option<String>,
|
||||
pub limit: Option<u32>,
|
||||
|
||||
/// Optional filter on the coarse media class.
|
||||
///
|
||||
/// Usage:
|
||||
/// - `?filter_media_class=image`
|
||||
/// - `?filter_media_class=video`
|
||||
/// - `?filter_media_class=mesh`
|
||||
/// - `?filter_media_class=splat`
|
||||
pub filter_media_class: Option<MediaFileClass>,
|
||||
}
|
||||
|
||||
// ── PUT /v1/folders/media_files/{folder_token}/bulk_add ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
|
||||
@@ -13,7 +13,6 @@ pub mod omni_gen;
|
||||
pub mod prompts;
|
||||
pub mod stripe_artcraft;
|
||||
pub mod subscriptions;
|
||||
pub mod tags;
|
||||
pub mod user_referral_codes;
|
||||
pub mod users;
|
||||
pub mod utils;
|
||||
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
//! Shared types for the by-class session media file list endpoints
|
||||
//! (`/v1/media_files/mesh/list`, `/v1/media_files/splat/list`).
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
|
||||
use crate::common::responses::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::common::responses::media_links::MediaLinks;
|
||||
use crate::common::responses::user_details_light::UserDetailsLight;
|
||||
|
||||
/// Query string parameters for the by-class session media file lists.
|
||||
/// Pagination only — the media class is fixed by the endpoint.
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
pub struct ListSessionMediaFilesByTypeQueryParams {
|
||||
pub sort_ascending: Option<bool>,
|
||||
pub page_size: Option<usize>,
|
||||
pub cursor: Option<String>,
|
||||
pub cursor_is_reversed: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct SessionMediaFileInfo {
|
||||
/// Primary key identifier
|
||||
pub token: MediaFileToken,
|
||||
|
||||
/// The coarse-grained class of media file.
|
||||
/// Fixed per endpoint (`mesh` or `splat`).
|
||||
pub media_class: MediaFileClass,
|
||||
|
||||
/// Type of media will dictate which fields are populated and what
|
||||
/// the frontend should display (eg. video player vs audio player).
|
||||
/// This is closer in meaning to a "mime type".
|
||||
pub media_type: MediaFileType,
|
||||
|
||||
/// Rich CDN links to the media, including thumbnails, previews, and more.
|
||||
pub media_links: MediaLinks,
|
||||
|
||||
/// Information about the cover image. Many media files do not require a cover image,
|
||||
/// e.g. image files, video files with thumbnails, audio files, etc.
|
||||
/// 3D files require them.
|
||||
pub cover_image: MediaFileCoverImageDetails,
|
||||
|
||||
/// User info
|
||||
pub maybe_creator_user: Option<UserDetailsLight>,
|
||||
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
/// The generation's prompt record, when there is one. Clients resolve it
|
||||
/// to display the original prompt and model.
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
|
||||
/// The name or title of the media file (optional)
|
||||
pub maybe_title: Option<String>,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::common::responses::pagination_cursors::PaginationCursors;
|
||||
use crate::media_file::list::by_type::list_session_common::SessionMediaFileInfo;
|
||||
|
||||
pub const LIST_SESSION_MESH_MEDIA_FILES_URL_PATH: &str = "/v1/media_files/mesh/list";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct ListSessionMeshMediaFilesSuccessResponse {
|
||||
pub success: bool,
|
||||
pub results: Vec<SessionMediaFileInfo>,
|
||||
pub pagination: PaginationCursors,
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::common::responses::pagination_cursors::PaginationCursors;
|
||||
use crate::media_file::list::by_type::list_session_common::SessionMediaFileInfo;
|
||||
|
||||
pub const LIST_SESSION_SPLAT_MEDIA_FILES_URL_PATH: &str = "/v1/media_files/splat/list";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct ListSessionSplatMediaFilesSuccessResponse {
|
||||
pub success: bool,
|
||||
pub results: Vec<SessionMediaFileInfo>,
|
||||
pub pagination: PaginationCursors,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod list_session_common;
|
||||
pub mod list_session_mesh_media_files;
|
||||
pub mod list_session_splat_media_files;
|
||||
@@ -1 +0,0 @@
|
||||
pub mod by_type;
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod delete_media_file;
|
||||
pub mod list;
|
||||
pub mod list_batch_generated_media_files;
|
||||
pub mod list_session_project_media_files;
|
||||
pub mod project;
|
||||
pub mod project;
|
||||
@@ -1,35 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── POST /v1/tags/media_file/add/{media_file_token} ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct AddMediaFileTagsPathInfo {
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
|
||||
/// both or neither). Tag text is trimmed and deduped case-insensitively;
|
||||
/// empty entries are dropped. Must sanitize to at least one tag (400
|
||||
/// otherwise).
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct AddMediaFileTagsRequest {
|
||||
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
|
||||
pub maybe_tags: Option<String>,
|
||||
|
||||
/// Tags as a list. Entries are still trimmed.
|
||||
pub maybe_tags_list: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct AddMediaFileTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The tags from this request after upsert, with canonical tokens and
|
||||
/// fresh use counts. Tags already on the file are absorbed (no error).
|
||||
pub tags: Vec<TagDetails>,
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── POST /v1/tags/bulk_add ──
|
||||
|
||||
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
|
||||
/// both or neither). Tag text is trimmed and deduped case-insensitively;
|
||||
/// empty entries are dropped. Must sanitize to at least one tag (400
|
||||
/// otherwise). Media file tokens are deduped; tokens the user doesn't
|
||||
/// own (or that are deleted) are silently skipped.
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct BulkAddTagsRequest {
|
||||
/// Media files to tag.
|
||||
pub media_file_tokens: Vec<MediaFileToken>,
|
||||
|
||||
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
|
||||
pub maybe_tags: Option<String>,
|
||||
|
||||
/// Tags as a list. Entries are still trimmed.
|
||||
pub maybe_tags_list: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct BulkAddTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The subset of the input tokens that were actually tagged: files
|
||||
/// that exist, aren't deleted, and are owned by the caller.
|
||||
pub accepted_media_file_tokens: Vec<MediaFileToken>,
|
||||
|
||||
/// The tags from this request after upsert, with canonical tokens and
|
||||
/// fresh use counts.
|
||||
pub tags: Vec<TagDetails>,
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── POST /v1/tags/media_files/bulk_list_tags ──
|
||||
//
|
||||
// POST rather than GET: the token list belongs in a request body, and
|
||||
// GET request bodies are non-standard. The operation is still a pure
|
||||
// read.
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct BulkListMediaFileTagsRequest {
|
||||
/// Media files to look up. Tokens with no tags come back with an
|
||||
/// empty `tags` list.
|
||||
pub media_file_tokens: Vec<MediaFileToken>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct MediaFileTagsEntry {
|
||||
pub media_file_token: MediaFileToken,
|
||||
pub tags: Vec<TagDetails>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct BulkListMediaFileTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// One entry per requested media file token (deduped), in request order.
|
||||
pub media_files: Vec<MediaFileTagsEntry>,
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── POST /v1/tags/bulk_set ──
|
||||
|
||||
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
|
||||
/// both or neither). Tag text is trimmed and deduped case-insensitively;
|
||||
/// empty entries are dropped. Unlike `bulk_add`, sanitizing down to zero
|
||||
/// tags is allowed — it clears all tags from the listed media files.
|
||||
/// Media file tokens are deduped; tokens the user doesn't own (or that
|
||||
/// are deleted) are silently skipped.
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct BulkSetTagsRequest {
|
||||
/// Media files whose tag sets will be replaced.
|
||||
pub media_file_tokens: Vec<MediaFileToken>,
|
||||
|
||||
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
|
||||
pub maybe_tags: Option<String>,
|
||||
|
||||
/// Tags as a list. Entries are still trimmed.
|
||||
pub maybe_tags_list: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct BulkSetTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The subset of the input tokens that were actually updated: files
|
||||
/// that exist, aren't deleted, and are owned by the caller.
|
||||
pub accepted_media_file_tokens: Vec<MediaFileToken>,
|
||||
|
||||
/// The full tag set now on every accepted media file, with canonical
|
||||
/// tokens and fresh use counts.
|
||||
pub tags: Vec<TagDetails>,
|
||||
|
||||
/// How many previously-attached tag links were removed across all the
|
||||
/// accepted media files because they weren't mentioned in the request.
|
||||
/// (Orphaned tags are not deleted.)
|
||||
pub removed_count: u64,
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
// ── POST /v1/tags/media_file/clear/{media_file_token} ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct ClearMediaFileTagsPathInfo {
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ClearMediaFileTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// How many tag links were removed from the media file. (Orphaned
|
||||
/// tags are not deleted.)
|
||||
pub removed_count: u64,
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
|
||||
/// One tag as returned by every tags endpoint.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct TagDetails {
|
||||
pub tag_token: TagToken,
|
||||
|
||||
/// The display value of the tag, as entered by its creator.
|
||||
pub tag_value: String,
|
||||
|
||||
/// Lowercased form of `tag_value`. This is the tag's unique key
|
||||
/// within the creator's account.
|
||||
pub tag_value_lowercase: String,
|
||||
|
||||
/// Rollup statistic: how many media files currently carry this tag.
|
||||
pub use_count: u32,
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
|
||||
// ── DELETE /v1/tags/{tag_token} ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct DeleteTagPathInfo {
|
||||
pub tag_token: TagToken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct DeleteTagSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// How many media-file links were hard-deleted along with the tag.
|
||||
/// (The tag record itself is soft-deleted.)
|
||||
pub removed_link_count: u64,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── GET /v1/tags/media_file/list/{media_file_token} ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct ListMediaFileTagsPathInfo {
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ListMediaFileTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// All (live) tags on the media file, sorted by tag value.
|
||||
pub tags: Vec<TagDetails>,
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
use serde_derive::Deserialize;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
|
||||
// ── GET /v1/tags/media_files/with_tag/{tag_token} ──
|
||||
//
|
||||
// NB: The success response lives in `storyteller_web`'s handler because
|
||||
// the wire shape embeds `MediaLinks` / `MediaFileCoverImageDetails`
|
||||
// constructors that depend on the request's `MediaDomain` +
|
||||
// `ServerEnvironment`.
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct ListMediaFilesWithTagPathInfo {
|
||||
pub tag_token: TagToken,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct ListMediaFilesWithTagQueryParams {
|
||||
pub cursor: Option<String>,
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use serde_derive::Deserialize;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
// ── GET /v1/tags/media_files/list_tagged ──
|
||||
//
|
||||
// NB: The success response lives in `storyteller_web`'s handler because
|
||||
// the wire shape embeds `MediaLinks` / `MediaFileCoverImageDetails`
|
||||
// constructors that depend on the request's `MediaDomain` +
|
||||
// `ServerEnvironment`.
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct ListTaggedMediaFilesQueryParams {
|
||||
pub cursor: Option<String>,
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── GET /v1/tags/list ──
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct ListTagsQueryParams {
|
||||
pub cursor: Option<String>,
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ListTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The logged-in user's (live) tags, newest first.
|
||||
pub tags: Vec<TagDetails>,
|
||||
|
||||
/// Present when there may be more results; pass back as `cursor`.
|
||||
pub maybe_cursor: Option<String>,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use serde_derive::Deserialize;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
// ── GET /v1/tags/media_files/list_untagged ──
|
||||
//
|
||||
// NB: The success response lives in `storyteller_web`'s handler because
|
||||
// the wire shape embeds `MediaLinks` / `MediaFileCoverImageDetails`
|
||||
// constructors that depend on the request's `MediaDomain` +
|
||||
// `ServerEnvironment`.
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct ListUntaggedMediaFilesQueryParams {
|
||||
pub cursor: Option<String>,
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
pub mod add_media_file_tags;
|
||||
pub mod bulk_add_tags;
|
||||
pub mod bulk_list_media_file_tags;
|
||||
pub mod bulk_set_tags;
|
||||
pub mod clear_media_file_tags;
|
||||
pub mod common;
|
||||
pub mod delete_tag;
|
||||
pub mod list_media_file_tags;
|
||||
pub mod list_media_files_with_tag;
|
||||
pub mod list_tagged_media_files;
|
||||
pub mod list_tags;
|
||||
pub mod list_untagged_media_files;
|
||||
pub mod rename_tag;
|
||||
pub mod set_media_file_tags;
|
||||
@@ -1,29 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── PUT /v1/tags/rename/{tag_token} ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct RenameTagPathInfo {
|
||||
pub tag_token: TagToken,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct RenameTagRequest {
|
||||
/// The new tag text (trimmed server-side). Both case-only changes and
|
||||
/// wholesale renames are allowed. Fails with 400 if the caller already
|
||||
/// has a different tag with the same lowercased value.
|
||||
pub new_tag_value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct RenameTagSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The tag after the rename.
|
||||
pub tag: TagDetails,
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::tags::common::TagDetails;
|
||||
|
||||
// ── POST /v1/tags/media_file/set/{media_file_token} ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct SetMediaFileTagsPathInfo {
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
/// Exactly one of `maybe_tags` / `maybe_tags_list` must be set (400 if
|
||||
/// both or neither). Tag text is trimmed and deduped case-insensitively;
|
||||
/// empty entries are dropped. Unlike `add`, sanitizing down to zero tags
|
||||
/// is allowed — it clears all tags from the media file.
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct SetMediaFileTagsRequest {
|
||||
/// Comma-separated tags, e.g. `"cats, Sci-Fi, wallpaper"`.
|
||||
pub maybe_tags: Option<String>,
|
||||
|
||||
/// Tags as a list. Entries are still trimmed.
|
||||
pub maybe_tags_list: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct SetMediaFileTagsSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The media file's full tag set after the operation, with canonical
|
||||
/// tokens and fresh use counts.
|
||||
pub tags: Vec<TagDetails>,
|
||||
|
||||
/// How many previously-attached tag links were removed because they
|
||||
/// weren't mentioned in the request. (Orphaned tags are not deleted.)
|
||||
pub removed_count: u64,
|
||||
}
|
||||
@@ -182,46 +182,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hunyuan_3d_part_format_not_supported() {
|
||||
let webhook = load_test_webhook("failure/hunyuan_3d_3p1_part_fail_format_not_supported.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
match result {
|
||||
HydratedWebhookContents::Error(data) => {
|
||||
assert_eq!(
|
||||
data.error_type,
|
||||
Some(WebhookErrorType::ValueError),
|
||||
);
|
||||
assert_eq!(
|
||||
data.message.as_deref(),
|
||||
Some("Part generation only supports FBX format. Please provide an FBX file or use /convert-format to convert your model to FBX first."),
|
||||
);
|
||||
}
|
||||
other => panic!("Expected HydratedWebhookContents::Error, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triposplat_no_foreground_subject() {
|
||||
let webhook = load_test_webhook("failure/triposplat_fail_no_foreground_subject.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
match result {
|
||||
HydratedWebhookContents::Error(data) => {
|
||||
assert_eq!(
|
||||
data.error_type,
|
||||
Some(WebhookErrorType::InputValueError),
|
||||
);
|
||||
assert_eq!(
|
||||
data.message.as_deref(),
|
||||
Some("No foreground subject could be detected in the input image after background removal. Provide an image with a clear, visible subject against a plain or distinct background."),
|
||||
);
|
||||
}
|
||||
other => panic!("Expected HydratedWebhookContents::Error, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_error_case() {
|
||||
let webhook = RawWebhookPayload {
|
||||
|
||||
+1
-78
@@ -43,23 +43,10 @@ pub struct ExtractedContents {
|
||||
/// Parsed from `payload.video`.
|
||||
pub video: Option<VideoData>,
|
||||
|
||||
/// Parsed from `payload.audio` (e.g. Seed Audio 1.0 speech results).
|
||||
pub audio: Option<AudioData>,
|
||||
|
||||
/// Parsed from `payload.model_glb`.
|
||||
pub model_glb: Option<ModelGlbData>,
|
||||
|
||||
/// Parsed from `payload.model_glb_pbr` (e.g. Hunyuan 3D 2.1's PBR-textured
|
||||
/// GLB variant, sent alongside `model_glb`).
|
||||
pub model_glb_pbr: Option<ModelGlbData>,
|
||||
|
||||
/// Parsed from `payload.model_urls` (e.g. Hunyuan 3D 3.0's per-format file
|
||||
/// map, sent alongside `model_glb`).
|
||||
pub model_urls: Option<ModelUrlsData>,
|
||||
|
||||
/// Parsed from `payload.model_mesh`.
|
||||
/// NB: `triposplat` ply gaussian splat files also arrive via this payload handler.
|
||||
/// These are decidedly *not* "mesh" files!
|
||||
pub model_mesh: Option<ModelMeshData>,
|
||||
|
||||
/// Parsed from `payload.model_obj` (e.g. Hunyuan 3D v3.1 Rapid's OBJ output).
|
||||
@@ -70,14 +57,6 @@ pub struct ExtractedContents {
|
||||
|
||||
/// Parsed from `payload.thumbnail`.
|
||||
pub thumbnail: Option<ThumbnailData>,
|
||||
|
||||
/// Parsed from `payload.rendered_image` (e.g. Tripo 3D's preview image,
|
||||
/// used as a cover image like `thumbnail`).
|
||||
pub rendered_image: Option<ThumbnailData>,
|
||||
|
||||
/// Parsed from `payload.preprocessed_image` (e.g. TripoSplat's segmented
|
||||
/// input image, usable as a cover image for the splat result).
|
||||
pub preprocessed_image: Option<PreprocessedImageData>,
|
||||
}
|
||||
|
||||
/// Data under `payload.image`:
|
||||
@@ -111,22 +90,7 @@ pub struct VideoData {
|
||||
pub file_size: Option<u64>,
|
||||
}
|
||||
|
||||
/// Data under `payload.audio` (e.g. Seed Audio 1.0):
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AudioData {
|
||||
pub url: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub file_name: Option<String>,
|
||||
pub file_size: Option<u64>,
|
||||
pub bitrate: Option<u64>,
|
||||
pub channels: Option<u64>,
|
||||
/// Duration in seconds.
|
||||
pub duration: Option<f64>,
|
||||
pub sample_rate: Option<u64>,
|
||||
}
|
||||
|
||||
/// Data under `payload.model_glb` (there may be other sibling keys too).
|
||||
/// Also used for `payload.model_glb_pbr`, which has the same shape.
|
||||
/// Data under `payload.model_glb` (there may be other sibling keys too)
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ModelGlbData {
|
||||
pub content_type: Option<String>,
|
||||
@@ -136,8 +100,6 @@ pub struct ModelGlbData {
|
||||
}
|
||||
|
||||
/// Data under `payload.model_mesh` (there may be other sibling keys too)
|
||||
/// NB: `triposplat` ply gaussian splat files also arrive via this payload.
|
||||
/// These are decidedly *not* "mesh" files!
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ModelMeshData {
|
||||
pub content_type: Option<String>,
|
||||
@@ -146,31 +108,6 @@ pub struct ModelMeshData {
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
/// Data under `payload.model_urls` (e.g. Hunyuan 3D 3.0 / 3.1, Tripo 3D): a
|
||||
/// map of the generation's output files by format. Any slot may be null or
|
||||
/// absent. The `glb` entry frequently duplicates the top-level model key
|
||||
/// (same URL), but may point to a different file. Slots may also duplicate
|
||||
/// each other's URLs (e.g. Tripo 3D's `glb` and `pbr_model`).
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ModelUrlsData {
|
||||
/// Untextured base model (Tripo 3D).
|
||||
pub base_model: Option<ModelGlbData>,
|
||||
/// Blender project file (Meshy).
|
||||
pub blend: Option<ModelGlbData>,
|
||||
pub fbx: Option<ModelGlbData>,
|
||||
pub glb: Option<ModelGlbData>,
|
||||
/// OBJ material file (Hunyuan 3D 3.1).
|
||||
pub mtl: Option<ModelGlbData>,
|
||||
pub obj: Option<ModelGlbData>,
|
||||
/// PBR-textured model (Tripo 3D).
|
||||
pub pbr_model: Option<ModelGlbData>,
|
||||
/// STL model (Meshy).
|
||||
pub stl: Option<ModelGlbData>,
|
||||
/// PBR texture image for the OBJ (Hunyuan 3D 3.1).
|
||||
pub texture: Option<ModelGlbData>,
|
||||
pub usdz: Option<ModelGlbData>,
|
||||
}
|
||||
|
||||
/// Data under `payload.model_obj` (there may be other sibling keys too).
|
||||
/// Some endpoints (e.g. Hunyuan 3D v3.1 Rapid text-to-3d) return an OBJ
|
||||
/// model instead of (or alongside) a GLB.
|
||||
@@ -195,7 +132,6 @@ pub struct ResultFileData {
|
||||
|
||||
/// Data under `payload.thumbnail` (there may be other sibling keys too)
|
||||
/// Frequently seen together with `model_glb`.
|
||||
/// Also used for `payload.rendered_image` (Tripo 3D), which has the same shape.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ThumbnailData {
|
||||
pub content_type: Option<String>,
|
||||
@@ -204,19 +140,6 @@ pub struct ThumbnailData {
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
/// Data under `payload.preprocessed_image` (there may be other sibling keys too).
|
||||
/// Seen together with `model_mesh` in TripoSplat results: the segmented input
|
||||
/// image, which we use as a cover image for the splat.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PreprocessedImageData {
|
||||
pub content_type: Option<String>,
|
||||
pub file_name: Option<String>,
|
||||
pub file_size: Option<usize>,
|
||||
pub height: Option<u64>,
|
||||
pub url: Option<String>,
|
||||
pub width: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ErrorData {
|
||||
/// The first human-readable message from `payload.detail[].msg`, if any.
|
||||
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::AudioData;
|
||||
|
||||
/// Extract and deserialize the `audio` key from a webhook success payload
|
||||
/// (e.g. Seed Audio 1.0 speech results).
|
||||
pub(crate) fn extract_audio(obj: &Map<String, Value>) -> Option<AudioData> {
|
||||
let value = obj.get("audio")?;
|
||||
serde_json::from_value(value.clone()).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::webhook_payload::hydrate_webhook_contents::hydrate_webhook_contents;
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::HydratedWebhookContents;
|
||||
use crate::webhook_payload::raw::raw_webhook_payload::RawWebhookPayload;
|
||||
|
||||
fn load_test_webhook(filename: &str) -> RawWebhookPayload {
|
||||
let path = format!("test_data/webhooks/{}", filename);
|
||||
let json = std::fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e));
|
||||
serde_json::from_str(&json)
|
||||
.unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_from_seed_audio_1p0_test_file() {
|
||||
let webhook = load_test_webhook("success/seed_audio_1p0_audio_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let audio = contents.audio.expect("audio should be Some");
|
||||
assert_eq!(audio.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bc36/vOSCcxsbibkZIiXR9BIZn_speech.mp3"));
|
||||
assert_eq!(audio.content_type.as_deref(), Some("audio/mpeg"));
|
||||
assert_eq!(audio.file_name.as_deref(), Some("speech.mp3"));
|
||||
assert_eq!(audio.file_size, Some(53612));
|
||||
assert_eq!(audio.duration, Some(6.63995));
|
||||
assert_eq!(audio.sample_rate, Some(24000));
|
||||
assert!(audio.bitrate.is_none());
|
||||
assert!(audio.channels.is_none());
|
||||
|
||||
// No other content types in this payload.
|
||||
assert!(contents.image.is_none());
|
||||
assert!(contents.images.is_none());
|
||||
assert!(contents.video.is_none());
|
||||
assert!(contents.model_glb.is_none());
|
||||
assert!(contents.thumbnail.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_audio_payload() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"audio": {
|
||||
"url": "https://cdn.example.com/speech.mp3",
|
||||
"content_type": "audio/mpeg",
|
||||
"file_name": "speech.mp3",
|
||||
"file_size": 12345,
|
||||
"bitrate": 128000,
|
||||
"channels": 2,
|
||||
"duration": 3.5,
|
||||
"sample_rate": 44100
|
||||
}
|
||||
}"#).unwrap();
|
||||
|
||||
let audio = extract_audio(&obj).expect("should extract audio");
|
||||
assert_eq!(audio.url.as_deref(), Some("https://cdn.example.com/speech.mp3"));
|
||||
assert_eq!(audio.content_type.as_deref(), Some("audio/mpeg"));
|
||||
assert_eq!(audio.file_name.as_deref(), Some("speech.mp3"));
|
||||
assert_eq!(audio.file_size, Some(12345));
|
||||
assert_eq!(audio.bitrate, Some(128000));
|
||||
assert_eq!(audio.channels, Some(2));
|
||||
assert_eq!(audio.duration, Some(3.5));
|
||||
assert_eq!(audio.sample_rate, Some(44100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_url_only() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"audio": {"url": "https://cdn.example.com/speech.wav"}
|
||||
}"#).unwrap();
|
||||
|
||||
let audio = extract_audio(&obj).expect("should extract audio");
|
||||
assert_eq!(audio.url.as_deref(), Some("https://cdn.example.com/speech.wav"));
|
||||
assert!(audio.content_type.is_none());
|
||||
assert!(audio.duration.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_audio_key_returns_none() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"video": {"url": "https://example.com/video.mp4"}
|
||||
}"#).unwrap();
|
||||
|
||||
assert!(extract_audio(&obj).is_none());
|
||||
}
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::ModelGlbData;
|
||||
|
||||
/// Extract and deserialize the `model_glb_pbr` key from a webhook success
|
||||
/// payload. Sent alongside `model_glb` by e.g. Hunyuan 3D 2.1; same shape.
|
||||
pub(crate) fn extract_model_glb_pbr(obj: &Map<String, Value>) -> Option<ModelGlbData> {
|
||||
let value = obj.get("model_glb_pbr")?;
|
||||
serde_json::from_value(value.clone()).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::webhook_payload::hydrate_webhook_contents::hydrate_webhook_contents;
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::HydratedWebhookContents;
|
||||
use crate::webhook_payload::raw::raw_webhook_payload::RawWebhookPayload;
|
||||
|
||||
fn load_test_webhook(filename: &str) -> RawWebhookPayload {
|
||||
let path = format!("test_data/webhooks/{}", filename);
|
||||
let json = std::fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e));
|
||||
serde_json::from_str(&json)
|
||||
.unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_glbs_from_hunyuan_3d_2p1_test_file() {
|
||||
let webhook = load_test_webhook("success/hunyuan_3d_2p1_model_glbs_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let glb = contents.model_glb.expect("model_glb should be Some");
|
||||
assert_eq!(glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b916/QKdBTd8WJW_rtPB3IwlEN_demo_textured.glb"));
|
||||
assert_eq!(glb.content_type.as_deref(), Some("application/octet-stream"));
|
||||
assert_eq!(glb.file_name.as_deref(), Some("demo_textured.glb"));
|
||||
assert_eq!(glb.file_size, Some(1574216));
|
||||
|
||||
let glb_pbr = contents.model_glb_pbr.expect("model_glb_pbr should be Some");
|
||||
assert_eq!(glb_pbr.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b921/SJw7AARzkc4l9QG5Lrvmu_demo_textured_pbr.glb"));
|
||||
assert_eq!(glb_pbr.content_type.as_deref(), Some("application/octet-stream"));
|
||||
assert_eq!(glb_pbr.file_name.as_deref(), Some("demo_textured_pbr.glb"));
|
||||
assert_eq!(glb_pbr.file_size, Some(7512760));
|
||||
|
||||
// The payload also carries a `model_mesh` zip archive of the whole
|
||||
// generation. It parses out, but the webhook handler prefers the GLBs.
|
||||
let mesh = contents.model_mesh.expect("model_mesh should be Some");
|
||||
assert_eq!(mesh.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b915/B25DrTdiZJNV-60RaUh4u_3d_model.zip"));
|
||||
assert_eq!(mesh.file_name.as_deref(), Some("3d_model.zip"));
|
||||
|
||||
assert!(contents.thumbnail.is_none());
|
||||
assert!(contents.preprocessed_image.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_model_glb_pbr_payload() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"model_glb_pbr": {
|
||||
"url": "https://cdn.example.com/model_pbr.glb",
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model_pbr.glb",
|
||||
"file_size": 7654321
|
||||
}
|
||||
}"#).unwrap();
|
||||
|
||||
let glb_pbr = extract_model_glb_pbr(&obj).expect("should extract model_glb_pbr");
|
||||
assert_eq!(glb_pbr.url.as_deref(), Some("https://cdn.example.com/model_pbr.glb"));
|
||||
assert_eq!(glb_pbr.content_type.as_deref(), Some("model/gltf-binary"));
|
||||
assert_eq!(glb_pbr.file_name.as_deref(), Some("model_pbr.glb"));
|
||||
assert_eq!(glb_pbr.file_size, Some(7654321));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_model_glb_pbr_key_returns_none() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"model_glb": {"url": "https://example.com/model.glb"}
|
||||
}"#).unwrap();
|
||||
|
||||
assert!(extract_model_glb_pbr(&obj).is_none());
|
||||
}
|
||||
}
|
||||
-27
@@ -49,33 +49,6 @@ mod tests {
|
||||
assert!(contents.thumbnail.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triposplat_ply_splat_from_test_file() {
|
||||
// TripoSplat sends its PLY gaussian splat under `model_mesh`.
|
||||
let webhook = load_test_webhook("success/triposplat_splat_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let mesh = contents.model_mesh.expect("model_mesh should be Some");
|
||||
assert_eq!(mesh.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b77c/hEzg5xn9A-1qrLsnywjN4_output.ply"));
|
||||
assert_eq!(mesh.content_type.as_deref(), Some("application/octet-stream"));
|
||||
assert_eq!(mesh.file_name.as_deref(), Some("output.ply"));
|
||||
assert_eq!(mesh.file_size, Some(17826208));
|
||||
|
||||
// The segmented input image rides along as `preprocessed_image`.
|
||||
assert!(contents.preprocessed_image.is_some());
|
||||
|
||||
assert!(contents.model_glb.is_none());
|
||||
assert!(contents.model_glb_pbr.is_none());
|
||||
assert!(contents.thumbnail.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_model_mesh_payload() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
|
||||
-327
@@ -1,327 +0,0 @@
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::ModelUrlsData;
|
||||
|
||||
/// Extract and deserialize the `model_urls` key from a webhook success
|
||||
/// payload (e.g. Hunyuan 3D 3.0's per-format file map).
|
||||
pub(crate) fn extract_model_urls(obj: &Map<String, Value>) -> Option<ModelUrlsData> {
|
||||
let value = obj.get("model_urls")?;
|
||||
serde_json::from_value(value.clone()).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::webhook_payload::hydrate_webhook_contents::hydrate_webhook_contents;
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::HydratedWebhookContents;
|
||||
use crate::webhook_payload::raw::raw_webhook_payload::RawWebhookPayload;
|
||||
|
||||
fn load_test_webhook(filename: &str) -> RawWebhookPayload {
|
||||
let path = format!("test_data/webhooks/{}", filename);
|
||||
let json = std::fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e));
|
||||
serde_json::from_str(&json)
|
||||
.unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_urls_from_hunyuan_3d_3p0_test_file() {
|
||||
let webhook = load_test_webhook("success/hunyuan_3d_3p0_model_urls_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let glb = contents.model_glb.expect("model_glb should be Some");
|
||||
assert_eq!(glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b969/k5FZWmTsKxHH71404bBR__model.glb"));
|
||||
assert_eq!(glb.content_type.as_deref(), Some("model/gltf-binary"));
|
||||
assert_eq!(glb.file_name.as_deref(), Some("model.glb"));
|
||||
assert_eq!(glb.file_size, Some(51001160));
|
||||
|
||||
let model_urls = contents.model_urls.expect("model_urls should be Some");
|
||||
|
||||
// The `glb` slot duplicates `model_glb` in this payload (same URL).
|
||||
let urls_glb = model_urls.glb.expect("model_urls.glb should be Some");
|
||||
assert_eq!(urls_glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b969/k5FZWmTsKxHH71404bBR__model.glb"));
|
||||
|
||||
let urls_obj = model_urls.obj.expect("model_urls.obj should be Some");
|
||||
assert_eq!(urls_obj.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b968/xngvKxLugXUlNh89Fp0K4_model.obj"));
|
||||
assert_eq!(urls_obj.content_type.as_deref(), Some("text/plain"));
|
||||
assert_eq!(urls_obj.file_name.as_deref(), Some("model.obj"));
|
||||
assert_eq!(urls_obj.file_size, Some(41107268));
|
||||
|
||||
assert!(model_urls.fbx.is_none());
|
||||
assert!(model_urls.mtl.is_none());
|
||||
assert!(model_urls.texture.is_none());
|
||||
assert!(model_urls.usdz.is_none());
|
||||
|
||||
let thumbnail = contents.thumbnail.expect("thumbnail should be Some");
|
||||
assert_eq!(thumbnail.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b969/EPgha8Kee7YZCAccjFUU7_preview.png"));
|
||||
assert_eq!(thumbnail.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(thumbnail.file_name.as_deref(), Some("preview.png"));
|
||||
assert_eq!(thumbnail.file_size, Some(136476));
|
||||
|
||||
assert!(contents.model_glb_pbr.is_none());
|
||||
assert!(contents.model_mesh.is_none());
|
||||
assert!(contents.preprocessed_image.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_urls_and_thumbnail_from_hunyuan_3d_3p1_test_file() {
|
||||
let webhook = load_test_webhook("success/hunyuan_3d_3p1_model_urls_thumbnail_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let glb = contents.model_glb.expect("model_glb should be Some");
|
||||
assert_eq!(glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1ba44/OdgIv8M9EkaBP_Mh4TVAB_model.glb"));
|
||||
assert_eq!(glb.content_type.as_deref(), Some("model/gltf-binary"));
|
||||
assert_eq!(glb.file_name.as_deref(), Some("model.glb"));
|
||||
assert_eq!(glb.file_size, Some(64261364));
|
||||
|
||||
let model_urls = contents.model_urls.expect("model_urls should be Some");
|
||||
|
||||
// The `glb` slot duplicates `model_glb` in this payload (same URL).
|
||||
let urls_glb = model_urls.glb.expect("model_urls.glb should be Some");
|
||||
assert_eq!(urls_glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1ba44/OdgIv8M9EkaBP_Mh4TVAB_model.glb"));
|
||||
|
||||
let urls_obj = model_urls.obj.expect("model_urls.obj should be Some");
|
||||
assert_eq!(urls_obj.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1ba43/WxPMNbzsDxaGRCmGNgNAL_c3a871e997ecd5f889bf671630de23b4.obj"));
|
||||
assert_eq!(urls_obj.content_type.as_deref(), Some("model/obj"));
|
||||
assert_eq!(urls_obj.file_size, Some(34563830));
|
||||
|
||||
// 3.1 additionally ships the OBJ's material and PBR texture files.
|
||||
let urls_mtl = model_urls.mtl.expect("model_urls.mtl should be Some");
|
||||
assert_eq!(urls_mtl.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1ba42/1T5VCp_xD0zCJlTLE96iK_material.mtl"));
|
||||
assert_eq!(urls_mtl.content_type.as_deref(), Some("text/plain"));
|
||||
assert_eq!(urls_mtl.file_name.as_deref(), Some("material.mtl"));
|
||||
assert_eq!(urls_mtl.file_size, Some(245));
|
||||
|
||||
let urls_texture = model_urls.texture.expect("model_urls.texture should be Some");
|
||||
assert_eq!(urls_texture.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1ba43/aKp3mMB9nrhlKfV2KVf0S_texture_pbr_20250901.png"));
|
||||
assert_eq!(urls_texture.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(urls_texture.file_name.as_deref(), Some("texture_pbr_20250901.png"));
|
||||
assert_eq!(urls_texture.file_size, Some(22794575));
|
||||
|
||||
assert!(model_urls.fbx.is_none());
|
||||
assert!(model_urls.usdz.is_none());
|
||||
|
||||
// The thumbnail is attached to the GLB as its cover image downstream.
|
||||
let thumbnail = contents.thumbnail.expect("thumbnail should be Some");
|
||||
assert_eq!(thumbnail.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1ba44/njq8zYhYahJNNQkGGiEZW_preview.png"));
|
||||
assert_eq!(thumbnail.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(thumbnail.file_name.as_deref(), Some("preview.png"));
|
||||
assert_eq!(thumbnail.file_size, Some(141254));
|
||||
|
||||
assert!(contents.model_glb_pbr.is_none());
|
||||
assert!(contents.model_mesh.is_none());
|
||||
assert!(contents.preprocessed_image.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn obj_and_thumbnail_from_hunyuan_3d_3p1_rapid_test_file() {
|
||||
let webhook = load_test_webhook("success/hunyuan_3d_3p1_rapid_obj_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
// NB: Hunyuan 3D 3.1 Rapid puts an OBJ under the `model_glb` key. The
|
||||
// key name doesn't matter downstream: the webhook handler resolves the
|
||||
// real file type from content_type / file name, so it uploads as an OBJ
|
||||
// mesh.
|
||||
let glb = contents.model_glb.expect("model_glb should be Some");
|
||||
assert_eq!(glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bae3/22zStkYp8-q6npG9dsMs5_23dce35fc95b34722f15a7329061d5df.obj"));
|
||||
assert_eq!(glb.content_type.as_deref(), Some("model/obj"));
|
||||
assert_eq!(glb.file_name.as_deref(), Some("23dce35fc95b34722f15a7329061d5df.obj"));
|
||||
assert_eq!(glb.file_size, Some(3258750));
|
||||
|
||||
let model_urls = contents.model_urls.expect("model_urls should be Some");
|
||||
|
||||
// The `glb` slot is null here; `obj` duplicates `model_glb` (same URL).
|
||||
assert!(model_urls.glb.is_none());
|
||||
assert!(model_urls.fbx.is_none());
|
||||
assert!(model_urls.usdz.is_none());
|
||||
|
||||
let urls_obj = model_urls.obj.expect("model_urls.obj should be Some");
|
||||
assert_eq!(urls_obj.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bae3/22zStkYp8-q6npG9dsMs5_23dce35fc95b34722f15a7329061d5df.obj"));
|
||||
assert_eq!(urls_obj.content_type.as_deref(), Some("model/obj"));
|
||||
assert_eq!(urls_obj.file_size, Some(3258750));
|
||||
|
||||
let urls_mtl = model_urls.mtl.expect("model_urls.mtl should be Some");
|
||||
assert_eq!(urls_mtl.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bae3/5cbE-q28i44GUOO6YeZPH_material.mtl"));
|
||||
assert_eq!(urls_mtl.file_size, Some(229));
|
||||
|
||||
let urls_texture = model_urls.texture.expect("model_urls.texture should be Some");
|
||||
assert_eq!(urls_texture.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bae3/7eBRddTHf6U8SUg2QOIHw_texture_pbr_v128.png"));
|
||||
assert_eq!(urls_texture.file_name.as_deref(), Some("texture_pbr_v128.png"));
|
||||
assert_eq!(urls_texture.file_size, Some(5503289));
|
||||
|
||||
// The thumbnail is attached to the mesh as its cover image downstream.
|
||||
let thumbnail = contents.thumbnail.expect("thumbnail should be Some");
|
||||
assert_eq!(thumbnail.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bae3/IO3aspD4ijEadoFxSF504_preview.png"));
|
||||
assert_eq!(thumbnail.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(thumbnail.file_name.as_deref(), Some("preview.png"));
|
||||
assert_eq!(thumbnail.file_size, Some(120530));
|
||||
|
||||
assert!(contents.model_glb_pbr.is_none());
|
||||
assert!(contents.model_mesh.is_none());
|
||||
assert!(contents.model_obj.is_none());
|
||||
assert!(contents.preprocessed_image.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_urls_and_rendered_image_from_tripo3d_test_file() {
|
||||
let webhook = load_test_webhook("success/tripo3d_model_urls_rendered_image_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
// Tripo 3D sends its GLB under `model_mesh` (not `model_glb`); the
|
||||
// webhook handler picks `model_urls.glb` as the primary GLB instead.
|
||||
assert!(contents.model_glb.is_none());
|
||||
let mesh = contents.model_mesh.expect("model_mesh should be Some");
|
||||
assert_eq!(mesh.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbdb/d8ATWvvXYHY-8zj-lQecf_model.glb"));
|
||||
assert_eq!(mesh.content_type.as_deref(), Some("model/gltf-binary"));
|
||||
|
||||
let model_urls = contents.model_urls.expect("model_urls should be Some");
|
||||
|
||||
// `glb` and `pbr_model` duplicate each other (same URL) in this payload;
|
||||
// the webhook handler dedupes by URL and uploads the file only once.
|
||||
let urls_glb = model_urls.glb.expect("model_urls.glb should be Some");
|
||||
assert_eq!(urls_glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbdb/d8ATWvvXYHY-8zj-lQecf_model.glb"));
|
||||
assert_eq!(urls_glb.file_size, Some(43981280));
|
||||
|
||||
let urls_pbr_model = model_urls.pbr_model.expect("model_urls.pbr_model should be Some");
|
||||
assert_eq!(urls_pbr_model.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbdb/d8ATWvvXYHY-8zj-lQecf_model.glb"));
|
||||
|
||||
assert!(model_urls.base_model.is_none());
|
||||
assert!(model_urls.fbx.is_none());
|
||||
assert!(model_urls.mtl.is_none());
|
||||
assert!(model_urls.obj.is_none());
|
||||
assert!(model_urls.texture.is_none());
|
||||
assert!(model_urls.usdz.is_none());
|
||||
|
||||
// The preview arrives as `rendered_image` (no `thumbnail` key) and is
|
||||
// attached to the mesh as its cover image downstream.
|
||||
let rendered_image = contents.rendered_image.expect("rendered_image should be Some");
|
||||
assert_eq!(rendered_image.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbd0/MblV2S5R6CeskRABMS7-V_preview.png"));
|
||||
assert!(contents.thumbnail.is_none());
|
||||
|
||||
assert!(contents.model_glb_pbr.is_none());
|
||||
assert!(contents.preprocessed_image.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_urls_and_thumbnail_from_meshy_test_file() {
|
||||
let webhook = load_test_webhook("success/meshy_model_urls_thumbnail_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let glb = contents.model_glb.expect("model_glb should be Some");
|
||||
assert_eq!(glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbe1/0vrf9NUhVxKZO4GScA0EI_model.glb"));
|
||||
assert_eq!(glb.content_type.as_deref(), Some("model/gltf-binary"));
|
||||
assert_eq!(glb.file_name.as_deref(), Some("model.glb"));
|
||||
assert_eq!(glb.file_size, Some(15943768));
|
||||
|
||||
let model_urls = contents.model_urls.expect("model_urls should be Some");
|
||||
|
||||
// The `glb` slot duplicates `model_glb` (same URL); the webhook handler
|
||||
// dedupes by URL so the file uploads only once.
|
||||
let urls_glb = model_urls.glb.expect("model_urls.glb should be Some");
|
||||
assert_eq!(urls_glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbe1/0vrf9NUhVxKZO4GScA0EI_model.glb"));
|
||||
|
||||
// Alternate formats of the same model; parsed but not uploaded.
|
||||
let urls_fbx = model_urls.fbx.expect("model_urls.fbx should be Some");
|
||||
assert_eq!(urls_fbx.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbed/yKO-HOjBnWA-LZpRLHHSB_model.fbx"));
|
||||
assert_eq!(urls_fbx.file_size, Some(13665964));
|
||||
|
||||
let urls_obj = model_urls.obj.expect("model_urls.obj should be Some");
|
||||
assert_eq!(urls_obj.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbe2/NBmqIWBS8LuY_BPYWkOOf_model.obj"));
|
||||
|
||||
let urls_stl = model_urls.stl.expect("model_urls.stl should be Some");
|
||||
assert_eq!(urls_stl.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbe2/3wtOE6iEeVHZFvwAGN6Rl_model.stl"));
|
||||
assert_eq!(urls_stl.content_type.as_deref(), Some("application/octet-stream"));
|
||||
assert_eq!(urls_stl.file_size, Some(1508384));
|
||||
|
||||
let urls_usdz = model_urls.usdz.expect("model_urls.usdz should be Some");
|
||||
assert_eq!(urls_usdz.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbed/BsniPBo6Uev85V4hLFACR_model.usdz"));
|
||||
assert_eq!(urls_usdz.content_type.as_deref(), Some("model/vnd.usdz+zip"));
|
||||
|
||||
assert!(model_urls.base_model.is_none());
|
||||
assert!(model_urls.blend.is_none());
|
||||
assert!(model_urls.mtl.is_none());
|
||||
assert!(model_urls.pbr_model.is_none());
|
||||
assert!(model_urls.texture.is_none());
|
||||
|
||||
// The thumbnail is attached to the GLB as its cover image downstream.
|
||||
let thumbnail = contents.thumbnail.expect("thumbnail should be Some");
|
||||
assert_eq!(thumbnail.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbe3/tn1qfOr12oGXecA4mRtlI_preview.png"));
|
||||
assert_eq!(thumbnail.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(thumbnail.file_name.as_deref(), Some("preview.png"));
|
||||
assert_eq!(thumbnail.file_size, Some(192910));
|
||||
|
||||
assert!(contents.model_glb_pbr.is_none());
|
||||
assert!(contents.model_mesh.is_none());
|
||||
assert!(contents.rendered_image.is_none());
|
||||
assert!(contents.preprocessed_image.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_model_urls_payload_with_distinct_glb() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"model_urls": {
|
||||
"fbx": null,
|
||||
"glb": {
|
||||
"url": "https://cdn.example.com/model_alt.glb",
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model_alt.glb",
|
||||
"file_size": 1234567
|
||||
},
|
||||
"obj": null,
|
||||
"usdz": null
|
||||
}
|
||||
}"#).unwrap();
|
||||
|
||||
let model_urls = extract_model_urls(&obj).expect("should extract model_urls");
|
||||
let glb = model_urls.glb.expect("glb slot should be Some");
|
||||
assert_eq!(glb.url.as_deref(), Some("https://cdn.example.com/model_alt.glb"));
|
||||
assert_eq!(glb.file_size, Some(1234567));
|
||||
assert!(model_urls.fbx.is_none());
|
||||
assert!(model_urls.obj.is_none());
|
||||
assert!(model_urls.usdz.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_model_urls_key_returns_none() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"model_glb": {"url": "https://example.com/model.glb"}
|
||||
}"#).unwrap();
|
||||
|
||||
assert!(extract_model_urls(&obj).is_none());
|
||||
}
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::PreprocessedImageData;
|
||||
|
||||
/// Extract and deserialize the `preprocessed_image` key from a webhook success payload.
|
||||
pub(crate) fn extract_preprocessed_image(obj: &Map<String, Value>) -> Option<PreprocessedImageData> {
|
||||
let value = obj.get("preprocessed_image")?;
|
||||
serde_json::from_value(value.clone()).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::webhook_payload::hydrate_webhook_contents::hydrate_webhook_contents;
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::HydratedWebhookContents;
|
||||
use crate::webhook_payload::raw::raw_webhook_payload::RawWebhookPayload;
|
||||
|
||||
fn load_test_webhook(filename: &str) -> RawWebhookPayload {
|
||||
let path = format!("test_data/webhooks/{}", filename);
|
||||
let json = std::fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e));
|
||||
serde_json::from_str(&json)
|
||||
.unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preprocessed_image_from_triposplat_test_file() {
|
||||
let webhook = load_test_webhook("success/triposplat_splat_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let image = contents.preprocessed_image.expect("preprocessed_image should be Some");
|
||||
assert_eq!(image.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b77c/YyJ19g7lJsRNXmvZvahTE_61fc73dbe84446b08b1fcce2167b71d5.png"));
|
||||
assert_eq!(image.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(image.file_name.as_deref(), Some("61fc73dbe84446b08b1fcce2167b71d5.png"));
|
||||
assert_eq!(image.file_size, Some(952754));
|
||||
assert_eq!(image.width, Some(1024));
|
||||
assert_eq!(image.height, Some(1024));
|
||||
|
||||
// The splat itself arrives under `model_mesh`.
|
||||
let mesh = contents.model_mesh.expect("model_mesh should be Some");
|
||||
assert_eq!(mesh.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1b77c/hEzg5xn9A-1qrLsnywjN4_output.ply"));
|
||||
assert_eq!(mesh.content_type.as_deref(), Some("application/octet-stream"));
|
||||
assert_eq!(mesh.file_name.as_deref(), Some("output.ply"));
|
||||
assert_eq!(mesh.file_size, Some(17826208));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_preprocessed_image_payload() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"preprocessed_image": {
|
||||
"url": "https://cdn.example.com/segmented.png",
|
||||
"content_type": "image/png",
|
||||
"file_name": "segmented.png",
|
||||
"file_size": 12345,
|
||||
"width": 1024,
|
||||
"height": 1024
|
||||
}
|
||||
}"#).unwrap();
|
||||
|
||||
let image = extract_preprocessed_image(&obj).expect("should extract preprocessed_image");
|
||||
assert_eq!(image.url.as_deref(), Some("https://cdn.example.com/segmented.png"));
|
||||
assert_eq!(image.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(image.file_name.as_deref(), Some("segmented.png"));
|
||||
assert_eq!(image.file_size, Some(12345));
|
||||
assert_eq!(image.width, Some(1024));
|
||||
assert_eq!(image.height, Some(1024));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_preprocessed_image_key_returns_none() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"model_mesh": {"url": "https://example.com/output.ply"}
|
||||
}"#).unwrap();
|
||||
|
||||
assert!(extract_preprocessed_image(&obj).is_none());
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::ThumbnailData;
|
||||
|
||||
/// Extract and deserialize the `rendered_image` key from a webhook success
|
||||
/// payload (e.g. Tripo 3D's preview image). Same shape as `thumbnail`, and
|
||||
/// used the same way: as a cover image for the model.
|
||||
pub(crate) fn extract_rendered_image(obj: &Map<String, Value>) -> Option<ThumbnailData> {
|
||||
let value = obj.get("rendered_image")?;
|
||||
serde_json::from_value(value.clone()).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::webhook_payload::hydrate_webhook_contents::hydrate_webhook_contents;
|
||||
use crate::webhook_payload::hydrated::hydrated_webhook_contents::HydratedWebhookContents;
|
||||
use crate::webhook_payload::raw::raw_webhook_payload::RawWebhookPayload;
|
||||
|
||||
fn load_test_webhook(filename: &str) -> RawWebhookPayload {
|
||||
let path = format!("test_data/webhooks/{}", filename);
|
||||
let json = std::fs::read_to_string(&path)
|
||||
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e));
|
||||
serde_json::from_str(&json)
|
||||
.unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rendered_image_from_tripo3d_test_file() {
|
||||
let webhook = load_test_webhook("success/tripo3d_model_urls_rendered_image_payload_1.json");
|
||||
let result = hydrate_webhook_contents(&webhook);
|
||||
|
||||
let HydratedWebhookContents::Success(data) = result else {
|
||||
panic!("Expected Success, got {:?}", result);
|
||||
};
|
||||
|
||||
let contents = data.extracted_contents
|
||||
.expect("extracted_contents should be Some");
|
||||
|
||||
let rendered_image = contents.rendered_image.expect("rendered_image should be Some");
|
||||
assert_eq!(rendered_image.url.as_deref(), Some("https://v3b.fal.media/files/b/0aa1bbd0/MblV2S5R6CeskRABMS7-V_preview.png"));
|
||||
assert_eq!(rendered_image.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(rendered_image.file_name.as_deref(), Some("preview.png"));
|
||||
assert_eq!(rendered_image.file_size, Some(30970));
|
||||
|
||||
// Tripo has no `thumbnail` key; the preview only arrives here.
|
||||
assert!(contents.thumbnail.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_rendered_image_payload() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"rendered_image": {
|
||||
"url": "https://cdn.example.com/preview.png",
|
||||
"content_type": "image/png",
|
||||
"file_name": "preview.png",
|
||||
"file_size": 12345
|
||||
}
|
||||
}"#).unwrap();
|
||||
|
||||
let image = extract_rendered_image(&obj).expect("should extract rendered_image");
|
||||
assert_eq!(image.url.as_deref(), Some("https://cdn.example.com/preview.png"));
|
||||
assert_eq!(image.content_type.as_deref(), Some("image/png"));
|
||||
assert_eq!(image.file_name.as_deref(), Some("preview.png"));
|
||||
assert_eq!(image.file_size, Some(12345));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_rendered_image_key_returns_none() {
|
||||
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(r#"{
|
||||
"thumbnail": {"url": "https://example.com/preview.png"}
|
||||
}"#).unwrap();
|
||||
|
||||
assert!(extract_rendered_image(&obj).is_none());
|
||||
}
|
||||
}
|
||||
+4
-23
@@ -1,14 +1,9 @@
|
||||
pub mod extract_audio_payload;
|
||||
pub mod extract_image_payload;
|
||||
pub mod extract_images_payload;
|
||||
pub mod extract_video_payload;
|
||||
pub mod extract_model_glb_payload;
|
||||
pub mod extract_model_glb_pbr_payload;
|
||||
pub mod extract_model_mesh_payload;
|
||||
pub mod extract_model_obj_payload;
|
||||
pub mod extract_model_urls_payload;
|
||||
pub mod extract_preprocessed_image_payload;
|
||||
pub mod extract_rendered_image_payload;
|
||||
pub mod extract_result_files_payload;
|
||||
pub mod extract_thumbnail_payload;
|
||||
|
||||
@@ -19,9 +14,7 @@ use crate::webhook_payload::hydrated::hydrated_webhook_contents::ExtractedConten
|
||||
/// Try to extract known content keys from a success payload.
|
||||
///
|
||||
/// Checks the payload (as a JSON object) for any of: `image`, `images`,
|
||||
/// `video`, `audio`, `model_glb`, `model_glb_pbr`, `model_urls`,
|
||||
/// `model_mesh`, `model_obj`, `result_files`, `thumbnail`, `rendered_image`,
|
||||
/// `preprocessed_image`. If at least one is found, returns
|
||||
/// `video`, `model_glb`, `model_mesh`. If at least one is found, returns
|
||||
/// `Some(ExtractedContents)` with copies of the relevant values. Multiple
|
||||
/// keys can be populated simultaneously. Returns `None` if the payload is
|
||||
/// not an object or none of the known keys are present.
|
||||
@@ -31,22 +24,15 @@ pub fn extract_contents_from_payload(payload: &Value) -> Option<ExtractedContent
|
||||
let image = extract_image_payload::extract_image(obj);
|
||||
let images = extract_images_payload::extract_images(obj);
|
||||
let video = extract_video_payload::extract_video(obj);
|
||||
let audio = extract_audio_payload::extract_audio(obj);
|
||||
let model_glb = extract_model_glb_payload::extract_model_glb(obj);
|
||||
let model_glb_pbr = extract_model_glb_pbr_payload::extract_model_glb_pbr(obj);
|
||||
let model_urls = extract_model_urls_payload::extract_model_urls(obj);
|
||||
let model_mesh = extract_model_mesh_payload::extract_model_mesh(obj);
|
||||
let model_obj = extract_model_obj_payload::extract_model_obj(obj);
|
||||
let result_files = extract_result_files_payload::extract_result_files(obj);
|
||||
let thumbnail = extract_thumbnail_payload::extract_thumbnail(obj);
|
||||
let rendered_image = extract_rendered_image_payload::extract_rendered_image(obj);
|
||||
let preprocessed_image = extract_preprocessed_image_payload::extract_preprocessed_image(obj);
|
||||
|
||||
if image.is_none() && images.is_none() && video.is_none() && audio.is_none()
|
||||
&& model_glb.is_none() && model_glb_pbr.is_none() && model_urls.is_none()
|
||||
&& model_mesh.is_none() && model_obj.is_none()
|
||||
&& result_files.is_none() && thumbnail.is_none() && rendered_image.is_none()
|
||||
&& preprocessed_image.is_none()
|
||||
if image.is_none() && images.is_none() && video.is_none()
|
||||
&& model_glb.is_none() && model_mesh.is_none() && model_obj.is_none()
|
||||
&& result_files.is_none() && thumbnail.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -55,15 +41,10 @@ pub fn extract_contents_from_payload(payload: &Value) -> Option<ExtractedContent
|
||||
image,
|
||||
images,
|
||||
video,
|
||||
audio,
|
||||
model_glb,
|
||||
model_glb_pbr,
|
||||
model_urls,
|
||||
model_mesh,
|
||||
model_obj,
|
||||
result_files,
|
||||
thumbnail,
|
||||
rendered_image,
|
||||
preprocessed_image,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,12 +19,6 @@ pub enum WebhookErrorType {
|
||||
ImageLoadError,
|
||||
FileDownloadError,
|
||||
FaceDetectionError,
|
||||
/// Generic input validation error. The `msg` field carries the specifics
|
||||
/// (e.g. TripoSplat's "No foreground subject could be detected...").
|
||||
InputValueError,
|
||||
/// Generic validation error. The `msg` field carries the specifics (e.g.
|
||||
/// Hunyuan 3D Part's "Part generation only supports FBX format...").
|
||||
ValueError,
|
||||
GreaterThan,
|
||||
GreaterThanEqual,
|
||||
LessThan,
|
||||
@@ -70,8 +64,6 @@ mod tests {
|
||||
("\"image_load_error\"", WebhookErrorType::ImageLoadError),
|
||||
("\"file_download_error\"", WebhookErrorType::FileDownloadError),
|
||||
("\"face_detection_error\"", WebhookErrorType::FaceDetectionError),
|
||||
("\"input_value_error\"", WebhookErrorType::InputValueError),
|
||||
("\"value_error\"", WebhookErrorType::ValueError),
|
||||
("\"greater_than\"", WebhookErrorType::GreaterThan),
|
||||
("\"greater_than_equal\"", WebhookErrorType::GreaterThanEqual),
|
||||
("\"less_than\"", WebhookErrorType::LessThan),
|
||||
@@ -103,8 +95,6 @@ mod tests {
|
||||
fn from_str_known() {
|
||||
assert_eq!(WebhookErrorType::from_str("content_policy_violation"), WebhookErrorType::ContentPolicyViolation);
|
||||
assert_eq!(WebhookErrorType::from_str("face_detection_error"), WebhookErrorType::FaceDetectionError);
|
||||
assert_eq!(WebhookErrorType::from_str("input_value_error"), WebhookErrorType::InputValueError);
|
||||
assert_eq!(WebhookErrorType::from_str("value_error"), WebhookErrorType::ValueError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"error": "Unexpected status code: 422",
|
||||
"gateway_request_id": "019f4dad-7ff7-7902-b206-cb11bf1ef843",
|
||||
"payload": {
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"body",
|
||||
"input_file_url"
|
||||
],
|
||||
"msg": "Part generation only supports FBX format. Please provide an FBX file or use /convert-format to convert your model to FBX first.",
|
||||
"type": "value_error"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request_id": "019f4dad-7ff7-7902-b206-cb11bf1ef843",
|
||||
"status": "ERROR"
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"error": "Unexpected status code: 422",
|
||||
"gateway_request_id": "019f4d0e-3f44-7ed1-bb12-f2f834bdde7b",
|
||||
"payload": {
|
||||
"detail": [
|
||||
{
|
||||
"input": "https://cdn-2.fakeyou.com/media/f/d/6/f/6/fd6f6czv21gkrdqhptz6nj072wh7w06y/artcraft_fd6f6czv21gkrdqhptz6nj072wh7w06y.png",
|
||||
"loc": [
|
||||
"body"
|
||||
],
|
||||
"msg": "No foreground subject could be detected in the input image after background removal. Provide an image with a clear, visible subject against a plain or distinct background.",
|
||||
"type": "input_value_error",
|
||||
"url": "https://docs.fal.ai/errors#input_value_error"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request_id": "019f4d0e-3f44-7ed1-bb12-f2f834bdde7b",
|
||||
"status": "ERROR"
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4d49-6859-77b3-aaac-3b91b248481a",
|
||||
"payload": {
|
||||
"model_glb": {
|
||||
"content_type": "application/octet-stream",
|
||||
"file_name": "demo_textured.glb",
|
||||
"file_size": 1574216,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b916/QKdBTd8WJW_rtPB3IwlEN_demo_textured.glb"
|
||||
},
|
||||
"model_glb_pbr": {
|
||||
"content_type": "application/octet-stream",
|
||||
"file_name": "demo_textured_pbr.glb",
|
||||
"file_size": 7512760,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b921/SJw7AARzkc4l9QG5Lrvmu_demo_textured_pbr.glb"
|
||||
},
|
||||
"model_mesh": {
|
||||
"content_type": "application/octet-stream",
|
||||
"file_name": "3d_model.zip",
|
||||
"file_size": 16332900,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b915/B25DrTdiZJNV-60RaUh4u_3d_model.zip"
|
||||
},
|
||||
"seed": 283904
|
||||
},
|
||||
"request_id": "019f4d49-6859-77b3-aaac-3b91b248481a",
|
||||
"status": "OK"
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4d58-0e11-7aa0-aa79-e0294bf9ff16",
|
||||
"payload": {
|
||||
"model_glb": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 51001160,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b969/k5FZWmTsKxHH71404bBR__model.glb"
|
||||
},
|
||||
"model_urls": {
|
||||
"fbx": null,
|
||||
"glb": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 51001160,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b969/k5FZWmTsKxHH71404bBR__model.glb"
|
||||
},
|
||||
"obj": {
|
||||
"content_type": "text/plain",
|
||||
"file_name": "model.obj",
|
||||
"file_size": 41107268,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b968/xngvKxLugXUlNh89Fp0K4_model.obj"
|
||||
},
|
||||
"usdz": null
|
||||
},
|
||||
"seed": null,
|
||||
"thumbnail": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "preview.png",
|
||||
"file_size": 136476,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b969/EPgha8Kee7YZCAccjFUU7_preview.png"
|
||||
}
|
||||
},
|
||||
"request_id": "019f4d58-0e11-7aa0-aa79-e0294bf9ff16",
|
||||
"status": "OK"
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4d79-35fd-7462-85be-359c8232f56e",
|
||||
"payload": {
|
||||
"model_glb": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 64261364,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1ba44/OdgIv8M9EkaBP_Mh4TVAB_model.glb"
|
||||
},
|
||||
"model_urls": {
|
||||
"fbx": null,
|
||||
"glb": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 64261364,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1ba44/OdgIv8M9EkaBP_Mh4TVAB_model.glb"
|
||||
},
|
||||
"mtl": {
|
||||
"content_type": "text/plain",
|
||||
"file_name": "material.mtl",
|
||||
"file_size": 245,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1ba42/1T5VCp_xD0zCJlTLE96iK_material.mtl"
|
||||
},
|
||||
"obj": {
|
||||
"content_type": "model/obj",
|
||||
"file_name": "c3a871e997ecd5f889bf671630de23b4.obj",
|
||||
"file_size": 34563830,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1ba43/WxPMNbzsDxaGRCmGNgNAL_c3a871e997ecd5f889bf671630de23b4.obj"
|
||||
},
|
||||
"texture": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "texture_pbr_20250901.png",
|
||||
"file_size": 22794575,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1ba43/aKp3mMB9nrhlKfV2KVf0S_texture_pbr_20250901.png"
|
||||
},
|
||||
"usdz": null
|
||||
},
|
||||
"seed": null,
|
||||
"thumbnail": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "preview.png",
|
||||
"file_size": 141254,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1ba44/njq8zYhYahJNNQkGGiEZW_preview.png"
|
||||
}
|
||||
},
|
||||
"request_id": "019f4d79-35fd-7462-85be-359c8232f56e",
|
||||
"status": "OK"
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4d93-5a68-7a23-b2e7-8a1bc55e50f8",
|
||||
"payload": {
|
||||
"material_mtl": {
|
||||
"content_type": "text/plain",
|
||||
"file_name": "material.mtl",
|
||||
"file_size": 229,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bae3/5cbE-q28i44GUOO6YeZPH_material.mtl"
|
||||
},
|
||||
"model_glb": {
|
||||
"content_type": "model/obj",
|
||||
"file_name": "23dce35fc95b34722f15a7329061d5df.obj",
|
||||
"file_size": 3258750,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bae3/22zStkYp8-q6npG9dsMs5_23dce35fc95b34722f15a7329061d5df.obj"
|
||||
},
|
||||
"model_urls": {
|
||||
"fbx": null,
|
||||
"glb": null,
|
||||
"mtl": {
|
||||
"content_type": "text/plain",
|
||||
"file_name": "material.mtl",
|
||||
"file_size": 229,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bae3/5cbE-q28i44GUOO6YeZPH_material.mtl"
|
||||
},
|
||||
"obj": {
|
||||
"content_type": "model/obj",
|
||||
"file_name": "23dce35fc95b34722f15a7329061d5df.obj",
|
||||
"file_size": 3258750,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bae3/22zStkYp8-q6npG9dsMs5_23dce35fc95b34722f15a7329061d5df.obj"
|
||||
},
|
||||
"texture": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "texture_pbr_v128.png",
|
||||
"file_size": 5503289,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bae3/7eBRddTHf6U8SUg2QOIHw_texture_pbr_v128.png"
|
||||
},
|
||||
"usdz": null
|
||||
},
|
||||
"texture": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "texture_pbr_v128.png",
|
||||
"file_size": 5503289,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bae3/7eBRddTHf6U8SUg2QOIHw_texture_pbr_v128.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "preview.png",
|
||||
"file_size": 120530,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bae3/IO3aspD4ijEadoFxSF504_preview.png"
|
||||
}
|
||||
},
|
||||
"request_id": "019f4d93-5a68-7a23-b2e7-8a1bc55e50f8",
|
||||
"status": "OK"
|
||||
}
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4db7-e55c-78d1-9719-0303ca50a961",
|
||||
"payload": {
|
||||
"animation_fbx": null,
|
||||
"animation_glb": null,
|
||||
"basic_animations": null,
|
||||
"model_glb": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 15943768,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe1/0vrf9NUhVxKZO4GScA0EI_model.glb"
|
||||
},
|
||||
"model_urls": {
|
||||
"blend": null,
|
||||
"fbx": {
|
||||
"content_type": "application/octet-stream",
|
||||
"file_name": "model.fbx",
|
||||
"file_size": 13665964,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbed/yKO-HOjBnWA-LZpRLHHSB_model.fbx"
|
||||
},
|
||||
"glb": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 15943768,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe1/0vrf9NUhVxKZO4GScA0EI_model.glb"
|
||||
},
|
||||
"obj": {
|
||||
"content_type": "text/plain",
|
||||
"file_name": "model.obj",
|
||||
"file_size": 3083671,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe2/NBmqIWBS8LuY_BPYWkOOf_model.obj"
|
||||
},
|
||||
"stl": {
|
||||
"content_type": "application/octet-stream",
|
||||
"file_name": "model.stl",
|
||||
"file_size": 1508384,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe2/3wtOE6iEeVHZFvwAGN6Rl_model.stl"
|
||||
},
|
||||
"usdz": {
|
||||
"content_type": "model/vnd.usdz+zip",
|
||||
"file_name": "model.usdz",
|
||||
"file_size": 16861118,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbed/BsniPBo6Uev85V4hLFACR_model.usdz"
|
||||
}
|
||||
},
|
||||
"rig_task_id": null,
|
||||
"rigged_character_fbx": null,
|
||||
"rigged_character_glb": null,
|
||||
"seed": 274321742,
|
||||
"texture_urls": [
|
||||
{
|
||||
"base_color": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "texture_0.png",
|
||||
"file_size": 7539838,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe3/EXWiAuAYPobYiJPnW66mQ_texture_0.png"
|
||||
},
|
||||
"metallic": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "texture_0_metallic.png",
|
||||
"file_size": 858941,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbee/RTZIZRCDQTvN7jdl4EL_C_texture_0_metallic.png"
|
||||
},
|
||||
"normal": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "texture_0_normal.png",
|
||||
"file_size": 8236038,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe3/yNk-S5Dhw2xc9fb3fIjwD_texture_0_normal.png"
|
||||
},
|
||||
"roughness": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "texture_0_roughness.png",
|
||||
"file_size": 1184908,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe3/nnT25g7wgul2oDTRlI_jX_texture_0_roughness.png"
|
||||
}
|
||||
}
|
||||
],
|
||||
"thumbnail": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "preview.png",
|
||||
"file_size": 192910,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbe3/tn1qfOr12oGXecA4mRtlI_preview.png"
|
||||
}
|
||||
},
|
||||
"request_id": "019f4db7-e55c-78d1-9719-0303ca50a961",
|
||||
"status": "OK"
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4dc7-e98b-7f90-94c5-a8b131ff2660",
|
||||
"payload": {
|
||||
"audio": {
|
||||
"bitrate": null,
|
||||
"channels": null,
|
||||
"content_type": "audio/mpeg",
|
||||
"duration": 6.63995,
|
||||
"file_name": "speech.mp3",
|
||||
"file_size": 53612,
|
||||
"sample_rate": 24000,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bc36/vOSCcxsbibkZIiXR9BIZn_speech.mp3"
|
||||
}
|
||||
},
|
||||
"request_id": "019f4dc7-e98b-7f90-94c5-a8b131ff2660",
|
||||
"status": "OK"
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4db5-aadd-76c3-8122-ff1ba53fe48c",
|
||||
"payload": {
|
||||
"model_mesh": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 43981280,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbdb/d8ATWvvXYHY-8zj-lQecf_model.glb"
|
||||
},
|
||||
"model_urls": {
|
||||
"base_model": null,
|
||||
"fbx": null,
|
||||
"glb": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 43981280,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbdb/d8ATWvvXYHY-8zj-lQecf_model.glb"
|
||||
},
|
||||
"pbr_model": {
|
||||
"content_type": "model/gltf-binary",
|
||||
"file_name": "model.glb",
|
||||
"file_size": 43981280,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbdb/d8ATWvvXYHY-8zj-lQecf_model.glb"
|
||||
}
|
||||
},
|
||||
"rendered_image": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "preview.png",
|
||||
"file_size": 30970,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1bbd0/MblV2S5R6CeskRABMS7-V_preview.png"
|
||||
}
|
||||
},
|
||||
"request_id": "019f4db5-aadd-76c3-8122-ff1ba53fe48c",
|
||||
"status": "OK"
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"error": null,
|
||||
"gateway_request_id": "019f4d0f-4cf2-76d3-b99b-c400641b3844",
|
||||
"payload": {
|
||||
"model_mesh": {
|
||||
"content_type": "application/octet-stream",
|
||||
"file_name": "output.ply",
|
||||
"file_size": 17826208,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b77c/hEzg5xn9A-1qrLsnywjN4_output.ply"
|
||||
},
|
||||
"num_gaussians": 262144,
|
||||
"preprocessed_image": {
|
||||
"content_type": "image/png",
|
||||
"file_name": "61fc73dbe84446b08b1fcce2167b71d5.png",
|
||||
"file_size": 952754,
|
||||
"height": 1024,
|
||||
"url": "https://v3b.fal.media/files/b/0aa1b77c/YyJ19g7lJsRNXmvZvahTE_61fc73dbe84446b08b1fcce2167b71d5.png",
|
||||
"width": 1024
|
||||
},
|
||||
"seed": 1637204116,
|
||||
"timings": {
|
||||
"export": 0.794984320000367,
|
||||
"inference": 4.911052849000043
|
||||
}
|
||||
},
|
||||
"request_id": "019f4d0f-4cf2-76d3-b99b-c400641b3844",
|
||||
"status": "OK"
|
||||
}
|
||||
+1
-1
@@ -55,7 +55,7 @@ pub async fn delete_all_anonymous_user_images(_args: &Args, mysql: &Pool<MySql>)
|
||||
}
|
||||
|
||||
info!("Deleting image file: {:?}", &file.token);
|
||||
delete_media_file_as_mod(&file.token, ECHELON_USER_TOKEN, mysql).await?;
|
||||
delete_media_file_as_mod(&file.token, ECHELON_USER_TOKEN, &mysql).await?;
|
||||
}
|
||||
|
||||
page_index += 1;
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ pub async fn delete_all_files(username: &str, mysql: &Pool<MySql>) -> AnyhowResu
|
||||
|
||||
for file in media_files.records {
|
||||
info!("Deleting file: {:?}", &file.token);
|
||||
delete_media_file_as_mod(&file.token, ECHELON_USER_TOKEN, mysql).await?;
|
||||
delete_media_file_as_mod(&file.token, ECHELON_USER_TOKEN, &mysql).await?;
|
||||
}
|
||||
|
||||
page_index += 1;
|
||||
|
||||
-216
@@ -1,216 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::media_files::list::media_file_list_row::MediaFileListRow;
|
||||
|
||||
pub struct ListMediaFilesWithoutFolderArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub owner_user_token: &'e UserToken,
|
||||
|
||||
/// Optional filter on the coarse media class (image / video / mesh / ...).
|
||||
pub maybe_filter_media_class: Option<MediaFileClass>,
|
||||
|
||||
pub maybe_cursor_id: Option<u64>,
|
||||
pub limit: u32,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Paginated list of the user's media files that sit in no folder at all.
|
||||
/// Memberships whose folder was soft-deleted (but whose link row wasn't
|
||||
/// cleaned up) count as unfoldered. Excludes soft-deleted and intermediate
|
||||
/// system files. Newest first (`media_files.id` descending; the id doubles
|
||||
/// as the cursor).
|
||||
///
|
||||
/// The NOT EXISTS probe is a point lookup on
|
||||
/// `folder_media_files.index_media_file_token` plus a primary-key lookup
|
||||
/// on `folders` for the soft-delete check — cheap per candidate row.
|
||||
pub async fn list_media_files_without_folder<'e, 'c: 'e, E>(
|
||||
args: ListMediaFilesWithoutFolderArgs<'e, 'c, E>,
|
||||
) -> Result<Vec<MediaFileListRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
// No-cursor requests use a sentinel above every possible id, which
|
||||
// keeps this to a single static query per filter variant.
|
||||
// (`media_files.id` is a signed BIGINT, so the sentinel is i64::MAX.)
|
||||
let cursor_id = args.maybe_cursor_id.unwrap_or(i64::MAX as u64);
|
||||
let limit = args.limit as i64;
|
||||
|
||||
match args.maybe_filter_media_class {
|
||||
Some(media_class) => {
|
||||
select_page_with_class_filter(
|
||||
args.owner_user_token,
|
||||
media_class,
|
||||
cursor_id,
|
||||
limit,
|
||||
args.mysql_executor,
|
||||
).await
|
||||
}
|
||||
None => {
|
||||
select_page_all_classes(
|
||||
args.owner_user_token,
|
||||
cursor_id,
|
||||
limit,
|
||||
args.mysql_executor,
|
||||
).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NB: The two queries below are intentionally duplicated so that sqlx can
|
||||
// check them at compile time (no QueryBuilder). Keep the SELECT lists and
|
||||
// predicates in sync; they differ only in the `media_class = ?` filter.
|
||||
|
||||
async fn select_page_all_classes<'e, 'c: 'e, E>(
|
||||
owner_user_token: &'e UserToken,
|
||||
cursor_id: u64,
|
||||
limit: i64,
|
||||
mysql_executor: E,
|
||||
) -> Result<Vec<MediaFileListRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
sqlx::query_as!(
|
||||
MediaFileListRow,
|
||||
r#"
|
||||
SELECT
|
||||
mf.id as `media_file_id: u64`,
|
||||
|
||||
mf.token as `media_file_token: MediaFileToken`,
|
||||
mf.media_class as `media_class: MediaFileClass`,
|
||||
mf.media_type as `media_type: MediaFileType`,
|
||||
|
||||
mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,
|
||||
|
||||
mf.public_bucket_directory_hash,
|
||||
mf.maybe_public_bucket_prefix,
|
||||
mf.maybe_public_bucket_extension,
|
||||
|
||||
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
|
||||
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
|
||||
cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,
|
||||
|
||||
mf.creator_set_visibility as `creator_set_visibility: Visibility`,
|
||||
mf.is_user_upload as `is_user_upload: bool`,
|
||||
|
||||
mf.maybe_title,
|
||||
mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,
|
||||
mf.maybe_origin_filename,
|
||||
|
||||
mf.maybe_duration_millis,
|
||||
mf.maybe_frame_width,
|
||||
mf.maybe_frame_height,
|
||||
|
||||
mf.created_at,
|
||||
mf.updated_at
|
||||
FROM media_files mf
|
||||
LEFT JOIN media_files cover
|
||||
ON cover.token = mf.maybe_cover_image_media_file_token
|
||||
WHERE mf.maybe_creator_user_token = ?
|
||||
AND mf.user_deleted_at IS NULL
|
||||
AND mf.mod_deleted_at IS NULL
|
||||
AND NOT mf.is_intermediate_system_file
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM folder_media_files fmf
|
||||
JOIN folders f
|
||||
ON f.token = fmf.folder_token
|
||||
AND f.maybe_deleted_at IS NULL
|
||||
WHERE fmf.media_file_token = mf.token
|
||||
)
|
||||
AND mf.id < ?
|
||||
ORDER BY mf.id DESC
|
||||
LIMIT ?
|
||||
"#,
|
||||
owner_user_token.as_str(),
|
||||
cursor_id,
|
||||
limit,
|
||||
)
|
||||
.fetch_all(mysql_executor)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn select_page_with_class_filter<'e, 'c: 'e, E>(
|
||||
owner_user_token: &'e UserToken,
|
||||
media_class: MediaFileClass,
|
||||
cursor_id: u64,
|
||||
limit: i64,
|
||||
mysql_executor: E,
|
||||
) -> Result<Vec<MediaFileListRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
sqlx::query_as!(
|
||||
MediaFileListRow,
|
||||
r#"
|
||||
SELECT
|
||||
mf.id as `media_file_id: u64`,
|
||||
|
||||
mf.token as `media_file_token: MediaFileToken`,
|
||||
mf.media_class as `media_class: MediaFileClass`,
|
||||
mf.media_type as `media_type: MediaFileType`,
|
||||
|
||||
mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,
|
||||
|
||||
mf.public_bucket_directory_hash,
|
||||
mf.maybe_public_bucket_prefix,
|
||||
mf.maybe_public_bucket_extension,
|
||||
|
||||
cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,
|
||||
cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,
|
||||
cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,
|
||||
|
||||
mf.creator_set_visibility as `creator_set_visibility: Visibility`,
|
||||
mf.is_user_upload as `is_user_upload: bool`,
|
||||
|
||||
mf.maybe_title,
|
||||
mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,
|
||||
mf.maybe_origin_filename,
|
||||
|
||||
mf.maybe_duration_millis,
|
||||
mf.maybe_frame_width,
|
||||
mf.maybe_frame_height,
|
||||
|
||||
mf.created_at,
|
||||
mf.updated_at
|
||||
FROM media_files mf
|
||||
LEFT JOIN media_files cover
|
||||
ON cover.token = mf.maybe_cover_image_media_file_token
|
||||
WHERE mf.maybe_creator_user_token = ?
|
||||
AND mf.media_class = ?
|
||||
AND mf.user_deleted_at IS NULL
|
||||
AND mf.mod_deleted_at IS NULL
|
||||
AND NOT mf.is_intermediate_system_file
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM folder_media_files fmf
|
||||
JOIN folders f
|
||||
ON f.token = fmf.folder_token
|
||||
AND f.maybe_deleted_at IS NULL
|
||||
WHERE fmf.media_file_token = mf.token
|
||||
)
|
||||
AND mf.id < ?
|
||||
ORDER BY mf.id DESC
|
||||
LIMIT ?
|
||||
"#,
|
||||
owner_user_token.as_str(),
|
||||
media_class.to_str(),
|
||||
cursor_id,
|
||||
limit,
|
||||
)
|
||||
.fetch_all(mysql_executor)
|
||||
.await
|
||||
}
|
||||
@@ -2,5 +2,4 @@ pub mod bulk_delete_folder_media_files;
|
||||
pub mod bulk_insert_folder_media_files;
|
||||
pub mod filter_existing_media_file_tokens;
|
||||
pub mod list_folder_media_files;
|
||||
pub mod list_media_files_without_folder;
|
||||
pub mod recompute_folder_last_media_files;
|
||||
|
||||
+143
-50
@@ -1,4 +1,5 @@
|
||||
use anyhow::anyhow;
|
||||
use sqlx;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
use enums::by_table::generic_synthetic_ids::id_category::IdCategory;
|
||||
@@ -21,7 +22,6 @@ use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::payloads::media_file_extra_info::media_file_extra_info::MediaFileExtraInfo;
|
||||
use crate::queries::generic_synthetic_ids::transactional_increment_generic_synthetic_id::transactional_increment_generic_synthetic_id;
|
||||
use crate::queries::media_files::create::generic_insert::insert_media_file_generic_executor::{insert_media_file_generic_executor, InsertMediaFileGenericExecutorArgs};
|
||||
|
||||
pub struct InsertArgs<'a> {
|
||||
pub pool: &'a MySqlPool,
|
||||
@@ -54,7 +54,7 @@ pub struct InsertArgs<'a> {
|
||||
pub maybe_frame_width: Option<u32>,
|
||||
pub maybe_frame_height: Option<u32>,
|
||||
pub checksum_sha2: &'a str,
|
||||
|
||||
|
||||
// Media info for certain product areas
|
||||
pub maybe_engine_category: Option<MediaFileEngineCategory>, // TODO: Deprecate
|
||||
|
||||
@@ -104,13 +104,12 @@ pub struct InsertArgs<'a> {
|
||||
pub maybe_mod_user_token: Option<&'a UserToken>,
|
||||
}
|
||||
|
||||
/// Insert a media file record, allocating the creator's synthetic IDs in the
|
||||
/// same transaction. Wraps [`insert_media_file_generic_executor`], which runs
|
||||
/// the actual INSERT.
|
||||
pub async fn insert_media_file_generic(
|
||||
args: InsertArgs<'_>
|
||||
) -> AnyhowResult<(MediaFileToken, u64)>
|
||||
{
|
||||
let result_token = MediaFileToken::generate();
|
||||
|
||||
let extra_file_modification_info = args
|
||||
.maybe_extra_media_info.map(|extra| extra.to_json_string())
|
||||
.transpose()?;
|
||||
@@ -118,8 +117,24 @@ pub async fn insert_media_file_generic(
|
||||
let mut maybe_creator_file_synthetic_id : Option<u64> = None;
|
||||
let mut maybe_creator_category_synthetic_id : Option<u64> = None;
|
||||
|
||||
let mut transaction = args.pool.begin().await?;
|
||||
let mut maybe_generation_provider_str = None;
|
||||
let mut is_intermediate_system_file = args.is_intermediate_system_file;
|
||||
let mut is_user_upload = args.is_user_upload;
|
||||
let mut origin_category = args.origin_category;
|
||||
|
||||
if let Some(generation_provider) = args.maybe_generation_provider {
|
||||
// Overrides if we're using a generation provider
|
||||
maybe_generation_provider_str = Some(generation_provider.to_str());
|
||||
is_intermediate_system_file = false;
|
||||
is_user_upload = false;
|
||||
if generation_provider != GenerationProvider::Artcraft {
|
||||
origin_category = MediaFileOriginCategory::ThirdPartyInference;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut transaction = args.pool.begin().await?;
|
||||
|
||||
if let Some(user_token) = args.maybe_creator_user_token.as_deref() {
|
||||
let next_media_file_id = transactional_increment_generic_synthetic_id(
|
||||
user_token,
|
||||
@@ -137,53 +152,131 @@ pub async fn insert_media_file_generic(
|
||||
maybe_creator_category_synthetic_id = Some(category_id);
|
||||
}
|
||||
|
||||
let query_result = insert_media_file_generic_executor(InsertMediaFileGenericExecutorArgs {
|
||||
maybe_creator_user_token: args.maybe_creator_user_token,
|
||||
maybe_creator_anonymous_visitor_token: args.maybe_creator_anonymous_visitor_token,
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
media_class: args.media_class,
|
||||
media_type: args.media_type,
|
||||
is_user_upload: args.is_user_upload,
|
||||
is_intermediate_system_file: args.is_intermediate_system_file,
|
||||
origin_category: args.origin_category,
|
||||
origin_product_category: args.origin_product_category,
|
||||
maybe_origin_model_type: args.maybe_origin_model_type,
|
||||
maybe_origin_model_token: args.maybe_origin_model_token,
|
||||
maybe_origin_filename: args.maybe_origin_filename,
|
||||
maybe_mime_type: args.maybe_mime_type,
|
||||
file_size_bytes: args.file_size_bytes,
|
||||
maybe_duration_millis: args.maybe_duration_millis,
|
||||
maybe_audio_encoding: args.maybe_audio_encoding,
|
||||
maybe_video_encoding: args.maybe_video_encoding,
|
||||
maybe_frame_width: args.maybe_frame_width,
|
||||
maybe_frame_height: args.maybe_frame_height,
|
||||
checksum_sha2: args.checksum_sha2,
|
||||
maybe_engine_category: args.maybe_engine_category,
|
||||
maybe_title: args.maybe_title,
|
||||
maybe_text_transcript: args.maybe_text_transcript,
|
||||
maybe_scene_source_media_file_token: args.maybe_scene_source_media_file_token,
|
||||
maybe_prompt_token: args.maybe_prompt_token,
|
||||
maybe_batch_token: args.maybe_batch_token,
|
||||
public_bucket_directory_hash: args.public_bucket_directory_hash,
|
||||
maybe_public_bucket_prefix: args.maybe_public_bucket_prefix,
|
||||
maybe_public_bucket_extension: args.maybe_public_bucket_extension,
|
||||
let query_result = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO media_files
|
||||
SET
|
||||
token = ?,
|
||||
|
||||
media_class = ?,
|
||||
media_type = ?,
|
||||
|
||||
is_user_upload = ?,
|
||||
is_intermediate_system_file = ?,
|
||||
|
||||
origin_category = ?,
|
||||
origin_product_category = ?,
|
||||
maybe_origin_model_type = ?,
|
||||
maybe_origin_model_token = ?,
|
||||
maybe_origin_filename = ?,
|
||||
|
||||
maybe_batch_token = ?,
|
||||
|
||||
maybe_mime_type = ?,
|
||||
file_size_bytes = ?,
|
||||
maybe_duration_millis = ?,
|
||||
maybe_audio_encoding = ?,
|
||||
maybe_video_encoding = ?,
|
||||
maybe_frame_width = ?,
|
||||
maybe_frame_height = ?,
|
||||
maybe_prompt_token = ?,
|
||||
checksum_sha2 = ?,
|
||||
|
||||
maybe_engine_category = ?,
|
||||
|
||||
maybe_title = ?,
|
||||
maybe_text_transcript = ?,
|
||||
|
||||
maybe_scene_source_media_file_token = ?,
|
||||
|
||||
public_bucket_directory_hash = ?,
|
||||
maybe_public_bucket_prefix = ?,
|
||||
maybe_public_bucket_extension = ?,
|
||||
|
||||
maybe_creator_user_token = ?,
|
||||
maybe_creator_anonymous_visitor_token = ?,
|
||||
|
||||
creator_ip_address = ?,
|
||||
creator_set_visibility = ?,
|
||||
|
||||
maybe_creator_file_synthetic_id = ?,
|
||||
maybe_creator_category_synthetic_id = ?,
|
||||
|
||||
extra_file_modification_info = ?,
|
||||
|
||||
maybe_generation_provider = ?,
|
||||
|
||||
platform_type = ?,
|
||||
|
||||
maybe_cover_image_media_file_token = ?,
|
||||
|
||||
maybe_mod_user_token = ?,
|
||||
is_generated_on_prem = ?,
|
||||
generated_by_worker = ?,
|
||||
generated_by_cluster = ?
|
||||
"#,
|
||||
result_token,
|
||||
|
||||
args.media_class.to_str(),
|
||||
args.media_type.to_str(),
|
||||
|
||||
is_user_upload,
|
||||
is_intermediate_system_file,
|
||||
|
||||
origin_category.to_str(),
|
||||
args.origin_product_category.to_str(),
|
||||
args.maybe_origin_model_type.map(|e| e.to_str()),
|
||||
args.maybe_origin_model_token.map(|t| t.to_string()),
|
||||
args.maybe_origin_filename,
|
||||
|
||||
args.maybe_batch_token.map(|t| t.as_str()),
|
||||
|
||||
args.maybe_mime_type,
|
||||
args.file_size_bytes,
|
||||
args.maybe_duration_millis,
|
||||
args.maybe_audio_encoding,
|
||||
args.maybe_video_encoding,
|
||||
args.maybe_frame_width,
|
||||
args.maybe_frame_height,
|
||||
args.maybe_prompt_token.map(|t| t.as_str()),
|
||||
args.checksum_sha2,
|
||||
|
||||
args.maybe_engine_category.map(|e| e.to_str()),
|
||||
|
||||
args.maybe_title,
|
||||
args.maybe_text_transcript,
|
||||
|
||||
args.maybe_scene_source_media_file_token.map(|t| t.as_str()),
|
||||
|
||||
args.public_bucket_directory_hash,
|
||||
args.maybe_public_bucket_prefix,
|
||||
args.maybe_public_bucket_extension,
|
||||
|
||||
args.maybe_creator_user_token.map(|t| t.as_str()),
|
||||
args.maybe_creator_anonymous_visitor_token.map(|t| t.as_str()),
|
||||
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
|
||||
maybe_creator_file_synthetic_id,
|
||||
maybe_creator_category_synthetic_id,
|
||||
maybe_extra_file_modification_info: extra_file_modification_info,
|
||||
is_generated_on_prem: args.is_generated_on_prem,
|
||||
generated_by_worker: args.generated_by_worker,
|
||||
generated_by_cluster: args.generated_by_cluster,
|
||||
maybe_generation_provider: args.maybe_generation_provider,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cover_image_media_file_token: args.maybe_cover_image_media_file_token,
|
||||
maybe_mod_user_token: args.maybe_mod_user_token,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let (result_token, record_id) = match query_result {
|
||||
Ok(result) => result,
|
||||
extra_file_modification_info,
|
||||
|
||||
maybe_generation_provider_str,
|
||||
|
||||
args.maybe_platform_type.map(|p| p.to_str()),
|
||||
|
||||
args.maybe_cover_image_media_file_token.map(|t| t.as_str()),
|
||||
|
||||
args.maybe_mod_user_token,
|
||||
args.is_generated_on_prem,
|
||||
args.generated_by_worker,
|
||||
args.generated_by_cluster
|
||||
).execute(&mut *transaction).await;
|
||||
|
||||
let record_id = match query_result {
|
||||
Ok(res) => res.last_insert_id(),
|
||||
Err(err) => {
|
||||
// TODO: handle better
|
||||
//transaction.rollback().await?;
|
||||
|
||||
-267
@@ -1,267 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx;
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
|
||||
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
|
||||
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
|
||||
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// Arguments for [`insert_media_file_generic_executor`].
|
||||
///
|
||||
/// This is the executor-generic core of `insert_media_file_generic`: it runs
|
||||
/// the single INSERT statement and nothing else. Synthetic-ID increments and
|
||||
/// extra-info serialization stay with the caller (see
|
||||
/// `insert_media_file_generic`, which wraps this in a transaction).
|
||||
pub struct InsertMediaFileGenericExecutorArgs<'a, 'c, E>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
// Creator info
|
||||
pub maybe_creator_user_token: Option<&'a UserToken>,
|
||||
pub maybe_creator_anonymous_visitor_token: Option<&'a AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: &'a str,
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
// Important database indices
|
||||
pub media_class: MediaFileClass,
|
||||
pub media_type: MediaFileType,
|
||||
pub is_user_upload: bool,
|
||||
pub is_intermediate_system_file: bool,
|
||||
|
||||
// Product and other origination information
|
||||
pub origin_category: MediaFileOriginCategory,
|
||||
pub origin_product_category: MediaFileOriginProductCategory,
|
||||
pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
|
||||
pub maybe_origin_model_token: Option<&'a ModelWeightToken>,
|
||||
pub maybe_origin_filename: Option<String>,
|
||||
|
||||
// Media info
|
||||
pub maybe_mime_type: Option<&'a str>,
|
||||
pub file_size_bytes: u64,
|
||||
pub maybe_duration_millis: Option<u64>,
|
||||
pub maybe_audio_encoding: Option<&'a str>,
|
||||
pub maybe_video_encoding: Option<&'a str>,
|
||||
pub maybe_frame_width: Option<u32>,
|
||||
pub maybe_frame_height: Option<u32>,
|
||||
pub checksum_sha2: &'a str,
|
||||
|
||||
// Media info for certain product areas
|
||||
pub maybe_engine_category: Option<MediaFileEngineCategory>, // TODO: Deprecate
|
||||
|
||||
// User text information
|
||||
pub maybe_title: Option<&'a str>,
|
||||
pub maybe_text_transcript: Option<&'a str>,
|
||||
|
||||
// If generated from a scene, this is the scene media file token.
|
||||
pub maybe_scene_source_media_file_token: Option<&'a MediaFileToken>,
|
||||
|
||||
// If additional prompt details are stored, this is the prompt token.
|
||||
pub maybe_prompt_token: Option<&'a PromptToken>,
|
||||
|
||||
// If batch generated, this is the batch token.
|
||||
pub maybe_batch_token: Option<&'a BatchGenerationToken>,
|
||||
|
||||
// Storage details
|
||||
pub public_bucket_directory_hash: &'a str,
|
||||
pub maybe_public_bucket_prefix: Option<&'a str>,
|
||||
pub maybe_public_bucket_extension: Option<&'a str>,
|
||||
|
||||
// Counters, pre-computed by the caller (see
|
||||
// `transactional_increment_generic_synthetic_id`).
|
||||
pub maybe_creator_file_synthetic_id: Option<u64>,
|
||||
pub maybe_creator_category_synthetic_id: Option<u64>,
|
||||
|
||||
/// Extra polymorphic data stored in `extra_file_modification_info`,
|
||||
/// pre-serialized to JSON by the caller.
|
||||
pub maybe_extra_file_modification_info: Option<String>,
|
||||
|
||||
// Worker generation info
|
||||
pub is_generated_on_prem: bool,
|
||||
pub generated_by_worker: Option<&'a str>,
|
||||
pub generated_by_cluster: Option<&'a str>,
|
||||
|
||||
/// If provided, the third-party provider that generated this file.
|
||||
pub maybe_generation_provider: Option<GenerationProvider>,
|
||||
|
||||
/// The platform the creating request came from, inferred from its User-Agent.
|
||||
/// For inference results, this is copied from the originating job.
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
// Cover image (e.g. thumbnail for 3D splats)
|
||||
pub maybe_cover_image_media_file_token: Option<&'a MediaFileToken>,
|
||||
|
||||
// Moderation details (deprecated)
|
||||
pub maybe_mod_user_token: Option<&'a UserToken>,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Insert a media file record using any executor (pool, connection, or open
|
||||
/// transaction). This is the single-statement core shared by
|
||||
/// `insert_media_file_generic` and the specialized inserts.
|
||||
pub async fn insert_media_file_generic_executor<'a, 'c: 'a, E>(
|
||||
args: InsertMediaFileGenericExecutorArgs<'a, 'c, E>,
|
||||
) -> Result<(MediaFileToken, u64), sqlx::Error>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result_token = MediaFileToken::generate();
|
||||
|
||||
let mut maybe_generation_provider_str = None;
|
||||
let is_intermediate_system_file = args.is_intermediate_system_file;
|
||||
let mut is_user_upload = args.is_user_upload;
|
||||
let mut origin_category = args.origin_category;
|
||||
|
||||
if let Some(generation_provider) = args.maybe_generation_provider {
|
||||
// Overrides if we're using a generation provider.
|
||||
// NB: `is_intermediate_system_file` is deliberately NOT reset here:
|
||||
// generated cover images / thumbnails set both a generation provider
|
||||
// and the intermediate flag, and clobbering the flag made them appear
|
||||
// in users' asset collections. The flag defaults to false at call
|
||||
// sites, so honoring the explicit setting is always correct.
|
||||
maybe_generation_provider_str = Some(generation_provider.to_str());
|
||||
is_user_upload = false;
|
||||
if generation_provider != GenerationProvider::Artcraft {
|
||||
origin_category = MediaFileOriginCategory::ThirdPartyInference;
|
||||
}
|
||||
}
|
||||
|
||||
let query_result = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO media_files
|
||||
SET
|
||||
token = ?,
|
||||
|
||||
media_class = ?,
|
||||
media_type = ?,
|
||||
|
||||
is_user_upload = ?,
|
||||
is_intermediate_system_file = ?,
|
||||
|
||||
origin_category = ?,
|
||||
origin_product_category = ?,
|
||||
maybe_origin_model_type = ?,
|
||||
maybe_origin_model_token = ?,
|
||||
maybe_origin_filename = ?,
|
||||
|
||||
maybe_batch_token = ?,
|
||||
|
||||
maybe_mime_type = ?,
|
||||
file_size_bytes = ?,
|
||||
maybe_duration_millis = ?,
|
||||
maybe_audio_encoding = ?,
|
||||
maybe_video_encoding = ?,
|
||||
maybe_frame_width = ?,
|
||||
maybe_frame_height = ?,
|
||||
maybe_prompt_token = ?,
|
||||
checksum_sha2 = ?,
|
||||
|
||||
maybe_engine_category = ?,
|
||||
|
||||
maybe_title = ?,
|
||||
maybe_text_transcript = ?,
|
||||
|
||||
maybe_scene_source_media_file_token = ?,
|
||||
|
||||
public_bucket_directory_hash = ?,
|
||||
maybe_public_bucket_prefix = ?,
|
||||
maybe_public_bucket_extension = ?,
|
||||
|
||||
maybe_creator_user_token = ?,
|
||||
maybe_creator_anonymous_visitor_token = ?,
|
||||
|
||||
creator_ip_address = ?,
|
||||
creator_set_visibility = ?,
|
||||
|
||||
maybe_creator_file_synthetic_id = ?,
|
||||
maybe_creator_category_synthetic_id = ?,
|
||||
|
||||
extra_file_modification_info = ?,
|
||||
|
||||
maybe_generation_provider = ?,
|
||||
|
||||
platform_type = ?,
|
||||
|
||||
maybe_cover_image_media_file_token = ?,
|
||||
|
||||
maybe_mod_user_token = ?,
|
||||
is_generated_on_prem = ?,
|
||||
generated_by_worker = ?,
|
||||
generated_by_cluster = ?
|
||||
"#,
|
||||
result_token,
|
||||
|
||||
args.media_class.to_str(),
|
||||
args.media_type.to_str(),
|
||||
|
||||
is_user_upload,
|
||||
is_intermediate_system_file,
|
||||
|
||||
origin_category.to_str(),
|
||||
args.origin_product_category.to_str(),
|
||||
args.maybe_origin_model_type.map(|e| e.to_str()),
|
||||
args.maybe_origin_model_token.map(|t| t.to_string()),
|
||||
args.maybe_origin_filename,
|
||||
|
||||
args.maybe_batch_token.map(|t| t.as_str()),
|
||||
|
||||
args.maybe_mime_type,
|
||||
args.file_size_bytes,
|
||||
args.maybe_duration_millis,
|
||||
args.maybe_audio_encoding,
|
||||
args.maybe_video_encoding,
|
||||
args.maybe_frame_width,
|
||||
args.maybe_frame_height,
|
||||
args.maybe_prompt_token.map(|t| t.as_str()),
|
||||
args.checksum_sha2,
|
||||
|
||||
args.maybe_engine_category.map(|e| e.to_str()),
|
||||
|
||||
args.maybe_title,
|
||||
args.maybe_text_transcript,
|
||||
|
||||
args.maybe_scene_source_media_file_token.map(|t| t.as_str()),
|
||||
|
||||
args.public_bucket_directory_hash,
|
||||
args.maybe_public_bucket_prefix,
|
||||
args.maybe_public_bucket_extension,
|
||||
|
||||
args.maybe_creator_user_token.map(|t| t.as_str()),
|
||||
args.maybe_creator_anonymous_visitor_token.map(|t| t.as_str()),
|
||||
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
|
||||
args.maybe_creator_file_synthetic_id,
|
||||
args.maybe_creator_category_synthetic_id,
|
||||
|
||||
args.maybe_extra_file_modification_info,
|
||||
|
||||
maybe_generation_provider_str,
|
||||
|
||||
args.maybe_platform_type.map(|p| p.to_str()),
|
||||
|
||||
args.maybe_cover_image_media_file_token.map(|t| t.as_str()),
|
||||
|
||||
args.maybe_mod_user_token,
|
||||
args.is_generated_on_prem,
|
||||
args.generated_by_worker,
|
||||
args.generated_by_cluster
|
||||
).execute(args.mysql_executor).await?;
|
||||
|
||||
Ok((result_token, query_result.last_insert_id()))
|
||||
}
|
||||
-1
@@ -1,3 +1,2 @@
|
||||
pub mod insert_media_file_generic;
|
||||
pub mod insert_media_file_generic_executor;
|
||||
pub mod insert_media_file_generic_from_job;
|
||||
|
||||
+4
-7
@@ -302,22 +302,19 @@ impl MediaFileInsertBuilder {
|
||||
"Origin category is required".to_string()))?;
|
||||
|
||||
let mut maybe_generation_provider_str = None;
|
||||
let is_intermediate_system_file = self.is_intermediate_system_file;
|
||||
let mut is_intermediate_system_file = self.is_intermediate_system_file;
|
||||
let mut is_user_upload = self.is_user_upload;
|
||||
|
||||
if let Some(generation_provider) = self.maybe_generation_provider {
|
||||
// Overrides if we're using a generation provider.
|
||||
// NB: `is_intermediate_system_file` is deliberately NOT reset here:
|
||||
// generated cover images / thumbnails set both a generation provider
|
||||
// and the intermediate flag, and clobbering the flag made them appear
|
||||
// in users' asset collections. The flag defaults to false, so honoring
|
||||
// the explicit setter is always correct.
|
||||
// Overrides if we're using a generation provider
|
||||
maybe_generation_provider_str = Some(generation_provider.to_str());
|
||||
is_intermediate_system_file = false;
|
||||
is_user_upload = false;
|
||||
if generation_provider != GenerationProvider::Artcraft {
|
||||
origin_category = MediaFileOriginCategory::ThirdPartyInference;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let result = insert_media_file_generic(InsertArgs {
|
||||
pool: mysql_pool,
|
||||
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
|
||||
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::media_files::create::generic_insert::insert_media_file_generic_executor::{insert_media_file_generic_executor, InsertMediaFileGenericExecutorArgs};
|
||||
|
||||
pub struct InsertCoverMediaFileArgs<'a, 'c, E>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
// Creator info (copied from the generation job).
|
||||
pub maybe_creator_user_token: Option<&'a UserToken>,
|
||||
pub maybe_creator_anonymous_visitor_token: Option<&'a AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: &'a str,
|
||||
|
||||
/// The image format of the cover (png, jpg, webp, ...).
|
||||
pub media_file_type: MediaFileType,
|
||||
pub mime_type: &'a str,
|
||||
pub file_size_bytes: u64,
|
||||
pub checksum_sha2: &'a str,
|
||||
pub public_bucket_path: &'a MediaFileBucketPath,
|
||||
|
||||
/// Product category of the generation the cover belongs to (e.g.
|
||||
/// `WorldGeneration` for gaussian splat covers).
|
||||
pub maybe_origin_product_category: Option<MediaFileOriginProductCategory>,
|
||||
pub maybe_prompt_token: Option<&'a PromptToken>,
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Insert a cover image media file record (e.g. a mesh/splat thumbnail
|
||||
/// downloaded from a generation provider).
|
||||
///
|
||||
/// Cover images are always `media_class = image` and — critically — always
|
||||
/// `is_intermediate_system_file = TRUE`, so they never appear in the user's
|
||||
/// asset collection. They are low-quality preview renders, not assets the
|
||||
/// user paid to generate.
|
||||
///
|
||||
/// Cover images deliberately do not consume the creator's synthetic file IDs:
|
||||
/// those number the user's visible assets, and covers are hidden.
|
||||
pub async fn insert_cover_media_file<'a, 'c: 'a, E>(
|
||||
args: InsertCoverMediaFileArgs<'a, 'c, E>,
|
||||
) -> Result<MediaFileToken, sqlx::Error>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let (media_token, _record_id) = insert_media_file_generic_executor(InsertMediaFileGenericExecutorArgs {
|
||||
// User details
|
||||
maybe_creator_user_token: args.maybe_creator_user_token,
|
||||
maybe_creator_anonymous_visitor_token: args.maybe_creator_anonymous_visitor_token,
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
|
||||
// Storage
|
||||
public_bucket_directory_hash: args.public_bucket_path.get_object_hash(),
|
||||
maybe_public_bucket_prefix: args.public_bucket_path.get_optional_prefix(),
|
||||
maybe_public_bucket_extension: args.public_bucket_path.get_optional_extension(),
|
||||
|
||||
// File details
|
||||
checksum_sha2: args.checksum_sha2,
|
||||
file_size_bytes: args.file_size_bytes,
|
||||
maybe_mime_type: Some(args.mime_type),
|
||||
media_class: MediaFileClass::Image,
|
||||
|
||||
// Additional foreign keys and misc
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_prompt_token: args.maybe_prompt_token,
|
||||
media_type: args.media_file_type,
|
||||
origin_product_category: args.maybe_origin_product_category // TODO: Not useful here.
|
||||
.unwrap_or(MediaFileOriginProductCategory::Unknown),
|
||||
|
||||
// Essential - this is a hidden file
|
||||
is_intermediate_system_file: true,
|
||||
creator_set_visibility: Visibility::Hidden,
|
||||
|
||||
// Other Constants
|
||||
generated_by_cluster: None,
|
||||
generated_by_worker: None,
|
||||
is_generated_on_prem: false,
|
||||
is_user_upload: false,
|
||||
maybe_audio_encoding: None,
|
||||
maybe_batch_token: None,
|
||||
maybe_cover_image_media_file_token: None,
|
||||
maybe_creator_category_synthetic_id: None,
|
||||
maybe_creator_file_synthetic_id: None,
|
||||
maybe_duration_millis: None,
|
||||
maybe_engine_category: None,
|
||||
maybe_extra_file_modification_info: None,
|
||||
maybe_frame_height: None,
|
||||
maybe_frame_width: None,
|
||||
maybe_mod_user_token: None,
|
||||
maybe_origin_filename: None,
|
||||
maybe_origin_model_token: None,
|
||||
maybe_origin_model_type: None,
|
||||
maybe_scene_source_media_file_token: None,
|
||||
maybe_text_transcript: None,
|
||||
maybe_title: None,
|
||||
maybe_video_encoding: None,
|
||||
origin_category: MediaFileOriginCategory::Inference, // TODO: Not useful here.
|
||||
|
||||
// Connection
|
||||
mysql_executor: args.mysql_executor,
|
||||
phantom: Default::default(),
|
||||
}).await?;
|
||||
|
||||
Ok(media_token)
|
||||
}
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
pub mod insert_cover_media_file;
|
||||
pub mod insert_media_file_from_cli_tool;
|
||||
pub mod insert_media_file_from_comfy_ui;
|
||||
pub mod insert_media_file_from_f5_tts;
|
||||
@@ -10,4 +9,4 @@ pub mod insert_media_file_from_seed_vc;
|
||||
pub mod insert_media_file_from_studio_gen2;
|
||||
pub mod insert_media_file_from_tacotron2;
|
||||
pub mod insert_media_file_from_voice_conversion;
|
||||
pub mod insert_media_file_from_zero_shot_tts;
|
||||
pub mod insert_media_file_from_zero_shot_tts;
|
||||
+17
-29
@@ -1,15 +1,12 @@
|
||||
use sqlx::{Executor, MySql};
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn delete_media_file_as_user<'e, 'c: 'e, E>(
|
||||
pub async fn delete_media_file_as_user(
|
||||
media_file_token: &MediaFileToken,
|
||||
mysql_executor: E,
|
||||
) -> AnyhowResult<()>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
mysql_pool: &MySqlPool
|
||||
) -> AnyhowResult<()> {
|
||||
let _r = sqlx::query!(
|
||||
r#"
|
||||
UPDATE media_files
|
||||
@@ -21,19 +18,16 @@ LIMIT 1
|
||||
"#,
|
||||
media_file_token,
|
||||
)
|
||||
.execute(mysql_executor)
|
||||
.execute(mysql_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_media_file_as_mod<'e, 'c: 'e, E>(
|
||||
pub async fn delete_media_file_as_mod(
|
||||
media_file_token: &MediaFileToken,
|
||||
mod_user_token: &str,
|
||||
mysql_executor: E,
|
||||
) -> AnyhowResult<()>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
mysql_pool: &MySqlPool
|
||||
) -> AnyhowResult<()> {
|
||||
let _r = sqlx::query!(
|
||||
r#"
|
||||
UPDATE media_files
|
||||
@@ -48,18 +42,15 @@ LIMIT 1
|
||||
mod_user_token,
|
||||
media_file_token,
|
||||
)
|
||||
.execute(mysql_executor)
|
||||
.execute(mysql_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn undelete_media_file_as_user<'e, 'c: 'e, E>(
|
||||
pub async fn undelete_media_file_as_user(
|
||||
media_file_token: &MediaFileToken,
|
||||
mysql_executor: E,
|
||||
) -> AnyhowResult<()>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
mysql_pool: &MySqlPool
|
||||
) -> AnyhowResult<()> {
|
||||
let _r = sqlx::query!(
|
||||
r#"
|
||||
UPDATE media_files
|
||||
@@ -71,19 +62,16 @@ LIMIT 1
|
||||
"#,
|
||||
media_file_token,
|
||||
)
|
||||
.execute(mysql_executor)
|
||||
.execute(mysql_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn undelete_media_file_as_mod<'e, 'c: 'e, E>(
|
||||
pub async fn undelete_media_file_as_mod(
|
||||
media_file_token: &MediaFileToken,
|
||||
mod_user_token: &str,
|
||||
mysql_executor: E,
|
||||
) -> AnyhowResult<()>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
mysql_pool: &MySqlPool
|
||||
) -> AnyhowResult<()> {
|
||||
let _r = sqlx::query!(
|
||||
r#"
|
||||
UPDATE media_files
|
||||
@@ -97,7 +85,7 @@ LIMIT 1
|
||||
mod_user_token,
|
||||
media_file_token,
|
||||
)
|
||||
.execute(mysql_executor)
|
||||
.execute(mysql_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
-249
@@ -1,249 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct SessionMediaFileListPage {
|
||||
pub records: Vec<SessionMediaFileListItem>,
|
||||
|
||||
/// ID of the first record (for building the "previous" cursor).
|
||||
pub first_id: Option<u64>,
|
||||
|
||||
/// ID of the last record (for building the "next" cursor).
|
||||
pub last_id: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct SessionMediaFileListItem {
|
||||
pub id: u64,
|
||||
pub token: MediaFileToken,
|
||||
|
||||
pub media_class: MediaFileClass,
|
||||
pub media_type: MediaFileType,
|
||||
|
||||
pub public_bucket_directory_hash: String,
|
||||
pub maybe_public_bucket_prefix: Option<String>,
|
||||
pub maybe_public_bucket_extension: Option<String>,
|
||||
|
||||
pub maybe_creator_user_token: Option<UserToken>,
|
||||
pub maybe_creator_username: Option<String>,
|
||||
pub maybe_creator_display_name: Option<String>,
|
||||
pub maybe_creator_gravatar_hash: Option<String>,
|
||||
|
||||
pub maybe_file_cover_image_public_bucket_hash: Option<String>,
|
||||
pub maybe_file_cover_image_public_bucket_prefix: Option<String>,
|
||||
pub maybe_file_cover_image_public_bucket_extension: Option<String>,
|
||||
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
pub maybe_title: Option<String>,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct ListSessionMediaFilesByClassArgs<'a, 'c, E>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub user_token: &'a UserToken,
|
||||
|
||||
/// The media class to list (e.g. `mesh` or `splat`).
|
||||
pub media_class: MediaFileClass,
|
||||
|
||||
pub limit: usize,
|
||||
|
||||
/// Keyset cursor: a `media_files.id` from a previous page.
|
||||
pub maybe_cursor_id: Option<u64>,
|
||||
pub cursor_is_reversed: bool,
|
||||
pub sort_ascending: bool,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// List the session user's media files of a single class (e.g. `mesh` or
|
||||
/// `splat`), newest first by default, with keyset pagination on `id`.
|
||||
pub async fn list_session_media_files_by_class<'a, 'c: 'a, E>(
|
||||
args: ListSessionMediaFilesByClassArgs<'a, 'c, E>,
|
||||
) -> Result<SessionMediaFileListPage, sqlx::Error>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
// A reversed cursor walks the opposite direction of the sort, so the effective
|
||||
// scan direction is the XOR of the two flags (same scheme as `list_media_files`).
|
||||
let ascending = args.sort_ascending != args.cursor_is_reversed;
|
||||
|
||||
let limit = args.limit as u64;
|
||||
|
||||
let records = if ascending {
|
||||
// NB: IDs start at 1, so a zero sentinel means "no cursor".
|
||||
let cursor_id = args.maybe_cursor_id.unwrap_or(0);
|
||||
select_page_ascending(
|
||||
args.user_token,
|
||||
args.media_class,
|
||||
cursor_id,
|
||||
limit,
|
||||
args.mysql_executor,
|
||||
).await?
|
||||
} else {
|
||||
let cursor_id = args.maybe_cursor_id.unwrap_or(u64::MAX);
|
||||
select_page_descending(
|
||||
args.user_token,
|
||||
args.media_class,
|
||||
cursor_id,
|
||||
limit,
|
||||
args.mysql_executor,
|
||||
).await?
|
||||
};
|
||||
|
||||
let first_id = records.first().map(|record| record.id);
|
||||
let last_id = records.last().map(|record| record.id);
|
||||
|
||||
Ok(SessionMediaFileListPage {
|
||||
records,
|
||||
first_id,
|
||||
last_id,
|
||||
})
|
||||
}
|
||||
|
||||
// NB: The two queries below are intentionally duplicated so that sqlx can check
|
||||
// them at compile time (no QueryBuilder). Keep the SELECT lists and predicates
|
||||
// in sync; they differ only in the cursor comparison direction and sort order.
|
||||
|
||||
async fn select_page_ascending<'a, 'c: 'a, E>(
|
||||
user_token: &'a UserToken,
|
||||
media_class: MediaFileClass,
|
||||
cursor_id: u64,
|
||||
limit: u64,
|
||||
mysql_executor: E,
|
||||
) -> Result<Vec<SessionMediaFileListItem>, sqlx::Error>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
sqlx::query_as!(
|
||||
SessionMediaFileListItem,
|
||||
r#"
|
||||
SELECT
|
||||
m.id as `id: u64`,
|
||||
m.token as `token: tokens::tokens::media_files::MediaFileToken`,
|
||||
|
||||
m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,
|
||||
m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,
|
||||
|
||||
m.public_bucket_directory_hash,
|
||||
m.maybe_public_bucket_prefix,
|
||||
m.maybe_public_bucket_extension,
|
||||
|
||||
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
|
||||
users.username as maybe_creator_username,
|
||||
users.display_name as maybe_creator_display_name,
|
||||
users.email_gravatar_hash as maybe_creator_gravatar_hash,
|
||||
|
||||
media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,
|
||||
media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,
|
||||
media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,
|
||||
|
||||
m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
|
||||
|
||||
m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,
|
||||
m.maybe_title,
|
||||
|
||||
m.created_at,
|
||||
m.updated_at
|
||||
|
||||
FROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)
|
||||
LEFT OUTER JOIN users
|
||||
ON m.maybe_creator_user_token = users.token
|
||||
LEFT OUTER JOIN media_files as media_file_cover_image
|
||||
ON media_file_cover_image.token = m.maybe_cover_image_media_file_token
|
||||
WHERE
|
||||
m.maybe_creator_user_token = ?
|
||||
AND m.media_class = ?
|
||||
AND m.user_deleted_at IS NULL
|
||||
AND m.mod_deleted_at IS NULL
|
||||
AND NOT m.is_intermediate_system_file
|
||||
AND m.id > ?
|
||||
ORDER BY m.id ASC
|
||||
LIMIT ?
|
||||
"#,
|
||||
user_token,
|
||||
media_class.to_str(),
|
||||
cursor_id,
|
||||
limit,
|
||||
)
|
||||
.fetch_all(mysql_executor)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn select_page_descending<'a, 'c: 'a, E>(
|
||||
user_token: &'a UserToken,
|
||||
media_class: MediaFileClass,
|
||||
cursor_id: u64,
|
||||
limit: u64,
|
||||
mysql_executor: E,
|
||||
) -> Result<Vec<SessionMediaFileListItem>, sqlx::Error>
|
||||
where
|
||||
E: 'a + Executor<'c, Database = MySql>,
|
||||
{
|
||||
sqlx::query_as!(
|
||||
SessionMediaFileListItem,
|
||||
r#"
|
||||
SELECT
|
||||
m.id as `id: u64`,
|
||||
m.token as `token: tokens::tokens::media_files::MediaFileToken`,
|
||||
|
||||
m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,
|
||||
m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,
|
||||
|
||||
m.public_bucket_directory_hash,
|
||||
m.maybe_public_bucket_prefix,
|
||||
m.maybe_public_bucket_extension,
|
||||
|
||||
users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
|
||||
users.username as maybe_creator_username,
|
||||
users.display_name as maybe_creator_display_name,
|
||||
users.email_gravatar_hash as maybe_creator_gravatar_hash,
|
||||
|
||||
media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,
|
||||
media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,
|
||||
media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,
|
||||
|
||||
m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
|
||||
|
||||
m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,
|
||||
m.maybe_title,
|
||||
|
||||
m.created_at,
|
||||
m.updated_at
|
||||
|
||||
FROM media_files AS m FORCE INDEX (fk_maybe_creator_user_token)
|
||||
LEFT OUTER JOIN users
|
||||
ON m.maybe_creator_user_token = users.token
|
||||
LEFT OUTER JOIN media_files as media_file_cover_image
|
||||
ON media_file_cover_image.token = m.maybe_cover_image_media_file_token
|
||||
WHERE
|
||||
m.maybe_creator_user_token = ?
|
||||
AND m.media_class = ?
|
||||
AND m.user_deleted_at IS NULL
|
||||
AND m.mod_deleted_at IS NULL
|
||||
AND NOT m.is_intermediate_system_file
|
||||
AND m.id < ?
|
||||
ORDER BY m.id DESC
|
||||
LIMIT ?
|
||||
"#,
|
||||
user_token,
|
||||
media_class.to_str(),
|
||||
cursor_id,
|
||||
limit,
|
||||
)
|
||||
.fetch_all(mysql_executor)
|
||||
.await
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
|
||||
/// The common media-file row shape shared by list queries (folder
|
||||
/// listings, tag listings, etc.): the file's own metadata plus the
|
||||
/// joined cover-image bucket fields the endpoints need to materialize
|
||||
/// MediaLinks, MediaFileCoverImageDetails, and the rest of the typed
|
||||
/// response shape.
|
||||
///
|
||||
/// Queries can `sqlx::query_as!` directly into this struct when they
|
||||
/// select exactly these columns (alias `media_files.id` to
|
||||
/// `media_file_id`, and the cover LEFT JOIN fields to the
|
||||
/// `maybe_cover_*` names).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaFileListRow {
|
||||
/// `media_files.id` — used as the pagination cursor.
|
||||
pub media_file_id: u64,
|
||||
|
||||
pub media_file_token: MediaFileToken,
|
||||
pub media_class: MediaFileClass,
|
||||
pub media_type: MediaFileType,
|
||||
|
||||
pub maybe_batch_token: Option<BatchGenerationToken>,
|
||||
|
||||
// The media file's own bucket fields (caller uses these to build MediaLinks).
|
||||
pub public_bucket_directory_hash: String,
|
||||
pub maybe_public_bucket_prefix: Option<String>,
|
||||
pub maybe_public_bucket_extension: Option<String>,
|
||||
|
||||
// Cover image's bucket fields, from a LEFT JOIN on
|
||||
// mf.maybe_cover_image_media_file_token. All three are Some-or-None
|
||||
// together (matched / unmatched join row).
|
||||
pub maybe_cover_public_bucket_directory_hash: Option<String>,
|
||||
pub maybe_cover_public_bucket_prefix: Option<String>,
|
||||
pub maybe_cover_public_bucket_extension: Option<String>,
|
||||
|
||||
pub creator_set_visibility: Visibility,
|
||||
pub is_user_upload: bool,
|
||||
|
||||
pub maybe_title: Option<String>,
|
||||
pub maybe_prompt_token: Option<PromptToken>,
|
||||
pub maybe_origin_filename: Option<String>,
|
||||
|
||||
pub maybe_duration_millis: Option<i32>,
|
||||
pub maybe_frame_width: Option<i32>,
|
||||
pub maybe_frame_height: Option<i32>,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -4,6 +4,4 @@ pub mod list_media_files;
|
||||
pub mod list_media_files_by_batch_token;
|
||||
pub mod list_media_files_for_elastic_search_backfill_using_cursor;
|
||||
pub mod list_media_files_for_user;
|
||||
pub mod list_session_media_files_by_class;
|
||||
pub mod list_session_project_media_files;
|
||||
pub mod media_file_list_row;
|
||||
|
||||
@@ -32,7 +32,6 @@ pub mod prompts;
|
||||
pub mod public_event_feed;
|
||||
pub mod staff_audit_logs;
|
||||
pub mod stats;
|
||||
pub mod tags;
|
||||
pub mod trending_model_analytics;
|
||||
pub mod tts;
|
||||
pub mod twitch;
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
|
||||
pub struct GetFirstImagerefForPromptArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub prompt_token: &'e PromptToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Find the first (oldest-attached) non-deleted image reference attached to a
|
||||
/// prompt. Used as a cover-image fallback for mesh/splat generations whose
|
||||
/// webhook payload carries no thumbnail: the image the user generated the
|
||||
/// model from stands in as the cover.
|
||||
pub async fn get_first_imageref_for_prompt<'e, 'c: 'e, E>(
|
||||
args: GetFirstImagerefForPromptArgs<'e, 'c, E>,
|
||||
) -> Result<Option<MediaFileToken>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
pci.media_token as `media_token: MediaFileToken`
|
||||
FROM prompt_context_items pci
|
||||
JOIN media_files m
|
||||
ON pci.media_token = m.token
|
||||
WHERE pci.prompt_token = ?
|
||||
AND m.media_class = 'image'
|
||||
AND m.user_deleted_at IS NULL
|
||||
AND m.mod_deleted_at IS NULL
|
||||
ORDER BY pci.id ASC
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.prompt_token.as_str(),
|
||||
)
|
||||
.fetch_optional(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| r.media_token))
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod batch_list_prompt_context_items;
|
||||
pub mod get_first_imageref_for_prompt;
|
||||
pub mod insert_batch_prompt_context_items;
|
||||
pub mod list_prompt_context_items;
|
||||
pub mod list_prompt_context_items;
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql, QueryBuilder, Row};
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::tags::TagToken;
|
||||
|
||||
/// One (media file, tag) pairing from the bulk lookup. The same tag can
|
||||
/// appear under many media files.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaFileTagPairRow {
|
||||
pub media_file_token: MediaFileToken,
|
||||
pub tag_token: TagToken,
|
||||
pub tag_value: String,
|
||||
pub tag_value_lowercase: String,
|
||||
pub use_count: u32,
|
||||
}
|
||||
|
||||
pub struct BulkListTagsForMediaFilesArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub media_file_tokens: &'e [MediaFileToken],
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// All (live) tags on each of the given media files, in one round-trip.
|
||||
/// Media files with no tags simply produce no rows. Not scoped to any
|
||||
/// user: per-file tags are publicly visible.
|
||||
pub async fn bulk_list_tags_for_media_files<'e, 'c: 'e, E>(
|
||||
args: BulkListTagsForMediaFilesArgs<'e, 'c, E>,
|
||||
) -> Result<Vec<MediaFileTagPairRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if args.media_file_tokens.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut builder = QueryBuilder::<MySql>::new(
|
||||
"SELECT mft.media_file_token, t.token, t.tag_value, t.tag_value_lowercase, t.use_count \
|
||||
FROM media_file_tags mft \
|
||||
JOIN tags t ON t.token = mft.tag_token \
|
||||
WHERE t.maybe_deleted_at IS NULL AND mft.media_file_token IN (",
|
||||
);
|
||||
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.media_file_tokens {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
let rows = builder.build().fetch_all(args.mysql_executor).await?;
|
||||
|
||||
Ok(rows.into_iter()
|
||||
.map(|row| MediaFileTagPairRow {
|
||||
media_file_token: MediaFileToken::new(row.get::<String, _>(0)),
|
||||
tag_token: TagToken::new(row.get::<String, _>(1)),
|
||||
tag_value: row.get::<String, _>(2),
|
||||
tag_value_lowercase: row.get::<String, _>(3),
|
||||
use_count: row.get::<u32, _>(4),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql, QueryBuilder};
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct ClearMediaFileTagsArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub media_file_tokens: &'e [MediaFileToken],
|
||||
pub user_token: &'e UserToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Hard-delete ALL of the user's tag links on the given media files.
|
||||
/// Backs the clear endpoint, and the set endpoints when the requested
|
||||
/// tag set is empty. Returns the number of links deleted. (Tags orphaned
|
||||
/// by this are not deleted.)
|
||||
pub async fn clear_media_file_tags<'e, 'c: 'e, E>(
|
||||
args: ClearMediaFileTagsArgs<'e, 'c, E>,
|
||||
) -> Result<u64, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if args.media_file_tokens.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut builder = QueryBuilder::<MySql>::new(
|
||||
"DELETE FROM media_file_tags WHERE user_token = ",
|
||||
);
|
||||
builder.push_bind(args.user_token.as_str());
|
||||
|
||||
builder.push(" AND media_file_token IN (");
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.media_file_tokens {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
let result = builder.build().execute(args.mysql_executor).await?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql, QueryBuilder};
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
|
||||
pub struct DeleteLinksToSoftDeletedTagsArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub tag_tokens: &'e [TagToken],
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Remove any `media_file_tags` links pointing at tags that are
|
||||
/// soft-deleted, among the given tag tokens. Guards the apply-tags
|
||||
/// transaction against a concurrent tag delete: the DELETE is a locking
|
||||
/// read, so unlike the transaction's snapshot SELECTs it observes a
|
||||
/// just-committed soft-delete and cleans up links the snapshot happily
|
||||
/// inserted. Cheap — an IN-list probe on `index_tag_token` plus a
|
||||
/// primary-key lookup per link.
|
||||
pub async fn delete_links_to_soft_deleted_tags<'e, 'c: 'e, E>(
|
||||
args: DeleteLinksToSoftDeletedTagsArgs<'e, 'c, E>,
|
||||
) -> Result<u64, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if args.tag_tokens.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut builder = QueryBuilder::<MySql>::new(
|
||||
"DELETE media_file_tags FROM media_file_tags \
|
||||
JOIN tags ON tags.token = media_file_tags.tag_token \
|
||||
WHERE tags.maybe_deleted_at IS NOT NULL \
|
||||
AND media_file_tags.tag_token IN (",
|
||||
);
|
||||
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.tag_tokens {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
let result = builder.build().execute(args.mysql_executor).await?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
|
||||
pub struct DeleteMediaFileTagsForTagArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub tag_token: &'e TagToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Hard-delete every media-file link for a tag, across all media files.
|
||||
/// Used when the tag itself is being deleted. Returns the number of
|
||||
/// links deleted.
|
||||
pub async fn delete_media_file_tags_for_tag<'e, 'c: 'e, E>(
|
||||
args: DeleteMediaFileTagsForTagArgs<'e, 'c, E>,
|
||||
) -> Result<u64, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"DELETE FROM media_file_tags WHERE tag_token = ?"#,
|
||||
args.tag_token.as_str(),
|
||||
)
|
||||
.execute(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql, QueryBuilder};
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::tags::TagToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct DeleteMediaFileTagsNotInSetArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub media_file_tokens: &'e [MediaFileToken],
|
||||
pub keep_tag_tokens: &'e [TagToken],
|
||||
pub user_token: &'e UserToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Hard-delete the user's tag links on the given media files, EXCEPT
|
||||
/// links whose tag is in `keep_tag_tokens`. This is the "remove what
|
||||
/// wasn't mentioned" half of the set-tags endpoints. Returns the number
|
||||
/// of links deleted. (Tags orphaned by this are not deleted.)
|
||||
///
|
||||
/// `keep_tag_tokens` must be non-empty — to remove everything, use
|
||||
/// `clear_media_file_tags` instead (an empty NOT IN list is invalid SQL).
|
||||
pub async fn delete_media_file_tags_not_in_set<'e, 'c: 'e, E>(
|
||||
args: DeleteMediaFileTagsNotInSetArgs<'e, 'c, E>,
|
||||
) -> Result<u64, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if args.media_file_tokens.is_empty() || args.keep_tag_tokens.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut builder = QueryBuilder::<MySql>::new(
|
||||
"DELETE FROM media_file_tags WHERE user_token = ",
|
||||
);
|
||||
builder.push_bind(args.user_token.as_str());
|
||||
|
||||
builder.push(" AND media_file_token IN (");
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.media_file_tokens {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
builder.push(" AND tag_token NOT IN (");
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.keep_tag_tokens {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
let result = builder.build().execute(args.mysql_executor).await?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql, QueryBuilder, Row};
|
||||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct FilterOwnedMediaFileTokensArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub candidate_tokens: &'e [MediaFileToken],
|
||||
pub owner_user_token: &'e UserToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Return only the input tokens that exist, aren't soft-deleted, and are
|
||||
/// owned (created) by the given user. Tagging endpoints run their inputs
|
||||
/// through this — for now only a media file's creator may tag it.
|
||||
pub async fn filter_owned_media_file_tokens<'e, 'c: 'e, E>(
|
||||
args: FilterOwnedMediaFileTokensArgs<'e, 'c, E>,
|
||||
) -> Result<Vec<MediaFileToken>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if args.candidate_tokens.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut builder = QueryBuilder::<MySql>::new(
|
||||
"SELECT token FROM media_files \
|
||||
WHERE maybe_creator_user_token = ",
|
||||
);
|
||||
builder.push_bind(args.owner_user_token.as_str());
|
||||
builder.push(
|
||||
" AND user_deleted_at IS NULL AND mod_deleted_at IS NULL AND token IN (",
|
||||
);
|
||||
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.candidate_tokens {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
let rows = builder.build().fetch_all(args.mysql_executor).await?;
|
||||
|
||||
Ok(rows.into_iter()
|
||||
.map(|row| MediaFileToken::new(row.get::<String, _>(0)))
|
||||
.collect())
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql, QueryBuilder, Row};
|
||||
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct FilterVisibleMediaFileTokensArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub candidate_tokens: &'e [MediaFileToken],
|
||||
pub requester_user_token: &'e UserToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Return only the input tokens whose media file exists, isn't
|
||||
/// soft-deleted, and whose tags the requester may see: any non-private
|
||||
/// file (public and hidden files are viewable by anyone with the URL),
|
||||
/// or the requester's own files.
|
||||
pub async fn filter_visible_media_file_tokens<'e, 'c: 'e, E>(
|
||||
args: FilterVisibleMediaFileTokensArgs<'e, 'c, E>,
|
||||
) -> Result<Vec<MediaFileToken>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
if args.candidate_tokens.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut builder = QueryBuilder::<MySql>::new(
|
||||
"SELECT token FROM media_files \
|
||||
WHERE user_deleted_at IS NULL \
|
||||
AND mod_deleted_at IS NULL \
|
||||
AND (creator_set_visibility != ",
|
||||
);
|
||||
builder.push_bind(Visibility::Private.to_str());
|
||||
builder.push(" OR maybe_creator_user_token = ");
|
||||
builder.push_bind(args.requester_user_token.as_str());
|
||||
builder.push(") AND token IN (");
|
||||
|
||||
let mut separated = builder.separated(", ");
|
||||
for token in args.candidate_tokens {
|
||||
separated.push_bind(token.as_str());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
|
||||
let rows = builder.build().fetch_all(args.mysql_executor).await?;
|
||||
|
||||
Ok(rows.into_iter()
|
||||
.map(|row| MediaFileToken::new(row.get::<String, _>(0)))
|
||||
.collect())
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use enums::common::visibility::Visibility;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// Just enough of a media file to decide whether a requester may see
|
||||
/// its tags.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaFileAccessFields {
|
||||
pub maybe_creator_user_token: Option<UserToken>,
|
||||
pub creator_set_visibility: Visibility,
|
||||
}
|
||||
|
||||
pub struct GetMediaFileAccessFieldsArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub media_file_token: &'e MediaFileToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Fetch the owner + visibility of a (live) media file. Returns
|
||||
/// `Ok(None)` if the file doesn't exist or is soft-deleted.
|
||||
pub async fn get_media_file_access_fields<'e, 'c: 'e, E>(
|
||||
args: GetMediaFileAccessFieldsArgs<'e, 'c, E>,
|
||||
) -> Result<Option<MediaFileAccessFields>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
maybe_creator_user_token as `maybe_creator_user_token: UserToken`,
|
||||
creator_set_visibility as `creator_set_visibility: Visibility`
|
||||
FROM media_files
|
||||
WHERE token = ?
|
||||
AND user_deleted_at IS NULL
|
||||
AND mod_deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.media_file_token.as_str(),
|
||||
)
|
||||
.fetch_optional(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| MediaFileAccessFields {
|
||||
maybe_creator_user_token: r.maybe_creator_user_token,
|
||||
creator_set_visibility: r.creator_set_visibility,
|
||||
}))
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct GetSoftDeletedTagByLowercaseArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub tag_value_lowercase: &'e str,
|
||||
pub creator_user_token: &'e UserToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Find a user's SOFT-DELETED tag by its lowercased value. Soft-deleted
|
||||
/// rows still occupy the `(tag_value_lowercase, creator_user_token)`
|
||||
/// unique key, so a rename onto that value must purge the dead row
|
||||
/// first — this lookup feeds that purge.
|
||||
pub async fn get_soft_deleted_tag_by_lowercase<'e, 'c: 'e, E>(
|
||||
args: GetSoftDeletedTagByLowercaseArgs<'e, 'c, E>,
|
||||
) -> Result<Option<TagToken>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT token as `token: TagToken`
|
||||
FROM tags
|
||||
WHERE creator_user_token = ?
|
||||
AND tag_value_lowercase = ?
|
||||
AND maybe_deleted_at IS NOT NULL
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.creator_user_token.as_str(),
|
||||
args.tag_value_lowercase,
|
||||
)
|
||||
.fetch_optional(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| r.token))
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::tags::TagToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::tags::tag_row::TagRow;
|
||||
|
||||
pub struct GetTagForOwnerArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub tag_token: &'e TagToken,
|
||||
pub creator_user_token: &'e UserToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Fetch a single (live) tag by token, scoped to its creator. Returns
|
||||
/// `Ok(None)` if the tag doesn't exist, is soft-deleted, or belongs to a
|
||||
/// different user (don't leak existence of others' tags).
|
||||
pub async fn get_tag_for_owner<'e, 'c: 'e, E>(
|
||||
args: GetTagForOwnerArgs<'e, 'c, E>,
|
||||
) -> Result<Option<TagRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
id as `id: u64`,
|
||||
token as `token: TagToken`,
|
||||
tag_value,
|
||||
tag_value_lowercase,
|
||||
use_count as `use_count: u32`
|
||||
FROM tags
|
||||
WHERE token = ?
|
||||
AND creator_user_token = ?
|
||||
AND maybe_deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.tag_token.as_str(),
|
||||
args.creator_user_token.as_str(),
|
||||
)
|
||||
.fetch_optional(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| TagRow {
|
||||
id: r.id,
|
||||
token: r.token,
|
||||
tag_value: r.tag_value,
|
||||
tag_value_lowercase: r.tag_value_lowercase,
|
||||
use_count: r.use_count,
|
||||
}))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user