From 89e1ab097d9b3b109eb09a8b462628363f949db6 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 14 Jan 2026 22:55:40 -0600 Subject: [PATCH] refactor: cleanup struct fields, resolve clippy lints, resolve/ignore svelte lints, checks error on warn --- ...00082144745cbe3c5d214bf837a17acfd336.json} | 10 +- ...75a3462f33033e68a091d770d22acc39e4c0.json} | 12 +- ...f2dad6a07e7b46d7c546fa10e2568a35e710d.json | 105 --------------- ...080bfd1c7128c79b86be1b94169b563e4836.json} | 10 +- ...8b5cc8d294b1529d7566ddf3b607b0c3e47f.json} | 10 +- ...f67332706bbb21096311f8ae015c4c5ea46e.json} | 42 +----- ...52f91c2bb0a2cd2025366ac2af0cb0717e7d5.json | 52 -------- ...950532dfdc030d8f4b4d7c59a624f23b41d7.json} | 42 +----- ...b5379b4435e6222d018d385d932a038cc9de.json} | 10 +- ...84ac90072472d4c309b2080d1f104f26dd0d.json} | 42 +----- ...d335dc02fdf3a8a2a9eb39fc9bfa612dfa8f.json} | 16 +-- ...8914863e2a8c407eed4d9dd5d73909330f7d.json} | 12 +- ...58d537ec59a1f569ecb03c52be96e0fdfdfc.json} | 10 +- ...35a1605065f136bf350b030d1b86d3cc92b37.json | 22 --- ...789d9cb6085d3338d3c18eb78b86d320a34a.json} | 12 +- ...355d86116211aead48f44626fb5d066df41d.json} | 28 +--- ...d995a6d36a0e16c3156eaba7c48393a37fa2.json} | 10 +- ...c6fbcabf90d2627d194c3fb23f80c24dcc57a.json | 22 --- ...0b5112c8a5d126c3a735b3954e65da3082ce.json} | 10 +- ...09b450be4ff3541dc2dd8a9d1da45ef25883.json} | 12 +- ...70279265d0cc5bbd2e4bcdcecb17b8998338.json} | 16 +-- ...d83fc4a68d4917721cbca149bc841b39a9481.json | 50 ------- ...48319ecc5fba284a5a76778f2faf55e8cc12.json} | 10 +- ...796eee9a786e2ca01d339fd0e8356f8ad3824.json | 14 -- ...23b64bb20bbf58ef18acc37046b34996cefd.json} | 10 +- ...a1edf3176e94179b176c43f0445f7ea51656.json} | 28 +--- ...c760ae1efc041d1acd5c3462b96a9236dd3dc.json | 94 ------------- Justfile | 4 +- src/cache.rs | 48 ------- src/cli/api/auth.rs | 18 ++- src/cli/api/tags.rs | 17 +-- src/cli/mod.rs | 2 +- src/db/media.rs | 126 +++++------------- src/db/projects.rs | 58 ++------ src/db/settings.rs | 14 +- src/db/tags.rs | 100 ++------------ src/github.rs | 2 +- src/handlers/assets.rs | 14 +- src/handlers/auth.rs | 11 +- src/handlers/tags.rs | 44 +++--- src/http.rs | 5 - src/main.rs | 2 +- src/media_processing.rs | 3 - src/proxy.rs | 105 +++++++-------- src/r2.rs | 21 --- src/state.rs | 2 - src/utils.rs | 54 ++++---- web/src/lib/components/Clouds.svelte | 1 + web/src/routes/admin/tags/+page.svelte | 17 ++- web/src/routes/admin/tags/[slug]/+page.svelte | 11 +- 50 files changed, 276 insertions(+), 1114 deletions(-) rename .sqlx/{query-b7a265d9e8e14728f9e6b469d52c7c236aa9113b4d95aee0b1318e5a9f1bb4f1.json => query-117fa54d46f5faea8625a3d4a9a700082144745cbe3c5d214bf837a17acfd336.json} (65%) rename .sqlx/{query-8cbc29e0bd4d323907b8d47196ef9193ded06c170ce5646cbdc9fbeb3f842869.json => query-1b154e5d7859553178aeefbd02a975a3462f33033e68a091d770d22acc39e4c0.json} (67%) delete mode 100644 .sqlx/query-2697c981355b4a1d46699aa5fe0f2dad6a07e7b46d7c546fa10e2568a35e710d.json rename .sqlx/{query-4a2a394398d4597d2366023baad3327cc50ed2c914abe1293644cc860df74d34.json => query-32037afd58945a43daa21fc369a6080bfd1c7128c79b86be1b94169b563e4836.json} (82%) rename .sqlx/{query-170d08a3b1effa554fe831e43a8d7b640fbddaa348289360fb520057c0ef3272.json => query-347673cd07409cc8bfc4f532d0918b5cc8d294b1529d7566ddf3b607b0c3e47f.json} (81%) rename .sqlx/{query-a9bd8fffc6963610443422abcc07352c88f6ccf65a51b3088ed21648c2b9c193.json => query-3711799242c81652506593cf046bf67332706bbb21096311f8ae015c4c5ea46e.json} (68%) delete mode 100644 .sqlx/query-46df2723810fa84cd1c2f228b5052f91c2bb0a2cd2025366ac2af0cb0717e7d5.json rename .sqlx/{query-9d0e8c98364de65920482389d7f1699ae4710394ed27b472d4e33190cdc0bd19.json => query-486e18360f7e1b0ffeda90defab5950532dfdc030d8f4b4d7c59a624f23b41d7.json} (58%) rename .sqlx/{query-538842bf07a2ac3fca612413e5a8e10769b2fc4d90d463040911f416314a42ac.json => query-4ba4e68c3c71210bf020102d9ff5b5379b4435e6222d018d385d932a038cc9de.json} (90%) rename .sqlx/{query-54b5eb8bf65df8dd3caa1a1fcac9cf71cb9c665ac8d3b86dd63a9692788f7392.json => query-62a61815450f395f31570d77194084ac90072472d4c309b2080d1f104f26dd0d.json} (57%) rename .sqlx/{query-8f08a65d180757f0fcbe65a43e3a2e3fdd95cc1aba80eb40c1042af7301d0fa9.json => query-6b3d5778e254ef9c28d0dbd76c06d335dc02fdf3a8a2a9eb39fc9bfa612dfa8f.json} (75%) rename .sqlx/{query-99a63a1f9b888490418aa4e81f5c185e8de55044321c5f85883610e40e51d286.json => query-70c83f74cbefaf822a238f970f248914863e2a8c407eed4d9dd5d73909330f7d.json} (77%) rename .sqlx/{query-82c27809a4e88b594c41b65c573c4c28aeceba2cc20b1c16cbdfb8c7ea4b23c0.json => query-73407c6dbd8dc1ce39c64ced72cf58d537ec59a1f569ecb03c52be96e0fdfdfc.json} (65%) delete mode 100644 .sqlx/query-778619a2d55b7e311f9d8bfd75335a1605065f136bf350b030d1b86d3cc92b37.json rename .sqlx/{query-7221aa294be3a27520a25739ebf5d4d8a57ac35b7b04be94027a33b4bf845195.json => query-88d45f0f8d9d92b7ff775561c7a7789d9cb6085d3338d3c18eb78b86d320a34a.json} (76%) rename .sqlx/{query-2ed3555fc0cfe71b8a59c89f6d3fea53458b995f342d623f7ab43824392973c9.json => query-9a1787fbc71a970af3a6dbcd1b4e355d86116211aead48f44626fb5d066df41d.json} (59%) rename .sqlx/{query-8302d5621d743bd3e5e2a029e30ad1e017e1f2ef1f9cb09aa924436b0c6b8c22.json => query-a2c5034b43cd20676e01d9471ce1d995a6d36a0e16c3156eaba7c48393a37fa2.json} (82%) delete mode 100644 .sqlx/query-a55f7d64fc0f41f150663aae82ac6fbcabf90d2627d194c3fb23f80c24dcc57a.json rename .sqlx/{query-3401838a054f29a17d96086529b7222026aafa8a385615129eb0d407e44faa9c.json => query-a7d7a4069df7b453d5cab42da7040b5112c8a5d126c3a735b3954e65da3082ce.json} (81%) rename .sqlx/{query-3f662485c3beb37febc746c49a9bdd6c99e08b19add7b9872068cc960afcd7c8.json => query-cc34a13eda021eb1dcdb6f49eb1909b450be4ff3541dc2dd8a9d1da45ef25883.json} (67%) rename .sqlx/{query-9e55ffc9d9138ea02678ff432db47488da976516f53b53d7a0bd36665b39b628.json => query-d2c1348ab4e37c89c815e3cb916070279265d0cc5bbd2e4bcdcecb17b8998338.json} (68%) delete mode 100644 .sqlx/query-d50291c2483aa95fa6a734d69dcd83fc4a68d4917721cbca149bc841b39a9481.json rename .sqlx/{query-fdd7f3743ad2e6d1d571ee2d3dbd8b4d49074ef2cbe74dc79c6f4f638bdd3a88.json => query-d631f4abcaa1be60499ccedd219948319ecc5fba284a5a76778f2faf55e8cc12.json} (89%) delete mode 100644 .sqlx/query-dd0d0e3fd03f130aab947d13580796eee9a786e2ca01d339fd0e8356f8ad3824.json rename .sqlx/{query-d851d8d7117d3cfcd8c50304d60565cd2cc275e2776929d660f9351dad5b418f.json => query-e35380efe500836a7a7bbcfcd54023b64bb20bbf58ef18acc37046b34996cefd.json} (81%) rename .sqlx/{query-f45329eab47a84c6c921f4e78c443835135fa095f16a7a0a9080249c4136fc7d.json => query-f83560f1e54983a8b9f96e12423ca1edf3176e94179b176c43f0445f7ea51656.json} (50%) delete mode 100644 .sqlx/query-f97c44ba8b156a2f97cdbc240d1c760ae1efc041d1acd5c3462b96a9236dd3dc.json diff --git a/.sqlx/query-b7a265d9e8e14728f9e6b469d52c7c236aa9113b4d95aee0b1318e5a9f1bb4f1.json b/.sqlx/query-117fa54d46f5faea8625a3d4a9a700082144745cbe3c5d214bf837a17acfd336.json similarity index 65% rename from .sqlx/query-b7a265d9e8e14728f9e6b469d52c7c236aa9113b4d95aee0b1318e5a9f1bb4f1.json rename to .sqlx/query-117fa54d46f5faea8625a3d4a9a700082144745cbe3c5d214bf837a17acfd336.json index 3219e19..b777604 100644 --- a/.sqlx/query-b7a265d9e8e14728f9e6b469d52c7c236aa9113b4d95aee0b1318e5a9f1bb4f1.json +++ b/.sqlx/query-117fa54d46f5faea8625a3d4a9a700082144745cbe3c5d214bf837a17acfd336.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n t.id, \n t.slug, \n t.name,\n t.icon,\n t.color,\n t.created_at,\n tc.count\n FROM tag_cooccurrence tc\n JOIN tags t ON (tc.tag_a = t.id OR tc.tag_b = t.id)\n WHERE (tc.tag_a = $1 OR tc.tag_b = $1) AND t.id != $1\n ORDER BY tc.count DESC, t.name ASC\n LIMIT $2\n ", + "query": "\n SELECT \n t.id, \n t.slug, \n t.name,\n t.icon,\n t.color,\n tc.count\n FROM tag_cooccurrence tc\n JOIN tags t ON (tc.tag_a = t.id OR tc.tag_b = t.id)\n WHERE (tc.tag_a = $1 OR tc.tag_b = $1) AND t.id != $1\n ORDER BY tc.count DESC, t.name ASC\n LIMIT $2\n ", "describe": { "columns": [ { @@ -30,11 +30,6 @@ }, { "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 6, "name": "count", "type_info": "Int4" } @@ -51,9 +46,8 @@ false, true, true, - false, false ] }, - "hash": "b7a265d9e8e14728f9e6b469d52c7c236aa9113b4d95aee0b1318e5a9f1bb4f1" + "hash": "117fa54d46f5faea8625a3d4a9a700082144745cbe3c5d214bf837a17acfd336" } diff --git a/.sqlx/query-8cbc29e0bd4d323907b8d47196ef9193ded06c170ce5646cbdc9fbeb3f842869.json b/.sqlx/query-1b154e5d7859553178aeefbd02a975a3462f33033e68a091d770d22acc39e4c0.json similarity index 67% rename from .sqlx/query-8cbc29e0bd4d323907b8d47196ef9193ded06c170ce5646cbdc9fbeb3f842869.json rename to .sqlx/query-1b154e5d7859553178aeefbd02a975a3462f33033e68a091d770d22acc39e4c0.json index c02b79f..ffe3dee 100644 --- a/.sqlx/query-8cbc29e0bd4d323907b8d47196ef9193ded06c170ce5646cbdc9fbeb3f842869.json +++ b/.sqlx/query-1b154e5d7859553178aeefbd02a975a3462f33033e68a091d770d22acc39e4c0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, slug, name, icon, color, created_at\n FROM tags\n WHERE slug = $1\n ", + "query": "\n SELECT id, slug, name, icon, color\n FROM tags\n WHERE slug = $1\n ", "describe": { "columns": [ { @@ -27,11 +27,6 @@ "ordinal": 4, "name": "color", "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -44,9 +39,8 @@ false, false, true, - true, - false + true ] }, - "hash": "8cbc29e0bd4d323907b8d47196ef9193ded06c170ce5646cbdc9fbeb3f842869" + "hash": "1b154e5d7859553178aeefbd02a975a3462f33033e68a091d770d22acc39e4c0" } diff --git a/.sqlx/query-2697c981355b4a1d46699aa5fe0f2dad6a07e7b46d7c546fa10e2568a35e710d.json b/.sqlx/query-2697c981355b4a1d46699aa5fe0f2dad6a07e7b46d7c546fa10e2568a35e710d.json deleted file mode 100644 index a7ef4a7..0000000 --- a/.sqlx/query-2697c981355b4a1d46699aa5fe0f2dad6a07e7b46d7c546fa10e2568a35e710d.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE project_media\n SET metadata = $2\n WHERE id = $1\n RETURNING \n id,\n project_id,\n display_order,\n media_type as \"media_type: MediaType\",\n original_filename,\n r2_base_path,\n variants,\n width,\n height,\n size_bytes,\n blurhash,\n metadata,\n created_at\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "project_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "display_order", - "type_info": "Int4" - }, - { - "ordinal": 3, - "name": "media_type: MediaType", - "type_info": { - "Custom": { - "name": "media_type", - "kind": { - "Enum": [ - "image", - "video" - ] - } - } - } - }, - { - "ordinal": 4, - "name": "original_filename", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "r2_base_path", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "variants", - "type_info": "Jsonb" - }, - { - "ordinal": 7, - "name": "width", - "type_info": "Int4" - }, - { - "ordinal": 8, - "name": "height", - "type_info": "Int4" - }, - { - "ordinal": 9, - "name": "size_bytes", - "type_info": "Int8" - }, - { - "ordinal": 10, - "name": "blurhash", - "type_info": "Text" - }, - { - "ordinal": 11, - "name": "metadata", - "type_info": "Jsonb" - }, - { - "ordinal": 12, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Jsonb" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - true, - true, - false, - true, - true, - false - ] - }, - "hash": "2697c981355b4a1d46699aa5fe0f2dad6a07e7b46d7c546fa10e2568a35e710d" -} diff --git a/.sqlx/query-4a2a394398d4597d2366023baad3327cc50ed2c914abe1293644cc860df74d34.json b/.sqlx/query-32037afd58945a43daa21fc369a6080bfd1c7128c79b86be1b94169b563e4836.json similarity index 82% rename from .sqlx/query-4a2a394398d4597d2366023baad3327cc50ed2c914abe1293644cc860df74d34.json rename to .sqlx/query-32037afd58945a43daa21fc369a6080bfd1c7128c79b86be1b94169b563e4836.json index bf4c4c5..6f539db 100644 --- a/.sqlx/query-4a2a394398d4597d2366023baad3327cc50ed2c914abe1293644cc860df74d34.json +++ b/.sqlx/query-32037afd58945a43daa21fc369a6080bfd1c7128c79b86be1b94169b563e4836.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n last_github_activity,\n created_at,\n updated_at\n FROM projects\n ORDER BY COALESCE(last_github_activity, created_at) DESC\n ", + "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n last_github_activity,\n created_at\n FROM projects\n ORDER BY COALESCE(last_github_activity, created_at) DESC\n ", "describe": { "columns": [ { @@ -64,11 +64,6 @@ "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -84,9 +79,8 @@ true, true, true, - false, false ] }, - "hash": "4a2a394398d4597d2366023baad3327cc50ed2c914abe1293644cc860df74d34" + "hash": "32037afd58945a43daa21fc369a6080bfd1c7128c79b86be1b94169b563e4836" } diff --git a/.sqlx/query-170d08a3b1effa554fe831e43a8d7b640fbddaa348289360fb520057c0ef3272.json b/.sqlx/query-347673cd07409cc8bfc4f532d0918b5cc8d294b1529d7566ddf3b607b0c3e47f.json similarity index 81% rename from .sqlx/query-170d08a3b1effa554fe831e43a8d7b640fbddaa348289360fb520057c0ef3272.json rename to .sqlx/query-347673cd07409cc8bfc4f532d0918b5cc8d294b1529d7566ddf3b607b0c3e47f.json index 44302f6..04906e3 100644 --- a/.sqlx/query-170d08a3b1effa554fe831e43a8d7b640fbddaa348289360fb520057c0ef3272.json +++ b/.sqlx/query-347673cd07409cc8bfc4f532d0918b5cc8d294b1529d7566ddf3b607b0c3e47f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n\n last_github_activity,\n created_at,\n updated_at\n FROM projects\n WHERE id = $1\n ", + "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n last_github_activity,\n created_at\n FROM projects\n WHERE id = $1\n ", "describe": { "columns": [ { @@ -64,11 +64,6 @@ "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -86,9 +81,8 @@ true, true, true, - false, false ] }, - "hash": "170d08a3b1effa554fe831e43a8d7b640fbddaa348289360fb520057c0ef3272" + "hash": "347673cd07409cc8bfc4f532d0918b5cc8d294b1529d7566ddf3b607b0c3e47f" } diff --git a/.sqlx/query-a9bd8fffc6963610443422abcc07352c88f6ccf65a51b3088ed21648c2b9c193.json b/.sqlx/query-3711799242c81652506593cf046bf67332706bbb21096311f8ae015c4c5ea46e.json similarity index 68% rename from .sqlx/query-a9bd8fffc6963610443422abcc07352c88f6ccf65a51b3088ed21648c2b9c193.json rename to .sqlx/query-3711799242c81652506593cf046bf67332706bbb21096311f8ae015c4c5ea46e.json index 5abcf39..7fc719c 100644 --- a/.sqlx/query-a9bd8fffc6963610443422abcc07352c88f6ccf65a51b3088ed21648c2b9c193.json +++ b/.sqlx/query-3711799242c81652506593cf046bf67332706bbb21096311f8ae015c4c5ea46e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO project_media (\n project_id, display_order, media_type, original_filename,\n r2_base_path, variants, width, height, size_bytes, blurhash, metadata\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\n RETURNING \n id,\n project_id,\n display_order,\n media_type as \"media_type: MediaType\",\n original_filename,\n r2_base_path,\n variants,\n width,\n height,\n size_bytes,\n blurhash,\n metadata,\n created_at\n ", + "query": "\n INSERT INTO project_media (\n project_id, display_order, media_type, original_filename,\n r2_base_path, variants, width, height, size_bytes, blurhash, metadata\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\n RETURNING \n id,\n project_id,\n display_order,\n media_type as \"media_type: MediaType\",\n r2_base_path,\n variants,\n blurhash,\n metadata\n ", "describe": { "columns": [ { @@ -35,48 +35,23 @@ }, { "ordinal": 4, - "name": "original_filename", - "type_info": "Text" - }, - { - "ordinal": 5, "name": "r2_base_path", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "variants", "type_info": "Jsonb" }, { - "ordinal": 7, - "name": "width", - "type_info": "Int4" - }, - { - "ordinal": 8, - "name": "height", - "type_info": "Int4" - }, - { - "ordinal": 9, - "name": "size_bytes", - "type_info": "Int8" - }, - { - "ordinal": 10, + "ordinal": 6, "name": "blurhash", "type_info": "Text" }, { - "ordinal": 11, + "ordinal": 7, "name": "metadata", "type_info": "Jsonb" - }, - { - "ordinal": 12, - "name": "created_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -111,14 +86,9 @@ false, false, false, - false, true, - true, - false, - true, - true, - false + true ] }, - "hash": "a9bd8fffc6963610443422abcc07352c88f6ccf65a51b3088ed21648c2b9c193" + "hash": "3711799242c81652506593cf046bf67332706bbb21096311f8ae015c4c5ea46e" } diff --git a/.sqlx/query-46df2723810fa84cd1c2f228b5052f91c2bb0a2cd2025366ac2af0cb0717e7d5.json b/.sqlx/query-46df2723810fa84cd1c2f228b5052f91c2bb0a2cd2025366ac2af0cb0717e7d5.json deleted file mode 100644 index 8c6626a..0000000 --- a/.sqlx/query-46df2723810fa84cd1c2f228b5052f91c2bb0a2cd2025366ac2af0cb0717e7d5.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT t.id, t.slug, t.name, t.icon, t.color, t.created_at\n FROM tags t\n JOIN project_tags pt ON t.id = pt.tag_id\n WHERE pt.project_id = $1\n ORDER BY t.name ASC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "slug", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "icon", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "color", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false, - false, - false, - true, - true, - false - ] - }, - "hash": "46df2723810fa84cd1c2f228b5052f91c2bb0a2cd2025366ac2af0cb0717e7d5" -} diff --git a/.sqlx/query-9d0e8c98364de65920482389d7f1699ae4710394ed27b472d4e33190cdc0bd19.json b/.sqlx/query-486e18360f7e1b0ffeda90defab5950532dfdc030d8f4b4d7c59a624f23b41d7.json similarity index 58% rename from .sqlx/query-9d0e8c98364de65920482389d7f1699ae4710394ed27b472d4e33190cdc0bd19.json rename to .sqlx/query-486e18360f7e1b0ffeda90defab5950532dfdc030d8f4b4d7c59a624f23b41d7.json index fd5bd43..9b99683 100644 --- a/.sqlx/query-9d0e8c98364de65920482389d7f1699ae4710394ed27b472d4e33190cdc0bd19.json +++ b/.sqlx/query-486e18360f7e1b0ffeda90defab5950532dfdc030d8f4b4d7c59a624f23b41d7.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n id,\n project_id,\n display_order,\n media_type as \"media_type: MediaType\",\n original_filename,\n r2_base_path,\n variants,\n width,\n height,\n size_bytes,\n blurhash,\n metadata,\n created_at\n FROM project_media\n WHERE id = $1\n ", + "query": "\n SELECT \n id,\n project_id,\n display_order,\n media_type as \"media_type: MediaType\",\n r2_base_path,\n variants,\n blurhash,\n metadata\n FROM project_media\n WHERE project_id = $1\n ORDER BY display_order ASC\n ", "describe": { "columns": [ { @@ -35,48 +35,23 @@ }, { "ordinal": 4, - "name": "original_filename", - "type_info": "Text" - }, - { - "ordinal": 5, "name": "r2_base_path", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "variants", "type_info": "Jsonb" }, { - "ordinal": 7, - "name": "width", - "type_info": "Int4" - }, - { - "ordinal": 8, - "name": "height", - "type_info": "Int4" - }, - { - "ordinal": 9, - "name": "size_bytes", - "type_info": "Int8" - }, - { - "ordinal": 10, + "ordinal": 6, "name": "blurhash", "type_info": "Text" }, { - "ordinal": 11, + "ordinal": 7, "name": "metadata", "type_info": "Jsonb" - }, - { - "ordinal": 12, - "name": "created_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -91,14 +66,9 @@ false, false, false, - false, true, - true, - false, - true, - true, - false + true ] }, - "hash": "9d0e8c98364de65920482389d7f1699ae4710394ed27b472d4e33190cdc0bd19" + "hash": "486e18360f7e1b0ffeda90defab5950532dfdc030d8f4b4d7c59a624f23b41d7" } diff --git a/.sqlx/query-538842bf07a2ac3fca612413e5a8e10769b2fc4d90d463040911f416314a42ac.json b/.sqlx/query-4ba4e68c3c71210bf020102d9ff5b5379b4435e6222d018d385d932a038cc9de.json similarity index 90% rename from .sqlx/query-538842bf07a2ac3fca612413e5a8e10769b2fc4d90d463040911f416314a42ac.json rename to .sqlx/query-4ba4e68c3c71210bf020102d9ff5b5379b4435e6222d018d385d932a038cc9de.json index 76e9fa5..39c5077 100644 --- a/.sqlx/query-538842bf07a2ac3fca612413e5a8e10769b2fc4d90d463040911f416314a42ac.json +++ b/.sqlx/query-4ba4e68c3c71210bf020102d9ff5b5379b4435e6222d018d385d932a038cc9de.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO projects (slug, name, short_description, description, status, github_repo, demo_url)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n RETURNING id, slug, name, short_description, description, status as \"status: ProjectStatus\",\n github_repo, demo_url, last_github_activity, created_at, updated_at\n ", + "query": "\n INSERT INTO projects (slug, name, short_description, description, status, github_repo, demo_url)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n RETURNING id, slug, name, short_description, description, status as \"status: ProjectStatus\",\n github_repo, demo_url, last_github_activity, created_at\n ", "describe": { "columns": [ { @@ -64,11 +64,6 @@ "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -104,9 +99,8 @@ true, true, true, - false, false ] }, - "hash": "538842bf07a2ac3fca612413e5a8e10769b2fc4d90d463040911f416314a42ac" + "hash": "4ba4e68c3c71210bf020102d9ff5b5379b4435e6222d018d385d932a038cc9de" } diff --git a/.sqlx/query-54b5eb8bf65df8dd3caa1a1fcac9cf71cb9c665ac8d3b86dd63a9692788f7392.json b/.sqlx/query-62a61815450f395f31570d77194084ac90072472d4c309b2080d1f104f26dd0d.json similarity index 57% rename from .sqlx/query-54b5eb8bf65df8dd3caa1a1fcac9cf71cb9c665ac8d3b86dd63a9692788f7392.json rename to .sqlx/query-62a61815450f395f31570d77194084ac90072472d4c309b2080d1f104f26dd0d.json index 62f8d0c..d231a42 100644 --- a/.sqlx/query-54b5eb8bf65df8dd3caa1a1fcac9cf71cb9c665ac8d3b86dd63a9692788f7392.json +++ b/.sqlx/query-62a61815450f395f31570d77194084ac90072472d4c309b2080d1f104f26dd0d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n id,\n project_id,\n display_order,\n media_type as \"media_type: MediaType\",\n original_filename,\n r2_base_path,\n variants,\n width,\n height,\n size_bytes,\n blurhash,\n metadata,\n created_at\n FROM project_media\n WHERE project_id = $1\n ORDER BY display_order ASC\n ", + "query": "\n SELECT \n id,\n project_id,\n display_order,\n media_type as \"media_type: MediaType\",\n r2_base_path,\n variants,\n blurhash,\n metadata\n FROM project_media\n WHERE id = $1\n ", "describe": { "columns": [ { @@ -35,48 +35,23 @@ }, { "ordinal": 4, - "name": "original_filename", - "type_info": "Text" - }, - { - "ordinal": 5, "name": "r2_base_path", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "variants", "type_info": "Jsonb" }, { - "ordinal": 7, - "name": "width", - "type_info": "Int4" - }, - { - "ordinal": 8, - "name": "height", - "type_info": "Int4" - }, - { - "ordinal": 9, - "name": "size_bytes", - "type_info": "Int8" - }, - { - "ordinal": 10, + "ordinal": 6, "name": "blurhash", "type_info": "Text" }, { - "ordinal": 11, + "ordinal": 7, "name": "metadata", "type_info": "Jsonb" - }, - { - "ordinal": 12, - "name": "created_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -91,14 +66,9 @@ false, false, false, - false, true, - true, - false, - true, - true, - false + true ] }, - "hash": "54b5eb8bf65df8dd3caa1a1fcac9cf71cb9c665ac8d3b86dd63a9692788f7392" + "hash": "62a61815450f395f31570d77194084ac90072472d4c309b2080d1f104f26dd0d" } diff --git a/.sqlx/query-8f08a65d180757f0fcbe65a43e3a2e3fdd95cc1aba80eb40c1042af7301d0fa9.json b/.sqlx/query-6b3d5778e254ef9c28d0dbd76c06d335dc02fdf3a8a2a9eb39fc9bfa612dfa8f.json similarity index 75% rename from .sqlx/query-8f08a65d180757f0fcbe65a43e3a2e3fdd95cc1aba80eb40c1042af7301d0fa9.json rename to .sqlx/query-6b3d5778e254ef9c28d0dbd76c06d335dc02fdf3a8a2a9eb39fc9bfa612dfa8f.json index d58b864..070580c 100644 --- a/.sqlx/query-8f08a65d180757f0fcbe65a43e3a2e3fdd95cc1aba80eb40c1042af7301d0fa9.json +++ b/.sqlx/query-6b3d5778e254ef9c28d0dbd76c06d335dc02fdf3a8a2a9eb39fc9bfa612dfa8f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE social_links\n SET platform = $2, label = $3, value = $4, icon = $5, visible = $6, display_order = $7\n WHERE id = $1\n RETURNING id, platform, label, value, icon, visible, display_order, created_at, updated_at\n ", + "query": "\n UPDATE social_links\n SET platform = $2, label = $3, value = $4, icon = $5, visible = $6, display_order = $7\n WHERE id = $1\n RETURNING id, platform, label, value, icon, visible, display_order\n ", "describe": { "columns": [ { @@ -37,16 +37,6 @@ "ordinal": 6, "name": "display_order", "type_info": "Int4" - }, - { - "ordinal": 7, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -67,10 +57,8 @@ false, false, false, - false, - false, false ] }, - "hash": "8f08a65d180757f0fcbe65a43e3a2e3fdd95cc1aba80eb40c1042af7301d0fa9" + "hash": "6b3d5778e254ef9c28d0dbd76c06d335dc02fdf3a8a2a9eb39fc9bfa612dfa8f" } diff --git a/.sqlx/query-99a63a1f9b888490418aa4e81f5c185e8de55044321c5f85883610e40e51d286.json b/.sqlx/query-70c83f74cbefaf822a238f970f248914863e2a8c407eed4d9dd5d73909330f7d.json similarity index 77% rename from .sqlx/query-99a63a1f9b888490418aa4e81f5c185e8de55044321c5f85883610e40e51d286.json rename to .sqlx/query-70c83f74cbefaf822a238f970f248914863e2a8c407eed4d9dd5d73909330f7d.json index 7c1bd26..9b49ce3 100644 --- a/.sqlx/query-99a63a1f9b888490418aa4e81f5c185e8de55044321c5f85883610e40e51d286.json +++ b/.sqlx/query-70c83f74cbefaf822a238f970f248914863e2a8c407eed4d9dd5d73909330f7d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO tags (slug, name, icon, color)\n VALUES ($1, $2, $3, $4)\n RETURNING id, slug, name, icon, color, created_at\n ", + "query": "\n INSERT INTO tags (slug, name, icon, color)\n VALUES ($1, $2, $3, $4)\n RETURNING id, slug, name, icon, color\n ", "describe": { "columns": [ { @@ -27,11 +27,6 @@ "ordinal": 4, "name": "color", "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -47,9 +42,8 @@ false, false, true, - true, - false + true ] }, - "hash": "99a63a1f9b888490418aa4e81f5c185e8de55044321c5f85883610e40e51d286" + "hash": "70c83f74cbefaf822a238f970f248914863e2a8c407eed4d9dd5d73909330f7d" } diff --git a/.sqlx/query-82c27809a4e88b594c41b65c573c4c28aeceba2cc20b1c16cbdfb8c7ea4b23c0.json b/.sqlx/query-73407c6dbd8dc1ce39c64ced72cf58d537ec59a1f569ecb03c52be96e0fdfdfc.json similarity index 65% rename from .sqlx/query-82c27809a4e88b594c41b65c573c4c28aeceba2cc20b1c16cbdfb8c7ea4b23c0.json rename to .sqlx/query-73407c6dbd8dc1ce39c64ced72cf58d537ec59a1f569ecb03c52be96e0fdfdfc.json index b3039ee..535acf2 100644 --- a/.sqlx/query-82c27809a4e88b594c41b65c573c4c28aeceba2cc20b1c16cbdfb8c7ea4b23c0.json +++ b/.sqlx/query-73407c6dbd8dc1ce39c64ced72cf58d537ec59a1f569ecb03c52be96e0fdfdfc.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n t.id, \n t.slug, \n t.name,\n t.icon,\n t.color,\n t.created_at,\n COUNT(pt.project_id)::int as \"project_count!\"\n FROM tags t\n LEFT JOIN project_tags pt ON t.id = pt.tag_id\n GROUP BY t.id, t.slug, t.name, t.icon, t.color, t.created_at\n ORDER BY t.name ASC\n ", + "query": "\n SELECT \n t.id, \n t.slug, \n t.name,\n t.icon,\n t.color,\n COUNT(pt.project_id)::int as \"project_count!\"\n FROM tags t\n LEFT JOIN project_tags pt ON t.id = pt.tag_id\n GROUP BY t.id, t.slug, t.name, t.icon, t.color\n ORDER BY t.name ASC\n ", "describe": { "columns": [ { @@ -30,11 +30,6 @@ }, { "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 6, "name": "project_count!", "type_info": "Int4" } @@ -48,9 +43,8 @@ false, true, true, - false, null ] }, - "hash": "82c27809a4e88b594c41b65c573c4c28aeceba2cc20b1c16cbdfb8c7ea4b23c0" + "hash": "73407c6dbd8dc1ce39c64ced72cf58d537ec59a1f569ecb03c52be96e0fdfdfc" } diff --git a/.sqlx/query-778619a2d55b7e311f9d8bfd75335a1605065f136bf350b030d1b86d3cc92b37.json b/.sqlx/query-778619a2d55b7e311f9d8bfd75335a1605065f136bf350b030d1b86d3cc92b37.json deleted file mode 100644 index fd1037a..0000000 --- a/.sqlx/query-778619a2d55b7e311f9d8bfd75335a1605065f136bf350b030d1b86d3cc92b37.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT EXISTS(SELECT 1 FROM tags WHERE LOWER(name) = LOWER($1)) as \"exists!\"\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "778619a2d55b7e311f9d8bfd75335a1605065f136bf350b030d1b86d3cc92b37" -} diff --git a/.sqlx/query-7221aa294be3a27520a25739ebf5d4d8a57ac35b7b04be94027a33b4bf845195.json b/.sqlx/query-88d45f0f8d9d92b7ff775561c7a7789d9cb6085d3338d3c18eb78b86d320a34a.json similarity index 76% rename from .sqlx/query-7221aa294be3a27520a25739ebf5d4d8a57ac35b7b04be94027a33b4bf845195.json rename to .sqlx/query-88d45f0f8d9d92b7ff775561c7a7789d9cb6085d3338d3c18eb78b86d320a34a.json index 847f4d1..fc54795 100644 --- a/.sqlx/query-7221aa294be3a27520a25739ebf5d4d8a57ac35b7b04be94027a33b4bf845195.json +++ b/.sqlx/query-88d45f0f8d9d92b7ff775561c7a7789d9cb6085d3338d3c18eb78b86d320a34a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE tags\n SET slug = $2, name = $3, icon = $4, color = $5\n WHERE id = $1\n RETURNING id, slug, name, icon, color, created_at\n ", + "query": "\n UPDATE tags\n SET slug = $2, name = $3, icon = $4, color = $5\n WHERE id = $1\n RETURNING id, slug, name, icon, color\n ", "describe": { "columns": [ { @@ -27,11 +27,6 @@ "ordinal": 4, "name": "color", "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -48,9 +43,8 @@ false, false, true, - true, - false + true ] }, - "hash": "7221aa294be3a27520a25739ebf5d4d8a57ac35b7b04be94027a33b4bf845195" + "hash": "88d45f0f8d9d92b7ff775561c7a7789d9cb6085d3338d3c18eb78b86d320a34a" } diff --git a/.sqlx/query-2ed3555fc0cfe71b8a59c89f6d3fea53458b995f342d623f7ab43824392973c9.json b/.sqlx/query-9a1787fbc71a970af3a6dbcd1b4e355d86116211aead48f44626fb5d066df41d.json similarity index 59% rename from .sqlx/query-2ed3555fc0cfe71b8a59c89f6d3fea53458b995f342d623f7ab43824392973c9.json rename to .sqlx/query-9a1787fbc71a970af3a6dbcd1b4e355d86116211aead48f44626fb5d066df41d.json index 1b38754..9fbc8ae 100644 --- a/.sqlx/query-2ed3555fc0cfe71b8a59c89f6d3fea53458b995f342d623f7ab43824392973c9.json +++ b/.sqlx/query-9a1787fbc71a970af3a6dbcd1b4e355d86116211aead48f44626fb5d066df41d.json @@ -1,42 +1,27 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE site_identity\n SET display_name = $1, occupation = $2, bio = $3, site_title = $4\n WHERE id = 1\n RETURNING id, display_name, occupation, bio, site_title, created_at, updated_at\n ", + "query": "\n UPDATE site_identity\n SET display_name = $1, occupation = $2, bio = $3, site_title = $4\n WHERE id = 1\n RETURNING display_name, occupation, bio, site_title\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, "name": "display_name", "type_info": "Text" }, { - "ordinal": 2, + "ordinal": 1, "name": "occupation", "type_info": "Text" }, { - "ordinal": 3, + "ordinal": 2, "name": "bio", "type_info": "Text" }, { - "ordinal": 4, + "ordinal": 3, "name": "site_title", "type_info": "Text" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 6, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -48,14 +33,11 @@ ] }, "nullable": [ - false, - false, - false, false, false, false, false ] }, - "hash": "2ed3555fc0cfe71b8a59c89f6d3fea53458b995f342d623f7ab43824392973c9" + "hash": "9a1787fbc71a970af3a6dbcd1b4e355d86116211aead48f44626fb5d066df41d" } diff --git a/.sqlx/query-8302d5621d743bd3e5e2a029e30ad1e017e1f2ef1f9cb09aa924436b0c6b8c22.json b/.sqlx/query-a2c5034b43cd20676e01d9471ce1d995a6d36a0e16c3156eaba7c48393a37fa2.json similarity index 82% rename from .sqlx/query-8302d5621d743bd3e5e2a029e30ad1e017e1f2ef1f9cb09aa924436b0c6b8c22.json rename to .sqlx/query-a2c5034b43cd20676e01d9471ce1d995a6d36a0e16c3156eaba7c48393a37fa2.json index 426d3cd..e61f615 100644 --- a/.sqlx/query-8302d5621d743bd3e5e2a029e30ad1e017e1f2ef1f9cb09aa924436b0c6b8c22.json +++ b/.sqlx/query-a2c5034b43cd20676e01d9471ce1d995a6d36a0e16c3156eaba7c48393a37fa2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n last_github_activity,\n created_at,\n updated_at\n FROM projects\n WHERE github_repo IS NOT NULL\n ORDER BY updated_at DESC\n ", + "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n last_github_activity,\n created_at\n FROM projects\n WHERE status != 'hidden'\n ORDER BY COALESCE(last_github_activity, created_at) DESC\n ", "describe": { "columns": [ { @@ -64,11 +64,6 @@ "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -84,9 +79,8 @@ true, true, true, - false, false ] }, - "hash": "8302d5621d743bd3e5e2a029e30ad1e017e1f2ef1f9cb09aa924436b0c6b8c22" + "hash": "a2c5034b43cd20676e01d9471ce1d995a6d36a0e16c3156eaba7c48393a37fa2" } diff --git a/.sqlx/query-a55f7d64fc0f41f150663aae82ac6fbcabf90d2627d194c3fb23f80c24dcc57a.json b/.sqlx/query-a55f7d64fc0f41f150663aae82ac6fbcabf90d2627d194c3fb23f80c24dcc57a.json deleted file mode 100644 index 6ad3e6e..0000000 --- a/.sqlx/query-a55f7d64fc0f41f150663aae82ac6fbcabf90d2627d194c3fb23f80c24dcc57a.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT EXISTS(SELECT 1 FROM tags WHERE slug = $1) as \"exists!\"\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "a55f7d64fc0f41f150663aae82ac6fbcabf90d2627d194c3fb23f80c24dcc57a" -} diff --git a/.sqlx/query-3401838a054f29a17d96086529b7222026aafa8a385615129eb0d407e44faa9c.json b/.sqlx/query-a7d7a4069df7b453d5cab42da7040b5112c8a5d126c3a735b3954e65da3082ce.json similarity index 81% rename from .sqlx/query-3401838a054f29a17d96086529b7222026aafa8a385615129eb0d407e44faa9c.json rename to .sqlx/query-a7d7a4069df7b453d5cab42da7040b5112c8a5d126c3a735b3954e65da3082ce.json index f00ce7c..f94ea6a 100644 --- a/.sqlx/query-3401838a054f29a17d96086529b7222026aafa8a385615129eb0d407e44faa9c.json +++ b/.sqlx/query-a7d7a4069df7b453d5cab42da7040b5112c8a5d126c3a735b3954e65da3082ce.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n p.id, \n p.slug, \n p.name,\n p.short_description,\n p.description, \n p.status as \"status: super::ProjectStatus\", \n p.github_repo, \n p.demo_url, \n p.last_github_activity, \n p.created_at, \n p.updated_at\n FROM projects p\n JOIN project_tags pt ON p.id = pt.project_id\n WHERE pt.tag_id = $1\n ORDER BY COALESCE(p.last_github_activity, p.created_at) DESC\n ", + "query": "\n SELECT \n p.id, \n p.slug, \n p.name,\n p.short_description,\n p.description, \n p.status as \"status: super::ProjectStatus\", \n p.github_repo, \n p.demo_url, \n p.last_github_activity, \n p.created_at\n FROM projects p\n JOIN project_tags pt ON p.id = pt.project_id\n WHERE pt.tag_id = $1\n ORDER BY COALESCE(p.last_github_activity, p.created_at) DESC\n ", "describe": { "columns": [ { @@ -64,11 +64,6 @@ "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -86,9 +81,8 @@ true, true, true, - false, false ] }, - "hash": "3401838a054f29a17d96086529b7222026aafa8a385615129eb0d407e44faa9c" + "hash": "a7d7a4069df7b453d5cab42da7040b5112c8a5d126c3a735b3954e65da3082ce" } diff --git a/.sqlx/query-3f662485c3beb37febc746c49a9bdd6c99e08b19add7b9872068cc960afcd7c8.json b/.sqlx/query-cc34a13eda021eb1dcdb6f49eb1909b450be4ff3541dc2dd8a9d1da45ef25883.json similarity index 67% rename from .sqlx/query-3f662485c3beb37febc746c49a9bdd6c99e08b19add7b9872068cc960afcd7c8.json rename to .sqlx/query-cc34a13eda021eb1dcdb6f49eb1909b450be4ff3541dc2dd8a9d1da45ef25883.json index 95e5ad6..054fa0a 100644 --- a/.sqlx/query-3f662485c3beb37febc746c49a9bdd6c99e08b19add7b9872068cc960afcd7c8.json +++ b/.sqlx/query-cc34a13eda021eb1dcdb6f49eb1909b450be4ff3541dc2dd8a9d1da45ef25883.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, slug, name, icon, color, created_at\n FROM tags\n WHERE id = $1\n ", + "query": "\n SELECT t.id, t.slug, t.name, t.icon, t.color\n FROM tags t\n JOIN project_tags pt ON t.id = pt.tag_id\n WHERE pt.project_id = $1\n ORDER BY t.name ASC\n ", "describe": { "columns": [ { @@ -27,11 +27,6 @@ "ordinal": 4, "name": "color", "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -44,9 +39,8 @@ false, false, true, - true, - false + true ] }, - "hash": "3f662485c3beb37febc746c49a9bdd6c99e08b19add7b9872068cc960afcd7c8" + "hash": "cc34a13eda021eb1dcdb6f49eb1909b450be4ff3541dc2dd8a9d1da45ef25883" } diff --git a/.sqlx/query-9e55ffc9d9138ea02678ff432db47488da976516f53b53d7a0bd36665b39b628.json b/.sqlx/query-d2c1348ab4e37c89c815e3cb916070279265d0cc5bbd2e4bcdcecb17b8998338.json similarity index 68% rename from .sqlx/query-9e55ffc9d9138ea02678ff432db47488da976516f53b53d7a0bd36665b39b628.json rename to .sqlx/query-d2c1348ab4e37c89c815e3cb916070279265d0cc5bbd2e4bcdcecb17b8998338.json index 02e79d1..fc30bd8 100644 --- a/.sqlx/query-9e55ffc9d9138ea02678ff432db47488da976516f53b53d7a0bd36665b39b628.json +++ b/.sqlx/query-d2c1348ab4e37c89c815e3cb916070279265d0cc5bbd2e4bcdcecb17b8998338.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, platform, label, value, icon, visible, display_order, created_at, updated_at\n FROM social_links\n ORDER BY display_order ASC\n ", + "query": "\n SELECT id, platform, label, value, icon, visible, display_order\n FROM social_links\n ORDER BY display_order ASC\n ", "describe": { "columns": [ { @@ -37,16 +37,6 @@ "ordinal": 6, "name": "display_order", "type_info": "Int4" - }, - { - "ordinal": 7, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -59,10 +49,8 @@ false, false, false, - false, - false, false ] }, - "hash": "9e55ffc9d9138ea02678ff432db47488da976516f53b53d7a0bd36665b39b628" + "hash": "d2c1348ab4e37c89c815e3cb916070279265d0cc5bbd2e4bcdcecb17b8998338" } diff --git a/.sqlx/query-d50291c2483aa95fa6a734d69dcd83fc4a68d4917721cbca149bc841b39a9481.json b/.sqlx/query-d50291c2483aa95fa6a734d69dcd83fc4a68d4917721cbca149bc841b39a9481.json deleted file mode 100644 index ea71c8f..0000000 --- a/.sqlx/query-d50291c2483aa95fa6a734d69dcd83fc4a68d4917721cbca149bc841b39a9481.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT id, slug, name, icon, color, created_at\n FROM tags\n ORDER BY name ASC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "slug", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "icon", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "color", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false, - false, - true, - true, - false - ] - }, - "hash": "d50291c2483aa95fa6a734d69dcd83fc4a68d4917721cbca149bc841b39a9481" -} diff --git a/.sqlx/query-fdd7f3743ad2e6d1d571ee2d3dbd8b4d49074ef2cbe74dc79c6f4f638bdd3a88.json b/.sqlx/query-d631f4abcaa1be60499ccedd219948319ecc5fba284a5a76778f2faf55e8cc12.json similarity index 89% rename from .sqlx/query-fdd7f3743ad2e6d1d571ee2d3dbd8b4d49074ef2cbe74dc79c6f4f638bdd3a88.json rename to .sqlx/query-d631f4abcaa1be60499ccedd219948319ecc5fba284a5a76778f2faf55e8cc12.json index 76d1664..88113ae 100644 --- a/.sqlx/query-fdd7f3743ad2e6d1d571ee2d3dbd8b4d49074ef2cbe74dc79c6f4f638bdd3a88.json +++ b/.sqlx/query-d631f4abcaa1be60499ccedd219948319ecc5fba284a5a76778f2faf55e8cc12.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE projects\n SET slug = $2, name = $3, short_description = $4, description = $5,\n status = $6, github_repo = $7, demo_url = $8\n WHERE id = $1\n RETURNING id, slug, name, short_description, description, status as \"status: ProjectStatus\",\n github_repo, demo_url, last_github_activity, created_at, updated_at\n ", + "query": "\n UPDATE projects\n SET slug = $2, name = $3, short_description = $4, description = $5,\n status = $6, github_repo = $7, demo_url = $8\n WHERE id = $1\n RETURNING id, slug, name, short_description, description, status as \"status: ProjectStatus\",\n github_repo, demo_url, last_github_activity, created_at\n ", "describe": { "columns": [ { @@ -64,11 +64,6 @@ "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -105,9 +100,8 @@ true, true, true, - false, false ] }, - "hash": "fdd7f3743ad2e6d1d571ee2d3dbd8b4d49074ef2cbe74dc79c6f4f638bdd3a88" + "hash": "d631f4abcaa1be60499ccedd219948319ecc5fba284a5a76778f2faf55e8cc12" } diff --git a/.sqlx/query-dd0d0e3fd03f130aab947d13580796eee9a786e2ca01d339fd0e8356f8ad3824.json b/.sqlx/query-dd0d0e3fd03f130aab947d13580796eee9a786e2ca01d339fd0e8356f8ad3824.json deleted file mode 100644 index 4539a5e..0000000 --- a/.sqlx/query-dd0d0e3fd03f130aab947d13580796eee9a786e2ca01d339fd0e8356f8ad3824.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM tags WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "dd0d0e3fd03f130aab947d13580796eee9a786e2ca01d339fd0e8356f8ad3824" -} diff --git a/.sqlx/query-d851d8d7117d3cfcd8c50304d60565cd2cc275e2776929d660f9351dad5b418f.json b/.sqlx/query-e35380efe500836a7a7bbcfcd54023b64bb20bbf58ef18acc37046b34996cefd.json similarity index 81% rename from .sqlx/query-d851d8d7117d3cfcd8c50304d60565cd2cc275e2776929d660f9351dad5b418f.json rename to .sqlx/query-e35380efe500836a7a7bbcfcd54023b64bb20bbf58ef18acc37046b34996cefd.json index b0f30d9..8fb6678 100644 --- a/.sqlx/query-d851d8d7117d3cfcd8c50304d60565cd2cc275e2776929d660f9351dad5b418f.json +++ b/.sqlx/query-e35380efe500836a7a7bbcfcd54023b64bb20bbf58ef18acc37046b34996cefd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n last_github_activity,\n created_at,\n updated_at\n FROM projects\n WHERE status != 'hidden'\n ORDER BY COALESCE(last_github_activity, created_at) DESC\n ", + "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n last_github_activity,\n created_at\n FROM projects\n WHERE github_repo IS NOT NULL\n ORDER BY updated_at DESC\n ", "describe": { "columns": [ { @@ -64,11 +64,6 @@ "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { @@ -84,9 +79,8 @@ true, true, true, - false, false ] }, - "hash": "d851d8d7117d3cfcd8c50304d60565cd2cc275e2776929d660f9351dad5b418f" + "hash": "e35380efe500836a7a7bbcfcd54023b64bb20bbf58ef18acc37046b34996cefd" } diff --git a/.sqlx/query-f45329eab47a84c6c921f4e78c443835135fa095f16a7a0a9080249c4136fc7d.json b/.sqlx/query-f83560f1e54983a8b9f96e12423ca1edf3176e94179b176c43f0445f7ea51656.json similarity index 50% rename from .sqlx/query-f45329eab47a84c6c921f4e78c443835135fa095f16a7a0a9080249c4136fc7d.json rename to .sqlx/query-f83560f1e54983a8b9f96e12423ca1edf3176e94179b176c43f0445f7ea51656.json index d3208ff..a79e3ec 100644 --- a/.sqlx/query-f45329eab47a84c6c921f4e78c443835135fa095f16a7a0a9080249c4136fc7d.json +++ b/.sqlx/query-f83560f1e54983a8b9f96e12423ca1edf3176e94179b176c43f0445f7ea51656.json @@ -1,56 +1,38 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, display_name, occupation, bio, site_title, created_at, updated_at\n FROM site_identity\n WHERE id = 1\n ", + "query": "\n SELECT display_name, occupation, bio, site_title\n FROM site_identity\n WHERE id = 1\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, "name": "display_name", "type_info": "Text" }, { - "ordinal": 2, + "ordinal": 1, "name": "occupation", "type_info": "Text" }, { - "ordinal": 3, + "ordinal": 2, "name": "bio", "type_info": "Text" }, { - "ordinal": 4, + "ordinal": 3, "name": "site_title", "type_info": "Text" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 6, - "name": "updated_at", - "type_info": "Timestamptz" } ], "parameters": { "Left": [] }, "nullable": [ - false, - false, - false, false, false, false, false ] }, - "hash": "f45329eab47a84c6c921f4e78c443835135fa095f16a7a0a9080249c4136fc7d" + "hash": "f83560f1e54983a8b9f96e12423ca1edf3176e94179b176c43f0445f7ea51656" } diff --git a/.sqlx/query-f97c44ba8b156a2f97cdbc240d1c760ae1efc041d1acd5c3462b96a9236dd3dc.json b/.sqlx/query-f97c44ba8b156a2f97cdbc240d1c760ae1efc041d1acd5c3462b96a9236dd3dc.json deleted file mode 100644 index 9c2de97..0000000 --- a/.sqlx/query-f97c44ba8b156a2f97cdbc240d1c760ae1efc041d1acd5c3462b96a9236dd3dc.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n id,\n slug,\n name,\n short_description,\n description,\n status as \"status: ProjectStatus\",\n github_repo,\n demo_url,\n\n last_github_activity,\n created_at,\n updated_at\n FROM projects\n WHERE slug = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "slug", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "short_description", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "status: ProjectStatus", - "type_info": { - "Custom": { - "name": "project_status", - "kind": { - "Enum": [ - "active", - "maintained", - "archived", - "hidden" - ] - } - } - } - }, - { - "ordinal": 6, - "name": "github_repo", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "demo_url", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "last_github_activity", - "type_info": "Timestamptz" - }, - { - "ordinal": 9, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - true, - true, - false, - false - ] - }, - "hash": "f97c44ba8b156a2f97cdbc240d1c760ae1efc041d1acd5c3462b96a9236dd3dc" -} diff --git a/Justfile b/Justfile index c7cf02d..2e31ab5 100644 --- a/Justfile +++ b/Justfile @@ -8,8 +8,8 @@ check: const checks = [ { name: "prettier", cmd: ["bun", "run", "--cwd", "web", "format:check"] }, { name: "eslint", cmd: ["bun", "run", "--cwd", "web", "lint"] }, - { name: "svelte-check", cmd: ["bun", "run", "--cwd", "web", "check"] }, - { name: "clippy", cmd: ["cargo", "clippy", "--all-targets"] }, + { name: "svelte-check", cmd: ["bun", "run", "--cwd", "web", "check", "--fail-on-warnings"] }, + { name: "clippy", cmd: ["cargo", "clippy", "--all-targets", "--", "-D", "warnings"] }, { name: "sqlx-prepare", cmd: ["cargo", "sqlx", "prepare", "--check"] }, { name: "rustfmt", cmd: ["cargo", "fmt", "--check"] }, ]; diff --git a/src/cache.rs b/src/cache.rs index 387170c..f012ee4 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -146,11 +146,6 @@ impl IsrCache { self.cache.insert(path, Arc::new(response)).await; } - /// Check if a path is currently being refreshed - pub fn is_refreshing(&self, path: &str) -> bool { - self.refreshing.contains(path) - } - /// Mark a path as being refreshed. Returns true if it wasn't already refreshing. pub fn start_refresh(&self, path: &str) -> bool { self.refreshing.insert(path.to_string()) @@ -166,49 +161,6 @@ impl IsrCache { self.cache.invalidate(path).await; tracing::debug!(path = %path, "Cache entry invalidated"); } - - /// Invalidate multiple cached paths - pub async fn invalidate_many(&self, paths: &[&str]) { - for path in paths { - self.cache.invalidate(*path).await; - } - tracing::info!(paths = ?paths, "Cache entries invalidated"); - } - - /// Invalidate all entries matching a prefix - pub async fn invalidate_prefix(&self, prefix: &str) { - // moka doesn't have prefix invalidation, so we need to iterate - // This is O(n) but invalidation should be infrequent - let prefix_owned = prefix.to_string(); - self.cache - .invalidate_entries_if(move |key, _| key.starts_with(&prefix_owned)) - .ok(); - tracing::info!(prefix = %prefix, "Cache entries with prefix invalidated"); - } - - /// Invalidate all cached entries - pub async fn invalidate_all(&self) { - let previous_count = self.cache.entry_count(); - self.cache.invalidate_all(); - tracing::info!(previous_count, "All cache entries invalidated"); - } - - /// Get cache statistics - pub fn stats(&self) -> CacheStats { - CacheStats { - entry_count: self.cache.entry_count(), - weighted_size: self.cache.weighted_size(), - refreshing_count: self.refreshing.len(), - } - } -} - -/// Cache statistics for observability -#[derive(Debug, Clone, serde::Serialize)] -pub struct CacheStats { - pub entry_count: u64, - pub weighted_size: u64, - pub refreshing_count: usize, } /// Determines if a path should be cached diff --git a/src/cli/api/auth.rs b/src/cli/api/auth.rs index f2dcb70..ad3a3d4 100644 --- a/src/cli/api/auth.rs +++ b/src/cli/api/auth.rs @@ -111,17 +111,15 @@ pub async fn session(client: ApiClient, json: bool) -> Result<(), Box Result<(), Box> { // Validate color if provided - if let Some(ref c) = color { - if !c.chars().all(|ch| ch.is_ascii_hexdigit()) || c.len() != 6 { - return Err("Color must be a 6-character hex string (e.g., '3b82f6')".into()); - } + if let Some(ref c) = color + && (!c.chars().all(|ch| ch.is_ascii_hexdigit()) || c.len() != 6) + { + return Err("Color must be a 6-character hex string (e.g., '3b82f6')".into()); } let request = CreateTagRequest { @@ -128,10 +128,11 @@ async fn update( json: bool, ) -> Result<(), Box> { // Validate color if provided - if let Some(ref c) = color { - if !c.is_empty() && (!c.chars().all(|ch| ch.is_ascii_hexdigit()) || c.len() != 6) { - return Err("Color must be a 6-character hex string (e.g., '3b82f6')".into()); - } + if let Some(ref c) = color + && !c.is_empty() + && (!c.chars().all(|ch| ch.is_ascii_hexdigit()) || c.len() != 6) + { + return Err("Color must be a 6-character hex string (e.g., '3b82f6')".into()); } // First fetch the current tag diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 1cd8dba..0f7dda9 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -36,7 +36,7 @@ pub enum Command { Seed, /// API client for managing content remotely - Api(ApiArgs), + Api(Box), } #[derive(Parser, Debug)] diff --git a/src/db/media.rs b/src/db/media.rs index b95da00..709cdca 100644 --- a/src/db/media.rs +++ b/src/db/media.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; use sqlx::PgPool; -use time::OffsetDateTime; use uuid::Uuid; /// Media type enum matching PostgreSQL enum @@ -19,15 +18,10 @@ pub struct DbProjectMedia { pub project_id: Uuid, pub display_order: i32, pub media_type: MediaType, - pub original_filename: String, pub r2_base_path: String, pub variants: serde_json::Value, - pub width: Option, - pub height: Option, - pub size_bytes: i64, pub blurhash: Option, pub metadata: Option, - pub created_at: OffsetDateTime, } /// Variant info for images @@ -158,34 +152,34 @@ impl DbProjectMedia { // Parse the JSONB variants if let Some(obj) = self.variants.as_object() { // Handle image variants - if let Some(thumb) = obj.get("thumb") { - if let Ok(v) = serde_json::from_value::(thumb.clone()) { - variants.thumb = Some(ApiMediaVariant { - url: format!("{}/{}", base_url, v.key), - width: v.width, - height: v.height, - }); - } + if let Some(thumb) = obj.get("thumb") + && let Ok(v) = serde_json::from_value::(thumb.clone()) + { + variants.thumb = Some(ApiMediaVariant { + url: format!("{}/{}", base_url, v.key), + width: v.width, + height: v.height, + }); } - if let Some(medium) = obj.get("medium") { - if let Ok(v) = serde_json::from_value::(medium.clone()) { - variants.medium = Some(ApiMediaVariant { - url: format!("{}/{}", base_url, v.key), - width: v.width, - height: v.height, - }); - } + if let Some(medium) = obj.get("medium") + && let Ok(v) = serde_json::from_value::(medium.clone()) + { + variants.medium = Some(ApiMediaVariant { + url: format!("{}/{}", base_url, v.key), + width: v.width, + height: v.height, + }); } - if let Some(full) = obj.get("full") { - if let Ok(v) = serde_json::from_value::(full.clone()) { - variants.full = Some(ApiMediaVariant { - url: format!("{}/{}", base_url, v.key), - width: v.width, - height: v.height, - }); - } + if let Some(full) = obj.get("full") + && let Ok(v) = serde_json::from_value::(full.clone()) + { + variants.full = Some(ApiMediaVariant { + url: format!("{}/{}", base_url, v.key), + width: v.width, + height: v.height, + }); } // Handle original - could be image or video @@ -212,14 +206,14 @@ impl DbProjectMedia { } // Handle video poster - if let Some(poster) = obj.get("poster") { - if let Ok(v) = serde_json::from_value::(poster.clone()) { - variants.poster = Some(ApiMediaVariant { - url: format!("{}/{}", base_url, v.key), - width: v.width, - height: v.height, - }); - } + if let Some(poster) = obj.get("poster") + && let Ok(v) = serde_json::from_value::(poster.clone()) + { + variants.poster = Some(ApiMediaVariant { + url: format!("{}/{}", base_url, v.key), + width: v.width, + height: v.height, + }); } } @@ -242,15 +236,10 @@ pub async fn get_media_for_project( project_id, display_order, media_type as "media_type: MediaType", - original_filename, r2_base_path, variants, - width, - height, - size_bytes, blurhash, - metadata, - created_at + metadata FROM project_media WHERE project_id = $1 ORDER BY display_order ASC @@ -274,15 +263,10 @@ pub async fn get_media_by_id( project_id, display_order, media_type as "media_type: MediaType", - original_filename, r2_base_path, variants, - width, - height, - size_bytes, blurhash, - metadata, - created_at + metadata FROM project_media WHERE id = $1 "#, @@ -309,6 +293,7 @@ pub async fn get_next_display_order(pool: &PgPool, project_id: Uuid) -> Result Result { - sqlx::query_as!( - DbProjectMedia, - r#" - UPDATE project_media - SET metadata = $2 - WHERE id = $1 - RETURNING - id, - project_id, - display_order, - media_type as "media_type: MediaType", - original_filename, - r2_base_path, - variants, - width, - height, - size_bytes, - blurhash, - metadata, - created_at - "#, - id, - metadata - ) - .fetch_one(pool) - .await -} diff --git a/src/db/projects.rs b/src/db/projects.rs index e1570be..ca746f3 100644 --- a/src/db/projects.rs +++ b/src/db/projects.rs @@ -24,7 +24,6 @@ pub struct DbProject { pub demo_url: Option, pub last_github_activity: Option, pub created_at: OffsetDateTime, - pub updated_at: OffsetDateTime, } // API response types @@ -168,8 +167,7 @@ pub async fn get_public_projects(pool: &PgPool) -> Result, sqlx:: github_repo, demo_url, last_github_activity, - created_at, - updated_at + created_at FROM projects WHERE status != 'hidden' ORDER BY COALESCE(last_github_activity, created_at) DESC @@ -209,8 +207,7 @@ pub async fn get_all_projects_admin(pool: &PgPool) -> Result, sql github_repo, demo_url, last_github_activity, - created_at, - updated_at + created_at FROM projects ORDER BY COALESCE(last_github_activity, created_at) DESC "# @@ -249,10 +246,8 @@ pub async fn get_project_by_id(pool: &PgPool, id: Uuid) -> Result Result, sqlx::Error> { - query_as!( - DbProject, - r#" - SELECT - id, - slug, - name, - short_description, - description, - status as "status: ProjectStatus", - github_repo, - demo_url, - - last_github_activity, - created_at, - updated_at - FROM projects - WHERE slug = $1 - "#, - slug - ) - .fetch_optional(pool) - .await -} - /// Create project (without tags - tags handled separately) +#[allow(clippy::too_many_arguments)] pub async fn create_project( pool: &PgPool, name: &str, @@ -320,9 +286,7 @@ pub async fn create_project( github_repo: Option<&str>, demo_url: Option<&str>, ) -> Result { - let slug = slug_override - .map(|s| slugify(s)) - .unwrap_or_else(|| slugify(name)); + let slug = slug_override.map(slugify).unwrap_or_else(|| slugify(name)); query_as!( DbProject, @@ -330,7 +294,7 @@ pub async fn create_project( INSERT INTO projects (slug, name, short_description, description, status, github_repo, demo_url) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, slug, name, short_description, description, status as "status: ProjectStatus", - github_repo, demo_url, last_github_activity, created_at, updated_at + github_repo, demo_url, last_github_activity, created_at "#, slug, name, @@ -345,6 +309,7 @@ pub async fn create_project( } /// Update project (without tags - tags handled separately) +#[allow(clippy::too_many_arguments)] pub async fn update_project( pool: &PgPool, id: Uuid, @@ -356,9 +321,7 @@ pub async fn update_project( github_repo: Option<&str>, demo_url: Option<&str>, ) -> Result { - let slug = slug_override - .map(|s| slugify(s)) - .unwrap_or_else(|| slugify(name)); + let slug = slug_override.map(slugify).unwrap_or_else(|| slugify(name)); query_as!( DbProject, @@ -368,7 +331,7 @@ pub async fn update_project( status = $6, github_repo = $7, demo_url = $8 WHERE id = $1 RETURNING id, slug, name, short_description, description, status as "status: ProjectStatus", - github_repo, demo_url, last_github_activity, created_at, updated_at + github_repo, demo_url, last_github_activity, created_at "#, id, slug, @@ -447,8 +410,7 @@ pub async fn get_projects_with_github_repo(pool: &PgPool) -> Result Result Result, pub color: Option, - pub created_at: OffsetDateTime, -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct DbProjectTag { - pub project_id: Uuid, - pub tag_id: Uuid, -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct DbTagCooccurrence { - pub tag_a: Uuid, - pub tag_b: Uuid, - pub count: i32, } // API response types @@ -80,16 +65,14 @@ pub async fn create_tag( icon: Option<&str>, color: Option<&str>, ) -> Result { - let slug = slug_override - .map(|s| slugify(s)) - .unwrap_or_else(|| slugify(name)); + let slug = slug_override.map(slugify).unwrap_or_else(|| slugify(name)); sqlx::query_as!( DbTag, r#" INSERT INTO tags (slug, name, icon, color) VALUES ($1, $2, $3, $4) - RETURNING id, slug, name, icon, color, created_at + RETURNING id, slug, name, icon, color "#, slug, name, @@ -100,25 +83,11 @@ pub async fn create_tag( .await } -pub async fn get_tag_by_id(pool: &PgPool, id: Uuid) -> Result, sqlx::Error> { - sqlx::query_as!( - DbTag, - r#" - SELECT id, slug, name, icon, color, created_at - FROM tags - WHERE id = $1 - "#, - id - ) - .fetch_optional(pool) - .await -} - pub async fn get_tag_by_slug(pool: &PgPool, slug: &str) -> Result, sqlx::Error> { sqlx::query_as!( DbTag, r#" - SELECT id, slug, name, icon, color, created_at + SELECT id, slug, name, icon, color FROM tags WHERE slug = $1 "#, @@ -128,19 +97,6 @@ pub async fn get_tag_by_slug(pool: &PgPool, slug: &str) -> Result, .await } -pub async fn get_all_tags(pool: &PgPool) -> Result, sqlx::Error> { - sqlx::query_as!( - DbTag, - r#" - SELECT id, slug, name, icon, color, created_at - FROM tags - ORDER BY name ASC - "# - ) - .fetch_all(pool) - .await -} - pub async fn get_all_tags_with_counts(pool: &PgPool) -> Result, sqlx::Error> { let rows = sqlx::query!( r#" @@ -150,11 +106,10 @@ pub async fn get_all_tags_with_counts(pool: &PgPool) -> Result t.name, t.icon, t.color, - t.created_at, COUNT(pt.project_id)::int as "project_count!" FROM tags t LEFT JOIN project_tags pt ON t.id = pt.tag_id - GROUP BY t.id, t.slug, t.name, t.icon, t.color, t.created_at + GROUP BY t.id, t.slug, t.name, t.icon, t.color ORDER BY t.name ASC "# ) @@ -170,7 +125,6 @@ pub async fn get_all_tags_with_counts(pool: &PgPool) -> Result name: row.name, icon: row.icon, color: row.color, - created_at: row.created_at, }; (tag, row.project_count) }) @@ -185,9 +139,7 @@ pub async fn update_tag( icon: Option<&str>, color: Option<&str>, ) -> Result { - let slug = slug_override - .map(|s| slugify(s)) - .unwrap_or_else(|| slugify(name)); + let slug = slug_override.map(slugify).unwrap_or_else(|| slugify(name)); sqlx::query_as!( DbTag, @@ -195,7 +147,7 @@ pub async fn update_tag( UPDATE tags SET slug = $2, name = $3, icon = $4, color = $5 WHERE id = $1 - RETURNING id, slug, name, icon, color, created_at + RETURNING id, slug, name, icon, color "#, id, slug, @@ -207,39 +159,6 @@ pub async fn update_tag( .await } -pub async fn delete_tag(pool: &PgPool, id: Uuid) -> Result<(), sqlx::Error> { - sqlx::query!("DELETE FROM tags WHERE id = $1", id) - .execute(pool) - .await?; - Ok(()) -} - -pub async fn tag_exists_by_name(pool: &PgPool, name: &str) -> Result { - let result = sqlx::query!( - r#" - SELECT EXISTS(SELECT 1 FROM tags WHERE LOWER(name) = LOWER($1)) as "exists!" - "#, - name - ) - .fetch_one(pool) - .await?; - - Ok(result.exists) -} - -pub async fn tag_exists_by_slug(pool: &PgPool, slug: &str) -> Result { - let result = sqlx::query!( - r#" - SELECT EXISTS(SELECT 1 FROM tags WHERE slug = $1) as "exists!" - "#, - slug - ) - .fetch_one(pool) - .await?; - - Ok(result.exists) -} - // Project-Tag association queries pub async fn add_tag_to_project( @@ -283,7 +202,7 @@ pub async fn get_tags_for_project( sqlx::query_as!( DbTag, r#" - SELECT t.id, t.slug, t.name, t.icon, t.color, t.created_at + SELECT t.id, t.slug, t.name, t.icon, t.color FROM tags t JOIN project_tags pt ON t.id = pt.tag_id WHERE pt.project_id = $1 @@ -312,8 +231,7 @@ pub async fn get_projects_for_tag( p.github_repo, p.demo_url, p.last_github_activity, - p.created_at, - p.updated_at + p.created_at FROM projects p JOIN project_tags pt ON p.id = pt.project_id WHERE pt.tag_id = $1 @@ -404,7 +322,6 @@ pub async fn get_related_tags( t.name, t.icon, t.color, - t.created_at, tc.count FROM tag_cooccurrence tc JOIN tags t ON (tc.tag_a = t.id OR tc.tag_b = t.id) @@ -427,7 +344,6 @@ pub async fn get_related_tags( name: row.name, icon: row.icon, color: row.color, - created_at: row.created_at, }; (tag, row.count) }) diff --git a/src/github.rs b/src/github.rs index 8bfe066..f73c404 100644 --- a/src/github.rs +++ b/src/github.rs @@ -350,7 +350,7 @@ pub async fn sync_github_activity(pool: &PgPool) -> Result current); + .is_none_or(|current| activity_time > current); if should_update { if let Err(e) = crate::db::projects::update_last_github_activity( diff --git a/src/handlers/assets.rs b/src/handlers/assets.rs index f17f69f..b524bf1 100644 --- a/src/handlers/assets.rs +++ b/src/handlers/assets.rs @@ -85,14 +85,12 @@ pub async fn proxy_icons_handler( // Build trusted headers with session info let mut forward_headers = HeaderMap::new(); - if let Some(cookie) = jar.get("admin_session") { - if let Ok(session_id) = ulid::Ulid::from_string(cookie.value()) { - if let Some(session) = state.session_manager.validate_session(session_id) { - if let Ok(username_value) = axum::http::HeaderValue::from_str(&session.username) { - forward_headers.insert("x-session-user", username_value); - } - } - } + if let Some(cookie) = jar.get("admin_session") + && let Ok(session_id) = ulid::Ulid::from_string(cookie.value()) + && let Some(session) = state.session_manager.validate_session(session_id) + && let Ok(username_value) = axum::http::HeaderValue::from_str(&session.username) + { + forward_headers.insert("x-session-user", username_value); } match proxy::proxy_to_bun(&path_with_query, state, forward_headers).await { diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index cc04eca..681ed6e 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -121,12 +121,11 @@ pub async fn api_logout_handler( State(state): State>, jar: axum_extra::extract::CookieJar, ) -> (axum_extra::extract::CookieJar, StatusCode) { - if let Some(cookie) = jar.get("admin_session") { - if let Ok(session_id) = ulid::Ulid::from_string(cookie.value()) { - if let Err(e) = state.session_manager.delete_session(session_id).await { - tracing::error!(error = %e, "Failed to delete session during logout"); - } - } + if let Some(cookie) = jar.get("admin_session") + && let Ok(session_id) = ulid::Ulid::from_string(cookie.value()) + && let Err(e) = state.session_manager.delete_session(session_id).await + { + tracing::error!(error = %e, "Failed to delete session during logout"); } let cookie = axum_extra::extract::cookie::Cookie::build(("admin_session", "")) diff --git a/src/handlers/tags.rs b/src/handlers/tags.rs index 61a27f6..c492cca 100644 --- a/src/handlers/tags.rs +++ b/src/handlers/tags.rs @@ -56,17 +56,17 @@ pub async fn create_tag_handler( } // Validate color if provided - if let Some(ref color) = payload.color { - if !utils::validate_hex_color(color) { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({ - "error": "Validation error", - "message": "Invalid color format. Must be 6-character hex (e.g., '3b82f6')" - })), - ) - .into_response(); - } + if let Some(ref color) = payload.color + && !utils::validate_hex_color(color) + { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({ + "error": "Validation error", + "message": "Invalid color format. Must be 6-character hex (e.g., '3b82f6')" + })), + ) + .into_response(); } match db::create_tag( @@ -176,17 +176,17 @@ pub async fn update_tag_handler( } // Validate color if provided - if let Some(ref color) = payload.color { - if !utils::validate_hex_color(color) { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({ - "error": "Validation error", - "message": "Invalid color format. Must be 6-character hex (e.g., '3b82f6')" - })), - ) - .into_response(); - } + if let Some(ref color) = payload.color + && !utils::validate_hex_color(color) + { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({ + "error": "Validation error", + "message": "Invalid color format. Must be 6-character hex (e.g., '3b82f6')" + })), + ) + .into_response(); } let tag = match db::get_tag_by_slug(&state.pool, &slug).await { diff --git a/src/http.rs b/src/http.rs index e5f542b..2bb5e57 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,4 +1,3 @@ -use reqwest::Method; use std::path::PathBuf; use std::time::Duration; use thiserror::Error; @@ -86,10 +85,6 @@ impl HttpClient { pub fn post(&self, path: &str) -> reqwest::RequestBuilder { self.client.post(self.build_url(path)) } - - pub fn request(&self, method: Method, path: &str) -> reqwest::RequestBuilder { - self.client.request(method, self.build_url(path)) - } } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index eb3b8ec..9b86e37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,7 +93,7 @@ async fn main() { } Some(Command::Api(api_args)) => { // API client commands - no tracing needed - if let Err(e) = cli::api::run(api_args).await { + if let Err(e) = cli::api::run(*api_args).await { eprintln!("Error: {}", e); std::process::exit(1); } diff --git a/src/media_processing.rs b/src/media_processing.rs index 388e6cf..c475fb5 100644 --- a/src/media_processing.rs +++ b/src/media_processing.rs @@ -10,9 +10,6 @@ pub const THUMB_WIDTH: u32 = 300; pub const MEDIUM_WIDTH: u32 = 800; pub const FULL_WIDTH: u32 = 1600; -/// Quality setting for WebP encoding (0-100) -const WEBP_QUALITY: u8 = 85; - /// Result of processing an uploaded image #[derive(Debug)] pub struct ProcessedImage { diff --git a/src/proxy.rs b/src/proxy.rs index d71e25e..fa70f60 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -84,12 +84,12 @@ pub async fn isr_handler(State(state): State>, req: Request) -> Re } // Check if this is a static asset that exists in embedded CLIENT_ASSETS - if utils::is_static_asset(path) { - if let Some(response) = assets::try_serve_embedded_asset(path) { - return response; - } - // If not found in embedded assets, continue to proxy (might be in Bun's static dir) + if utils::is_static_asset(path) + && let Some(response) = assets::try_serve_embedded_asset(path) + { + return response; } + // If not found in embedded assets, continue to proxy (might be in Bun's static dir) // Check if this is a prerendered page if let Some(response) = assets::try_serve_prerendered_page(path) { @@ -104,39 +104,36 @@ pub async fn isr_handler(State(state): State>, req: Request) -> Re let mut is_authenticated = false; // Forward request ID to Bun (set by RequestIdLayer) - if let Some(request_id) = req.extensions().get::() { - if let Ok(header_value) = axum::http::HeaderValue::from_str(&request_id.0) { - forward_headers.insert("x-request-id", header_value); - } + if let Some(request_id) = req.extensions().get::() + && let Ok(header_value) = axum::http::HeaderValue::from_str(&request_id.0) + { + forward_headers.insert("x-request-id", header_value); } // SECURITY: Strip any X-Session-User header from incoming request to prevent spoofing // Extract and validate session from cookie - if let Some(cookie_header) = req.headers().get(axum::http::header::COOKIE) { - if let Ok(cookie_str) = cookie_header.to_str() { - // Parse cookies manually to find admin_session - for cookie_pair in cookie_str.split(';') { - let cookie_pair = cookie_pair.trim(); - if let Some((name, value)) = cookie_pair.split_once('=') { - if name == "admin_session" { - // Found session cookie, validate it - if let Ok(session_id) = ulid::Ulid::from_string(value) { - if let Some(session) = - state.session_manager.validate_session(session_id) - { - // Session is valid - add trusted header - if let Ok(username_value) = - axum::http::HeaderValue::from_str(&session.username) - { - forward_headers.insert("x-session-user", username_value); - is_authenticated = true; - } - } - } - break; + if let Some(cookie_header) = req.headers().get(axum::http::header::COOKIE) + && let Ok(cookie_str) = cookie_header.to_str() + { + // Parse cookies manually to find admin_session + for cookie_pair in cookie_str.split(';') { + let cookie_pair = cookie_pair.trim(); + if let Some((name, value)) = cookie_pair.split_once('=') + && name == "admin_session" + { + // Found session cookie, validate it + if let Ok(session_id) = ulid::Ulid::from_string(value) + && let Some(session) = state.session_manager.validate_session(session_id) + { + // Session is valid - add trusted header + if let Ok(username_value) = axum::http::HeaderValue::from_str(&session.username) + { + forward_headers.insert("x-session-user", username_value); + is_authenticated = true; } } + break; } } } @@ -146,35 +143,33 @@ pub async fn isr_handler(State(state): State>, req: Request) -> Re let use_cache = !is_authenticated && cache::is_cacheable_path(path); // Try to serve from cache for public requests - if use_cache { - if let Some(cached) = state.isr_cache.get(&path_with_query).await { - let fresh_duration = state.isr_cache.config.fresh_duration; - let stale_duration = state.isr_cache.config.stale_duration; + if use_cache && let Some(cached) = state.isr_cache.get(&path_with_query).await { + let fresh_duration = state.isr_cache.config.fresh_duration; + let stale_duration = state.isr_cache.config.stale_duration; - if cached.is_fresh(fresh_duration) { - // Fresh cache hit - serve immediately - let age_ms = cached.age().as_millis() as u64; - tracing::debug!(cache = "hit", age_ms, "ISR cache hit (fresh)"); + if cached.is_fresh(fresh_duration) { + // Fresh cache hit - serve immediately + let age_ms = cached.age().as_millis() as u64; + tracing::debug!(cache = "hit", age_ms, "ISR cache hit (fresh)"); - return serve_cached_response(&cached, is_head); - } else if cached.is_stale_but_usable(fresh_duration, stale_duration) { - // Stale cache hit - serve immediately and refresh in background - let age_ms = cached.age().as_millis() as u64; - tracing::debug!(cache = "stale", age_ms, "ISR cache hit (stale, refreshing)"); + return serve_cached_response(&cached, is_head); + } else if cached.is_stale_but_usable(fresh_duration, stale_duration) { + // Stale cache hit - serve immediately and refresh in background + let age_ms = cached.age().as_millis() as u64; + tracing::debug!(cache = "stale", age_ms, "ISR cache hit (stale, refreshing)"); - // Spawn background refresh if not already refreshing - if state.isr_cache.start_refresh(&path_with_query) { - let state_clone = state.clone(); - let path_clone = path_with_query.clone(); - tokio::spawn(async move { - refresh_cache_entry(state_clone, path_clone).await; - }); - } - - return serve_cached_response(&cached, is_head); + // Spawn background refresh if not already refreshing + if state.isr_cache.start_refresh(&path_with_query) { + let state_clone = state.clone(); + let path_clone = path_with_query.clone(); + tokio::spawn(async move { + refresh_cache_entry(state_clone, path_clone).await; + }); } - // Cache entry is too old - fall through to fetch + + return serve_cached_response(&cached, is_head); } + // Cache entry is too old - fall through to fetch } // Cache miss or non-cacheable - fetch from Bun diff --git a/src/r2.rs b/src/r2.rs index 5e7d16a..372c043 100644 --- a/src/r2.rs +++ b/src/r2.rs @@ -57,27 +57,6 @@ impl R2Client { .cloned() } - pub async fn get_object(&self, key: &str) -> Result, String> { - let result = self - .client - .get_object() - .bucket(&self.bucket) - .key(key) - .send() - .await - .map_err(|e| format!("Failed to get object from R2: {e}"))?; - - let bytes = result - .body - .collect() - .await - .map_err(|e| format!("Failed to read object body: {e}"))? - .into_bytes() - .to_vec(); - - Ok(bytes) - } - pub async fn put_object( &self, key: &str, diff --git a/src/state.rs b/src/state.rs index cefdd2e..744e375 100644 --- a/src/state.rs +++ b/src/state.rs @@ -20,14 +20,12 @@ pub struct AppState { #[derive(Debug)] pub enum ProxyError { Network(reqwest::Error), - Other(String), } impl std::fmt::Display for ProxyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ProxyError::Network(e) => write!(f, "Network error: {e}"), - ProxyError::Other(s) => write!(f, "{s}"), } } } diff --git a/src/utils.rs b/src/utils.rs index 511d706..40b0b9d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -34,10 +34,10 @@ pub fn is_page_route(path: &str) -> bool { /// Check if the request accepts HTML responses pub fn accepts_html(headers: &HeaderMap) -> bool { - if let Some(accept) = headers.get(header::ACCEPT) { - if let Ok(accept_str) = accept.to_str() { - return accept_str.contains("text/html") || accept_str.contains("*/*"); - } + if let Some(accept) = headers.get(header::ACCEPT) + && let Ok(accept_str) = accept.to_str() + { + return accept_str.contains("text/html") || accept_str.contains("*/*"); } // Default to true for requests without Accept header (browsers typically send it) true @@ -46,34 +46,34 @@ pub fn accepts_html(headers: &HeaderMap) -> bool { /// Determines if request prefers raw content (CLI tools) over HTML pub fn prefers_raw_content(headers: &HeaderMap) -> bool { // Check User-Agent for known CLI tools first (most reliable) - if let Some(ua) = headers.get(header::USER_AGENT) { - if let Ok(ua_str) = ua.to_str() { - let ua_lower = ua_str.to_lowercase(); - if ua_lower.starts_with("curl/") - || ua_lower.starts_with("wget/") - || ua_lower.starts_with("httpie/") - || ua_lower.contains("curlie") - { - return true; - } + if let Some(ua) = headers.get(header::USER_AGENT) + && let Ok(ua_str) = ua.to_str() + { + let ua_lower = ua_str.to_lowercase(); + if ua_lower.starts_with("curl/") + || ua_lower.starts_with("wget/") + || ua_lower.starts_with("httpie/") + || ua_lower.contains("curlie") + { + return true; } } // Check Accept header - if it explicitly prefers text/html, serve HTML - if let Some(accept) = headers.get(header::ACCEPT) { - if let Ok(accept_str) = accept.to_str() { - // If text/html appears before */* in the list, they prefer HTML - if let Some(html_pos) = accept_str.find("text/html") { - if let Some(wildcard_pos) = accept_str.find("*/*") { - return html_pos > wildcard_pos; - } - // Has text/html but no */* → prefers HTML - return false; - } - // Has */* but no text/html → probably a CLI tool - if accept_str.contains("*/*") && !accept_str.contains("text/html") { - return true; + if let Some(accept) = headers.get(header::ACCEPT) + && let Ok(accept_str) = accept.to_str() + { + // If text/html appears before */* in the list, they prefer HTML + if let Some(html_pos) = accept_str.find("text/html") { + if let Some(wildcard_pos) = accept_str.find("*/*") { + return html_pos > wildcard_pos; } + // Has text/html but no */* → prefers HTML + return false; + } + // Has */* but no text/html → probably a CLI tool + if accept_str.contains("*/*") && !accept_str.contains("text/html") { + return true; } } diff --git a/web/src/lib/components/Clouds.svelte b/web/src/lib/components/Clouds.svelte index b32ef4c..da42929 100644 --- a/web/src/lib/components/Clouds.svelte +++ b/web/src/lib/components/Clouds.svelte @@ -167,6 +167,7 @@ ...(themeStore.isDark ? darkModeOverrides : lightModeOverrides), }); + // svelte-ignore non_reactive_update let canvas: HTMLCanvasElement; let cleanupFns: (() => void)[] = []; let ready = $state(false); diff --git a/web/src/routes/admin/tags/+page.svelte b/web/src/routes/admin/tags/+page.svelte index 11273c4..aa95d6e 100644 --- a/web/src/routes/admin/tags/+page.svelte +++ b/web/src/routes/admin/tags/+page.svelte @@ -102,6 +102,13 @@ // Otherwise, let the link navigate normally } + function handleTagKeyDown(tag: TagWithIconAndCount, event: KeyboardEvent) { + if (deleteMode && (event.key === "Enter" || event.key === " ")) { + event.preventDefault(); + initiateDelete(tag); + } + } + function initiateDelete(tag: TagWithIconAndCount) { deleteTarget = tag; deleteConfirmReady = false; @@ -243,8 +250,14 @@
{#each data.tags as tag (tag.id)} - -
handleTagClick(tag, e)} class="contents"> + +
handleTagClick(tag, e)} + onkeydown={(e) => handleTagKeyDown(tag, e)} + role={deleteMode ? "button" : undefined} + tabindex={deleteMode ? 0 : undefined} + class="contents" + > (data.tag.color); let saving = $state(false); // Preview icon SVG - starts with server-rendered, updates on icon change + // svelte-ignore state_referenced_locally let previewIconSvg = $state(data.tag.iconSvg ?? ""); let iconLoadTimeout: ReturnType | null = null; @@ -179,9 +184,9 @@
- +