From ef4722e1dd9a135bd0f724ea07fe8ab226c6437f Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Mon, 24 Aug 2026 18:10:53 +0100 Subject: [PATCH 1/6] fix(db): bump sqlx to 0.9, honour no-transaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sqlx-sqlite 0.8 ignored Migration.no_tx and wrapped every migration in a transaction, where PRAGMA foreign_keys = OFF is a no-op — so any table rebuild cascade-deleted children. 0.9 honours `-- no-transaction`, so 0014 drops its movie_releases_backup / episode_releases_backup workaround and runs the plain rebuild recipe with foreign keys off. A migration test rebuilds both parents from a pre-0014 database and asserts the child link rows survive. Closes #155 Co-Authored-By: Claude Fable 5 --- ...080cd50793c8bb0bfee16ef932dcee2b85bf4.json | 37 +- ...0d3144fc5a071893c74b2c1eea51bfe00ac97.json | 16 +- ...caffa70f9ec44f5a5da1dc0560f29f445ceab.json | 10 +- ...e6086f857f6c7efff660ae4a958a9f4ce033a.json | 3 +- ...3b99efeddf05aae9a32765680a97b5db73bb0.json | 74 ++- ...afe5fecb377c6fd6cbeb6c8feb41f8e11ef77.json | 26 +- ...0fff699d5c80399c0d0139e1e89732b58b169.json | 34 +- ...a03b642654d2d99a23b4319bd018e7389cdff.json | 48 +- ...f0d7f4d2742acad26efbc7644193966c39325.json | 8 +- ...f6510115149c13dae9c31cfb39ccd14e549f9.json | 3 +- ...21bbc42c5d412e31d2303b45813efa52201b9.json | 8 +- ...e91bf5f86e9a968d7b61f12daa022cc27f4d1.json | 122 +++- ...cd6329037fe678ed628ea3ee92d14c7692540.json | 35 +- ...d97ed1764321d76022ff55684969be0171cb6.json | 32 +- ...60547c4fbac9959c79a59e2192c5fb448b4bb.json | 3 +- ...5f1a0ce0d9b7fe9940e573f50c87209ba10a5.json | 112 +++- ...c46633a8834822ec4c13e63266bec1f7d0ada.json | 50 +- ...ef5e6f9acf430ae419ca68dc3800b0390b5b7.json | 72 ++- ...80e5935b3550ee2c024e09aef9b76fd47a708.json | 58 +- ...7e2182c4478153c9096c1522c5ab23ad472e9.json | 10 +- ...8a8efb0c7af09fa6cff4b1f31fb963da58bd5.json | 8 +- ...6ac1579490b29da670fe9062117451911576a.json | 8 +- ...ca40e2d883b9990bec497355422f5b5e6dfe5.json | 60 +- ...16583c0dd62e9a25e955866c3b8690489443e.json | 72 ++- ...c1b4ab1db895065f65db4a99bb0a4dc6e71ed.json | 98 +++- ...5a65d4c6a2b890a54af9d5d2d956cca783892.json | 70 ++- ...d45d048a8c44efd78b75e08e74299fed072ea.json | 112 +++- ...28c4adf0257f1ab78a1cd23d27dd4dc2e0be1.json | 18 +- ...8cbc7c1da617502c818e5dbe0f35c87b175d5.json | 96 +++- ...a6f5bad8db69e0827e620264700f4affb3652.json | 98 +++- ...ae0c4a718e95f9718629f0aba9f3d00d179e1.json | 13 +- ...c9b0219c18d2321720c060e1d22bb398d7d3d.json | 3 +- ...586dc4f7682ab4edbd483d36fedbc985a27f4.json | 8 +- ...a56981adbecbb4722305cdad7241f4f0d8fec.json | 3 +- ...4f01f7d58d8a424310ae0743fb0c91577665e.json | 32 +- ...4f36e1c32672bd171009fb164e6593f98ec31.json | 16 +- ...32db6568360e8c3fc7e31e968508d88c1bc67.json | 43 +- ...69cc8ece28966cfa003f4bfe1190c55831a23.json | 120 +++- ...fedb04380e047c4e0b2fb1def0b40faa02432.json | 8 +- ...879a0e57d6357ab9b278503cdf8fb5252886a.json | 98 +++- ...aa55931045af290894e396b3b410123bfd539.json | 8 +- ...946ce75f94e00bf0d3aa6ac54811080725cc2.json | 120 +++- ...341bf16a61029fb82d621930c137b2895b381.json | 26 +- ...4db5fa3008b08e8e173b5ca21f51cca1dd1ee.json | 64 ++- ...ed823cca790c1439d94fba1c7dcea024d9467.json | 96 +++- ...bf757e0f85d1d86ee6322253f223fae49265f.json | 72 ++- ...71f115f9bf97edbc5ff6621bcbe90d60f5644.json | 104 +++- ...0e287a243607735185be7d67fe3f7d7c3c506.json | 112 +++- ...27aecea4ee2f5412894dfe99057e8e88c7db3.json | 3 +- ...c077c27b647a6f0b411e13105398a2963cd51.json | 48 +- ...e23d46571c894fa42a0d109079c738dee97ac.json | 24 +- ...4802e4a5556c388b84fe7a6102444a887c2cc.json | 82 ++- ...fcc049e3754e5b1837ff54289e8908523d40c.json | 98 +++- ...6cf3b60362a5c2007793fe1c2ff5a57fa0c40.json | 75 ++- ...8643e732850b0a1ec331e216c3aedf6987ad4.json | 90 ++- ...ed1912b950a84d1ce7eaa406339b024aac82d.json | 8 +- ...a7271c33b1da4ec9f6389b8d7212be80c97ea.json | 78 ++- ...6889889ab06d9a77f26c4d745659c8e1f4a07.json | 120 +++- ...e5681e0c00b1f9692a7142043067ff64a00a1.json | 3 +- ...f20c69e83f05f9acb568bc42270c7753b74cd.json | 26 +- ...0767a8603335eaed198ebd4648d9a38b5984f.json | 8 +- ...90843a8bc71c820b56847e0a86a9497f03952.json | 48 +- ...5a41b345fec599ba1562ae1fdf617ef49ff54.json | 96 +++- ...8139d45cb658b94103f677dfa49dcd9f0e07d.json | 24 +- ...4b49cbad1d65ea0f130d091995bfed87cb844.json | 98 +++- ...ff5d6b78e7844476731b309a3859287ff8c2f.json | 112 +++- ...1ade93fed055b6bcc4b5e7f579be5cc0aa829.json | 3 +- ...773b3af29299658a21681237cfa11cc806a39.json | 8 +- ...9008b387a96c251606cfe196a78075c119f8e.json | 3 +- ...1cfd20b095d56b961b67e8599c3a0e8b9770e.json | 26 +- ...d2c5cbb459b97e1a7a093d7c86617312cf289.json | 26 +- ...570f6f8f3dfbf8ae93bc515b087872044dffe.json | 3 +- ...f1caac0b1b104ed78f34799f494d85d1eccc2.json | 120 +++- ...d29ca685cdaa1fb2b93124e099b0bbd6a26af.json | 8 +- ...6e501b3ef1e0d742e6eb06e1dc2fb4c524fb7.json | 42 +- ...1aa9522dc9a07fb5d681655622971398a0ff4.json | 34 +- ...ea5bc67e37123d7e30b18b575f0dd566952ae.json | 47 +- ...9e7b21d2906779bfbbe9e375fa3a70a2de555.json | 112 +++- ...b3d455289dab8a6c505dfb8b388b1e7a8e9cf.json | 47 +- ...6e5f88ffa4644999dee42af3e387dd2cf8702.json | 56 +- ...afb91c076ae3976b11cf18eb7583b8ce69a7e.json | 98 +++- ...dbc153097fd7626f68b316d810ccc8305222d.json | 120 +++- ...b6d54cbad525b1d1d63c2a1495c24af44db7f.json | 98 +++- ...96527b87bb0c61a7c692a42b2fada7e1ed8e8.json | 18 +- ...3cc4e14a9e7cf00e0decc15186ec39b74cf30.json | 10 +- ...f63bf97436d5af605413f8660066d4779f6ca.json | 31 +- ...68df7fa42de0b4322c78cc4cf43a695d16e54.json | 3 +- ...ffeeabfaa027360e3317100687fbf0334a95d.json | 24 +- ...9ccaf45973fbe052f5ef4fdf693529e24f994.json | 8 +- ...f9caff1712321c6398abed99408a7c6cb31e4.json | 24 +- ...bd08215204fadfb92b7f676e857e0b3400408.json | 66 ++- ...d4f6b493df9a5f8e140138cce32395b9d937d.json | 40 +- ...235220cda3d8894d7cb5c2a411e816eb2ce0e.json | 72 ++- ...40e9247723f3ce10ca95114bb2bee849957a0.json | 3 +- ...502186ff6fddddd98cc15423fcd41ca308a04.json | 8 +- ...a9030f528bee609fd0590cc715cf0343a51ba.json | 8 +- ...007d7cb65f063274a322702ffcfef133ea454.json | 47 +- ...586bfa8b6ac2eb2cdb556071f6382283d81ae.json | 40 +- ...e73285778a186fd1da64a91ad18cd1c5c14d5.json | 8 +- ...008835310ae18c9904cea0080267186d64a34.json | 74 ++- ...1218e15b57d565039f3de2d48b320d3cec92b.json | 8 +- ...274701e8b508de480e03bab593e449ef8d9d4.json | 40 +- ...1cbe1aea538f04e31b74f1bca0e7bd00c7248.json | 3 +- ...13915645c35af5349032be9898a697d0d05ba.json | 98 +++- ...8e4e1db0fc5f15cee6a9c78af077ee1277153.json | 122 +++- ...fb538474842113d3f1f7c36d7f33f23ce65e8.json | 16 +- ...fdb4d18ad0d6b5083221531fb70962670ef1d.json | 8 +- ...43c5665c865f9d3d3699105790fe09d8b3fc9.json | 42 +- ...2efa4b9a08d4aaaf8a6883ee9f949d6a5cfe7.json | 8 +- ...455e065bfb670ce4c39a5bea04ceaff05780f.json | 8 +- ...a6aa695ee3b54e15b7259df2cddaba4ce2644.json | 120 +++- ...46450066ac2997437175e30c60d6364701dcf.json | 48 +- ...b315d3ce220f99dca9a727bb6e4b2eaa45edb.json | 8 +- ...a4bebbc1a3d80c1304ed1fb790ccbfc6cc420.json | 112 +++- Cargo.lock | 523 +++++------------- Cargo.toml | 2 +- .../arr-db/migrations/0014_parked_state.sql | 14 +- crates/arr-db/src/lib.rs | 95 +++- 118 files changed, 4864 insertions(+), 1134 deletions(-) diff --git a/.sqlx/query-0696aa0067ba82e31b5e9e46793080cd50793c8bb0bfee16ef932dcee2b85bf4.json b/.sqlx/query-0696aa0067ba82e31b5e9e46793080cd50793c8bb0bfee16ef932dcee2b85bf4.json index 6ffb27f..c5b72ab 100644 --- a/.sqlx/query-0696aa0067ba82e31b5e9e46793080cd50793c8bb0bfee16ef932dcee2b85bf4.json +++ b/.sqlx/query-0696aa0067ba82e31b5e9e46793080cd50793c8bb0bfee16ef932dcee2b85bf4.json @@ -6,34 +6,59 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "id" + } + } }, { "name": "path!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } }, { "name": "size!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "size" + } + } }, { "name": "probed?: serde_json::Value", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "probed" + } + } }, { "name": "waiver?: String", "ordinal": 4, - "type_info": "Null" + "type_info": "Null", + "origin": "Expression" } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, true, diff --git a/.sqlx/query-071c14544e225001d31c5da60c90d3144fc5a071893c74b2c1eea51bfe00ac97.json b/.sqlx/query-071c14544e225001d31c5da60c90d3144fc5a071893c74b2c1eea51bfe00ac97.json index 14a5622..a849773 100644 --- a/.sqlx/query-071c14544e225001d31c5da60c90d3144fc5a071893c74b2c1eea51bfe00ac97.json +++ b/.sqlx/query-071c14544e225001d31c5da60c90d3144fc5a071893c74b2c1eea51bfe00ac97.json @@ -6,12 +6,24 @@ { "name": "normalised_name!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "blacklist", + "name": "normalised_name" + } + } }, { "name": "infohash", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "blacklist", + "name": "infohash" + } + } } ], "parameters": { diff --git a/.sqlx/query-0aea0f15e8a03a37b5d9d32d3c7caffa70f9ec44f5a5da1dc0560f29f445ceab.json b/.sqlx/query-0aea0f15e8a03a37b5d9d32d3c7caffa70f9ec44f5a5da1dc0560f29f445ceab.json index 0466a6f..89958b7 100644 --- a/.sqlx/query-0aea0f15e8a03a37b5d9d32d3c7caffa70f9ec44f5a5da1dc0560f29f445ceab.json +++ b/.sqlx/query-0aea0f15e8a03a37b5d9d32d3c7caffa70f9ec44f5a5da1dc0560f29f445ceab.json @@ -6,14 +6,20 @@ { "name": "id", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } } ], "parameters": { "Right": 3 }, "nullable": [ - true + null ] }, "hash": "0aea0f15e8a03a37b5d9d32d3c7caffa70f9ec44f5a5da1dc0560f29f445ceab" diff --git a/.sqlx/query-0b17cf3d6cc89efacd65d11d15ee6086f857f6c7efff660ae4a958a9f4ce033a.json b/.sqlx/query-0b17cf3d6cc89efacd65d11d15ee6086f857f6c7efff660ae4a958a9f4ce033a.json index ec29074..e113b3c 100644 --- a/.sqlx/query-0b17cf3d6cc89efacd65d11d15ee6086f857f6c7efff660ae4a958a9f4ce033a.json +++ b/.sqlx/query-0b17cf3d6cc89efacd65d11d15ee6086f857f6c7efff660ae4a958a9f4ce033a.json @@ -6,7 +6,8 @@ { "name": "count!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-0db845eb00a34dce18b6a26efc83b99efeddf05aae9a32765680a97b5db73bb0.json b/.sqlx/query-0db845eb00a34dce18b6a26efc83b99efeddf05aae9a32765680a97b5db73bb0.json index 0175fcc..b9b08d1 100644 --- a/.sqlx/query-0db845eb00a34dce18b6a26efc83b99efeddf05aae9a32765680a97b5db73bb0.json +++ b/.sqlx/query-0db845eb00a34dce18b6a26efc83b99efeddf05aae9a32765680a97b5db73bb0.json @@ -6,54 +6,108 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "search_attempts!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "digital_release", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "digital_release" + } + } }, { "name": "metadata_refreshed_at", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "metadata_refreshed_at" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, true, diff --git a/.sqlx/query-0e5facf744fb0766253f352f51dafe5fecb377c6fd6cbeb6c8feb41f8e11ef77.json b/.sqlx/query-0e5facf744fb0766253f352f51dafe5fecb377c6fd6cbeb6c8feb41f8e11ef77.json index c1ded97..2e1b79e 100644 --- a/.sqlx/query-0e5facf744fb0766253f352f51dafe5fecb377c6fd6cbeb6c8feb41f8e11ef77.json +++ b/.sqlx/query-0e5facf744fb0766253f352f51dafe5fecb377c6fd6cbeb6c8feb41f8e11ef77.json @@ -6,24 +6,42 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "owners", + "name": "id" + } + } }, { "name": "name!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "name" + } + } }, { "name": "ntfy_topic!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "ntfy_topic" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, false ] diff --git a/.sqlx/query-107d5702236ede18bc5a88daedd0fff699d5c80399c0d0139e1e89732b58b169.json b/.sqlx/query-107d5702236ede18bc5a88daedd0fff699d5c80399c0d0139e1e89732b58b169.json index 0f3b42b..31bf92b 100644 --- a/.sqlx/query-107d5702236ede18bc5a88daedd0fff699d5c80399c0d0139e1e89732b58b169.json +++ b/.sqlx/query-107d5702236ede18bc5a88daedd0fff699d5c80399c0d0139e1e89732b58b169.json @@ -6,29 +6,53 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "id" + } + } }, { "name": "infohash!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "grabs", + "name": "infohash" + } + } }, { "name": "target_kind!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "grabs", + "name": "target_kind" + } + } }, { "name": "target_id!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "target_id" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, false, false diff --git a/.sqlx/query-115eae1f44b4eed8ea9e75666f1a03b642654d2d99a23b4319bd018e7389cdff.json b/.sqlx/query-115eae1f44b4eed8ea9e75666f1a03b642654d2d99a23b4319bd018e7389cdff.json index dba0c19..18935ea 100644 --- a/.sqlx/query-115eae1f44b4eed8ea9e75666f1a03b642654d2d99a23b4319bd018e7389cdff.json +++ b/.sqlx/query-115eae1f44b4eed8ea9e75666f1a03b642654d2d99a23b4319bd018e7389cdff.json @@ -6,32 +6,68 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "roots", + "name": "id" + } + } }, { "name": "kind!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "kind" + } + } }, { "name": "audience!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "audience" + } + } }, { "name": "path!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "policy_id!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "policies", + "name": "id" + } + } }, { "name": "policy_name!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } } ], "parameters": { diff --git a/.sqlx/query-12386f2efee70bd2a8e59ccccb8f0d7f4d2742acad26efbc7644193966c39325.json b/.sqlx/query-12386f2efee70bd2a8e59ccccb8f0d7f4d2742acad26efbc7644193966c39325.json index e918e44..678c2b8 100644 --- a/.sqlx/query-12386f2efee70bd2a8e59ccccb8f0d7f4d2742acad26efbc7644193966c39325.json +++ b/.sqlx/query-12386f2efee70bd2a8e59ccccb8f0d7f4d2742acad26efbc7644193966c39325.json @@ -6,7 +6,13 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "id" + } + } } ], "parameters": { diff --git a/.sqlx/query-1578519edb5411dd191819f5af9f6510115149c13dae9c31cfb39ccd14e549f9.json b/.sqlx/query-1578519edb5411dd191819f5af9f6510115149c13dae9c31cfb39ccd14e549f9.json index 477aac0..a5a2f3c 100644 --- a/.sqlx/query-1578519edb5411dd191819f5af9f6510115149c13dae9c31cfb39ccd14e549f9.json +++ b/.sqlx/query-1578519edb5411dd191819f5af9f6510115149c13dae9c31cfb39ccd14e549f9.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-1612f76ea880bbd06a396b3043c21bbc42c5d412e31d2303b45813efa52201b9.json b/.sqlx/query-1612f76ea880bbd06a396b3043c21bbc42c5d412e31d2303b45813efa52201b9.json index 059d71c..b95a154 100644 --- a/.sqlx/query-1612f76ea880bbd06a396b3043c21bbc42c5d412e31d2303b45813efa52201b9.json +++ b/.sqlx/query-1612f76ea880bbd06a396b3043c21bbc42c5d412e31d2303b45813efa52201b9.json @@ -6,7 +6,13 @@ { "name": "tmdb_id", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } } ], "parameters": { diff --git a/.sqlx/query-16778fe075b61babcb7f5436113e91bf5f86e9a968d7b61f12daa022cc27f4d1.json b/.sqlx/query-16778fe075b61babcb7f5436113e91bf5f86e9a968d7b61f12daa022cc27f4d1.json index 07f99ac..d5890bd 100644 --- a/.sqlx/query-16778fe075b61babcb7f5436113e91bf5f86e9a968d7b61f12daa022cc27f4d1.json +++ b/.sqlx/query-16778fe075b61babcb7f5436113e91bf5f86e9a968d7b61f12daa022cc27f4d1.json @@ -6,84 +6,174 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, true, diff --git a/.sqlx/query-17c53bd3b8dee72d62a23d41b7bcd6329037fe678ed628ea3ee92d14c7692540.json b/.sqlx/query-17c53bd3b8dee72d62a23d41b7bcd6329037fe678ed628ea3ee92d14c7692540.json index 0eb9c79..1e1d655 100644 --- a/.sqlx/query-17c53bd3b8dee72d62a23d41b7bcd6329037fe678ed628ea3ee92d14c7692540.json +++ b/.sqlx/query-17c53bd3b8dee72d62a23d41b7bcd6329037fe678ed628ea3ee92d14c7692540.json @@ -6,27 +6,52 @@ { "name": "episode_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "path!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } }, { "name": "size!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "media_files", + "name": "size" + } + } }, { "name": "probed?: serde_json::Value", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "probed" + } + } }, { "name": "waiver?: String", "ordinal": 4, - "type_info": "Null" + "type_info": "Null", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-1878841679d1664139dfedffae9d97ed1764321d76022ff55684969be0171cb6.json b/.sqlx/query-1878841679d1664139dfedffae9d97ed1764321d76022ff55684969be0171cb6.json index 953970e..bcd58e4 100644 --- a/.sqlx/query-1878841679d1664139dfedffae9d97ed1764321d76022ff55684969be0171cb6.json +++ b/.sqlx/query-1878841679d1664139dfedffae9d97ed1764321d76022ff55684969be0171cb6.json @@ -6,22 +6,46 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "title!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "episode_id!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "target_id" + } + } } ], "parameters": { diff --git a/.sqlx/query-1b594ec9db13ac2eacffe98d8cb60547c4fbac9959c79a59e2192c5fb448b4bb.json b/.sqlx/query-1b594ec9db13ac2eacffe98d8cb60547c4fbac9959c79a59e2192c5fb448b4bb.json index df3a631..2dd8659 100644 --- a/.sqlx/query-1b594ec9db13ac2eacffe98d8cb60547c4fbac9959c79a59e2192c5fb448b4bb.json +++ b/.sqlx/query-1b594ec9db13ac2eacffe98d8cb60547c4fbac9959c79a59e2192c5fb448b4bb.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-227701e40f5e500f328082537645f1a0ce0d9b7fe9940e573f50c87209ba10a5.json b/.sqlx/query-227701e40f5e500f328082537645f1a0ce0d9b7fe9940e573f50c87209ba10a5.json index a9247cb..8873d12 100644 --- a/.sqlx/query-227701e40f5e500f328082537645f1a0ce0d9b7fe9940e573f50c87209ba10a5.json +++ b/.sqlx/query-227701e40f5e500f328082537645f1a0ce0d9b7fe9940e573f50c87209ba10a5.json @@ -6,72 +6,156 @@ { "name": "overrides!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "overrides" + } + } }, { "name": "root_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "roots", + "name": "id" + } + } }, { "name": "root_kind!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "kind" + } + } }, { "name": "root_audience!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "audience" + } + } }, { "name": "root_path!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "policy_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "policies", + "name": "id" + } + } }, { "name": "policy_name!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } }, { "name": "required_audio!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "required_audio" + } + } }, { "name": "dub_blacklist!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "dub_blacklist" + } + } }, { "name": "hdr_rules!: String", "ordinal": 9, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "hdr_rules" + } + } }, { "name": "size_bands!: String", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "size_bands" + } + } }, { "name": "resolution_pref!: String", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "resolution_pref" + } + } }, { "name": "source_weights!: String", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "source_weights" + } + } }, { "name": "score_weights!: String", "ordinal": 13, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "score_weights" + } + } } ], "parameters": { diff --git a/.sqlx/query-245a6a48bac54b33a4d6fd70dadc46633a8834822ec4c13e63266bec1f7d0ada.json b/.sqlx/query-245a6a48bac54b33a4d6fd70dadc46633a8834822ec4c13e63266bec1f7d0ada.json index 6507e42..fe677d6 100644 --- a/.sqlx/query-245a6a48bac54b33a4d6fd70dadc46633a8834822ec4c13e63266bec1f7d0ada.json +++ b/.sqlx/query-245a6a48bac54b33a4d6fd70dadc46633a8834822ec4c13e63266bec1f7d0ada.json @@ -6,39 +6,75 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "roots", + "name": "id" + } + } }, { "name": "kind!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "kind" + } + } }, { "name": "audience!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "audience" + } + } }, { "name": "path!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "policy_id", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "roots", + "name": "policy_id" + } + } }, { "name": "policy_name!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-268dc95276f57bf3c457c98d172ef5e6f9acf430ae419ca68dc3800b0390b5b7.json b/.sqlx/query-268dc95276f57bf3c457c98d172ef5e6f9acf430ae419ca68dc3800b0390b5b7.json index a16ec64..e2b521a 100644 --- a/.sqlx/query-268dc95276f57bf3c457c98d172ef5e6f9acf430ae419ca68dc3800b0390b5b7.json +++ b/.sqlx/query-268dc95276f57bf3c457c98d172ef5e6f9acf430ae419ca68dc3800b0390b5b7.json @@ -6,47 +6,101 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "blocked!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } }, { "name": "poster_path", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 8, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "series", + "name": "vote_average" + } + } } ], "parameters": { diff --git a/.sqlx/query-29d6fdda533e0552f5da24a8e5180e5935b3550ee2c024e09aef9b76fd47a708.json b/.sqlx/query-29d6fdda533e0552f5da24a8e5180e5935b3550ee2c024e09aef9b76fd47a708.json index 92132e9..361c234 100644 --- a/.sqlx/query-29d6fdda533e0552f5da24a8e5180e5935b3550ee2c024e09aef9b76fd47a708.json +++ b/.sqlx/query-29d6fdda533e0552f5da24a8e5180e5935b3550ee2c024e09aef9b76fd47a708.json @@ -6,37 +6,79 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "episode_id!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_number!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "episode_number!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } } ], "parameters": { @@ -47,7 +89,7 @@ false, false, true, - true, + false, false, false ] diff --git a/.sqlx/query-2d0b92ba9aa4bc257286a67f0d37e2182c4478153c9096c1522c5ab23ad472e9.json b/.sqlx/query-2d0b92ba9aa4bc257286a67f0d37e2182c4478153c9096c1522c5ab23ad472e9.json index ed58b52..c5562b2 100644 --- a/.sqlx/query-2d0b92ba9aa4bc257286a67f0d37e2182c4478153c9096c1522c5ab23ad472e9.json +++ b/.sqlx/query-2d0b92ba9aa4bc257286a67f0d37e2182c4478153c9096c1522c5ab23ad472e9.json @@ -6,14 +6,20 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "id" + } + } } ], "parameters": { "Right": 4 }, "nullable": [ - true + null ] }, "hash": "2d0b92ba9aa4bc257286a67f0d37e2182c4478153c9096c1522c5ab23ad472e9" diff --git a/.sqlx/query-321ce3a18bd627b357ba6a98b958a8efb0c7af09fa6cff4b1f31fb963da58bd5.json b/.sqlx/query-321ce3a18bd627b357ba6a98b958a8efb0c7af09fa6cff4b1f31fb963da58bd5.json index 70d7aae..a742b9f 100644 --- a/.sqlx/query-321ce3a18bd627b357ba6a98b958a8efb0c7af09fa6cff4b1f31fb963da58bd5.json +++ b/.sqlx/query-321ce3a18bd627b357ba6a98b958a8efb0c7af09fa6cff4b1f31fb963da58bd5.json @@ -6,7 +6,13 @@ { "name": "title!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } } ], "parameters": { diff --git a/.sqlx/query-32c562fdb70cabb3265e351583b6ac1579490b29da670fe9062117451911576a.json b/.sqlx/query-32c562fdb70cabb3265e351583b6ac1579490b29da670fe9062117451911576a.json index 52fd192..4d22a12 100644 --- a/.sqlx/query-32c562fdb70cabb3265e351583b6ac1579490b29da670fe9062117451911576a.json +++ b/.sqlx/query-32c562fdb70cabb3265e351583b6ac1579490b29da670fe9062117451911576a.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-33de7e17f5defa761de0d6e174dca40e2d883b9990bec497355422f5b5e6dfe5.json b/.sqlx/query-33de7e17f5defa761de0d6e174dca40e2d883b9990bec497355422f5b5e6dfe5.json index 2d5e8f5..8b5d570 100644 --- a/.sqlx/query-33de7e17f5defa761de0d6e174dca40e2d883b9990bec497355422f5b5e6dfe5.json +++ b/.sqlx/query-33de7e17f5defa761de0d6e174dca40e2d883b9990bec497355422f5b5e6dfe5.json @@ -6,56 +6,94 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "air_date", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "search_attempts!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } }, { "name": "has_file!: bool", "ordinal": 6, - "type_info": "Null" + "type_info": "Integer", + "origin": "Expression" }, { "name": "in_flight!: bool", "ordinal": 7, - "type_info": "Null" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, true, false, false, true, - null, - null + false, + false ] }, "hash": "33de7e17f5defa761de0d6e174dca40e2d883b9990bec497355422f5b5e6dfe5" diff --git a/.sqlx/query-3437ff6e5b0262e3a298d3883ef16583c0dd62e9a25e955866c3b8690489443e.json b/.sqlx/query-3437ff6e5b0262e3a298d3883ef16583c0dd62e9a25e955866c3b8690489443e.json index 867379a..b9717c7 100644 --- a/.sqlx/query-3437ff6e5b0262e3a298d3883ef16583c0dd62e9a25e955866c3b8690489443e.json +++ b/.sqlx/query-3437ff6e5b0262e3a298d3883ef16583c0dd62e9a25e955866c3b8690489443e.json @@ -6,47 +6,101 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "policies", + "name": "id" + } + } }, { "name": "name!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } }, { "name": "required_audio!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "required_audio" + } + } }, { "name": "dub_blacklist!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "dub_blacklist" + } + } }, { "name": "hdr_rules!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "hdr_rules" + } + } }, { "name": "size_bands!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "size_bands" + } + } }, { "name": "resolution_pref!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "resolution_pref" + } + } }, { "name": "source_weights!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "source_weights" + } + } }, { "name": "score_weights!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "score_weights" + } + } } ], "parameters": { diff --git a/.sqlx/query-3a02f30ced2765dcdbf7575d680c1b4ab1db895065f65db4a99bb0a4dc6e71ed.json b/.sqlx/query-3a02f30ced2765dcdbf7575d680c1b4ab1db895065f65db4a99bb0a4dc6e71ed.json index 207dad9..93236c3 100644 --- a/.sqlx/query-3a02f30ced2765dcdbf7575d680c1b4ab1db895065f65db4a99bb0a4dc6e71ed.json +++ b/.sqlx/query-3a02f30ced2765dcdbf7575d680c1b4ab1db895065f65db4a99bb0a4dc6e71ed.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { @@ -69,7 +141,7 @@ }, "nullable": [ false, - true, + false, false, false, false, diff --git a/.sqlx/query-3adb45309bbd3f1de7d968abd8e5a65d4c6a2b890a54af9d5d2d956cca783892.json b/.sqlx/query-3adb45309bbd3f1de7d968abd8e5a65d4c6a2b890a54af9d5d2d956cca783892.json index a9d0770..a72e3fc 100644 --- a/.sqlx/query-3adb45309bbd3f1de7d968abd8e5a65d4c6a2b890a54af9d5d2d956cca783892.json +++ b/.sqlx/query-3adb45309bbd3f1de7d968abd8e5a65d4c6a2b890a54af9d5d2d956cca783892.json @@ -6,52 +6,102 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "wanted!: bool", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "root_path!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "created_at!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "created_at" + } + } }, { "name": "file_count!: i64", "ordinal": 8, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" }, { "name": "size_on_disk!: i64", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-3b518839d694b872f83005ca059d45d048a8c44efd78b75e08e74299fed072ea.json b/.sqlx/query-3b518839d694b872f83005ca059d45d048a8c44efd78b75e08e74299fed072ea.json index ec46c2b..2e4dadf 100644 --- a/.sqlx/query-3b518839d694b872f83005ca059d45d048a8c44efd78b75e08e74299fed072ea.json +++ b/.sqlx/query-3b518839d694b872f83005ca059d45d048a8c44efd78b75e08e74299fed072ea.json @@ -6,72 +6,156 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "tvdb_id", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "title!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "auto_track!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "auto_track" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "overrides" + } + } }, { "name": "upstream_ended!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "upstream_ended" + } + } }, { "name": "blocked!: bool", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } }, { "name": "poster_path", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 12, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "series", + "name": "vote_average" + } + } }, { "name": "metadata_refreshed_at", "ordinal": 13, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "metadata_refreshed_at" + } + } } ], "parameters": { diff --git a/.sqlx/query-3bc011ffdc966aa9ec540286bb728c4adf0257f1ab78a1cd23d27dd4dc2e0be1.json b/.sqlx/query-3bc011ffdc966aa9ec540286bb728c4adf0257f1ab78a1cd23d27dd4dc2e0be1.json index 7813a11..56df1d7 100644 --- a/.sqlx/query-3bc011ffdc966aa9ec540286bb728c4adf0257f1ab78a1cd23d27dd4dc2e0be1.json +++ b/.sqlx/query-3bc011ffdc966aa9ec540286bb728c4adf0257f1ab78a1cd23d27dd4dc2e0be1.json @@ -6,19 +6,31 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "tracked!: bool", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "tracked" + } + } } ], "parameters": { "Right": 2 }, "nullable": [ - true, + false, false ] }, diff --git a/.sqlx/query-3c4fdf30427dc972e695940a7a98cbc7c1da617502c818e5dbe0f35c87b175d5.json b/.sqlx/query-3c4fdf30427dc972e695940a7a98cbc7c1da617502c818e5dbe0f35c87b175d5.json index 3095604..8c42e46 100644 --- a/.sqlx/query-3c4fdf30427dc972e695940a7a98cbc7c1da617502c818e5dbe0f35c87b175d5.json +++ b/.sqlx/query-3c4fdf30427dc972e695940a7a98cbc7c1da617502c818e5dbe0f35c87b175d5.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { diff --git a/.sqlx/query-3ee558a18edee423eab6fe572f2a6f5bad8db69e0827e620264700f4affb3652.json b/.sqlx/query-3ee558a18edee423eab6fe572f2a6f5bad8db69e0827e620264700f4affb3652.json index eec33a5..e1e9246 100644 --- a/.sqlx/query-3ee558a18edee423eab6fe572f2a6f5bad8db69e0827e620264700f4affb3652.json +++ b/.sqlx/query-3ee558a18edee423eab6fe572f2a6f5bad8db69e0827e620264700f4affb3652.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { @@ -69,7 +141,7 @@ }, "nullable": [ false, - true, + false, false, false, false, diff --git a/.sqlx/query-3fc7d7e39520dc60fa81da21a4cae0c4a718e95f9718629f0aba9f3d00d179e1.json b/.sqlx/query-3fc7d7e39520dc60fa81da21a4cae0c4a718e95f9718629f0aba9f3d00d179e1.json index 8c94688..87164d4 100644 --- a/.sqlx/query-3fc7d7e39520dc60fa81da21a4cae0c4a718e95f9718629f0aba9f3d00d179e1.json +++ b/.sqlx/query-3fc7d7e39520dc60fa81da21a4cae0c4a718e95f9718629f0aba9f3d00d179e1.json @@ -6,12 +6,19 @@ { "name": "air_date", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "on_disk!: bool", "ordinal": 1, - "type_info": "Null" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { @@ -19,7 +26,7 @@ }, "nullable": [ true, - null + false ] }, "hash": "3fc7d7e39520dc60fa81da21a4cae0c4a718e95f9718629f0aba9f3d00d179e1" diff --git a/.sqlx/query-40f68614d733827b00499cb65a4c9b0219c18d2321720c060e1d22bb398d7d3d.json b/.sqlx/query-40f68614d733827b00499cb65a4c9b0219c18d2321720c060e1d22bb398d7d3d.json index 4b60ee0..c861b23 100644 --- a/.sqlx/query-40f68614d733827b00499cb65a4c9b0219c18d2321720c060e1d22bb398d7d3d.json +++ b/.sqlx/query-40f68614d733827b00499cb65a4c9b0219c18d2321720c060e1d22bb398d7d3d.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-4322e03b45c5a7ec6be51a33107586dc4f7682ab4edbd483d36fedbc985a27f4.json b/.sqlx/query-4322e03b45c5a7ec6be51a33107586dc4f7682ab4edbd483d36fedbc985a27f4.json index 07fecc6..8ef6de3 100644 --- a/.sqlx/query-4322e03b45c5a7ec6be51a33107586dc4f7682ab4edbd483d36fedbc985a27f4.json +++ b/.sqlx/query-4322e03b45c5a7ec6be51a33107586dc4f7682ab4edbd483d36fedbc985a27f4.json @@ -6,7 +6,13 @@ { "name": "title!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } } ], "parameters": { diff --git a/.sqlx/query-4492267683b60d3104c3bf315cca56981adbecbb4722305cdad7241f4f0d8fec.json b/.sqlx/query-4492267683b60d3104c3bf315cca56981adbecbb4722305cdad7241f4f0d8fec.json index dce9b1e..1d53887 100644 --- a/.sqlx/query-4492267683b60d3104c3bf315cca56981adbecbb4722305cdad7241f4f0d8fec.json +++ b/.sqlx/query-4492267683b60d3104c3bf315cca56981adbecbb4722305cdad7241f4f0d8fec.json @@ -6,7 +6,8 @@ { "name": "count!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-4ddb143ab51ca61ac782f22cff84f01f7d58d8a424310ae0743fb0c91577665e.json b/.sqlx/query-4ddb143ab51ca61ac782f22cff84f01f7d58d8a424310ae0743fb0c91577665e.json index fd6ce99..a1a303a 100644 --- a/.sqlx/query-4ddb143ab51ca61ac782f22cff84f01f7d58d8a424310ae0743fb0c91577665e.json +++ b/.sqlx/query-4ddb143ab51ca61ac782f22cff84f01f7d58d8a424310ae0743fb0c91577665e.json @@ -6,22 +6,46 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "title!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "season_id!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "target_id" + } + } } ], "parameters": { diff --git a/.sqlx/query-500cfed4016c5956bc73f2c9bd14f36e1c32672bd171009fb164e6593f98ec31.json b/.sqlx/query-500cfed4016c5956bc73f2c9bd14f36e1c32672bd171009fb164e6593f98ec31.json index f9c6de9..f4e21a5 100644 --- a/.sqlx/query-500cfed4016c5956bc73f2c9bd14f36e1c32672bd171009fb164e6593f98ec31.json +++ b/.sqlx/query-500cfed4016c5956bc73f2c9bd14f36e1c32672bd171009fb164e6593f98ec31.json @@ -6,12 +6,24 @@ { "name": "title_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "title_owners", + "name": "title_id" + } + } }, { "name": "owner_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "title_owners", + "name": "owner_id" + } + } } ], "parameters": { diff --git a/.sqlx/query-519d77ceec9ce13aa65abed28ea32db6568360e8c3fc7e31e968508d88c1bc67.json b/.sqlx/query-519d77ceec9ce13aa65abed28ea32db6568360e8c3fc7e31e968508d88c1bc67.json index 327079e..6132232 100644 --- a/.sqlx/query-519d77ceec9ce13aa65abed28ea32db6568360e8c3fc7e31e968508d88c1bc67.json +++ b/.sqlx/query-519d77ceec9ce13aa65abed28ea32db6568360e8c3fc7e31e968508d88c1bc67.json @@ -6,32 +6,63 @@ { "name": "season_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "season_number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "series_title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "series_tvdb_id", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "is_gap!: bool", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-53a2b5bcc6a753b8ef0a5f4110a69cc8ece28966cfa003f4bfe1190c55831a23.json b/.sqlx/query-53a2b5bcc6a753b8ef0a5f4110a69cc8ece28966cfa003f4bfe1190c55831a23.json index 45d5138..3386ccf 100644 --- a/.sqlx/query-53a2b5bcc6a753b8ef0a5f4110a69cc8ece28966cfa003f4bfe1190c55831a23.json +++ b/.sqlx/query-53a2b5bcc6a753b8ef0a5f4110a69cc8ece28966cfa003f4bfe1190c55831a23.json @@ -6,77 +6,167 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { diff --git a/.sqlx/query-57fda2b3aea40a2a5b26fdb9e19fedb04380e047c4e0b2fb1def0b40faa02432.json b/.sqlx/query-57fda2b3aea40a2a5b26fdb9e19fedb04380e047c4e0b2fb1def0b40faa02432.json index d060bf1..781c645 100644 --- a/.sqlx/query-57fda2b3aea40a2a5b26fdb9e19fedb04380e047c4e0b2fb1def0b40faa02432.json +++ b/.sqlx/query-57fda2b3aea40a2a5b26fdb9e19fedb04380e047c4e0b2fb1def0b40faa02432.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-580e3bf81062d253fd08c9a4e1a879a0e57d6357ab9b278503cdf8fb5252886a.json b/.sqlx/query-580e3bf81062d253fd08c9a4e1a879a0e57d6357ab9b278503cdf8fb5252886a.json index ac2fc80..fddb5c9 100644 --- a/.sqlx/query-580e3bf81062d253fd08c9a4e1a879a0e57d6357ab9b278503cdf8fb5252886a.json +++ b/.sqlx/query-580e3bf81062d253fd08c9a4e1a879a0e57d6357ab9b278503cdf8fb5252886a.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { @@ -69,7 +141,7 @@ }, "nullable": [ false, - true, + false, false, false, false, diff --git a/.sqlx/query-5b059a2f93a3fb46b6c55e0b665aa55931045af290894e396b3b410123bfd539.json b/.sqlx/query-5b059a2f93a3fb46b6c55e0b665aa55931045af290894e396b3b410123bfd539.json index be2bede..4fbc553 100644 --- a/.sqlx/query-5b059a2f93a3fb46b6c55e0b665aa55931045af290894e396b3b410123bfd539.json +++ b/.sqlx/query-5b059a2f93a3fb46b6c55e0b665aa55931045af290894e396b3b410123bfd539.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-6394f95e9e437ed3c46bb325c2d946ce75f94e00bf0d3aa6ac54811080725cc2.json b/.sqlx/query-6394f95e9e437ed3c46bb325c2d946ce75f94e00bf0d3aa6ac54811080725cc2.json index dafa631..4034b84 100644 --- a/.sqlx/query-6394f95e9e437ed3c46bb325c2d946ce75f94e00bf0d3aa6ac54811080725cc2.json +++ b/.sqlx/query-6394f95e9e437ed3c46bb325c2d946ce75f94e00bf0d3aa6ac54811080725cc2.json @@ -6,77 +6,167 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { diff --git a/.sqlx/query-68f44b694f26c51f54b8ebb12c3341bf16a61029fb82d621930c137b2895b381.json b/.sqlx/query-68f44b694f26c51f54b8ebb12c3341bf16a61029fb82d621930c137b2895b381.json index 09cbcd8..d43f753 100644 --- a/.sqlx/query-68f44b694f26c51f54b8ebb12c3341bf16a61029fb82d621930c137b2895b381.json +++ b/.sqlx/query-68f44b694f26c51f54b8ebb12c3341bf16a61029fb82d621930c137b2895b381.json @@ -6,24 +6,42 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "owners", + "name": "id" + } + } }, { "name": "name!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "name" + } + } }, { "name": "ntfy_topic!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "ntfy_topic" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false ] diff --git a/.sqlx/query-6b34f7c8d536d505c166f5b9b5a4db5fa3008b08e8e173b5ca21f51cca1dd1ee.json b/.sqlx/query-6b34f7c8d536d505c166f5b9b5a4db5fa3008b08e8e173b5ca21f51cca1dd1ee.json index 692277d..27ba8a3 100644 --- a/.sqlx/query-6b34f7c8d536d505c166f5b9b5a4db5fa3008b08e8e173b5ca21f51cca1dd1ee.json +++ b/.sqlx/query-6b34f7c8d536d505c166f5b9b5a4db5fa3008b08e8e173b5ca21f51cca1dd1ee.json @@ -6,42 +6,90 @@ { "name": "name!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } }, { "name": "required_audio!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "required_audio" + } + } }, { "name": "dub_blacklist!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "dub_blacklist" + } + } }, { "name": "hdr_rules!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "hdr_rules" + } + } }, { "name": "size_bands!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "size_bands" + } + } }, { "name": "resolution_pref!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "resolution_pref" + } + } }, { "name": "source_weights!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "source_weights" + } + } }, { "name": "score_weights!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "score_weights" + } + } } ], "parameters": { diff --git a/.sqlx/query-6bda2cef202e94b64f7bbcf88bbed823cca790c1439d94fba1c7dcea024d9467.json b/.sqlx/query-6bda2cef202e94b64f7bbcf88bbed823cca790c1439d94fba1c7dcea024d9467.json index ae6ada8..87da56c 100644 --- a/.sqlx/query-6bda2cef202e94b64f7bbcf88bbed823cca790c1439d94fba1c7dcea024d9467.json +++ b/.sqlx/query-6bda2cef202e94b64f7bbcf88bbed823cca790c1439d94fba1c7dcea024d9467.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { diff --git a/.sqlx/query-6f41ffaa0679bbbe51ea5d54fbdbf757e0f85d1d86ee6322253f223fae49265f.json b/.sqlx/query-6f41ffaa0679bbbe51ea5d54fbdbf757e0f85d1d86ee6322253f223fae49265f.json index 183928e..28e6aee 100644 --- a/.sqlx/query-6f41ffaa0679bbbe51ea5d54fbdbf757e0f85d1d86ee6322253f223fae49265f.json +++ b/.sqlx/query-6f41ffaa0679bbbe51ea5d54fbdbf757e0f85d1d86ee6322253f223fae49265f.json @@ -6,47 +6,101 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "blocked!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } }, { "name": "poster_path", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 8, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "series", + "name": "vote_average" + } + } } ], "parameters": { diff --git a/.sqlx/query-6fb25e97d46957c92475679190d71f115f9bf97edbc5ff6621bcbe90d60f5644.json b/.sqlx/query-6fb25e97d46957c92475679190d71f115f9bf97edbc5ff6621bcbe90d60f5644.json index c6982bd..509e530 100644 --- a/.sqlx/query-6fb25e97d46957c92475679190d71f115f9bf97edbc5ff6621bcbe90d60f5644.json +++ b/.sqlx/query-6fb25e97d46957c92475679190d71f115f9bf97edbc5ff6621bcbe90d60f5644.json @@ -6,67 +6,145 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "tvdb_id", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "title!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "auto_track!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "auto_track" + } + } }, { "name": "upstream_ended!: bool", "ordinal": 8, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "upstream_ended" + } + } }, { "name": "metadata_refreshed_at", "ordinal": 9, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "metadata_refreshed_at" + } + } }, { "name": "poster_path", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } }, { "name": "backdrop_path", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "backdrop_path" + } + } }, { "name": "vote_average", "ordinal": 12, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "series", + "name": "vote_average" + } + } } ], "parameters": { diff --git a/.sqlx/query-729af2ef365c572428b2ef1edcd0e287a243607735185be7d67fe3f7d7c3c506.json b/.sqlx/query-729af2ef365c572428b2ef1edcd0e287a243607735185be7d67fe3f7d7c3c506.json index 70e0708..2865972 100644 --- a/.sqlx/query-729af2ef365c572428b2ef1edcd0e287a243607735185be7d67fe3f7d7c3c506.json +++ b/.sqlx/query-729af2ef365c572428b2ef1edcd0e287a243607735185be7d67fe3f7d7c3c506.json @@ -6,72 +6,156 @@ { "name": "overrides!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "overrides" + } + } }, { "name": "root_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "roots", + "name": "id" + } + } }, { "name": "root_kind!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "kind" + } + } }, { "name": "root_audience!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "audience" + } + } }, { "name": "root_path!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "policy_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "policies", + "name": "id" + } + } }, { "name": "policy_name!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } }, { "name": "required_audio!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "required_audio" + } + } }, { "name": "dub_blacklist!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "dub_blacklist" + } + } }, { "name": "hdr_rules!: String", "ordinal": 9, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "hdr_rules" + } + } }, { "name": "size_bands!: String", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "size_bands" + } + } }, { "name": "resolution_pref!: String", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "resolution_pref" + } + } }, { "name": "source_weights!: String", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "source_weights" + } + } }, { "name": "score_weights!: String", "ordinal": 13, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "score_weights" + } + } } ], "parameters": { diff --git a/.sqlx/query-7505ce51ba10ce886c5b6888e8d27aecea4ee2f5412894dfe99057e8e88c7db3.json b/.sqlx/query-7505ce51ba10ce886c5b6888e8d27aecea4ee2f5412894dfe99057e8e88c7db3.json index 57c0166..498710c 100644 --- a/.sqlx/query-7505ce51ba10ce886c5b6888e8d27aecea4ee2f5412894dfe99057e8e88c7db3.json +++ b/.sqlx/query-7505ce51ba10ce886c5b6888e8d27aecea4ee2f5412894dfe99057e8e88c7db3.json @@ -6,7 +6,8 @@ { "name": "count!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-7930e2d10b25627dcbf81f60a5ac077c27b647a6f0b411e13105398a2963cd51.json b/.sqlx/query-7930e2d10b25627dcbf81f60a5ac077c27b647a6f0b411e13105398a2963cd51.json index bdd7c6c..feed8c1 100644 --- a/.sqlx/query-7930e2d10b25627dcbf81f60a5ac077c27b647a6f0b411e13105398a2963cd51.json +++ b/.sqlx/query-7930e2d10b25627dcbf81f60a5ac077c27b647a6f0b411e13105398a2963cd51.json @@ -6,32 +6,68 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "season_id!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "target_id" + } + } }, { "name": "season_number!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } } ], "parameters": { diff --git a/.sqlx/query-79411288157a57119c31625aa46e23d46571c894fa42a0d109079c738dee97ac.json b/.sqlx/query-79411288157a57119c31625aa46e23d46571c894fa42a0d109079c738dee97ac.json index abd1bf9..89390b1 100644 --- a/.sqlx/query-79411288157a57119c31625aa46e23d46571c894fa42a0d109079c738dee97ac.json +++ b/.sqlx/query-79411288157a57119c31625aa46e23d46571c894fa42a0d109079c738dee97ac.json @@ -6,17 +6,35 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "tracked!: bool", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "tracked" + } + } } ], "parameters": { diff --git a/.sqlx/query-7963cce11a588f8b8697e596da24802e4a5556c388b84fe7a6102444a887c2cc.json b/.sqlx/query-7963cce11a588f8b8697e596da24802e4a5556c388b84fe7a6102444a887c2cc.json index 21eaf64..7559915 100644 --- a/.sqlx/query-7963cce11a588f8b8697e596da24802e4a5556c388b84fe7a6102444a887c2cc.json +++ b/.sqlx/query-7963cce11a588f8b8697e596da24802e4a5556c388b84fe7a6102444a887c2cc.json @@ -6,59 +6,119 @@ { "name": "grab_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "id" + } + } }, { "name": "infohash!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "grabs", + "name": "infohash" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "series_id!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "series_tmdb_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "series_title!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "series_year", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "release_name!: String", "ordinal": 9, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-7c638b148b697645d250fe43cf7fcc049e3754e5b1837ff54289e8908523d40c.json b/.sqlx/query-7c638b148b697645d250fe43cf7fcc049e3754e5b1837ff54289e8908523d40c.json index 064d958..3e4ddad 100644 --- a/.sqlx/query-7c638b148b697645d250fe43cf7fcc049e3754e5b1837ff54289e8908523d40c.json +++ b/.sqlx/query-7c638b148b697645d250fe43cf7fcc049e3754e5b1837ff54289e8908523d40c.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { @@ -69,7 +141,7 @@ }, "nullable": [ false, - true, + false, false, false, false, diff --git a/.sqlx/query-7caab9e1f6676b11d8053d48df86cf3b60362a5c2007793fe1c2ff5a57fa0c40.json b/.sqlx/query-7caab9e1f6676b11d8053d48df86cf3b60362a5c2007793fe1c2ff5a57fa0c40.json index 3af084b..2822bee 100644 --- a/.sqlx/query-7caab9e1f6676b11d8053d48df86cf3b60362a5c2007793fe1c2ff5a57fa0c40.json +++ b/.sqlx/query-7caab9e1f6676b11d8053d48df86cf3b60362a5c2007793fe1c2ff5a57fa0c40.json @@ -6,52 +6,107 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "search_attempts!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "digital_release", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "digital_release" + } + } }, { "name": "metadata_refreshed_at", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "metadata_refreshed_at" + } + } }, { "name": "is_gap!: i64", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-7f94d0bad8dd346c5e930c2606c8643e732850b0a1ec331e216c3aedf6987ad4.json b/.sqlx/query-7f94d0bad8dd346c5e930c2606c8643e732850b0a1ec331e216c3aedf6987ad4.json index cb7b214..70f41db 100644 --- a/.sqlx/query-7f94d0bad8dd346c5e930c2606c8643e732850b0a1ec331e216c3aedf6987ad4.json +++ b/.sqlx/query-7f94d0bad8dd346c5e930c2606c8643e732850b0a1ec331e216c3aedf6987ad4.json @@ -6,64 +6,130 @@ { "name": "grab_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "id" + } + } }, { "name": "infohash!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "grabs", + "name": "infohash" + } + } }, { "name": "episode_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "season_number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "series_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "series_tmdb_id!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "series_title!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "series_year", "ordinal": 8, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 9, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "release_name!: String", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-7fa29678b941e5eb680db2dcb11ed1912b950a84d1ce7eaa406339b024aac82d.json b/.sqlx/query-7fa29678b941e5eb680db2dcb11ed1912b950a84d1ce7eaa406339b024aac82d.json index f601041..f4241fd 100644 --- a/.sqlx/query-7fa29678b941e5eb680db2dcb11ed1912b950a84d1ce7eaa406339b024aac82d.json +++ b/.sqlx/query-7fa29678b941e5eb680db2dcb11ed1912b950a84d1ce7eaa406339b024aac82d.json @@ -6,7 +6,13 @@ { "name": "original_language", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } } ], "parameters": { diff --git a/.sqlx/query-80210068b65f85f4e6840a98defa7271c33b1da4ec9f6389b8d7212be80c97ea.json b/.sqlx/query-80210068b65f85f4e6840a98defa7271c33b1da4ec9f6389b8d7212be80c97ea.json index eae3f95..6d82fbe 100644 --- a/.sqlx/query-80210068b65f85f4e6840a98defa7271c33b1da4ec9f6389b8d7212be80c97ea.json +++ b/.sqlx/query-80210068b65f85f4e6840a98defa7271c33b1da4ec9f6389b8d7212be80c97ea.json @@ -6,57 +6,113 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "air_date", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "has_file!: bool", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" }, { "name": "in_flight!: bool", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" }, { "name": "season_id!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "season_number!: i64", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "series_title!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "series_tvdb_id", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "original_language", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } } ], "parameters": { diff --git a/.sqlx/query-8c61a4e4f6b3b8cc02cd4b6289f6889889ab06d9a77f26c4d745659c8e1f4a07.json b/.sqlx/query-8c61a4e4f6b3b8cc02cd4b6289f6889889ab06d9a77f26c4d745659c8e1f4a07.json index 7ae4af3..b80174d 100644 --- a/.sqlx/query-8c61a4e4f6b3b8cc02cd4b6289f6889889ab06d9a77f26c4d745659c8e1f4a07.json +++ b/.sqlx/query-8c61a4e4f6b3b8cc02cd4b6289f6889889ab06d9a77f26c4d745659c8e1f4a07.json @@ -6,77 +6,167 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { diff --git a/.sqlx/query-8cdf7339d395e21af86bd71e011e5681e0c00b1f9692a7142043067ff64a00a1.json b/.sqlx/query-8cdf7339d395e21af86bd71e011e5681e0c00b1f9692a7142043067ff64a00a1.json index 548b46d..92c0152 100644 --- a/.sqlx/query-8cdf7339d395e21af86bd71e011e5681e0c00b1f9692a7142043067ff64a00a1.json +++ b/.sqlx/query-8cdf7339d395e21af86bd71e011e5681e0c00b1f9692a7142043067ff64a00a1.json @@ -6,7 +6,8 @@ { "name": "failed!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-8ce7b19c7e6524f4d14c92401d4f20c69e83f05f9acb568bc42270c7753b74cd.json b/.sqlx/query-8ce7b19c7e6524f4d14c92401d4f20c69e83f05f9acb568bc42270c7753b74cd.json index 3c6e26e..6b4f038 100644 --- a/.sqlx/query-8ce7b19c7e6524f4d14c92401d4f20c69e83f05f9acb568bc42270c7753b74cd.json +++ b/.sqlx/query-8ce7b19c7e6524f4d14c92401d4f20c69e83f05f9acb568bc42270c7753b74cd.json @@ -6,24 +6,42 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "owners", + "name": "id" + } + } }, { "name": "name!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "name" + } + } }, { "name": "ntfy_topic!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "ntfy_topic" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false ] diff --git a/.sqlx/query-8ff544fa49afd3fa76952995b2b0767a8603335eaed198ebd4648d9a38b5984f.json b/.sqlx/query-8ff544fa49afd3fa76952995b2b0767a8603335eaed198ebd4648d9a38b5984f.json index e9d2d84..e36897a 100644 --- a/.sqlx/query-8ff544fa49afd3fa76952995b2b0767a8603335eaed198ebd4648d9a38b5984f.json +++ b/.sqlx/query-8ff544fa49afd3fa76952995b2b0767a8603335eaed198ebd4648d9a38b5984f.json @@ -6,7 +6,13 @@ { "name": "blocked!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } } ], "parameters": { diff --git a/.sqlx/query-90799bcbc272af8890296ca4f2d90843a8bc71c820b56847e0a86a9497f03952.json b/.sqlx/query-90799bcbc272af8890296ca4f2d90843a8bc71c820b56847e0a86a9497f03952.json index 6061b48..5f92eed 100644 --- a/.sqlx/query-90799bcbc272af8890296ca4f2d90843a8bc71c820b56847e0a86a9497f03952.json +++ b/.sqlx/query-90799bcbc272af8890296ca4f2d90843a8bc71c820b56847e0a86a9497f03952.json @@ -6,32 +6,68 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "episode!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "season!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "series_tmdb_id!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "series_title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } } ], "parameters": { diff --git a/.sqlx/query-90813e43938fcf91b96e444354a5a41b345fec599ba1562ae1fdf617ef49ff54.json b/.sqlx/query-90813e43938fcf91b96e444354a5a41b345fec599ba1562ae1fdf617ef49ff54.json index efa7d3a..450d95a 100644 --- a/.sqlx/query-90813e43938fcf91b96e444354a5a41b345fec599ba1562ae1fdf617ef49ff54.json +++ b/.sqlx/query-90813e43938fcf91b96e444354a5a41b345fec599ba1562ae1fdf617ef49ff54.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { diff --git a/.sqlx/query-91d1ee1e8e206569b57d2a699228139d45cb658b94103f677dfa49dcd9f0e07d.json b/.sqlx/query-91d1ee1e8e206569b57d2a699228139d45cb658b94103f677dfa49dcd9f0e07d.json index 1c3c1b6..ae097a4 100644 --- a/.sqlx/query-91d1ee1e8e206569b57d2a699228139d45cb658b94103f677dfa49dcd9f0e07d.json +++ b/.sqlx/query-91d1ee1e8e206569b57d2a699228139d45cb658b94103f677dfa49dcd9f0e07d.json @@ -6,17 +6,35 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "title!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } } ], "parameters": { diff --git a/.sqlx/query-97033814ba641d94878f16f23224b49cbad1d65ea0f130d091995bfed87cb844.json b/.sqlx/query-97033814ba641d94878f16f23224b49cbad1d65ea0f130d091995bfed87cb844.json index bb3138e..9b9d1c3 100644 --- a/.sqlx/query-97033814ba641d94878f16f23224b49cbad1d65ea0f130d091995bfed87cb844.json +++ b/.sqlx/query-97033814ba641d94878f16f23224b49cbad1d65ea0f130d091995bfed87cb844.json @@ -6,62 +6,134 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "season_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "season_id" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "wanted!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "wanted" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "state" + } + } }, { "name": "vanished!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "last_searched_at" + } + } } ], "parameters": { @@ -69,7 +141,7 @@ }, "nullable": [ false, - true, + false, false, false, false, diff --git a/.sqlx/query-9755322057875ad786326cf533bff5d6b78e7844476731b309a3859287ff8c2f.json b/.sqlx/query-9755322057875ad786326cf533bff5d6b78e7844476731b309a3859287ff8c2f.json index 1596a41..711fc61 100644 --- a/.sqlx/query-9755322057875ad786326cf533bff5d6b78e7844476731b309a3859287ff8c2f.json +++ b/.sqlx/query-9755322057875ad786326cf533bff5d6b78e7844476731b309a3859287ff8c2f.json @@ -6,72 +6,156 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "tvdb_id", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "title!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "auto_track!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "auto_track" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "overrides" + } + } }, { "name": "upstream_ended!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "upstream_ended" + } + } }, { "name": "blocked!: bool", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } }, { "name": "poster_path", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 12, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "series", + "name": "vote_average" + } + } }, { "name": "metadata_refreshed_at", "ordinal": 13, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "metadata_refreshed_at" + } + } } ], "parameters": { diff --git a/.sqlx/query-9758f269a51528417e6f75785441ade93fed055b6bcc4b5e7f579be5cc0aa829.json b/.sqlx/query-9758f269a51528417e6f75785441ade93fed055b6bcc4b5e7f579be5cc0aa829.json index 418d413..c844025 100644 --- a/.sqlx/query-9758f269a51528417e6f75785441ade93fed055b6bcc4b5e7f579be5cc0aa829.json +++ b/.sqlx/query-9758f269a51528417e6f75785441ade93fed055b6bcc4b5e7f579be5cc0aa829.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-98af62bdeb2be99ece2ebcb3567773b3af29299658a21681237cfa11cc806a39.json b/.sqlx/query-98af62bdeb2be99ece2ebcb3567773b3af29299658a21681237cfa11cc806a39.json index 7c43374..acf6062 100644 --- a/.sqlx/query-98af62bdeb2be99ece2ebcb3567773b3af29299658a21681237cfa11cc806a39.json +++ b/.sqlx/query-98af62bdeb2be99ece2ebcb3567773b3af29299658a21681237cfa11cc806a39.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-9a6924f163d5e2b61e77eb80b199008b387a96c251606cfe196a78075c119f8e.json b/.sqlx/query-9a6924f163d5e2b61e77eb80b199008b387a96c251606cfe196a78075c119f8e.json index a518075..e00b0d6 100644 --- a/.sqlx/query-9a6924f163d5e2b61e77eb80b199008b387a96c251606cfe196a78075c119f8e.json +++ b/.sqlx/query-9a6924f163d5e2b61e77eb80b199008b387a96c251606cfe196a78075c119f8e.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-9bad020cbe19c8c5d5f0888c1f21cfd20b095d56b961b67e8599c3a0e8b9770e.json b/.sqlx/query-9bad020cbe19c8c5d5f0888c1f21cfd20b095d56b961b67e8599c3a0e8b9770e.json index 73f638d..43424d9 100644 --- a/.sqlx/query-9bad020cbe19c8c5d5f0888c1f21cfd20b095d56b961b67e8599c3a0e8b9770e.json +++ b/.sqlx/query-9bad020cbe19c8c5d5f0888c1f21cfd20b095d56b961b67e8599c3a0e8b9770e.json @@ -6,24 +6,42 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "title!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, true ] diff --git a/.sqlx/query-9dbf884481041722d16521ef773d2c5cbb459b97e1a7a093d7c86617312cf289.json b/.sqlx/query-9dbf884481041722d16521ef773d2c5cbb459b97e1a7a093d7c86617312cf289.json index 7e29637..f5c3034 100644 --- a/.sqlx/query-9dbf884481041722d16521ef773d2c5cbb459b97e1a7a093d7c86617312cf289.json +++ b/.sqlx/query-9dbf884481041722d16521ef773d2c5cbb459b97e1a7a093d7c86617312cf289.json @@ -6,24 +6,42 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "tracked!: bool", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "tracked" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false ] diff --git a/.sqlx/query-9de2cc5614917f11ada32f9ff0b570f6f8f3dfbf8ae93bc515b087872044dffe.json b/.sqlx/query-9de2cc5614917f11ada32f9ff0b570f6f8f3dfbf8ae93bc515b087872044dffe.json index e676ff2..85d0a72 100644 --- a/.sqlx/query-9de2cc5614917f11ada32f9ff0b570f6f8f3dfbf8ae93bc515b087872044dffe.json +++ b/.sqlx/query-9de2cc5614917f11ada32f9ff0b570f6f8f3dfbf8ae93bc515b087872044dffe.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-9e5df0da99c02d3bd2f9235bb53f1caac0b1b104ed78f34799f494d85d1eccc2.json b/.sqlx/query-9e5df0da99c02d3bd2f9235bb53f1caac0b1b104ed78f34799f494d85d1eccc2.json index 3157baf..d9e9698 100644 --- a/.sqlx/query-9e5df0da99c02d3bd2f9235bb53f1caac0b1b104ed78f34799f494d85d1eccc2.json +++ b/.sqlx/query-9e5df0da99c02d3bd2f9235bb53f1caac0b1b104ed78f34799f494d85d1eccc2.json @@ -6,77 +6,167 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { diff --git a/.sqlx/query-9f2ce3364cd2c227d6dbccf9973d29ca685cdaa1fb2b93124e099b0bbd6a26af.json b/.sqlx/query-9f2ce3364cd2c227d6dbccf9973d29ca685cdaa1fb2b93124e099b0bbd6a26af.json index 401c95f..feb122a 100644 --- a/.sqlx/query-9f2ce3364cd2c227d6dbccf9973d29ca685cdaa1fb2b93124e099b0bbd6a26af.json +++ b/.sqlx/query-9f2ce3364cd2c227d6dbccf9973d29ca685cdaa1fb2b93124e099b0bbd6a26af.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-a0653d4df0c32346bede52dbc3c6e501b3ef1e0d742e6eb06e1dc2fb4c524fb7.json b/.sqlx/query-a0653d4df0c32346bede52dbc3c6e501b3ef1e0d742e6eb06e1dc2fb4c524fb7.json index 1505ebc..634a0b3 100644 --- a/.sqlx/query-a0653d4df0c32346bede52dbc3c6e501b3ef1e0d742e6eb06e1dc2fb4c524fb7.json +++ b/.sqlx/query-a0653d4df0c32346bede52dbc3c6e501b3ef1e0d742e6eb06e1dc2fb4c524fb7.json @@ -6,34 +6,64 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "air_date", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "air_date" + } + } }, { "name": "vanished!: bool", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "vanished" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, true, diff --git a/.sqlx/query-a071c1bedfdd1347713cac523cb1aa9522dc9a07fb5d681655622971398a0ff4.json b/.sqlx/query-a071c1bedfdd1347713cac523cb1aa9522dc9a07fb5d681655622971398a0ff4.json index 820dfd0..f748bd4 100644 --- a/.sqlx/query-a071c1bedfdd1347713cac523cb1aa9522dc9a07fb5d681655622971398a0ff4.json +++ b/.sqlx/query-a071c1bedfdd1347713cac523cb1aa9522dc9a07fb5d681655622971398a0ff4.json @@ -6,22 +6,46 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "title!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "episode_id!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } } ], "parameters": { @@ -31,7 +55,7 @@ false, false, true, - true + false ] }, "hash": "a071c1bedfdd1347713cac523cb1aa9522dc9a07fb5d681655622971398a0ff4" diff --git a/.sqlx/query-a0d7296eced171dd9ce02faadfaea5bc67e37123d7e30b18b575f0dd566952ae.json b/.sqlx/query-a0d7296eced171dd9ce02faadfaea5bc67e37123d7e30b18b575f0dd566952ae.json index 1f2a2bf..dbac6ef 100644 --- a/.sqlx/query-a0d7296eced171dd9ce02faadfaea5bc67e37123d7e30b18b575f0dd566952ae.json +++ b/.sqlx/query-a0d7296eced171dd9ce02faadfaea5bc67e37123d7e30b18b575f0dd566952ae.json @@ -6,44 +6,75 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "id" + } + } }, { "name": "indexer_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "indexer_id" + } + } }, { "name": "guid!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "guid" + } + } }, { "name": "name!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } }, { "name": "download_url!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "download_url" + } + } }, { "name": "score!: i64", "ordinal": 5, - "type_info": "Null" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { "Right": 2 }, "nullable": [ - true, false, false, false, false, - null + false, + false ] }, "hash": "a0d7296eced171dd9ce02faadfaea5bc67e37123d7e30b18b575f0dd566952ae" diff --git a/.sqlx/query-a27c937b6039582e0615a1bec479e7b21d2906779bfbbe9e375fa3a70a2de555.json b/.sqlx/query-a27c937b6039582e0615a1bec479e7b21d2906779bfbbe9e375fa3a70a2de555.json index b66af1f..dce3850 100644 --- a/.sqlx/query-a27c937b6039582e0615a1bec479e7b21d2906779bfbbe9e375fa3a70a2de555.json +++ b/.sqlx/query-a27c937b6039582e0615a1bec479e7b21d2906779bfbbe9e375fa3a70a2de555.json @@ -6,72 +6,156 @@ { "name": "overrides!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "root_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "roots", + "name": "id" + } + } }, { "name": "root_kind!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "kind" + } + } }, { "name": "root_audience!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "audience" + } + } }, { "name": "root_path!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "policy_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "policies", + "name": "id" + } + } }, { "name": "policy_name!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } }, { "name": "required_audio!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "required_audio" + } + } }, { "name": "dub_blacklist!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "dub_blacklist" + } + } }, { "name": "hdr_rules!: String", "ordinal": 9, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "hdr_rules" + } + } }, { "name": "size_bands!: String", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "size_bands" + } + } }, { "name": "resolution_pref!: String", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "resolution_pref" + } + } }, { "name": "source_weights!: String", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "source_weights" + } + } }, { "name": "score_weights!: String", "ordinal": 13, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "score_weights" + } + } } ], "parameters": { diff --git a/.sqlx/query-a7d0c78949266aa91b0e685a804b3d455289dab8a6c505dfb8b388b1e7a8e9cf.json b/.sqlx/query-a7d0c78949266aa91b0e685a804b3d455289dab8a6c505dfb8b388b1e7a8e9cf.json index 8e2eb8c..13ef74a 100644 --- a/.sqlx/query-a7d0c78949266aa91b0e685a804b3d455289dab8a6c505dfb8b388b1e7a8e9cf.json +++ b/.sqlx/query-a7d0c78949266aa91b0e685a804b3d455289dab8a6c505dfb8b388b1e7a8e9cf.json @@ -6,44 +6,75 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "id" + } + } }, { "name": "indexer_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "indexer_id" + } + } }, { "name": "guid!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "guid" + } + } }, { "name": "name!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } }, { "name": "download_url!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "download_url" + } + } }, { "name": "score!: i64", "ordinal": 5, - "type_info": "Null" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { "Right": 2 }, "nullable": [ - true, false, false, false, false, - null + false, + false ] }, "hash": "a7d0c78949266aa91b0e685a804b3d455289dab8a6c505dfb8b388b1e7a8e9cf" diff --git a/.sqlx/query-aaafc2e7577fad8be202f0d27e16e5f88ffa4644999dee42af3e387dd2cf8702.json b/.sqlx/query-aaafc2e7577fad8be202f0d27e16e5f88ffa4644999dee42af3e387dd2cf8702.json index b730f17..bc48f31 100644 --- a/.sqlx/query-aaafc2e7577fad8be202f0d27e16e5f88ffa4644999dee42af3e387dd2cf8702.json +++ b/.sqlx/query-aaafc2e7577fad8be202f0d27e16e5f88ffa4644999dee42af3e387dd2cf8702.json @@ -6,37 +6,79 @@ { "name": "series_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "episode_id!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "target_id" + } + } }, { "name": "season_number!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "episode_number!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } } ], "parameters": { diff --git a/.sqlx/query-aaf6f4f7243bffa925fa17c9af3afb91c076ae3976b11cf18eb7583b8ce69a7e.json b/.sqlx/query-aaf6f4f7243bffa925fa17c9af3afb91c076ae3976b11cf18eb7583b8ce69a7e.json index f3960af..8b93c58 100644 --- a/.sqlx/query-aaf6f4f7243bffa925fa17c9af3afb91c076ae3976b11cf18eb7583b8ce69a7e.json +++ b/.sqlx/query-aaf6f4f7243bffa925fa17c9af3afb91c076ae3976b11cf18eb7583b8ce69a7e.json @@ -6,69 +6,141 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "id" + } + } }, { "name": "indexer_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "indexer_id" + } + } }, { "name": "guid!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "guid" + } + } }, { "name": "name!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } }, { "name": "size!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "size" + } + } }, { "name": "seeders", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "seeders" + } + } }, { "name": "publish_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "publish_date" + } + } }, { "name": "download_url!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "download_url" + } + } }, { "name": "parsed!: serde_json::Value", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "parsed" + } + } }, { "name": "score", "ordinal": 9, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "releases", + "name": "score" + } + } }, { "name": "verdict", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "verdict" + } + } }, { "name": "rejected_rule", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "rejected_rule" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-ad1a3b886179c638051ed9bf34ddbc153097fd7626f68b316d810ccc8305222d.json b/.sqlx/query-ad1a3b886179c638051ed9bf34ddbc153097fd7626f68b316d810ccc8305222d.json index c7b663a..73c5057 100644 --- a/.sqlx/query-ad1a3b886179c638051ed9bf34ddbc153097fd7626f68b316d810ccc8305222d.json +++ b/.sqlx/query-ad1a3b886179c638051ed9bf34ddbc153097fd7626f68b316d810ccc8305222d.json @@ -6,77 +6,167 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { diff --git a/.sqlx/query-aebbabd41e2086ac37dbd9d2151b6d54cbad525b1d1d63c2a1495c24af44db7f.json b/.sqlx/query-aebbabd41e2086ac37dbd9d2151b6d54cbad525b1d1d63c2a1495c24af44db7f.json index 5e79f1e..6deb1c5 100644 --- a/.sqlx/query-aebbabd41e2086ac37dbd9d2151b6d54cbad525b1d1d63c2a1495c24af44db7f.json +++ b/.sqlx/query-aebbabd41e2086ac37dbd9d2151b6d54cbad525b1d1d63c2a1495c24af44db7f.json @@ -6,69 +6,141 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "id" + } + } }, { "name": "indexer_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "indexer_id" + } + } }, { "name": "guid!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "guid" + } + } }, { "name": "name!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } }, { "name": "size!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "size" + } + } }, { "name": "seeders", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "seeders" + } + } }, { "name": "publish_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "publish_date" + } + } }, { "name": "download_url!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "download_url" + } + } }, { "name": "parsed!: serde_json::Value", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "parsed" + } + } }, { "name": "score", "ordinal": 9, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "releases", + "name": "score" + } + } }, { "name": "verdict", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "verdict" + } + } }, { "name": "rejected_rule", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "rejected_rule" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-b5aed94114cf4f1764ff5d6110196527b87bb0c61a7c692a42b2fada7e1ed8e8.json b/.sqlx/query-b5aed94114cf4f1764ff5d6110196527b87bb0c61a7c692a42b2fada7e1ed8e8.json index 35c070f..9df0277 100644 --- a/.sqlx/query-b5aed94114cf4f1764ff5d6110196527b87bb0c61a7c692a42b2fada7e1ed8e8.json +++ b/.sqlx/query-b5aed94114cf4f1764ff5d6110196527b87bb0c61a7c692a42b2fada7e1ed8e8.json @@ -6,19 +6,31 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "owners", + "name": "id" + } + } }, { "name": "label!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "name" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false ] }, diff --git a/.sqlx/query-b6974993af21da17e3f935cd0ab3cc4e14a9e7cf00e0decc15186ec39b74cf30.json b/.sqlx/query-b6974993af21da17e3f935cd0ab3cc4e14a9e7cf00e0decc15186ec39b74cf30.json index b776aee..fcfa95f 100644 --- a/.sqlx/query-b6974993af21da17e3f935cd0ab3cc4e14a9e7cf00e0decc15186ec39b74cf30.json +++ b/.sqlx/query-b6974993af21da17e3f935cd0ab3cc4e14a9e7cf00e0decc15186ec39b74cf30.json @@ -6,14 +6,20 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } } ], "parameters": { "Right": 2 }, "nullable": [ - true + false ] }, "hash": "b6974993af21da17e3f935cd0ab3cc4e14a9e7cf00e0decc15186ec39b74cf30" diff --git a/.sqlx/query-b9511e409f9521914406f522c9cf63bf97436d5af605413f8660066d4779f6ca.json b/.sqlx/query-b9511e409f9521914406f522c9cf63bf97436d5af605413f8660066d4779f6ca.json index 5bee8dc..7a89a5b 100644 --- a/.sqlx/query-b9511e409f9521914406f522c9cf63bf97436d5af605413f8660066d4779f6ca.json +++ b/.sqlx/query-b9511e409f9521914406f522c9cf63bf97436d5af605413f8660066d4779f6ca.json @@ -6,32 +6,51 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "id" + } + } }, { "name": "number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "episodes", + "name": "title" + } + } }, { "name": "has_file!: bool", "ordinal": 3, - "type_info": "Null" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { "Right": 1 }, "nullable": [ - true, false, false, - null + false, + false ] }, "hash": "b9511e409f9521914406f522c9cf63bf97436d5af605413f8660066d4779f6ca" diff --git a/.sqlx/query-bb612df5646dba7ce195aa0a00068df7fa42de0b4322c78cc4cf43a695d16e54.json b/.sqlx/query-bb612df5646dba7ce195aa0a00068df7fa42de0b4322c78cc4cf43a695d16e54.json index 1db00e6..b62d576 100644 --- a/.sqlx/query-bb612df5646dba7ce195aa0a00068df7fa42de0b4322c78cc4cf43a695d16e54.json +++ b/.sqlx/query-bb612df5646dba7ce195aa0a00068df7fa42de0b4322c78cc4cf43a695d16e54.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-bcec423ef801043a1d1934bb241ffeeabfaa027360e3317100687fbf0334a95d.json b/.sqlx/query-bcec423ef801043a1d1934bb241ffeeabfaa027360e3317100687fbf0334a95d.json index 69f698d..2b173b6 100644 --- a/.sqlx/query-bcec423ef801043a1d1934bb241ffeeabfaa027360e3317100687fbf0334a95d.json +++ b/.sqlx/query-bcec423ef801043a1d1934bb241ffeeabfaa027360e3317100687fbf0334a95d.json @@ -6,17 +6,35 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "owners", + "name": "id" + } + } }, { "name": "name!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "name" + } + } }, { "name": "ntfy_topic!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "ntfy_topic" + } + } } ], "parameters": { diff --git a/.sqlx/query-bd10698fda9b55789f3b5806cda9ccaf45973fbe052f5ef4fdf693529e24f994.json b/.sqlx/query-bd10698fda9b55789f3b5806cda9ccaf45973fbe052f5ef4fdf693529e24f994.json index 3516ab6..f26241a 100644 --- a/.sqlx/query-bd10698fda9b55789f3b5806cda9ccaf45973fbe052f5ef4fdf693529e24f994.json +++ b/.sqlx/query-bd10698fda9b55789f3b5806cda9ccaf45973fbe052f5ef4fdf693529e24f994.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-be5b70aed19b1d1e81dd26f84faf9caff1712321c6398abed99408a7c6cb31e4.json b/.sqlx/query-be5b70aed19b1d1e81dd26f84faf9caff1712321c6398abed99408a7c6cb31e4.json index a7a631c..679b384 100644 --- a/.sqlx/query-be5b70aed19b1d1e81dd26f84faf9caff1712321c6398abed99408a7c6cb31e4.json +++ b/.sqlx/query-be5b70aed19b1d1e81dd26f84faf9caff1712321c6398abed99408a7c6cb31e4.json @@ -6,17 +6,35 @@ { "name": "title!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "original_language", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } } ], "parameters": { diff --git a/.sqlx/query-c602f6e08e0a8d828dfff1d821cbd08215204fadfb92b7f676e857e0b3400408.json b/.sqlx/query-c602f6e08e0a8d828dfff1d821cbd08215204fadfb92b7f676e857e0b3400408.json index 75b7fee..d734843 100644 --- a/.sqlx/query-c602f6e08e0a8d828dfff1d821cbd08215204fadfb92b7f676e857e0b3400408.json +++ b/.sqlx/query-c602f6e08e0a8d828dfff1d821cbd08215204fadfb92b7f676e857e0b3400408.json @@ -6,49 +6,97 @@ { "name": "grab_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "grabs", + "name": "id" + } + } }, { "name": "infohash!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "grabs", + "name": "infohash" + } + } }, { "name": "movie_id!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "release_name!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-c6c072e04e334786f6cbd514cdfd4f6b493df9a5f8e140138cce32395b9d937d.json b/.sqlx/query-c6c072e04e334786f6cbd514cdfd4f6b493df9a5f8e140138cce32395b9d937d.json index 84096da..ea47275 100644 --- a/.sqlx/query-c6c072e04e334786f6cbd514cdfd4f6b493df9a5f8e140138cce32395b9d937d.json +++ b/.sqlx/query-c6c072e04e334786f6cbd514cdfd4f6b493df9a5f8e140138cce32395b9d937d.json @@ -6,27 +6,57 @@ { "name": "series_title!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "series_tvdb_id", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "original_language", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "season_number!: i64", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "episode_number!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "episodes", + "name": "number" + } + } } ], "parameters": { diff --git a/.sqlx/query-c724a7b776e5fcafadce90cb7bd235220cda3d8894d7cb5c2a411e816eb2ce0e.json b/.sqlx/query-c724a7b776e5fcafadce90cb7bd235220cda3d8894d7cb5c2a411e816eb2ce0e.json index c311402..3b51b32 100644 --- a/.sqlx/query-c724a7b776e5fcafadce90cb7bd235220cda3d8894d7cb5c2a411e816eb2ce0e.json +++ b/.sqlx/query-c724a7b776e5fcafadce90cb7bd235220cda3d8894d7cb5c2a411e816eb2ce0e.json @@ -6,47 +6,101 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "tvdb_id", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "title!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "root_path!: String", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "upstream_ended!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "upstream_ended" + } + } }, { "name": "created_at!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "created_at" + } + } } ], "parameters": { diff --git a/.sqlx/query-cbaa326819ded9bf6e74654b4ef40e9247723f3ce10ca95114bb2bee849957a0.json b/.sqlx/query-cbaa326819ded9bf6e74654b4ef40e9247723f3ce10ca95114bb2bee849957a0.json index c906e94..65acaf3 100644 --- a/.sqlx/query-cbaa326819ded9bf6e74654b4ef40e9247723f3ce10ca95114bb2bee849957a0.json +++ b/.sqlx/query-cbaa326819ded9bf6e74654b4ef40e9247723f3ce10ca95114bb2bee849957a0.json @@ -6,7 +6,8 @@ { "name": "count!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-ce34d0f329c6e3f8ec2df1b9c85502186ff6fddddd98cc15423fcd41ca308a04.json b/.sqlx/query-ce34d0f329c6e3f8ec2df1b9c85502186ff6fddddd98cc15423fcd41ca308a04.json index 75262e1..e5d989f 100644 --- a/.sqlx/query-ce34d0f329c6e3f8ec2df1b9c85502186ff6fddddd98cc15423fcd41ca308a04.json +++ b/.sqlx/query-ce34d0f329c6e3f8ec2df1b9c85502186ff6fddddd98cc15423fcd41ca308a04.json @@ -6,7 +6,13 @@ { "name": "title", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } } ], "parameters": { diff --git a/.sqlx/query-cf7554342c87190b40e41db90a4a9030f528bee609fd0590cc715cf0343a51ba.json b/.sqlx/query-cf7554342c87190b40e41db90a4a9030f528bee609fd0590cc715cf0343a51ba.json index f4c209d..313f58d 100644 --- a/.sqlx/query-cf7554342c87190b40e41db90a4a9030f528bee609fd0590cc715cf0343a51ba.json +++ b/.sqlx/query-cf7554342c87190b40e41db90a4a9030f528bee609fd0590cc715cf0343a51ba.json @@ -6,7 +6,13 @@ { "name": "tmdb_id", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } } ], "parameters": { diff --git a/.sqlx/query-d102c8a0fde1dbd5a0e34898f01007d7cb65f063274a322702ffcfef133ea454.json b/.sqlx/query-d102c8a0fde1dbd5a0e34898f01007d7cb65f063274a322702ffcfef133ea454.json index 0a53f27..286f83d 100644 --- a/.sqlx/query-d102c8a0fde1dbd5a0e34898f01007d7cb65f063274a322702ffcfef133ea454.json +++ b/.sqlx/query-d102c8a0fde1dbd5a0e34898f01007d7cb65f063274a322702ffcfef133ea454.json @@ -6,44 +6,75 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "id" + } + } }, { "name": "indexer_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "indexer_id" + } + } }, { "name": "guid!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "guid" + } + } }, { "name": "name!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } }, { "name": "download_url!: String", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "download_url" + } + } }, { "name": "score!: i64", "ordinal": 5, - "type_info": "Null" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { "Right": 2 }, "nullable": [ - true, false, false, false, false, - null + false, + false ] }, "hash": "d102c8a0fde1dbd5a0e34898f01007d7cb65f063274a322702ffcfef133ea454" diff --git a/.sqlx/query-d58ad94e15e4b4db9dde68f418f586bfa8b6ac2eb2cdb556071f6382283d81ae.json b/.sqlx/query-d58ad94e15e4b4db9dde68f418f586bfa8b6ac2eb2cdb556071f6382283d81ae.json index 87c2cca..c6fe68d 100644 --- a/.sqlx/query-d58ad94e15e4b4db9dde68f418f586bfa8b6ac2eb2cdb556071f6382283d81ae.json +++ b/.sqlx/query-d58ad94e15e4b4db9dde68f418f586bfa8b6ac2eb2cdb556071f6382283d81ae.json @@ -6,27 +6,57 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "imdb_id: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "imdb_id" + } + } }, { "name": "title!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } } ], "parameters": { diff --git a/.sqlx/query-d6d54d747b699ed28ebf5eb1ba5e73285778a186fd1da64a91ad18cd1c5c14d5.json b/.sqlx/query-d6d54d747b699ed28ebf5eb1ba5e73285778a186fd1da64a91ad18cd1c5c14d5.json index 1b5cef1..4826347 100644 --- a/.sqlx/query-d6d54d747b699ed28ebf5eb1ba5e73285778a186fd1da64a91ad18cd1c5c14d5.json +++ b/.sqlx/query-d6d54d747b699ed28ebf5eb1ba5e73285778a186fd1da64a91ad18cd1c5c14d5.json @@ -6,7 +6,13 @@ { "name": "original_language", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } } ], "parameters": { diff --git a/.sqlx/query-da5d4fba049300621dc1c3d44ce008835310ae18c9904cea0080267186d64a34.json b/.sqlx/query-da5d4fba049300621dc1c3d44ce008835310ae18c9904cea0080267186d64a34.json index d7d861c..6457755 100644 --- a/.sqlx/query-da5d4fba049300621dc1c3d44ce008835310ae18c9904cea0080267186d64a34.json +++ b/.sqlx/query-da5d4fba049300621dc1c3d44ce008835310ae18c9904cea0080267186d64a34.json @@ -6,54 +6,108 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "blocked!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } }, { "name": "poster_path", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 8, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "series", + "name": "vote_average" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, true, diff --git a/.sqlx/query-e5c40363c81cebf7026b49699981218e15b57d565039f3de2d48b320d3cec92b.json b/.sqlx/query-e5c40363c81cebf7026b49699981218e15b57d565039f3de2d48b320d3cec92b.json index 54439ae..e44a2ea 100644 --- a/.sqlx/query-e5c40363c81cebf7026b49699981218e15b57d565039f3de2d48b320d3cec92b.json +++ b/.sqlx/query-e5c40363c81cebf7026b49699981218e15b57d565039f3de2d48b320d3cec92b.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-e8258c109f1d5a3fab62f702fbc274701e8b508de480e03bab593e449ef8d9d4.json b/.sqlx/query-e8258c109f1d5a3fab62f702fbc274701e8b508de480e03bab593e449ef8d9d4.json index 12b415f..d946c81 100644 --- a/.sqlx/query-e8258c109f1d5a3fab62f702fbc274701e8b508de480e03bab593e449ef8d9d4.json +++ b/.sqlx/query-e8258c109f1d5a3fab62f702fbc274701e8b508de480e03bab593e449ef8d9d4.json @@ -6,27 +6,57 @@ { "name": "season_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "season_number!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "series_title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "series_tvdb_id", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } } ], "parameters": { diff --git a/.sqlx/query-ee86c3a4d78ef8ec0ca71343c5f1cbe1aea538f04e31b74f1bca0e7bd00c7248.json b/.sqlx/query-ee86c3a4d78ef8ec0ca71343c5f1cbe1aea538f04e31b74f1bca0e7bd00c7248.json index bfc5699..8bf0994 100644 --- a/.sqlx/query-ee86c3a4d78ef8ec0ca71343c5f1cbe1aea538f04e31b74f1bca0e7bd00c7248.json +++ b/.sqlx/query-ee86c3a4d78ef8ec0ca71343c5f1cbe1aea538f04e31b74f1bca0e7bd00c7248.json @@ -6,7 +6,8 @@ { "name": "exists!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": "Expression" } ], "parameters": { diff --git a/.sqlx/query-f203b69afb44bfb5f906ff2e1ec13915645c35af5349032be9898a697d0d05ba.json b/.sqlx/query-f203b69afb44bfb5f906ff2e1ec13915645c35af5349032be9898a697d0d05ba.json index 4e5f6a1..6e0ebcd 100644 --- a/.sqlx/query-f203b69afb44bfb5f906ff2e1ec13915645c35af5349032be9898a697d0d05ba.json +++ b/.sqlx/query-f203b69afb44bfb5f906ff2e1ec13915645c35af5349032be9898a697d0d05ba.json @@ -6,69 +6,141 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "id" + } + } }, { "name": "indexer_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "indexer_id" + } + } }, { "name": "guid!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "guid" + } + } }, { "name": "name!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "name" + } + } }, { "name": "size!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "size" + } + } }, { "name": "seeders", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "releases", + "name": "seeders" + } + } }, { "name": "publish_date", "ordinal": 6, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "publish_date" + } + } }, { "name": "download_url!: String", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "download_url" + } + } }, { "name": "parsed!: serde_json::Value", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "parsed" + } + } }, { "name": "score", "ordinal": 9, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "releases", + "name": "score" + } + } }, { "name": "verdict", "ordinal": 10, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "verdict" + } + } }, { "name": "rejected_rule", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "releases", + "name": "rejected_rule" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-f2ae7a2dec9da2a2bfebb2a4dbd8e4e1db0fc5f15cee6a9c78af077ee1277153.json b/.sqlx/query-f2ae7a2dec9da2a2bfebb2a4dbd8e4e1db0fc5f15cee6a9c78af077ee1277153.json index b59d108..b001317 100644 --- a/.sqlx/query-f2ae7a2dec9da2a2bfebb2a4dbd8e4e1db0fc5f15cee6a9c78af077ee1277153.json +++ b/.sqlx/query-f2ae7a2dec9da2a2bfebb2a4dbd8e4e1db0fc5f15cee6a9c78af077ee1277153.json @@ -6,84 +6,174 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { "Right": 0 }, "nullable": [ - true, + false, false, false, true, diff --git a/.sqlx/query-f4659aa8838d2572b24da09ec4efb538474842113d3f1f7c36d7f33f23ce65e8.json b/.sqlx/query-f4659aa8838d2572b24da09ec4efb538474842113d3f1f7c36d7f33f23ce65e8.json index 88d9ec4..603eb60 100644 --- a/.sqlx/query-f4659aa8838d2572b24da09ec4efb538474842113d3f1f7c36d7f33f23ce65e8.json +++ b/.sqlx/query-f4659aa8838d2572b24da09ec4efb538474842113d3f1f7c36d7f33f23ce65e8.json @@ -6,12 +6,24 @@ { "name": "title_id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "title_owners", + "name": "title_id" + } + } }, { "name": "owner_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "title_owners", + "name": "owner_id" + } + } } ], "parameters": { diff --git a/.sqlx/query-f513c2cc848ab8e42366ad83ad6fdb4d18ad0d6b5083221531fb70962670ef1d.json b/.sqlx/query-f513c2cc848ab8e42366ad83ad6fdb4d18ad0d6b5083221531fb70962670ef1d.json index caef66d..247ea73 100644 --- a/.sqlx/query-f513c2cc848ab8e42366ad83ad6fdb4d18ad0d6b5083221531fb70962670ef1d.json +++ b/.sqlx/query-f513c2cc848ab8e42366ad83ad6fdb4d18ad0d6b5083221531fb70962670ef1d.json @@ -6,7 +6,13 @@ { "name": "path!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } } ], "parameters": { diff --git a/.sqlx/query-f52980596624a03ae4c704926a443c5665c865f9d3d3699105790fe09d8b3fc9.json b/.sqlx/query-f52980596624a03ae4c704926a443c5665c865f9d3d3699105790fe09d8b3fc9.json index 68a8d8c..f164126 100644 --- a/.sqlx/query-f52980596624a03ae4c704926a443c5665c865f9d3d3699105790fe09d8b3fc9.json +++ b/.sqlx/query-f52980596624a03ae4c704926a443c5665c865f9d3d3699105790fe09d8b3fc9.json @@ -6,34 +6,64 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "id" + } + } }, { "name": "series_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "series_id" + } + } }, { "name": "number!: i64", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "number" + } + } }, { "name": "tracked!: bool", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "tracked" + } + } }, { "name": "vanished!: bool", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "seasons", + "name": "vanished" + } + } } ], "parameters": { "Right": 1 }, "nullable": [ - true, + false, false, false, false, diff --git a/.sqlx/query-f5a42a1fcc0361f58b7be7906962efa4b9a08d4aaaf8a6883ee9f949d6a5cfe7.json b/.sqlx/query-f5a42a1fcc0361f58b7be7906962efa4b9a08d4aaaf8a6883ee9f949d6a5cfe7.json index 4d78665..23d2baf 100644 --- a/.sqlx/query-f5a42a1fcc0361f58b7be7906962efa4b9a08d4aaaf8a6883ee9f949d6a5cfe7.json +++ b/.sqlx/query-f5a42a1fcc0361f58b7be7906962efa4b9a08d4aaaf8a6883ee9f949d6a5cfe7.json @@ -6,7 +6,13 @@ { "name": "ntfy_topic!: String", "ordinal": 0, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "owners", + "name": "ntfy_topic" + } + } } ], "parameters": { diff --git a/.sqlx/query-f6dae636487a0d5eab2ee45c2ae455e065bfb670ce4c39a5bea04ceaff05780f.json b/.sqlx/query-f6dae636487a0d5eab2ee45c2ae455e065bfb670ce4c39a5bea04ceaff05780f.json index ebf117b..03fa223 100644 --- a/.sqlx/query-f6dae636487a0d5eab2ee45c2ae455e065bfb670ce4c39a5bea04ceaff05780f.json +++ b/.sqlx/query-f6dae636487a0d5eab2ee45c2ae455e065bfb670ce4c39a5bea04ceaff05780f.json @@ -6,7 +6,13 @@ { "name": "blocked!: bool", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } } ], "parameters": { diff --git a/.sqlx/query-f986006f35b44d5e0ba9ad7ff9aa6aa695ee3b54e15b7259df2cddaba4ce2644.json b/.sqlx/query-f986006f35b44d5e0ba9ad7ff9aa6aa695ee3b54e15b7259df2cddaba4ce2644.json index 66d9680..963f14d 100644 --- a/.sqlx/query-f986006f35b44d5e0ba9ad7ff9aa6aa695ee3b54e15b7259df2cddaba4ce2644.json +++ b/.sqlx/query-f986006f35b44d5e0ba9ad7ff9aa6aa695ee3b54e15b7259df2cddaba4ce2644.json @@ -6,77 +6,167 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "tmdb_id" + } + } }, { "name": "title!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "title" + } + } }, { "name": "year", "ordinal": 3, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 4, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 5, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "root_id" + } + } }, { "name": "wanted!: bool", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "wanted" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 7, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "overrides" + } + } }, { "name": "state!: String", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "state" + } + } }, { "name": "blocked!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "blocked" + } + } }, { "name": "search_attempts!: i64", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "movies", + "name": "search_attempts" + } + } }, { "name": "last_searched_at", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "last_searched_at" + } + } }, { "name": "poster_path", "ordinal": 12, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "movies", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 13, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "movies", + "name": "vote_average" + } + } }, { "name": "waiver?: serde_json::Value", "ordinal": 14, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "media_files", + "name": "waiver" + } + } } ], "parameters": { diff --git a/.sqlx/query-fa84977e249cb6f3b95d5f3486346450066ac2997437175e30c60d6364701dcf.json b/.sqlx/query-fa84977e249cb6f3b95d5f3486346450066ac2997437175e30c60d6364701dcf.json index b66fc84..e38b2ca 100644 --- a/.sqlx/query-fa84977e249cb6f3b95d5f3486346450066ac2997437175e30c60d6364701dcf.json +++ b/.sqlx/query-fa84977e249cb6f3b95d5f3486346450066ac2997437175e30c60d6364701dcf.json @@ -6,32 +6,68 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "roots", + "name": "id" + } + } }, { "name": "kind!: String", "ordinal": 1, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "kind" + } + } }, { "name": "audience!: String", "ordinal": 2, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "audience" + } + } }, { "name": "path!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "roots", + "name": "path" + } + } }, { "name": "policy_id!: i64", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "policies", + "name": "id" + } + } }, { "name": "policy_name!: String", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "policies", + "name": "name" + } + } } ], "parameters": { diff --git a/.sqlx/query-fd07d4f50079cc11154a6b1a605b315d3ce220f99dca9a727bb6e4b2eaa45edb.json b/.sqlx/query-fd07d4f50079cc11154a6b1a605b315d3ce220f99dca9a727bb6e4b2eaa45edb.json index 9d4cbc0..6d4d412 100644 --- a/.sqlx/query-fd07d4f50079cc11154a6b1a605b315d3ce220f99dca9a727bb6e4b2eaa45edb.json +++ b/.sqlx/query-fd07d4f50079cc11154a6b1a605b315d3ce220f99dca9a727bb6e4b2eaa45edb.json @@ -6,7 +6,13 @@ { "name": "id", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "policies", + "name": "id" + } + } } ], "parameters": { diff --git a/.sqlx/query-fd340665e0593bf692f0ffbe60ba4bebbc1a3d80c1304ed1fb790ccbfc6cc420.json b/.sqlx/query-fd340665e0593bf692f0ffbe60ba4bebbc1a3d80c1304ed1fb790ccbfc6cc420.json index d375fa0..05ba566 100644 --- a/.sqlx/query-fd340665e0593bf692f0ffbe60ba4bebbc1a3d80c1304ed1fb790ccbfc6cc420.json +++ b/.sqlx/query-fd340665e0593bf692f0ffbe60ba4bebbc1a3d80c1304ed1fb790ccbfc6cc420.json @@ -6,72 +6,156 @@ { "name": "id!: i64", "ordinal": 0, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "id" + } + } }, { "name": "tmdb_id!: i64", "ordinal": 1, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tmdb_id" + } + } }, { "name": "tvdb_id", "ordinal": 2, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "tvdb_id" + } + } }, { "name": "title!: String", "ordinal": 3, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "title" + } + } }, { "name": "year", "ordinal": 4, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "year" + } + } }, { "name": "original_language", "ordinal": 5, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "original_language" + } + } }, { "name": "root_id!: i64", "ordinal": 6, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "root_id" + } + } }, { "name": "auto_track!: bool", "ordinal": 7, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "auto_track" + } + } }, { "name": "overrides!: serde_json::Value", "ordinal": 8, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "overrides" + } + } }, { "name": "upstream_ended!: bool", "ordinal": 9, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "upstream_ended" + } + } }, { "name": "blocked!: bool", "ordinal": 10, - "type_info": "Integer" + "type_info": "Integer", + "origin": { + "Table": { + "table": "series", + "name": "blocked" + } + } }, { "name": "poster_path", "ordinal": 11, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "poster_path" + } + } }, { "name": "vote_average", "ordinal": 12, - "type_info": "Float" + "type_info": "Float", + "origin": { + "Table": { + "table": "series", + "name": "vote_average" + } + } }, { "name": "metadata_refreshed_at", "ordinal": 13, - "type_info": "Text" + "type_info": "Text", + "origin": { + "Table": { + "table": "series", + "name": "metadata_refreshed_at" + } + } } ], "parameters": { diff --git a/Cargo.lock b/Cargo.lock index 828039d..ec522c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,12 +286,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bitflags" version = "2.13.1" @@ -310,6 +304,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -358,7 +361,7 @@ checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.1", + "rand_core", ] [[package]] @@ -374,10 +377,10 @@ dependencies = [ ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "cmov" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "core-foundation-sys" @@ -443,6 +446,24 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "deadpool" version = "0.12.3" @@ -461,27 +482,25 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -528,13 +547,12 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -561,9 +579,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -578,9 +596,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" @@ -723,7 +741,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "rand_core 0.10.1", + "rand_core", "wasm-bindgen", ] @@ -748,9 +766,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", @@ -765,11 +783,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -792,29 +810,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", + "digest 0.11.3", ] [[package]] @@ -862,6 +871,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.11.0" @@ -1110,9 +1128,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -1120,24 +1135,6 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.3", -] - [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1199,12 +1196,12 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest", + "digest 0.11.3", ] [[package]] @@ -1249,41 +1246,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.7", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1291,7 +1253,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1334,7 +1295,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -1345,15 +1306,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1366,39 +1318,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "potential_utf" version = "0.1.6" @@ -1408,15 +1333,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -1465,7 +1381,7 @@ dependencies = [ "bytes", "getrandom 0.4.3", "lru-slab", - "rand 0.10.2", + "rand", "rand_pcg", "ring", "rustc-hash", @@ -1507,17 +1423,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.10.2" @@ -1526,26 +1431,7 @@ checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", + "rand_core", ] [[package]] @@ -1560,7 +1446,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "rand_core 0.10.1", + "rand_core", ] [[package]] @@ -1572,15 +1458,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5" -dependencies = [ - "bitflags", -] - [[package]] name = "regex" version = "1.13.1" @@ -1662,26 +1539,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustc-hash" version = "2.1.3" @@ -1831,13 +1688,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.7" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1848,7 +1705,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1876,16 +1744,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "slab" version = "0.4.12" @@ -1920,21 +1778,11 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1945,12 +1793,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64", "bytes", + "cfg-if", "chrono", "crc", "crossbeam-queue", @@ -1960,16 +1809,15 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror", "tokio", @@ -1980,9 +1828,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -1993,77 +1841,62 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck", "hex", - "once_cell", "proc-macro2", "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "thiserror", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64", "bitflags", "byteorder", "bytes", "chrono", "crc", - "digest", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand 0.8.7", - "rsa", "serde", "sha1", - "sha2", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror", "tracing", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64", @@ -2079,16 +1912,14 @@ dependencies = [ "hex", "hkdf", "hmac", - "home", "itoa", "log", "md-5", "memchr", - "once_cell", - "rand 0.8.7", + "rand", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", @@ -2099,13 +1930,14 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "chrono", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -2115,7 +1947,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror", "tracing", @@ -2629,12 +2460,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -2721,13 +2546,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "windows-core" @@ -2788,22 +2609,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2815,67 +2627,34 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2888,48 +2667,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2997,26 +2752,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "zerofrom" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 0ef8df5..1e41ea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus base64 = "0.22" # Persistence -sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite", "macros", "migrate", "chrono", "json"] } +sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "sqlite", "macros", "migrate", "chrono", "json"] } # API surface utoipa = { version = "5", features = ["axum_extras", "chrono"] } diff --git a/crates/arr-db/migrations/0014_parked_state.sql b/crates/arr-db/migrations/0014_parked_state.sql index 82bf914..8f36dbb 100644 --- a/crates/arr-db/migrations/0014_parked_state.sql +++ b/crates/arr-db/migrations/0014_parked_state.sql @@ -1,11 +1,15 @@ +-- no-transaction -- #108: a vanished torrent must not re-grab. Overrides #86, which reopened -- the gap (state = 'missing', wanted untouched) so the next tick re-grabbed -- the same release. Adds 'parked' so the daemon can clear `wanted` and mark -- the title honestly instead — distinct from 'missing' (an open gap) and -- 'available' (satisfied). SQLite cannot alter a CHECK, so both tables are -- rebuilt (see 0007). +-- +-- #155: runs outside a transaction so `PRAGMA foreign_keys = OFF` takes +-- effect and dropping the old tables does not cascade into their children. -CREATE TABLE movie_releases_backup AS SELECT * FROM movie_releases; +PRAGMA foreign_keys = OFF; CREATE TABLE movies_new ( id INTEGER PRIMARY KEY, @@ -63,11 +67,6 @@ BEGIN SELECT RAISE(ABORT, 'movies require a movie root'); END; -INSERT INTO movie_releases SELECT * FROM movie_releases_backup; -DROP TABLE movie_releases_backup; - -CREATE TABLE episode_releases_backup AS SELECT * FROM episode_releases; - CREATE TABLE episodes_new ( id INTEGER PRIMARY KEY, season_id INTEGER NOT NULL REFERENCES seasons (id) ON DELETE CASCADE, @@ -102,5 +101,4 @@ CREATE INDEX episodes_pending_search CREATE INDEX episodes_state ON episodes (state); -INSERT INTO episode_releases SELECT * FROM episode_releases_backup; -DROP TABLE episode_releases_backup; +PRAGMA foreign_keys = ON; diff --git a/crates/arr-db/src/lib.rs b/crates/arr-db/src/lib.rs index 0b97cce..3a07143 100644 --- a/crates/arr-db/src/lib.rs +++ b/crates/arr-db/src/lib.rs @@ -297,16 +297,8 @@ mod tests { .await .expect("connect"); - let pre_0007 = sqlx::migrate::Migrator { - migrations: std::borrow::Cow::Owned( - MIGRATOR.iter().filter(|m| m.version < 7).cloned().collect(), - ), - ignore_missing: MIGRATOR.ignore_missing, - locking: MIGRATOR.locking, - no_tx: MIGRATOR.no_tx, - }; - pre_0007 - .run(db.pool()) + MIGRATOR + .run_to(6, db.pool()) .await .expect("migrations before #73"); @@ -344,6 +336,89 @@ mod tests { assert_eq!(renamed_from_imported, "available"); } + /// #155: migration 0014 rebuilds `movies` and `episodes` outside a + /// transaction so `PRAGMA foreign_keys = OFF` holds and dropping the old + /// tables does not cascade-delete `movie_releases`/`episode_releases`. + #[tokio::test] + async fn table_rebuild_preserves_child_rows() { + let dir = tempfile::tempdir().expect("tempdir"); + let db = Db::connect(dir.path().join("arr.db")) + .await + .expect("connect"); + + MIGRATOR + .run_to(13, db.pool()) + .await + .expect("migrations before the 0014 rebuild"); + + sqlx::query( + "INSERT INTO movies (tmdb_id, title, root_id) + SELECT 693134, 'Dune Part Two', id FROM roots WHERE kind = 'movie' LIMIT 1", + ) + .execute(db.pool()) + .await + .expect("movie"); + let series_id = sqlx::query( + "INSERT INTO series (tmdb_id, title, root_id) + SELECT 82728, 'Bluey', id FROM roots WHERE kind = 'tv' LIMIT 1", + ) + .execute(db.pool()) + .await + .expect("series") + .last_insert_rowid(); + let season_id = sqlx::query("INSERT INTO seasons (series_id, number) VALUES (?, 1)") + .bind(series_id) + .execute(db.pool()) + .await + .expect("season") + .last_insert_rowid(); + let episode_id = + sqlx::query("INSERT INTO episodes (season_id, number, title) VALUES (?, 1, 'x')") + .bind(season_id) + .execute(db.pool()) + .await + .expect("episode") + .last_insert_rowid(); + let release_id = sqlx::query( + "INSERT INTO releases (indexer_id, guid, name, size, download_url, parsed) + VALUES (1, 'guid-1', 'Some.Release', 1024, 'http://x', '{}')", + ) + .execute(db.pool()) + .await + .expect("release") + .last_insert_rowid(); + sqlx::query( + "INSERT INTO movie_releases (movie_id, release_id) + SELECT id, ? FROM movies", + ) + .bind(release_id) + .execute(db.pool()) + .await + .expect("movie link"); + sqlx::query("INSERT INTO episode_releases (episode_id, release_id) VALUES (?, ?)") + .bind(episode_id) + .bind(release_id) + .execute(db.pool()) + .await + .expect("episode link"); + + db.migrate().await.expect("remaining migrations"); + + let movie_links: i64 = sqlx::query_scalar("SELECT count(*) FROM movie_releases") + .fetch_one(db.pool()) + .await + .expect("movie_releases"); + let episode_links: i64 = sqlx::query_scalar("SELECT count(*) FROM episode_releases") + .fetch_one(db.pool()) + .await + .expect("episode_releases"); + assert_eq!(movie_links, 1, "movie_releases survive the movies rebuild"); + assert_eq!( + episode_links, 1, + "episode_releases survive the episodes rebuild" + ); + } + #[tokio::test] async fn seeds_two_tv_roots_with_distinct_policies() { let (_dir, db) = fresh().await; From d8797e1996106bc33ecab06e8e4cb0f0992c2825 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Mon, 24 Aug 2026 18:12:10 +0100 Subject: [PATCH 2/6] feat(daemon): refresh metadata on add MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The metadata lane runs daily, so a series added a moment ago showed no seasons for up to 24 hours and a movie had no digital release date — the field §6.2 gates targeted search on. AppState now carries a MetadataCommand channel alongside the movie, episode and season ones. Both create handlers send on it after the row is committed, and a new daemon lane drains it. Its own task rather than an arm of manual::run: a refresh against TMDB can take a while and must not sit in front of an operator's manual search. The add never waits on TMDB and never fails because of it. A refresh that fails leaves metadata_refreshed_at NULL, which is what the daily sweep already treats as due, so the title is retried rather than lost. A command naming a title deleted in between finds no row and does nothing. METADATA_INTERVAL is unchanged. Co-Authored-By: Claude Opus 5 --- ...35d45e9d1b2c639b71102e241f50982502eb.json} | 4 +- ...eadce81719e044c8fb65ba404b45b70bf8d8c.json | 12 + ...7c74f232ec6543787f64efeb53bffa8170113.json | 12 - ...337f18909a58d0f863c794f93f8285b9133c8.json | 26 ++ ...55064d5a83eafe5bc95ddda9a9056d5724f10.json | 92 +++++ crates/arr-api/src/lib.rs | 3 +- crates/arr-api/src/movies.rs | 59 ++- crates/arr-api/src/series.rs | 69 +++- crates/arr-api/src/state.rs | 41 ++ crates/arr-daemon/src/grab.rs | 157 ++++---- crates/arr-daemon/src/main.rs | 27 +- crates/arr-daemon/src/metadata.rs | 367 ++++++++++++++++++ crates/arr-daemon/src/series_refresh.rs | 34 +- 13 files changed, 803 insertions(+), 100 deletions(-) rename .sqlx/{query-de2153eefd6ec03ceeaf640d599271834cb06c94eda7a9956c5bd7399593c0ae.json => query-0516e88e07d3708ba4ccc52d7ba635d45e9d1b2c639b71102e241f50982502eb.json} (58%) create mode 100644 .sqlx/query-0f0966881a69a9f4009df7b27ebeadce81719e044c8fb65ba404b45b70bf8d8c.json delete mode 100644 .sqlx/query-462039959030ee881bcce2daf9d7c74f232ec6543787f64efeb53bffa8170113.json create mode 100644 .sqlx/query-a1be0fa3691226439f768d11600337f18909a58d0f863c794f93f8285b9133c8.json create mode 100644 .sqlx/query-e57dd914010e4346ee3b5cc64fe55064d5a83eafe5bc95ddda9a9056d5724f10.json create mode 100644 crates/arr-daemon/src/metadata.rs diff --git a/.sqlx/query-de2153eefd6ec03ceeaf640d599271834cb06c94eda7a9956c5bd7399593c0ae.json b/.sqlx/query-0516e88e07d3708ba4ccc52d7ba635d45e9d1b2c639b71102e241f50982502eb.json similarity index 58% rename from .sqlx/query-de2153eefd6ec03ceeaf640d599271834cb06c94eda7a9956c5bd7399593c0ae.json rename to .sqlx/query-0516e88e07d3708ba4ccc52d7ba635d45e9d1b2c639b71102e241f50982502eb.json index b1c5124..3ef8d12 100644 --- a/.sqlx/query-de2153eefd6ec03ceeaf640d599271834cb06c94eda7a9956c5bd7399593c0ae.json +++ b/.sqlx/query-0516e88e07d3708ba4ccc52d7ba635d45e9d1b2c639b71102e241f50982502eb.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "UPDATE movies SET metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n WHERE id = ?", + "query": "UPDATE movies SET metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n WHERE id = ?", "describe": { "columns": [], "parameters": { @@ -8,5 +8,5 @@ }, "nullable": [] }, - "hash": "de2153eefd6ec03ceeaf640d599271834cb06c94eda7a9956c5bd7399593c0ae" + "hash": "0516e88e07d3708ba4ccc52d7ba635d45e9d1b2c639b71102e241f50982502eb" } diff --git a/.sqlx/query-0f0966881a69a9f4009df7b27ebeadce81719e044c8fb65ba404b45b70bf8d8c.json b/.sqlx/query-0f0966881a69a9f4009df7b27ebeadce81719e044c8fb65ba404b45b70bf8d8c.json new file mode 100644 index 0000000..ef6824d --- /dev/null +++ b/.sqlx/query-0f0966881a69a9f4009df7b27ebeadce81719e044c8fb65ba404b45b70bf8d8c.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE movies\n SET title = ?, year = ?, original_language = ?, digital_release = ?,\n imdb_id = ?, poster_path = ?, backdrop_path = ?, vote_average = ?,\n metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),\n search_attempts = 0, last_searched_at = NULL,\n updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n WHERE id = ? AND (\n title IS NOT ? OR year IS NOT ? OR original_language IS NOT ?\n OR digital_release IS NOT ? OR imdb_id IS NOT ?\n OR poster_path IS NOT ? OR backdrop_path IS NOT ?\n OR vote_average IS NOT ?\n )", + "describe": { + "columns": [], + "parameters": { + "Right": 17 + }, + "nullable": [] + }, + "hash": "0f0966881a69a9f4009df7b27ebeadce81719e044c8fb65ba404b45b70bf8d8c" +} diff --git a/.sqlx/query-462039959030ee881bcce2daf9d7c74f232ec6543787f64efeb53bffa8170113.json b/.sqlx/query-462039959030ee881bcce2daf9d7c74f232ec6543787f64efeb53bffa8170113.json deleted file mode 100644 index c9c1698..0000000 --- a/.sqlx/query-462039959030ee881bcce2daf9d7c74f232ec6543787f64efeb53bffa8170113.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE movies\n SET title = ?, year = ?, original_language = ?, digital_release = ?,\n imdb_id = ?, poster_path = ?, backdrop_path = ?, vote_average = ?,\n metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),\n search_attempts = 0, last_searched_at = NULL,\n updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n WHERE id = ? AND (\n title IS NOT ? OR year IS NOT ? OR original_language IS NOT ?\n OR digital_release IS NOT ? OR imdb_id IS NOT ?\n OR poster_path IS NOT ? OR backdrop_path IS NOT ?\n OR vote_average IS NOT ?\n )", - "describe": { - "columns": [], - "parameters": { - "Right": 17 - }, - "nullable": [] - }, - "hash": "462039959030ee881bcce2daf9d7c74f232ec6543787f64efeb53bffa8170113" -} diff --git a/.sqlx/query-a1be0fa3691226439f768d11600337f18909a58d0f863c794f93f8285b9133c8.json b/.sqlx/query-a1be0fa3691226439f768d11600337f18909a58d0f863c794f93f8285b9133c8.json new file mode 100644 index 0000000..4720e1d --- /dev/null +++ b/.sqlx/query-a1be0fa3691226439f768d11600337f18909a58d0f863c794f93f8285b9133c8.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "SELECT tmdb_id AS \"tmdb_id!: i64\", metadata_refreshed_at\n FROM movies WHERE id = ?", + "describe": { + "columns": [ + { + "name": "tmdb_id!: i64", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "metadata_refreshed_at", + "ordinal": 1, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true + ] + }, + "hash": "a1be0fa3691226439f768d11600337f18909a58d0f863c794f93f8285b9133c8" +} diff --git a/.sqlx/query-e57dd914010e4346ee3b5cc64fe55064d5a83eafe5bc95ddda9a9056d5724f10.json b/.sqlx/query-e57dd914010e4346ee3b5cc64fe55064d5a83eafe5bc95ddda9a9056d5724f10.json new file mode 100644 index 0000000..42f6e34 --- /dev/null +++ b/.sqlx/query-e57dd914010e4346ee3b5cc64fe55064d5a83eafe5bc95ddda9a9056d5724f10.json @@ -0,0 +1,92 @@ +{ + "db_name": "SQLite", + "query": "SELECT id AS \"id!: i64\", tmdb_id AS \"tmdb_id!: i64\", tvdb_id,\n title AS \"title!: String\", year, original_language,\n root_id AS \"root_id!: i64\", auto_track AS \"auto_track!: bool\",\n upstream_ended AS \"upstream_ended!: bool\", metadata_refreshed_at,\n poster_path, backdrop_path, vote_average\n FROM series WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id!: i64", + "ordinal": 0, + "type_info": "Integer" + }, + { + "name": "tmdb_id!: i64", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "tvdb_id", + "ordinal": 2, + "type_info": "Integer" + }, + { + "name": "title!: String", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "year", + "ordinal": 4, + "type_info": "Integer" + }, + { + "name": "original_language", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "root_id!: i64", + "ordinal": 6, + "type_info": "Integer" + }, + { + "name": "auto_track!: bool", + "ordinal": 7, + "type_info": "Integer" + }, + { + "name": "upstream_ended!: bool", + "ordinal": 8, + "type_info": "Integer" + }, + { + "name": "metadata_refreshed_at", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "backdrop_path", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "vote_average", + "ordinal": 12, + "type_info": "Float" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + true, + false, + true, + true, + false, + false, + false, + true, + true, + true, + true + ] + }, + "hash": "e57dd914010e4346ee3b5cc64fe55064d5a83eafe5bc95ddda9a9056d5724f10" +} diff --git a/crates/arr-api/src/lib.rs b/crates/arr-api/src/lib.rs index 2f887c1..1770cbf 100644 --- a/crates/arr-api/src/lib.rs +++ b/crates/arr-api/src/lib.rs @@ -40,7 +40,8 @@ pub use series::{ UpdateSeason, UpdateSeries, }; pub use state::{ - AppState, EpisodeCommand, MovieCommand, SeasonCommand, Upstreams, DEFAULT_TMDB_URL, + AppState, EpisodeCommand, MetadataCommand, MovieCommand, SeasonCommand, Upstreams, + DEFAULT_TMDB_URL, }; pub use trailer::{Trailer, TrailerKind}; diff --git a/crates/arr-api/src/movies.rs b/crates/arr-api/src/movies.rs index b315b78..ebb7361 100644 --- a/crates/arr-api/src/movies.rs +++ b/crates/arr-api/src/movies.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use utoipa::{IntoParams, ToSchema}; use crate::owners::Owner; -use crate::state::{AppState, MovieCommand}; +use crate::state::{AppState, MetadataCommand, MovieCommand}; #[derive(Debug, Clone, Serialize, ToSchema)] pub struct Movie { @@ -330,9 +330,18 @@ pub async fn create( ) .execute(pool(&state)?) .await?; + let movie_id = result.last_insert_rowid(); + // Issue #176: the digital release date §6.2 gates targeted search on + // comes from a refresh, and the metadata lane is daily. Ask it to run + // now. Asynchronous by design — the add is already committed and must + // not wait on, or fail because of, TMDB. If nothing is draining, the + // daily sweep still picks the movie up: `metadata_refreshed_at` is NULL. + if let Err(error) = state.send_metadata_command(MetadataCommand::Movie { movie_id }) { + tracing::warn!(movie_id, %error, "metadata refresh not queued for the new movie"); + } Ok(( StatusCode::CREATED, - Json(load_movie(&state, result.last_insert_rowid()).await?), + Json(load_movie(&state, movie_id).await?), )) } @@ -1278,6 +1287,52 @@ mod tests { assert_eq!(title_target(root, root), None); } + /// Issue #176: §6.2 gates targeted search on the digital release date, + /// and that only ever arrives from a metadata refresh. Adding a movie + /// queues one rather than waiting for the daily lane. TMDB here is a + /// closed port: the add still returns 201 and the command is still + /// queued. + #[tokio::test] + async fn adding_a_movie_queues_a_refresh_even_with_tmdb_down() { + let dir = tempfile::tempdir().expect("tempdir"); + let database = arr_db::Db::connect(dir.path().join("arr.db")) + .await + .expect("connect database"); + database.migrate().await.expect("migrate database"); + let state = AppState::new( + Upstreams::new("http://127.0.0.1:1".into(), "http://127.0.0.1:1".into()) + .with_tmdb_url("http://127.0.0.1:1".into()) + .with_tmdb_api_key(Some("key".into())), + ) + .expect("state") + .with_database(database); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("bind"); + let address = listener.local_addr().expect("address"); + let served = state.clone(); + tokio::spawn(async move { axum::serve(listener, router(served)).await.expect("serve") }); + let base = format!("http://{address}"); + + let movie = add_movie(&base, 693_134, 1).await; + let movie_id = movie["id"].as_i64().expect("id"); + + assert_eq!( + state.next_metadata_command().await.expect("command"), + MetadataCommand::Movie { movie_id } + ); + let refreshed_at: Option = + sqlx::query_scalar("SELECT metadata_refreshed_at FROM movies WHERE id = ?") + .bind(movie_id) + .fetch_one(state.database().expect("database").pool()) + .await + .expect("movie row"); + assert!( + refreshed_at.is_none(), + "an unrefreshed movie stays due for the scheduled sweep" + ); + } + #[tokio::test] async fn release_actions_are_scoped_to_the_movie() { let (_dir, state, base) = application().await; diff --git a/crates/arr-api/src/series.rs b/crates/arr-api/src/series.rs index 2994d7f..a483891 100644 --- a/crates/arr-api/src/series.rs +++ b/crates/arr-api/src/series.rs @@ -32,7 +32,7 @@ use utoipa::{IntoParams, ToSchema}; use crate::movies::{pool, rescore, Accepted, ApiError, ErrorBody, Release}; use crate::owners::Owner; use crate::search::tmdb_client; -use crate::state::{AppState, EpisodeCommand, SeasonCommand}; +use crate::state::{AppState, EpisodeCommand, MetadataCommand, SeasonCommand}; /// A series with the status derived from its episodes (§4.2). #[derive(Debug, Clone, Serialize, ToSchema)] @@ -471,9 +471,18 @@ pub async fn create( ) .execute(pool(&state)?) .await?; + let series_id = result.last_insert_rowid(); + // Issue #176: a new series has no seasons until a refresh reveals them, + // and the metadata lane is daily. Ask it to run now. Asynchronous by + // design — the add is already committed and must not wait on, or fail + // because of, TMDB. If nothing is draining, the daily sweep still picks + // the series up: `metadata_refreshed_at` is NULL. + if let Err(error) = state.send_metadata_command(MetadataCommand::Series { series_id }) { + tracing::warn!(series_id, %error, "metadata refresh not queued for the new series"); + } Ok(( StatusCode::CREATED, - Json(load_series(&state, result.last_insert_rowid()).await?), + Json(load_series(&state, series_id).await?), )) } @@ -1740,6 +1749,62 @@ mod tests { assert_eq!(vote, Some(8.417)); } + /// Issue #176: the daily metadata lane is what reveals a new series' + /// seasons, so adding one queues a refresh instead of leaving the page + /// empty for up to a day. TMDB here is a closed port: the add still + /// returns 201 and the command is still queued, because the refresh is + /// the lane's problem and not the request's. + #[tokio::test] + async fn adding_a_series_queues_a_refresh_even_with_tmdb_down() { + let dir = tempfile::tempdir().expect("tempdir"); + let database = arr_db::Db::connect(dir.path().join("arr.db")) + .await + .expect("connect database"); + database.migrate().await.expect("migrate database"); + let state = AppState::new( + Upstreams::new("http://127.0.0.1:1".into(), "http://127.0.0.1:1".into()) + .with_tmdb_url("http://127.0.0.1:1".into()) + .with_tmdb_api_key(Some("key".into())), + ) + .expect("state") + .with_database(database); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("bind"); + let address = listener.local_addr().expect("address"); + let served = state.clone(); + tokio::spawn(async move { axum::serve(listener, router(served)).await.expect("serve") }); + let base = format!("http://{address}"); + + let response = reqwest::Client::new() + .post(format!("{base}/api/series")) + .json(&serde_json::json!({ + "tmdb_id": 82_728, "title": "Bluey", "root_id": 3 + })) + .send() + .await + .expect("create series"); + assert_eq!(response.status(), StatusCode::CREATED); + let series_id = response.json::().await.expect("json")["id"] + .as_i64() + .expect("series id"); + + assert_eq!( + state.next_metadata_command().await.expect("command"), + MetadataCommand::Series { series_id } + ); + let refreshed_at: Option = + sqlx::query_scalar("SELECT metadata_refreshed_at FROM series WHERE id = ?") + .bind(series_id) + .fetch_one(state.database().expect("database").pool()) + .await + .expect("series row"); + assert!( + refreshed_at.is_none(), + "an unrefreshed series stays due for the scheduled sweep" + ); + } + #[tokio::test] async fn series_must_sit_on_a_tv_root() { let (_dir, state, base) = application().await; diff --git a/crates/arr-api/src/state.rs b/crates/arr-api/src/state.rs index 61e3a7b..de8c4c0 100644 --- a/crates/arr-api/src/state.rs +++ b/crates/arr-api/src/state.rs @@ -74,6 +74,8 @@ pub struct AppState { pending_episode_commands: Arc>>, season_commands: mpsc::Sender, pending_season_commands: Arc>>, + metadata_commands: mpsc::Sender, + pending_metadata_commands: Arc>>, } /// Work explicitly requested through the movie API. @@ -104,6 +106,22 @@ pub enum SeasonCommand { Grab { season_id: i64, release_id: i64 }, } +/// A title whose TMDB metadata should be refreshed now rather than on the +/// daily lane's next tick (issue #176). +/// +/// Sent when a title is added: a new series has no seasons at all until a +/// refresh reveals them, and a new movie has no digital release date, so +/// waiting up to a day is the difference between a usable page and an empty +/// one. Unlike the other three commands this is not an operator action, so a +/// full channel is dropped rather than reported — the scheduled sweep still +/// owns the title, because a title that was never refreshed keeps +/// `metadata_refreshed_at` NULL. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MetadataCommand { + Series { series_id: i64 }, + Movie { movie_id: i64 }, +} + impl AppState { /// Build the state, including the shared HTTP client. /// @@ -115,6 +133,7 @@ impl AppState { let (movie_commands, pending_movie_commands) = mpsc::channel(64); let (episode_commands, pending_episode_commands) = mpsc::channel(64); let (season_commands, pending_season_commands) = mpsc::channel(64); + let (metadata_commands, pending_metadata_commands) = mpsc::channel(64); Ok(Self { http, upstreams: Arc::new(upstreams), @@ -125,6 +144,8 @@ impl AppState { pending_episode_commands: Arc::new(tokio::sync::Mutex::new(pending_episode_commands)), season_commands, pending_season_commands: Arc::new(tokio::sync::Mutex::new(pending_season_commands)), + metadata_commands, + pending_metadata_commands: Arc::new(tokio::sync::Mutex::new(pending_metadata_commands)), }) } @@ -162,6 +183,16 @@ impl AppState { self.pending_season_commands.lock().await.recv().await } + /// Wait for the next on-demand metadata refresh in the daemon's metadata + /// lane. + /// + /// # Errors + /// + /// If every sender has been dropped. + pub async fn next_metadata_command(&self) -> Option { + self.pending_metadata_commands.lock().await.recv().await + } + pub(crate) fn http(&self) -> &reqwest::Client { &self.http } @@ -194,4 +225,14 @@ impl AppState { ) -> Result<(), mpsc::error::TrySendError> { self.season_commands.try_send(command) } + + /// Ask the metadata lane to refresh one title now. Best effort by + /// design: an add must not fail because the channel is full or because + /// nothing is draining it, so the caller logs and carries on. + pub(crate) fn send_metadata_command( + &self, + command: MetadataCommand, + ) -> Result<(), mpsc::error::TrySendError> { + self.metadata_commands.try_send(command) + } } diff --git a/crates/arr-daemon/src/grab.rs b/crates/arr-daemon/src/grab.rs index 7add623..66b0eb9 100644 --- a/crates/arr-daemon/src/grab.rs +++ b/crates/arr-daemon/src/grab.rs @@ -183,7 +183,7 @@ impl GrabAction { let tmdb_id = u32::try_from(movie.tmdb_id).map_err(|_| GrabError::InvalidTmdbId(movie.id))?; let metadata = tmdb.movie(tmdb_id).await?; - let changed = self.store_metadata(database, &movie, &metadata).await?; + let changed = store_movie_metadata(database, movie.id, &metadata).await?; if changed { tracing::info!( movie_id = movie.id, @@ -219,82 +219,6 @@ impl GrabAction { )) } - /// Write one refresh's fields to the row, guarded so unchanged data moves - /// nothing. Returns whether anything did. - async fn store_metadata( - &self, - database: &Db, - movie: &PendingMovie, - metadata: &arr_meta::Movie, - ) -> Result { - let title = metadata.title.clone(); - let year = metadata.year().map(i64::from); - let original_language = - (!metadata.original_language.is_empty()).then_some(metadata.original_language.clone()); - let digital_release = metadata.digital_release.map(|date| date.to_string()); - // §6.2: the id RSS matching prefers, and the one Torznab movie - // searches take. TMDB does not know one for every title. - let imdb_id = metadata.imdb_id.clone(); - // §9.6: these three are the exception to "rich detail is not - // persisted" — pure-SQL views render artwork without a TMDB call. - let poster_path = metadata.poster_path.clone(); - let backdrop_path = metadata.backdrop_path.clone(); - let vote_average = metadata.vote_average; - let title_ref = title.as_str(); - let original_language_ref = original_language.as_deref(); - let digital_release_ref = digital_release.as_deref(); - let imdb_id_ref = imdb_id.as_deref(); - let poster_path_ref = poster_path.as_deref(); - let backdrop_path_ref = backdrop_path.as_deref(); - let changed = sqlx::query!( - r#"UPDATE movies - SET title = ?, year = ?, original_language = ?, digital_release = ?, - imdb_id = ?, poster_path = ?, backdrop_path = ?, vote_average = ?, - metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), - search_attempts = 0, last_searched_at = NULL, - updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') - WHERE id = ? AND ( - title IS NOT ? OR year IS NOT ? OR original_language IS NOT ? - OR digital_release IS NOT ? OR imdb_id IS NOT ? - OR poster_path IS NOT ? OR backdrop_path IS NOT ? - OR vote_average IS NOT ? - )"#, - title_ref, - year, - original_language_ref, - digital_release_ref, - imdb_id_ref, - poster_path_ref, - backdrop_path_ref, - vote_average, - movie.id, - title_ref, - year, - original_language_ref, - digital_release_ref, - imdb_id_ref, - poster_path_ref, - backdrop_path_ref, - vote_average, - ) - .execute(database.pool()) - .await? - .rows_affected() - != 0; - if !changed { - // Still stamp the refresh even when nothing changed, or the TTL - // gate above never engages and every tick pays for TMDB again. - sqlx::query!( - "UPDATE movies SET metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') - WHERE id = ?", - movie.id - ) - .execute(database.pool()) - .await?; - } - Ok(changed) - } - /// Search every indexer for one title, cache each candidate with its /// verdict and score (§9.3), and return the eligible ones best first. /// @@ -999,6 +923,85 @@ async fn pending_movies(database: &Db) -> Result, GrabError> { .collect()) } +/// Write one refresh's fields to the movie row, guarded so unchanged data +/// moves nothing. Returns whether anything did. +/// +/// A free function rather than a [`GrabAction`] method because the metadata +/// lane refreshes a title on demand (issue #176) with nothing but a TMDB +/// client — grabbing needs Prowlarr, refreshing does not. +pub(crate) async fn store_movie_metadata( + database: &Db, + movie_id: i64, + metadata: &arr_meta::Movie, +) -> Result { + let title = metadata.title.clone(); + let year = metadata.year().map(i64::from); + let original_language = + (!metadata.original_language.is_empty()).then_some(metadata.original_language.clone()); + let digital_release = metadata.digital_release.map(|date| date.to_string()); + // §6.2: the id RSS matching prefers, and the one Torznab movie + // searches take. TMDB does not know one for every title. + let imdb_id = metadata.imdb_id.clone(); + // §9.6: these three are the exception to "rich detail is not + // persisted" — pure-SQL views render artwork without a TMDB call. + let poster_path = metadata.poster_path.clone(); + let backdrop_path = metadata.backdrop_path.clone(); + let vote_average = metadata.vote_average; + let title_ref = title.as_str(); + let original_language_ref = original_language.as_deref(); + let digital_release_ref = digital_release.as_deref(); + let imdb_id_ref = imdb_id.as_deref(); + let poster_path_ref = poster_path.as_deref(); + let backdrop_path_ref = backdrop_path.as_deref(); + let changed = sqlx::query!( + r#"UPDATE movies + SET title = ?, year = ?, original_language = ?, digital_release = ?, + imdb_id = ?, poster_path = ?, backdrop_path = ?, vote_average = ?, + metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), + search_attempts = 0, last_searched_at = NULL, + updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') + WHERE id = ? AND ( + title IS NOT ? OR year IS NOT ? OR original_language IS NOT ? + OR digital_release IS NOT ? OR imdb_id IS NOT ? + OR poster_path IS NOT ? OR backdrop_path IS NOT ? + OR vote_average IS NOT ? + )"#, + title_ref, + year, + original_language_ref, + digital_release_ref, + imdb_id_ref, + poster_path_ref, + backdrop_path_ref, + vote_average, + movie_id, + title_ref, + year, + original_language_ref, + digital_release_ref, + imdb_id_ref, + poster_path_ref, + backdrop_path_ref, + vote_average, + ) + .execute(database.pool()) + .await? + .rows_affected() + != 0; + if !changed { + // Still stamp the refresh even when nothing changed, or the TTL + // gate above never engages and every tick pays for TMDB again. + sqlx::query!( + "UPDATE movies SET metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') + WHERE id = ?", + movie_id + ) + .execute(database.pool()) + .await?; + } + Ok(changed) +} + fn search_due(movie: &PendingMovie) -> bool { backoff_elapsed(movie.search_attempts, movie.last_searched_at.as_deref()) } diff --git a/crates/arr-daemon/src/main.rs b/crates/arr-daemon/src/main.rs index d243b0e..34c45c9 100644 --- a/crates/arr-daemon/src/main.rs +++ b/crates/arr-daemon/src/main.rs @@ -8,6 +8,7 @@ mod import; mod indexers; mod jellyfin; mod manual; +mod metadata; mod notify; mod reaper; pub mod reconcile; @@ -125,6 +126,10 @@ async fn run() -> Result<(), Error> { let notifier = Notifier::new(config.ntfy_url.clone())?; let (reconcile, manual_grab, manual_tv) = reconcile_loop(&database, &config, &transmission, tmdb.as_ref(), ¬ifier)?; + // Issue #176: the on-demand half of the metadata lane needs its own + // handle — the sweep's `SeriesRefreshAction` is owned by `ReconcileLoop`, + // and the compat shim takes the other clone below. + let metadata_tmdb = tmdb.clone(); // Jellyseerr's Radarr shim (DESIGN.md §9.4) reads the same database and // needs its own TMDB client for `movie/lookup`. @@ -162,12 +167,18 @@ async fn run() -> Result<(), Error> { // in the 64-slot buffer forever. Issue #132: the episode and season // channels are drained by the same lane. let mut manual_task = tokio::spawn(manual::run( - state, - database, + state.clone(), + database.clone(), manual_grab, manual_tv, - shutdown_rx, + shutdown_rx.clone(), )); + // Issue #176: adding a title queues a refresh here rather than waiting + // for the daily sweep. Its own task, not an arm of `manual::run`: a + // refresh can take a while against TMDB and must not sit in front of an + // operator's manual search. + let mut metadata_task = + tokio::spawn(metadata::run(state, database, metadata_tmdb, shutdown_rx)); let signal_tx = shutdown_tx.clone(); let server = async move { axum::serve(listener, app) @@ -184,18 +195,28 @@ async fn run() -> Result<(), Error> { let _ = shutdown_tx.send(true); reconcile_task.await?; manual_task.await?; + metadata_task.await?; result.map_err(Error::Serve) } result = &mut reconcile_task => { result?; let _ = shutdown_tx.send(true); manual_task.await?; + metadata_task.await?; server.await.map_err(Error::Serve) } result = &mut manual_task => { result?; let _ = shutdown_tx.send(true); reconcile_task.await?; + metadata_task.await?; + server.await.map_err(Error::Serve) + } + result = &mut metadata_task => { + result?; + let _ = shutdown_tx.send(true); + reconcile_task.await?; + manual_task.await?; server.await.map_err(Error::Serve) } } diff --git a/crates/arr-daemon/src/metadata.rs b/crates/arr-daemon/src/metadata.rs new file mode 100644 index 0000000..9156580 --- /dev/null +++ b/crates/arr-daemon/src/metadata.rs @@ -0,0 +1,367 @@ +//! The on-demand half of the metadata lane. See DESIGN.md §8 and issue #176. +//! +//! The scheduled sweep is daily (`Tick::Metadata`), which is right for +//! keeping a library current and wrong for a title added a moment ago: a new +//! series has no seasons at all until a refresh reveals them, and a new movie +//! has no digital release date, which is what §6.2 gates targeted search on. +//! Shortening the tick would not fix either — it would still leave a visible +//! wait and would spend a TMDB call per title per tick. +//! +//! So `POST /api/series` and `POST /api/movies` send a [`MetadataCommand`] +//! after the row is committed, exactly as the manual search and grab +//! endpoints send theirs (issues #107 and #132), and this lane drains it. +//! The add itself never waits on TMDB and never fails because of it; the +//! worst a broken refresh can do is leave `metadata_refreshed_at` NULL, which +//! is what the sweep already treats as due. + +use std::sync::Arc; + +use arr_api::{AppState, MetadataCommand}; +use arr_db::Db; +use arr_meta::TmdbClient; +use tokio::sync::watch; + +use crate::grab::{metadata_refresh_due, store_movie_metadata, GrabError}; +use crate::series_refresh::SeriesRefreshAction; + +/// Run until every sender is dropped or `shutdown` fires. +/// +/// `tmdb` is `None` when TMDB is not configured, which already disables the +/// scheduled sweep (`main.rs` warns about it). Commands are still drained so +/// the channel never fills. +pub async fn run( + state: AppState, + database: Db, + tmdb: Option>, + mut shutdown: watch::Receiver, +) { + let series = tmdb + .as_ref() + .map(|tmdb| SeriesRefreshAction::new(Arc::clone(tmdb))); + loop { + let command = tokio::select! { + biased; + changed = shutdown.changed() => { + if changed.is_err() || *shutdown.borrow() { + return; + } + continue; + } + command = state.next_metadata_command() => command, + }; + let Some(command) = command else { + return; + }; + let (Some(tmdb), Some(series)) = (tmdb.as_ref(), series.as_ref()) else { + tracing::warn!("metadata refresh requested but TMDB is not configured"); + continue; + }; + match command { + MetadataCommand::Series { series_id } => { + // A title deleted between the send and the drain is not an + // error: `refresh_now` finds no row and does nothing. + match series.refresh_now(&database, series_id).await { + Ok(_) => {} + // A failed refresh leaves `metadata_refreshed_at` NULL, + // so the daily sweep retries the series. The add stands. + Err(error) => { + tracing::error!(series_id, %error, "on-demand series refresh failed"); + } + } + } + MetadataCommand::Movie { movie_id } => { + if let Err(error) = refresh_movie(tmdb, &database, movie_id).await { + tracing::error!(movie_id, %error, "on-demand movie refresh failed"); + } + } + } + } +} + +/// Refresh one movie now, on the same terms as the series path: a row that +/// is gone is not an error, and the sweep's TTL gate applies so a title +/// something else already refreshed costs no second TMDB call. +async fn refresh_movie(tmdb: &TmdbClient, database: &Db, movie_id: i64) -> Result<(), GrabError> { + let movie = sqlx::query!( + r#"SELECT tmdb_id AS "tmdb_id!: i64", metadata_refreshed_at + FROM movies WHERE id = ?"#, + movie_id + ) + .fetch_optional(database.pool()) + .await?; + let Some(movie) = movie else { + return Ok(()); + }; + if !metadata_refresh_due(movie.metadata_refreshed_at.as_deref()) { + return Ok(()); + } + let tmdb_id = u32::try_from(movie.tmdb_id).map_err(|_| GrabError::InvalidTmdbId(movie_id))?; + let metadata = tmdb.movie(tmdb_id).await?; + store_movie_metadata(database, movie_id, &metadata).await?; + Ok(()) +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use arr_meta::TmdbClient; + use tempfile::TempDir; + use wiremock::matchers::{method, path, query_param}; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + use super::*; + + const MOVIE_METADATA: &str = r#"{ + "id": 693134, + "title": "Dune Part Two", + "original_title": "Dune: Part Two", + "original_language": "en", + "origin_country": ["US"], + "release_date": "2024-02-27", + "poster_path": "/dune-two.jpg", + "release_dates": {"results": [{"release_dates": [{ + "type": 4, "release_date": "2024-04-16T00:00:00.000Z" + }]}]} + }"#; + + fn series_detail() -> serde_json::Value { + serde_json::json!({ + "id": 82_728, + "name": "Bluey", + "original_language": "en", + "first_air_date": "2018-10-01", + "status": "Returning Series", + "seasons": [{"season_number": 1, "episode_count": 2}], + "external_ids": {"tvdb_id": 361_391} + }) + } + + fn season_detail() -> serde_json::Value { + serde_json::json!({ + "season_number": 1, + "episodes": [ + {"episode_number": 1, "name": "Magic Xylophone", "air_date": "2018-10-01"}, + {"episode_number": 2, "name": "Hospital", "air_date": "2018-10-02"} + ] + }) + } + + /// A TMDB that answers for the series, and one that answers for the + /// movie. `status` lets a test serve an error instead. + async fn tmdb_series(status: u16) -> MockServer { + let server = MockServer::start().await; + let response = if status == 200 { + ResponseTemplate::new(200).set_body_json(series_detail()) + } else { + ResponseTemplate::new(status) + }; + Mock::given(method("GET")) + .and(path("/tv/82728")) + .respond_with(response) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path("/tv/82728/season/1")) + .respond_with(ResponseTemplate::new(200).set_body_json(season_detail())) + .mount(&server) + .await; + server + } + + async fn tmdb_movie(status: u16) -> MockServer { + let server = MockServer::start().await; + let response = if status == 200 { + ResponseTemplate::new(200).set_body_string(MOVIE_METADATA) + } else { + ResponseTemplate::new(status) + }; + Mock::given(method("GET")) + .and(path("/3/movie/693134")) + .and(query_param("append_to_response", "release_dates")) + .respond_with(response) + .mount(&server) + .await; + server + } + + fn series_action(server: &MockServer) -> SeriesRefreshAction { + SeriesRefreshAction::new(Arc::new( + TmdbClient::builder("key".to_owned()) + .base_url(server.uri()) + .build() + .unwrap(), + )) + } + + fn movie_client(server: &MockServer) -> TmdbClient { + TmdbClient::builder("key".to_owned()) + .base_url(format!("{}/3/", server.uri())) + .build() + .unwrap() + } + + /// A series and a movie as `POST /api/series` and `POST /api/movies` + /// leave them: added, never refreshed. + async fn added_titles() -> (TempDir, Db) { + let directory = tempfile::tempdir().unwrap(); + let database = Db::connect(directory.path().join("arr.db")).await.unwrap(); + database.migrate().await.unwrap(); + sqlx::query( + "INSERT INTO series (tmdb_id, title, root_id, auto_track) + SELECT 82728, 'Bluey', id, 1 FROM roots WHERE kind = 'tv' AND audience = 'main'", + ) + .execute(database.pool()) + .await + .unwrap(); + sqlx::query( + "INSERT INTO movies (tmdb_id, title, year, original_language, root_id) + SELECT 693134, 'Dune Part Two', 2024, 'en', id + FROM roots WHERE kind = 'movie' AND audience = 'main'", + ) + .execute(database.pool()) + .await + .unwrap(); + (directory, database) + } + + async fn series_stamp(database: &Db) -> Option { + sqlx::query_scalar("SELECT metadata_refreshed_at FROM series WHERE id = 1") + .fetch_one(database.pool()) + .await + .unwrap() + } + + async fn movie_stamp(database: &Db) -> Option { + sqlx::query_scalar("SELECT metadata_refreshed_at FROM movies WHERE id = 1") + .fetch_one(database.pool()) + .await + .unwrap() + } + + /// Issue #176: the whole point. A series added a moment ago has its + /// seasons revealed by the command, not by a tick up to a day away. + #[tokio::test] + async fn an_added_series_is_refreshed_on_demand() { + let (_dir, database) = added_titles().await; + let server = tmdb_series(200).await; + + series_action(&server) + .refresh_now(&database, 1) + .await + .unwrap(); + + let episodes: i64 = sqlx::query_scalar("SELECT count(*) FROM episodes") + .fetch_one(database.pool()) + .await + .unwrap(); + assert_eq!(episodes, 2, "the seasons TMDB knows must be revealed"); + assert!(series_stamp(&database).await.is_some()); + } + + /// A movie's digital release date is what §6.2 gates targeted search on, + /// and it only ever arrives from a refresh. + #[tokio::test] + async fn an_added_movie_is_refreshed_on_demand() { + let (_dir, database) = added_titles().await; + let server = tmdb_movie(200).await; + + refresh_movie(&movie_client(&server), &database, 1) + .await + .unwrap(); + + let (digital_release, poster): (Option, Option) = + sqlx::query_as("SELECT digital_release, poster_path FROM movies WHERE id = 1") + .fetch_one(database.pool()) + .await + .unwrap(); + assert_eq!(digital_release.as_deref(), Some("2024-04-16")); + assert_eq!(poster.as_deref(), Some("/dune-two.jpg")); + assert!(movie_stamp(&database).await.is_some()); + } + + /// A failed refresh must not roll the add back and must not stamp the + /// row, or the scheduled sweep would treat the title as done. + #[tokio::test] + async fn a_failed_series_refresh_leaves_the_stamp_null_for_the_sweep() { + let (_dir, database) = added_titles().await; + let server = tmdb_series(500).await; + + let result = series_action(&server).refresh_now(&database, 1).await; + + assert!(result.is_err()); + assert!(series_stamp(&database).await.is_none()); + let series: i64 = sqlx::query_scalar("SELECT count(*) FROM series") + .fetch_one(database.pool()) + .await + .unwrap(); + assert_eq!(series, 1, "the add is not rolled back"); + } + + #[tokio::test] + async fn a_failed_movie_refresh_leaves_the_stamp_null_for_the_sweep() { + let (_dir, database) = added_titles().await; + let server = tmdb_movie(500).await; + + let result = refresh_movie(&movie_client(&server), &database, 1).await; + + assert!(result.is_err()); + assert!(movie_stamp(&database).await.is_none()); + let movies: i64 = sqlx::query_scalar("SELECT count(*) FROM movies") + .fetch_one(database.pool()) + .await + .unwrap(); + assert_eq!(movies, 1, "the add is not rolled back"); + } + + /// Add then delete before the lane drains: nothing to refresh, and that + /// is not a failure. + #[tokio::test] + async fn a_command_naming_a_deleted_title_is_not_an_error() { + let (_dir, database) = added_titles().await; + let series = tmdb_series(200).await; + let movie = tmdb_movie(200).await; + + series_action(&series) + .refresh_now(&database, 404) + .await + .unwrap(); + refresh_movie(&movie_client(&movie), &database, 404) + .await + .unwrap(); + + assert!(series.received_requests().await.unwrap().is_empty()); + assert!(movie.received_requests().await.unwrap().is_empty()); + } + + /// The TTL gate the scheduled sweep uses applies here too: a title the + /// sweep just refreshed does not pay for a second TMDB call. + #[tokio::test] + async fn a_title_the_sweep_just_refreshed_costs_no_second_call() { + let (_dir, database) = added_titles().await; + let series = tmdb_series(200).await; + let movie = tmdb_movie(200).await; + sqlx::query( + "UPDATE series SET metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')", + ) + .execute(database.pool()) + .await + .unwrap(); + sqlx::query( + "UPDATE movies SET metadata_refreshed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')", + ) + .execute(database.pool()) + .await + .unwrap(); + + series_action(&series) + .refresh_now(&database, 1) + .await + .unwrap(); + refresh_movie(&movie_client(&movie), &database, 1) + .await + .unwrap(); + + assert!(series.received_requests().await.unwrap().is_empty()); + assert!(movie.received_requests().await.unwrap().is_empty()); + } +} diff --git a/crates/arr-daemon/src/series_refresh.rs b/crates/arr-daemon/src/series_refresh.rs index 8a7880a..46d7471 100644 --- a/crates/arr-daemon/src/series_refresh.rs +++ b/crates/arr-daemon/src/series_refresh.rs @@ -46,7 +46,7 @@ fn is_upstream_ended(status: &str) -> bool { } #[derive(Debug, thiserror::Error)] -enum RefreshError { +pub(crate) enum RefreshError { #[error("database: {0}")] Database(#[from] sqlx::Error), #[error("tmdb: {0}")] @@ -101,6 +101,38 @@ impl SeriesRefreshAction { Ok(outcomes) } + /// Refresh one series now, for the on-demand metadata lane (issue #176). + /// + /// A series deleted between the command being sent and drained is not an + /// error — there is simply nothing to refresh. The sweep's TTL gate + /// applies here too, so a title the sweep already refreshed costs no + /// second TMDB call. + pub(crate) async fn refresh_now( + &self, + database: &Db, + series_id: i64, + ) -> Result, RefreshError> { + let stale = sqlx::query_as!( + DueSeries, + r#"SELECT id AS "id!: i64", tmdb_id AS "tmdb_id!: i64", tvdb_id, + title AS "title!: String", year, original_language, + root_id AS "root_id!: i64", auto_track AS "auto_track!: bool", + upstream_ended AS "upstream_ended!: bool", metadata_refreshed_at, + poster_path, backdrop_path, vote_average + FROM series WHERE id = ?"#, + series_id + ) + .fetch_optional(database.pool()) + .await?; + let Some(stale) = stale else { + return Ok(None); + }; + if !metadata_refresh_due(stale.metadata_refreshed_at.as_deref()) { + return Ok(None); + } + self.refresh_series(database, &stale).await + } + async fn refresh_series( &self, database: &Db, From 93a3485c508e7b15a5204c0b29ec51285831d4ea Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Mon, 24 Aug 2026 18:39:22 +0100 Subject: [PATCH 3/6] fix(db): drop 0.8 rebuild workarounds, enforce title With sqlx 0.9 honouring `-- no-transaction`, 0007 loses its movie_releases_backup stash like 0014 did, and 0021 gains the CHECK (title <> '') that #153 abandoned because the 0.8 migrator could not run a rebuild with foreign keys off. The rebuild test now enters at migration 6 so the child links ride through all three rebuilds, and a new test proves the '' -> 'TBA' backfill and the rejection of new empty titles. Co-Authored-By: Claude Fable 5 --- .../migrations/0007_movie_state_canonical.sql | 14 ++--- .../0021_episode_title_required.sql | 50 +++++++++++++++-- crates/arr-db/src/lib.rs | 55 ++++++++++++++++++- 3 files changed, 103 insertions(+), 16 deletions(-) diff --git a/crates/arr-db/migrations/0007_movie_state_canonical.sql b/crates/arr-db/migrations/0007_movie_state_canonical.sql index bfd5f2f..b3f104b 100644 --- a/crates/arr-db/migrations/0007_movie_state_canonical.sql +++ b/crates/arr-db/migrations/0007_movie_state_canonical.sql @@ -1,13 +1,13 @@ +-- no-transaction -- #73. movies.state used missing|grabbed|imported, predating arr-core's -- canonical MediaState vocabulary (missing|downloading|available) that -- series/episodes (0005) already follow. -- --- SQLite can't ALTER a CHECK constraint, so this rebuilds the table -- which --- means dropping the old copy. `movie_releases` references it with --- ON DELETE CASCADE, so the drop would take its rows down too; stash them --- first and restore them once the new `movies` exists with the same ids. +-- SQLite can't ALTER a CHECK constraint, so this rebuilds the table. It runs +-- outside a transaction (#155) so `PRAGMA foreign_keys = OFF` takes effect +-- and dropping the old `movies` does not cascade into `movie_releases`. -CREATE TABLE movie_releases_backup AS SELECT * FROM movie_releases; +PRAGMA foreign_keys = OFF; CREATE TABLE movies_new ( id INTEGER PRIMARY KEY, @@ -68,6 +68,4 @@ BEGIN SELECT RAISE(ABORT, 'movies require a movie root'); END; -INSERT INTO movie_releases SELECT * FROM movie_releases_backup; - -DROP TABLE movie_releases_backup; +PRAGMA foreign_keys = ON; diff --git a/crates/arr-db/migrations/0021_episode_title_required.sql b/crates/arr-db/migrations/0021_episode_title_required.sql index f98a8c5..b24cfe3 100644 --- a/crates/arr-db/migrations/0021_episode_title_required.sql +++ b/crates/arr-db/migrations/0021_episode_title_required.sql @@ -1,11 +1,51 @@ +-- no-transaction -- #153. `episodes.title` accepted the empty string, which TMDB sends for an -- unaired episode it has not named yet. Empty titles leaked into §9.2's -- search haystack, §7.4 filenames and the compat shim as if they were real -- text. Rows already carrying `''` take the same "TBA" placeholder the TMDB -- boundary now substitutes — #121's guarded update replaces it once TMDB --- fills the title in. --- --- A hard CHECK (title <> '') would need a table rebuild with foreign_keys --- off, which sqlx 0.8's migrator cannot run (it always wraps a migration in --- a transaction, where that pragma is a no-op); enforcement lives in code. +-- fills the title in — and the rebuilt table enforces non-empty with a +-- CHECK. The rebuild runs outside a transaction (#155) so +-- `PRAGMA foreign_keys = OFF` takes effect and dropping the old `episodes` +-- does not cascade into `episode_releases`. + UPDATE episodes SET title = 'TBA', updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE title = ''; + +PRAGMA foreign_keys = OFF; + +CREATE TABLE episodes_new ( + id INTEGER PRIMARY KEY, + season_id INTEGER NOT NULL REFERENCES seasons (id) ON DELETE CASCADE, + number INTEGER NOT NULL CHECK (number >= 0), + title TEXT NOT NULL CHECK (title <> ''), + air_date TEXT, + wanted INTEGER NOT NULL DEFAULT 0 CHECK (wanted IN (0, 1)), + state TEXT NOT NULL DEFAULT 'missing' + CHECK (state IN ('missing', 'downloading', 'available', 'parked')), + search_attempts INTEGER NOT NULL DEFAULT 0, + last_searched_at TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + vanished INTEGER NOT NULL DEFAULT 0 CHECK (vanished IN (0, 1)), + UNIQUE (season_id, number) +) STRICT; + +INSERT INTO episodes_new ( + id, season_id, number, title, air_date, wanted, state, search_attempts, + last_searched_at, created_at, updated_at, vanished +) +SELECT + id, season_id, number, title, air_date, wanted, state, search_attempts, + last_searched_at, created_at, updated_at, vanished +FROM episodes; + +DROP TABLE episodes; +ALTER TABLE episodes_new RENAME TO episodes; + +CREATE INDEX episodes_pending_search + ON episodes (last_searched_at) + WHERE wanted = 1 AND state = 'missing'; + +CREATE INDEX episodes_state ON episodes (state); + +PRAGMA foreign_keys = ON; diff --git a/crates/arr-db/src/lib.rs b/crates/arr-db/src/lib.rs index 3a07143..e5a86a9 100644 --- a/crates/arr-db/src/lib.rs +++ b/crates/arr-db/src/lib.rs @@ -336,7 +336,7 @@ mod tests { assert_eq!(renamed_from_imported, "available"); } - /// #155: migration 0014 rebuilds `movies` and `episodes` outside a + /// #155: the table-rebuild migrations (0007, 0014, 0021) run outside a /// transaction so `PRAGMA foreign_keys = OFF` holds and dropping the old /// tables does not cascade-delete `movie_releases`/`episode_releases`. #[tokio::test] @@ -347,9 +347,9 @@ mod tests { .expect("connect"); MIGRATOR - .run_to(13, db.pool()) + .run_to(6, db.pool()) .await - .expect("migrations before the 0014 rebuild"); + .expect("migrations before the first rebuild"); sqlx::query( "INSERT INTO movies (tmdb_id, title, root_id) @@ -419,6 +419,55 @@ mod tests { ); } + /// #153: an empty episode title takes the TBA placeholder during 0021's + /// backfill, and the rebuilt table's CHECK rejects new empty titles. + #[tokio::test] + async fn empty_episode_titles_are_backfilled_and_then_rejected() { + let dir = tempfile::tempdir().expect("tempdir"); + let db = Db::connect(dir.path().join("arr.db")) + .await + .expect("connect"); + + MIGRATOR + .run_to(20, db.pool()) + .await + .expect("migrations before the #153 backfill"); + + let series_id = sqlx::query( + "INSERT INTO series (tmdb_id, title, root_id) + SELECT 82728, 'Bluey', id FROM roots WHERE kind = 'tv' LIMIT 1", + ) + .execute(db.pool()) + .await + .expect("series") + .last_insert_rowid(); + let season_id = sqlx::query("INSERT INTO seasons (series_id, number) VALUES (?, 1)") + .bind(series_id) + .execute(db.pool()) + .await + .expect("season") + .last_insert_rowid(); + sqlx::query("INSERT INTO episodes (season_id, number, title) VALUES (?, 1, '')") + .bind(season_id) + .execute(db.pool()) + .await + .expect("empty title, valid before 0021"); + + db.migrate().await.expect("remaining migrations"); + + let title: String = sqlx::query_scalar("SELECT title FROM episodes") + .fetch_one(db.pool()) + .await + .expect("episode row survives the rebuild"); + assert_eq!(title, "TBA"); + + sqlx::query("INSERT INTO episodes (season_id, number, title) VALUES (?, 2, '')") + .bind(season_id) + .execute(db.pool()) + .await + .expect_err("the rebuilt column rejects the empty string"); + } + #[tokio::test] async fn seeds_two_tv_roots_with_distinct_policies() { let (_dir, db) = fresh().await; From ddf1c5fd9348aee3e55c7522328105c223d21f9c Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Mon, 24 Aug 2026 18:52:15 +0100 Subject: [PATCH 4/6] fix(api): search survives a down TMDB and errors name the upstream --- crates/arr-api/src/movies.rs | 13 ++- crates/arr-api/src/search.rs | 167 ++++++++++++++++++++++++++++++++--- web/src/main.ts | 14 ++- web/src/search.ts | 8 ++ 4 files changed, 185 insertions(+), 17 deletions(-) diff --git a/crates/arr-api/src/movies.rs b/crates/arr-api/src/movies.rs index b315b78..b8ce472 100644 --- a/crates/arr-api/src/movies.rs +++ b/crates/arr-api/src/movies.rs @@ -159,7 +159,12 @@ pub enum ApiError { NoTrailer, Conflict(String), Invalid(String), + /// The SQLite handle is gone. Distinct from [`Self::Upstream`] so a + /// TMDB or Prowlarr fault never reads as a database fault (issue #168). Unavailable, + /// An upstream service could not be reached, is misconfigured or is + /// unconfigured. The payload names it. + Upstream(&'static str), Database(String), /// A library delete that could not touch the disk. Named separately from /// [`Self::Database`] because the row is still there and a retry is the @@ -184,6 +189,10 @@ impl IntoResponse for ApiError { StatusCode::SERVICE_UNAVAILABLE, "database unavailable".into(), ), + Self::Upstream(name) => ( + StatusCode::SERVICE_UNAVAILABLE, + format!("{name} unavailable"), + ), Self::Database(error) => { tracing::error!(%error, "API database error"); (StatusCode::INTERNAL_SERVER_ERROR, "database error".into()) @@ -554,7 +563,7 @@ pub async fn search( } state .send_movie_command(MovieCommand::Search { movie_id: id }) - .map_err(|_| ApiError::Unavailable)?; + .map_err(|_| ApiError::Upstream("daemon"))?; Ok((StatusCode::ACCEPTED, Json(Accepted { accepted: true }))) } @@ -671,7 +680,7 @@ pub async fn grab( movie_id, release_id, }) - .map_err(|_| ApiError::Unavailable)?; + .map_err(|_| ApiError::Upstream("daemon"))?; Ok((StatusCode::ACCEPTED, Json(Accepted { accepted: true }))) } diff --git a/crates/arr-api/src/search.rs b/crates/arr-api/src/search.rs index 33c82c8..73dc4ce 100644 --- a/crates/arr-api/src/search.rs +++ b/crates/arr-api/src/search.rs @@ -36,9 +36,23 @@ pub struct SearchResponse { /// In-library hits, grouped first (§9.2): movies and series by title. pub library: Vec, pub tmdb: Vec, + /// Why the `tmdb` group is what it is. An unreachable TMDB must not cost + /// the operator the library half (issue #168), so the group goes empty + /// while this says why: `"ok"` with an empty group means TMDB answered + /// and found nothing — a different world from `"unavailable"`. + pub tmdb_status: TmdbStatus, pub manual: Option, } +#[derive(Debug, Clone, Copy, Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum TmdbStatus { + /// The `tmdb` group carries TMDB's answer — possibly an empty one. + Ok, + /// TMDB could not be reached or is unconfigured. + Unavailable, +} + /// One in-library hit. Tagged so a client can branch on what it found /// without re-deriving it from fields. #[derive(Debug, Clone, Serialize, ToSchema)] @@ -167,6 +181,7 @@ pub async fn search( kind, library: Vec::new(), tmdb: Vec::new(), + tmdb_status: TmdbStatus::Ok, manual: Some(input.to_owned()), })); } @@ -211,8 +226,27 @@ pub async fn search( }; library.extend(series_rows.into_iter().map(LibraryResult::Series)); - let tmdb = tmdb_client(&state)?; - let results = search_tmdb(&tmdb, kind, input).await?; + // A degraded TMDB must not discard the library results already in hand + // (issue #168): the group goes empty and `tmdb_status` says why, so the + // operator can tell "TMDB found nothing" from "TMDB is unreachable". + let (results, tmdb_status) = match tmdb_client(&state) { + Ok(tmdb) => match search_tmdb(&tmdb, kind, input).await { + Ok(found) => (found, TmdbStatus::Ok), + // TMDB answered and has no such title — an empty group that is + // not a fault. + Err(ApiError::NotFound) => (Vec::new(), TmdbStatus::Ok), + Err(ApiError::Upstream(name)) => { + tracing::warn!(upstream = name, "unified search: upstream unavailable"); + (Vec::new(), TmdbStatus::Unavailable) + } + Err(error) => return Err(error), + }, + Err(ApiError::Upstream(name)) => { + tracing::warn!(upstream = name, "unified search: upstream unavailable"); + (Vec::new(), TmdbStatus::Unavailable) + } + Err(error) => return Err(error), + }; if matches!(kind, SearchInputKind::ImdbId) { for result in &results { match result { @@ -240,6 +274,7 @@ pub async fn search( kind, library, tmdb: results, + tmdb_status, manual: None, })) } @@ -415,7 +450,7 @@ async fn movie_releases( let indexers = prowlarr .indexers() .await - .map_err(|_| ApiError::Unavailable)?; + .map_err(|_| ApiError::Upstream("prowlarr"))?; let blacklist = Blacklist::load(database.pool()).await?; let policy = loaded.policy; let overrides = loaded.overrides; @@ -512,7 +547,7 @@ async fn episode_releases( let indexers = prowlarr .indexers() .await - .map_err(|_| ApiError::Unavailable)?; + .map_err(|_| ApiError::Upstream("prowlarr"))?; let blacklist = Blacklist::load(database.pool()).await?; let mut classified = Vec::new(); @@ -634,8 +669,9 @@ fn prowlarr_client(state: &AppState) -> Result { let api_key = upstreams .prowlarr_api_key .clone() - .ok_or(ApiError::Unavailable)?; - ProwlarrClient::new(upstreams.prowlarr_url.clone(), api_key).map_err(|_| ApiError::Unavailable) + .ok_or(ApiError::Upstream("prowlarr"))?; + ProwlarrClient::new(upstreams.prowlarr_url.clone(), api_key) + .map_err(|_| ApiError::Upstream("prowlarr")) } pub(crate) fn tmdb_client(state: &AppState) -> Result { @@ -643,17 +679,17 @@ pub(crate) fn tmdb_client(state: &AppState) -> Result ApiError { match error { arr_meta::Error::NotFound { .. } => ApiError::NotFound, - _ => ApiError::Unavailable, + _ => ApiError::Upstream("tmdb"), } } @@ -781,6 +817,23 @@ mod tests { async fn application( tmdb: &MockServer, prowlarr: &MockServer, + ) -> (tempfile::TempDir, AppState, String) { + application_with( + tmdb.uri(), + Some("tmdb-key".into()), + Some("prowlarr-key".into()), + prowlarr.uri(), + ) + .await + } + + /// Like [`application`] but with each upstream knob explicit, so tests + /// can take TMDB or the API keys away. + async fn application_with( + tmdb_url: String, + tmdb_key: Option, + prowlarr_key: Option, + prowlarr_url: String, ) -> (tempfile::TempDir, AppState, String) { let dir = tempfile::tempdir().expect("tempdir"); let database = arr_db::Db::connect(dir.path().join("arr.db")) @@ -788,10 +841,10 @@ mod tests { .expect("database"); database.migrate().await.expect("migrate"); let state = AppState::new( - Upstreams::new(prowlarr.uri(), "http://127.0.0.1:1".into()) - .with_prowlarr_api_key(Some("prowlarr-key".into())) - .with_tmdb_url(tmdb.uri()) - .with_tmdb_api_key(Some("tmdb-key".into())), + Upstreams::new(prowlarr_url, "http://127.0.0.1:1".into()) + .with_prowlarr_api_key(prowlarr_key) + .with_tmdb_url(tmdb_url) + .with_tmdb_api_key(tmdb_key), ) .expect("state") .with_database(database); @@ -985,6 +1038,94 @@ mod tests { assert_eq!(response["tmdb"][0]["title"], "1917"); } + /// Issue #168: an unreachable TMDB must not cost the operator the + /// library half. The request succeeds, the tmdb group goes empty, and + /// `tmdb_status` says the upstream — not a clean "nothing found". + #[tokio::test] + async fn an_unreachable_tmdb_still_returns_the_library_half() { + // Port 1 is closed; TMDB is configured but cannot be reached. + let (dir, state, base) = application_with( + "http://127.0.0.1:1".into(), + Some("tmdb-key".into()), + Some("prowlarr-key".into()), + "http://127.0.0.1:1".into(), + ) + .await; + sqlx::query("INSERT INTO movies (tmdb_id, title, year, original_language, root_id) VALUES (438631, 'Dune', 2021, 'en', 2)") + .execute(state.database().expect("database").pool()).await.expect("movie"); + + let response = reqwest::get(format!("{base}/api/search?q=Dune")) + .await + .expect("search"); + assert_eq!(response.status(), 200); + let body: serde_json::Value = response.json().await.expect("json"); + assert_eq!(body["library"][0]["kind"], "movie"); + assert_eq!(body["library"][0]["title"], "Dune"); + assert!(body["tmdb"].as_array().expect("tmdb").is_empty()); + assert_eq!(body["tmdb_status"], "unavailable"); + drop(dir); + } + + /// Issue #168: "TMDB answered and found nothing" must stay + /// distinguishable from "TMDB could not be reached" — both leave the + /// tmdb group empty, only `tmdb_status` tells them apart. + #[tokio::test] + async fn an_empty_tmdb_answer_is_not_an_unavailable_tmdb() { + let tmdb = MockServer::start().await; + let prowlarr = MockServer::start().await; + for endpoint in ["/search/movie", "/search/tv"] { + Mock::given(method("GET")) + .and(path(endpoint)) + .respond_with( + ResponseTemplate::new(200).set_body_json(serde_json::json!({"results":[]})), + ) + .mount(&tmdb) + .await; + } + let (_dir, _state, base) = application(&tmdb, &prowlarr).await; + + let body: serde_json::Value = reqwest::get(format!("{base}/api/search?q=nonexistent")) + .await + .expect("search") + .json() + .await + .expect("json"); + assert_eq!(body["library"].as_array().expect("library").len(), 0); + assert_eq!(body["tmdb"].as_array().expect("tmdb").len(), 0); + assert_eq!(body["tmdb_status"], "ok"); + } + + /// Issue #168: a Prowlarr fault names Prowlarr, never the database. + #[tokio::test] + async fn a_prowlarr_fault_names_prowlarr() { + let tmdb = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/movie/693134")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "id":693_134, "imdb_id":"tt15239678", "title":"Dune Part Two", + "original_title":"Dune Part Two", "original_language":"en", "status":"Released" + }))) + .mount(&tmdb) + .await; + // No Prowlarr API key configured: the client cannot even be built. + let (_dir, state, base) = application_with( + tmdb.uri(), + Some("tmdb-key".into()), + None, + "http://127.0.0.1:1".into(), + ) + .await; + sqlx::query("INSERT INTO movies (tmdb_id, title, year, original_language, root_id) VALUES (693134, 'Dune Part Two', 2024, 'en', 2)") + .execute(state.database().expect("database").pool()).await.expect("movie"); + + let response = reqwest::get(format!("{base}/api/releases?movie_id=1")) + .await + .expect("releases"); + assert_eq!(response.status(), 503); + let body: serde_json::Value = response.json().await.expect("json"); + assert_eq!(body["error"], "prowlarr unavailable"); + } + /// §9.2: a pasted `tt` id resolves a series exactly as a TMDB id does — /// a hit on TMDB and, when tracked, in the library set too. #[tokio::test] diff --git a/web/src/main.ts b/web/src/main.ts index b1d6a33..509bfc6 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -576,11 +576,21 @@ function searchMain( (hit): hit is LibrarySeriesHit => hit.kind === "series", ); const inLibrary = new Set([...libraryMovies, ...librarySeries].map((title) => title.tmdb_id)); + // Issue #168: an unreachable TMDB degrades only its own group — the + // operator sees the library half plus why TMDB is missing, and can tell + // that apart from a clean "nothing found". + const tmdbDown = response.tmdb_status === "unavailable"; if (response.library.length === 0 && response.tmdb.length === 0) { - setStatus("no matches in library or on tmdb"); + setStatus( + tmdbDown ? "no matches in library — tmdb unavailable" : "no matches in library or on tmdb", + ); return; } - setStatus(null); + if (tmdbDown) { + setStatus("tmdb unavailable — showing library matches only", "fault"); + } else { + setStatus(null); + } if (response.library.length > 0) { refs.groups.library.section.hidden = false; diff --git a/web/src/search.ts b/web/src/search.ts index 896454c..c5082f7 100644 --- a/web/src/search.ts +++ b/web/src/search.ts @@ -66,10 +66,18 @@ export interface TmdbSeries { export type TmdbResult = TmdbMovie | TmdbSeries; +/** + * Why the tmdb group is what it is. "ok" with an empty tmdb array means + * TMDB answered and found nothing; "unavailable" means TMDB could not be + * reached — the library results still came back (issue #168). + */ +export type TmdbStatus = "ok" | "unavailable"; + export interface SearchResponse { kind: SearchInputKind; library: LibraryResult[]; tmdb: TmdbResult[]; + tmdb_status: TmdbStatus; manual: string | null; } From 3261741415e2529105eff688cc949b65ad55ec0f Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Mon, 24 Aug 2026 18:56:36 +0100 Subject: [PATCH 5/6] fix(meta): bound the TMDB response cache on disk Closes #158 --- Cargo.lock | 1 + DESIGN.md | 9 +- crates/arr-meta/Cargo.toml | 1 + crates/arr-meta/src/cache.rs | 302 +++++++++++++++++++++++++++++----- crates/arr-meta/src/client.rs | 80 ++++++++- crates/arr-meta/src/error.rs | 4 + crates/arr-meta/src/lib.rs | 9 +- crates/arr-meta/tests/tmdb.rs | 2 +- 8 files changed, 356 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec522c3..1042d70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "tempfile", "thiserror", "tokio", "tracing", diff --git a/DESIGN.md b/DESIGN.md index c2b07f4..457278a 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -578,8 +578,11 @@ thing that can be down. fragments, never URLs; the browser composes the URL and chooses the size. No image proxy and no image cache in the service. -**Rich detail is not persisted.** It is served through arr-meta's existing -24-hour response cache. The single exception is `poster_path`, `backdrop_path` +**Rich detail is not persisted.** It is served through arr-meta's 24-hour +response cache, which lives on disk in a directory alongside the database +(§10), bounded by both age and total size (#158) — not the database itself, +and not an image cache; images stay hotlinked as above. The single exception +is `poster_path`, `backdrop_path` and `vote_average`, stored on `movies` and `series` and written by the daily metadata refresh (§8), so library views render without a TMDB call. @@ -624,7 +627,7 @@ service. Policy lives in the database, not a config file — size targets and DV rules get tuned by hand during testing and a restart-to-reload loop gets old immediately. Only bootstrap settings (bind address, Prowlarr URL, Transmission URL, TMDB key, -media root) come from config/env. +media root, TMDB response cache directory) come from config/env. Backup is `sqlite3 .backup` on a timer. diff --git a/crates/arr-meta/Cargo.toml b/crates/arr-meta/Cargo.toml index d6e9525..e9ee44c 100644 --- a/crates/arr-meta/Cargo.toml +++ b/crates/arr-meta/Cargo.toml @@ -15,6 +15,7 @@ thiserror.workspace = true tracing.workspace = true [dev-dependencies] +tempfile.workspace = true tokio.workspace = true wiremock.workspace = true diff --git a/crates/arr-meta/src/cache.rs b/crates/arr-meta/src/cache.rs index 7519272..6566b83 100644 --- a/crates/arr-meta/src/cache.rs +++ b/crates/arr-meta/src/cache.rs @@ -1,69 +1,289 @@ -//! A small time-to-live cache over raw response bodies. +//! A time-to-live cache over raw response bodies, backed by files on disk. //! //! Keyed on a logical request key rather than the URL, so the API key never -//! becomes part of a cache key. Bodies are stored unparsed: parsing again on a -//! hit costs microseconds and keeps one cache serving every endpoint. +//! becomes part of a cache key (see `client::request_url`). The key is +//! hashed into the filename rather than embedded verbatim, so a search +//! query or a title id never becomes readable in a directory listing. +//! +//! Bounded two ways: an entry older than `ttl` is dropped whether or not +//! anyone reads it, and the directory as a whole is capped at `max_bytes` — +//! age alone does not stop a busy day filling the disk. Both are enforced by +//! [`Cache::sweep`], which runs after every write and is also safe to call +//! on a schedule (the daily metadata tick, DESIGN.md §8). +//! +//! File IO here is synchronous. Bodies are TMDB JSON responses — at most a +//! few hundred KB — and this runs once per miss, not in a hot loop, so +//! blocking the calling task briefly is a fair trade for not pulling an +//! async-fs dependency into a crate that otherwise doesn't need one. -use std::collections::HashMap; +use std::fs; +use std::io; +use std::path::PathBuf; +use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use std::sync::{Mutex, PoisonError}; -use std::time::{Duration, Instant}; +use std::time::{Duration, SystemTime}; -#[derive(Debug)] -struct Entry { - stored_at: Instant, - body: Arc, -} +const FILE_EXT: &str = "tmdb-cache"; #[derive(Debug)] pub(crate) struct Cache { + dir: PathBuf, ttl: Duration, - entries: Mutex>, + max_bytes: u64, + /// Whether this cache created `dir` itself (the default, process-unique + /// temp directory) rather than being pointed at one the caller owns. Only + /// a directory we created is ours to delete. + owns_dir: bool, } impl Cache { - pub(crate) fn new(ttl: Duration) -> Self { - Self { + pub(crate) fn new( + dir: PathBuf, + ttl: Duration, + max_bytes: u64, + owns_dir: bool, + ) -> io::Result { + fs::create_dir_all(&dir)?; + Ok(Self { + dir, ttl, - entries: Mutex::new(HashMap::new()), - } + max_bytes, + owns_dir, + }) } /// The cached body for `key`, if one is present and still fresh. pub(crate) fn get(&self, key: &str) -> Option> { - let mut entries = self.entries.lock().unwrap_or_else(PoisonError::into_inner); - let entry = entries.get(key)?; - if entry.stored_at.elapsed() < self.ttl { - return Some(Arc::clone(&entry.body)); + let path = self.path_for(key); + let metadata = fs::metadata(&path).ok()?; + let age = metadata.modified().ok()?.elapsed().unwrap_or_default(); + if age >= self.ttl { + let _ = fs::remove_file(&path); + return None; } - entries.remove(key); - None + fs::read_to_string(&path).ok().map(Arc::from) } - pub(crate) fn insert(&self, key: String, body: Arc) { - let mut entries = self.entries.lock().unwrap_or_else(PoisonError::into_inner); - entries.insert( - key, - Entry { - stored_at: Instant::now(), - body, - }, - ); + /// Write `body` for `key`, atomically: a temp file plus a rename, so a + /// killed process can never leave a half-written body that later reads + /// as valid JSON. + pub(crate) fn insert(&self, key: &str, body: &str) { + let path = self.path_for(key); + let tmp = self.tmp_path(); + if fs::write(&tmp, body).is_err() { + return; + } + if fs::rename(&tmp, &path).is_err() { + let _ = fs::remove_file(&tmp); + return; + } + self.sweep(); } pub(crate) fn remove(&self, key: &str) { - self.entries - .lock() - .unwrap_or_else(PoisonError::into_inner) - .remove(key); + let _ = fs::remove_file(self.path_for(key)); } - /// Drop everything. The daily metadata refresh does not need this — entries - /// expire on their own — but a forced refresh from the UI does. + /// Drop everything. The daily metadata refresh does not need this — + /// entries expire on their own — but a forced refresh from the UI does. pub(crate) fn clear(&self) { - self.entries - .lock() - .unwrap_or_else(PoisonError::into_inner) - .clear(); + self.for_each_entry(|path, _modified, _len| { + let _ = fs::remove_file(path); + }); + } + + /// Evict every expired entry, then, if the directory is still over + /// `max_bytes`, the oldest survivors until it is not. + pub(crate) fn sweep(&self) { + let mut survivors = Vec::new(); + self.for_each_entry(|path, modified, len| { + if modified.elapsed().unwrap_or_default() >= self.ttl { + let _ = fs::remove_file(&path); + } else { + survivors.push((path, modified, len)); + } + }); + + let mut total: u64 = survivors.iter().map(|(_, _, len)| len).sum(); + if total <= self.max_bytes { + return; + } + + // Oldest first, so the size sweep behaves like the age one: the most + // recently fetched title is the one that survives. + survivors.sort_by_key(|(_, modified, _)| *modified); + for (path, _, len) in survivors { + if total <= self.max_bytes { + break; + } + if fs::remove_file(&path).is_ok() { + total = total.saturating_sub(len); + } + } + } + + fn for_each_entry(&self, mut visit: impl FnMut(PathBuf, SystemTime, u64)) { + let Ok(read_dir) = fs::read_dir(&self.dir) else { + return; + }; + for entry in read_dir.flatten() { + let path = entry.path(); + if path.extension().is_none_or(|ext| ext != FILE_EXT) { + continue; + } + let Ok(metadata) = entry.metadata() else { + continue; + }; + let Ok(modified) = metadata.modified() else { + continue; + }; + visit(path, modified, metadata.len()); + } + } + + fn path_for(&self, key: &str) -> PathBuf { + self.dir + .join(format!("{:016x}.{FILE_EXT}", fnv1a64(key.as_bytes()))) + } + + fn tmp_path(&self) -> PathBuf { + static COUNTER: AtomicU64 = AtomicU64::new(0); + let unique = COUNTER.fetch_add(1, Ordering::Relaxed); + self.dir + .join(format!("{}-{unique}.{FILE_EXT}.tmp", std::process::id())) + } +} + +impl Drop for Cache { + fn drop(&mut self) { + if self.owns_dir { + let _ = fs::remove_dir_all(&self.dir); + } + } +} + +/// FNV-1a, 64-bit. Not cryptographic — it only needs to pick a filename — +/// but unlike `std`'s `DefaultHasher` it is stable across runs, so a +/// restarted process still hits its own cache from before. +fn fnv1a64(bytes: &[u8]) -> u64 { + const OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325; + const PRIME: u64 = 0x0000_0100_0000_01b3; + bytes.iter().fold(OFFSET_BASIS, |hash, &byte| { + (hash ^ u64::from(byte)).wrapping_mul(PRIME) + }) +} + +#[cfg(test)] +mod tests { + use super::Cache; + use std::time::Duration; + + fn cache(ttl: Duration, max_bytes: u64) -> (tempfile::TempDir, Cache) { + let dir = tempfile::tempdir().expect("tempdir"); + let cache = Cache::new(dir.path().to_owned(), ttl, max_bytes, false).expect("cache"); + (dir, cache) + } + + #[test] + fn hit_returns_what_was_inserted() { + let (_dir, cache) = cache(Duration::from_secs(60), 1024); + cache.insert("movie/1", "hello"); + assert_eq!(cache.get("movie/1").as_deref(), Some("hello")); + } + + #[test] + fn miss_is_none() { + let (_dir, cache) = cache(Duration::from_secs(60), 1024); + assert_eq!(cache.get("movie/absent"), None); + } + + #[test] + fn an_expired_entry_is_deleted_from_disk_not_just_hidden() { + let (dir, cache) = cache(Duration::from_millis(1), 1024); + cache.insert("movie/1", "hello"); + std::thread::sleep(Duration::from_millis(50)); + + assert_eq!(cache.get("movie/1"), None, "a stale entry must not hit"); + + let files: Vec<_> = std::fs::read_dir(dir.path()) + .expect("read dir") + .filter_map(Result::ok) + .collect(); + assert!( + files.is_empty(), + "expired entry should have been removed from disk, found {files:?}" + ); + } + + #[test] + fn sweep_evicts_expired_entries_without_being_read() { + let (dir, cache) = cache(Duration::from_millis(1), 1024); + cache.insert("movie/1", "hello"); + std::thread::sleep(Duration::from_millis(50)); + + cache.sweep(); + + let count = std::fs::read_dir(dir.path()) + .expect("read dir") + .filter_map(Result::ok) + .count(); + assert_eq!(count, 0, "sweep should have deleted the expired entry"); + } + + #[test] + fn over_the_size_budget_evicts_the_oldest_entry_first() { + // A generous TTL, so only the size budget can be doing the evicting. + let (dir, cache) = cache(Duration::from_secs(60), 12); + cache.insert("movie/old", "0123456789"); // 10 bytes, written first + std::thread::sleep(Duration::from_millis(10)); + cache.insert("movie/new", "0123456789"); // pushes total to 20 > 12 + + assert_eq!( + cache.get("movie/old"), + None, + "the older entry should have been evicted to stay under budget" + ); + assert_eq!( + cache.get("movie/new").as_deref(), + Some("0123456789"), + "the newer entry should survive" + ); + + let total: u64 = std::fs::read_dir(dir.path()) + .expect("read dir") + .filter_map(Result::ok) + .map(|entry| entry.metadata().expect("metadata").len()) + .sum(); + assert!( + total <= 12, + "directory should be back under budget, was {total}" + ); + } + + #[test] + fn dropping_an_owned_cache_removes_its_directory() { + let dir = tempfile::tempdir().expect("tempdir"); + let owned_subdir = dir.path().join("owned"); + let cache = + Cache::new(owned_subdir.clone(), Duration::from_secs(60), 1024, true).expect("cache"); + cache.insert("movie/1", "hello"); + drop(cache); + + assert!( + !owned_subdir.exists(), + "an owned cache directory should be cleaned up on drop" + ); + } + + #[test] + fn dropping_a_borrowed_cache_leaves_its_directory() { + let (dir, cache) = cache(Duration::from_secs(60), 1024); + cache.insert("movie/1", "hello"); + drop(cache); + + assert!( + dir.path().exists(), + "a caller-supplied cache directory must survive the client that used it" + ); } } diff --git a/crates/arr-meta/src/client.rs b/crates/arr-meta/src/client.rs index f0edbe7..782ca27 100644 --- a/crates/arr-meta/src/client.rs +++ b/crates/arr-meta/src/client.rs @@ -1,6 +1,7 @@ //! The TMDB HTTP client. -use std::sync::Arc; +use std::path::PathBuf; +use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; use reqwest::{StatusCode, Url}; @@ -21,12 +22,21 @@ pub const DEFAULT_BASE_URL: &str = "https://api.themoviedb.org/3/"; /// One day. Metadata refresh is a daily tick (§8), not a per-request cost. pub const DEFAULT_CACHE_TTL: Duration = Duration::from_hours(24); +/// 64 MiB. Dokploy's samples for `arr.n62.casa` showed resident memory going +/// from 16.1-16.5 MiB to 48.77 MiB across the TV-tracking and rich-metadata +/// milestones (issue #158) — roughly 32 MiB of growth attributable to the +/// same cache this now bounds, on one household's ordinary usage. Doubling +/// that for a busier day, and rounding up, gives a ceiling that is real — +/// unlike the memory it replaces — without being tight; `DEFAULT_CACHE_TTL` +/// clears the whole directory every day regardless. +pub const DEFAULT_CACHE_MAX_BYTES: u64 = 64 * 1024 * 1024; + const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); /// How much of an unexpected response body is worth keeping in an error. const MAX_ERROR_BODY: usize = 512; -/// A TMDB client with an in-process response cache. +/// A TMDB client with an on-disk response cache. /// /// Deliberately not `Clone`: the cache lives inside it, so build one and share /// it behind an `Arc` rather than handing out copies that each miss. @@ -66,6 +76,8 @@ impl TmdbClient { api_key: api_key.into(), base_url: DEFAULT_BASE_URL.to_owned(), cache_ttl: DEFAULT_CACHE_TTL, + cache_dir: None, + cache_max_bytes: DEFAULT_CACHE_MAX_BYTES, timeout: DEFAULT_TIMEOUT, } } @@ -254,6 +266,15 @@ impl TmdbClient { self.cache.clear(); } + /// Evict expired cache entries, and the oldest survivors if the + /// directory is still over its size budget. `insert` already does this + /// after every write, so calling this is only needed to reclaim space an + /// idle cache is sitting on — the daily metadata tick (§8) is the + /// intended caller. + pub fn sweep_cache(&self) { + self.cache.sweep(); + } + /// Fetch and decode, caching only what decoded. /// /// Decoding before the insert matters: a malformed response that got into @@ -279,7 +300,7 @@ impl TmdbClient { let body = self.fetch(url, path).await?; let value = serde_json::from_str(&body)?; - self.cache.insert(cache_key, Arc::from(body)); + self.cache.insert(&cache_key, &body); Ok(value) } @@ -364,6 +385,8 @@ pub struct TmdbClientBuilder { api_key: String, base_url: String, cache_ttl: Duration, + cache_dir: Option, + cache_max_bytes: u64, timeout: Duration, } @@ -383,6 +406,30 @@ impl TmdbClientBuilder { self } + /// Where cache files live. + /// + /// Left unset, `build` picks a process-unique directory under the OS + /// temp dir and removes it when the client is dropped — a warm cache + /// never outlives one process, same as before this cache moved to disk. + /// Production wants this pointed explicitly at a directory alongside the + /// database (DESIGN.md §10), from bootstrap config, so a restart keeps a + /// warm cache; a directory set this way is the caller's and is left + /// alone on drop. + #[must_use] + pub fn cache_dir(mut self, cache_dir: impl Into) -> Self { + self.cache_dir = Some(cache_dir.into()); + self + } + + /// Upper bound on total cache size in bytes, enforced by evicting the + /// oldest entries first. See [`DEFAULT_CACHE_MAX_BYTES`] for how the + /// default was chosen. + #[must_use] + pub fn cache_max_bytes(mut self, cache_max_bytes: u64) -> Self { + self.cache_max_bytes = cache_max_bytes; + self + } + /// Per-request timeout. #[must_use] pub fn timeout(mut self, timeout: Duration) -> Self { @@ -395,7 +442,8 @@ impl TmdbClientBuilder { /// # Errors /// /// [`Error::BaseUrl`] if the base URL will not parse, [`Error::Transport`] - /// if the HTTP client cannot be built. + /// if the HTTP client cannot be built, [`Error::Cache`] if the cache + /// directory cannot be created. pub fn build(self) -> Result { // Without a trailing slash `Url::join` replaces the last path segment // instead of appending, which silently drops the `/3`. @@ -410,11 +458,33 @@ impl TmdbClientBuilder { .user_agent(concat!("arr/", env!("CARGO_PKG_VERSION"))) .build()?; + let (cache_dir, owns_cache_dir) = match self.cache_dir { + Some(dir) => (dir, false), + None => (default_cache_dir(), true), + }; + let cache = Cache::new( + cache_dir, + self.cache_ttl, + self.cache_max_bytes, + owns_cache_dir, + )?; + Ok(TmdbClient { http, base_url, api_key: self.api_key, - cache: Cache::new(self.cache_ttl), + cache, }) } } + +/// A process-unique directory under the OS temp dir, used when the caller +/// does not configure `cache_dir` explicitly. Unique per built client (not +/// just per process) so that tests building several clients in the same +/// process, against the same TMDB paths but different mock responses, do not +/// share a cache and see each other's bodies. +fn default_cache_dir() -> PathBuf { + static COUNTER: AtomicU64 = AtomicU64::new(0); + let unique = COUNTER.fetch_add(1, Ordering::Relaxed); + std::env::temp_dir().join(format!("arr-meta-cache-{}-{unique}", std::process::id())) +} diff --git a/crates/arr-meta/src/error.rs b/crates/arr-meta/src/error.rs index 38716da..e6c617f 100644 --- a/crates/arr-meta/src/error.rs +++ b/crates/arr-meta/src/error.rs @@ -53,4 +53,8 @@ pub enum Error { /// The configured base URL is not a URL. #[error("invalid TMDB base URL: {0}")] BaseUrl(String), + + /// The on-disk response cache directory could not be created. + #[error("TMDB cache directory: {0}")] + Cache(#[from] std::io::Error), } diff --git a/crates/arr-meta/src/lib.rs b/crates/arr-meta/src/lib.rs index ca6d0f6..8eba96d 100644 --- a/crates/arr-meta/src/lib.rs +++ b/crates/arr-meta/src/lib.rs @@ -7,8 +7,11 @@ //! - The digital release date gates targeted search (§6.2). A movie with no //! digital release date must get zero searches. //! -//! Responses are cached in process with a time-to-live, defaulting to a day, +//! Responses are cached on disk with a time-to-live, defaulting to a day, //! because metadata refresh is a daily tick (§8) and not a per-request cost. +//! The cache directory is bounded by both age and total size (see +//! [`DEFAULT_CACHE_MAX_BYTES`]) so a service meant to run for months does +//! not grow without limit. // `unused_crate_dependencies` is a per-target lint and the library's own test // target links the dev-dependencies without using them. The real uses are in @@ -21,7 +24,9 @@ mod client; mod error; mod model; -pub use client::{TmdbClient, TmdbClientBuilder, DEFAULT_BASE_URL, DEFAULT_CACHE_TTL}; +pub use client::{ + TmdbClient, TmdbClientBuilder, DEFAULT_BASE_URL, DEFAULT_CACHE_MAX_BYTES, DEFAULT_CACHE_TTL, +}; pub use error::{Error, Result}; pub use model::{ CastMember, Episode, ExternalIds, FindResults, Genre, Movie, MovieDetail, MovieSearchResult, diff --git a/crates/arr-meta/tests/tmdb.rs b/crates/arr-meta/tests/tmdb.rs index 95f1c6f..88885b7 100644 --- a/crates/arr-meta/tests/tmdb.rs +++ b/crates/arr-meta/tests/tmdb.rs @@ -3,7 +3,7 @@ // Same per-target quirk as in `lib.rs`: an integration test links the library's // dependencies without using them directly. -use {reqwest as _, serde as _, serde_json as _, thiserror as _, tracing as _}; +use {reqwest as _, serde as _, serde_json as _, tempfile as _, thiserror as _, tracing as _}; use std::time::Duration; From b2147fe7b9c4c5a61118eb72a8c43b1fac80cc6c Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Mon, 24 Aug 2026 18:58:35 +0100 Subject: [PATCH 6/6] feat(web): tell pending seasons from settled empty --- crates/arr-api/src/lib.rs | 1 + crates/arr-api/src/series.rs | 86 ++++++++++++++++++++++++ web/src/main.ts | 127 +++++++++++++++++++++++++++++++++-- web/src/series.ts | 15 +++++ 4 files changed, 225 insertions(+), 4 deletions(-) diff --git a/crates/arr-api/src/lib.rs b/crates/arr-api/src/lib.rs index 1770cbf..ccd645d 100644 --- a/crates/arr-api/src/lib.rs +++ b/crates/arr-api/src/lib.rs @@ -89,6 +89,7 @@ fn api_router() -> OpenApiRouter { .routes(routes!(metadata::movie_metadata)) .routes(routes!(series::list, series::create)) .routes(routes!(series::get, series::update, series::delete)) + .routes(routes!(series::refresh_metadata)) .routes(routes!(series::seasons, series::create_season)) .routes(routes!(series::update_season)) .routes(routes!(series::delete_season_files)) diff --git a/crates/arr-api/src/series.rs b/crates/arr-api/src/series.rs index a483891..f7dd443 100644 --- a/crates/arr-api/src/series.rs +++ b/crates/arr-api/src/series.rs @@ -57,6 +57,10 @@ pub struct Series { pub poster_path: Option, /// TMDB's rating, out of 10; `null` when TMDB has no votes for it. pub vote_average: Option, + /// NULL until a metadata refresh has stamped it (#160). Issue #177: this + /// is what tells the SPA a series with no seasons yet is still seeding + /// its first refresh, rather than a title upstream genuinely lists none. + pub metadata_refreshed_at: Option, /// `airing`, `incomplete`, `waiting`, `complete` or `ended` (§4.2). pub status: String, /// Episodes currently marked wanted (§4.1 — the only intent). @@ -327,6 +331,7 @@ fn with_status(row: &SeriesRow, episodes: &[arr_core::Episode], now: SystemTime) blocked: row.blocked, poster_path: row.poster_path.clone(), vote_average: row.vote_average, + metadata_refreshed_at: row.metadata_refreshed_at.clone(), status: status_name(derive_series_status(&core_series(row), episodes, now)).to_owned(), wanted_episodes: i64::try_from(wanted.count()).unwrap_or(i64::MAX), available_episodes: i64::try_from(available.count()).unwrap_or(i64::MAX), @@ -509,6 +514,31 @@ pub async fn get( Ok(Json(load_series(&state, id).await?)) } +#[utoipa::path( + post, path = "/api/series/{series_id}/refresh-metadata", tag = "series", + params(("series_id" = i64, Path, description = "Series row id")), + responses( + (status = 202, body = Accepted), + (status = 404, body = ErrorBody), + (status = 500, body = ErrorBody), + (status = 503, body = ErrorBody) + ) +)] +/// Issue #177: the same on-demand command `create` sends on add, resent by +/// hand when a page gave up polling for it. Same terms — asynchronous, best +/// effort, and a failure leaves `metadata_refreshed_at` NULL for the daily +/// sweep to pick up. +pub async fn refresh_metadata( + State(state): State, + Path(series_id): Path, +) -> Result<(StatusCode, Json), ApiError> { + load_series_row(&state, series_id).await?; + state + .send_metadata_command(MetadataCommand::Series { series_id }) + .map_err(|_| ApiError::Unavailable)?; + Ok((StatusCode::ACCEPTED, Json(Accepted { accepted: true }))) +} + #[utoipa::path( patch, path = "/api/series/{series_id}", tag = "series", request_body = UpdateSeries, params(("series_id" = i64, Path, description = "Series row id")), @@ -1805,6 +1835,62 @@ mod tests { ); } + /// Issue #177: the series response carries `metadata_refreshed_at` so + /// the SPA can tell a never-refreshed series from a settled empty one. + #[tokio::test] + async fn series_response_exposes_metadata_refreshed_at() { + let (_dir, state, base) = application().await; + let root_id = tv_root(&state, "main").await; + let series = add_series(&base, root_id, true).await; + assert!(series["metadata_refreshed_at"].is_null()); + } + + /// Issue #177: the retry control a gave-up poll offers resends the same + /// on-demand command `create` sends, so a second refresh attempt does + /// not need the scheduled sweep to come around. + #[tokio::test] + async fn refreshing_metadata_by_hand_queues_the_same_command() { + let (_dir, state, base) = application().await; + let root_id = tv_root(&state, "main").await; + let series = add_series(&base, root_id, true).await; + let series_id = series["id"].as_i64().expect("series id"); + assert_eq!( + state + .next_metadata_command() + .await + .expect("create's command"), + MetadataCommand::Series { series_id } + ); + + let response = reqwest::Client::new() + .post(format!("{base}/api/series/{series_id}/refresh-metadata")) + .send() + .await + .expect("refresh metadata"); + + assert_eq!(response.status(), StatusCode::ACCEPTED); + assert_eq!( + state + .next_metadata_command() + .await + .expect("retry's command"), + MetadataCommand::Series { series_id } + ); + } + + #[tokio::test] + async fn refreshing_metadata_for_an_unknown_series_is_a_404() { + let (_dir, _state, base) = application().await; + + let response = reqwest::Client::new() + .post(format!("{base}/api/series/404/refresh-metadata")) + .send() + .await + .expect("refresh metadata"); + + assert_eq!(response.status(), StatusCode::NOT_FOUND); + } + #[tokio::test] async fn series_must_sit_on_a_tv_root() { let (_dir, state, base) = application().await; diff --git a/web/src/main.ts b/web/src/main.ts index b1d6a33..a53147b 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -87,6 +87,7 @@ import { fileAttributeTags, formatAirDate, isUnaired, + refreshSeriesMetadata, removeEpisodeFiles, removeSeasonFiles, removeSeries, @@ -2971,6 +2972,14 @@ interface SeriesView { const PAD_TWO = (value: number): string => String(value).padStart(2, "0"); +/** + * Issue #177: the on-demand refresh a new series' add sends (#176) lands + * within seconds. Poll modestly and give up bounded so a page left open + * does not spin forever. + */ +const SERIES_REFRESH_POLL_MS = 2000; +const SERIES_REFRESH_WAIT_MS = 30_000; + function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { const view = must("#series"); const deckEl = must("#deck"); @@ -3005,6 +3014,9 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { // guards a stale fetch from painting over a newer view let sequence = 0; let removed: ((parent: Route) => void) | null = null; + let refreshPollTimer: number | undefined; + // set once the bounded poll for a pending metadata refresh times out + let refreshGaveUp = false; function setStatus(text: string | null, tone?: "fault") { statusEl.hidden = text === null; @@ -3456,6 +3468,42 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { return item; } + /** + * A never-refreshed series (`metadata_refreshed_at` null) reads as a + * refresh under way, since #176 queues one on add and it lands within + * seconds. A refreshed series with no seasons is a settled, if rare, + * empty state upstream — the two must not share a message (#177). + */ + function seasonsEmptyRow(): HTMLLIElement { + const item = document.createElement("li"); + item.className = "rel rel-none readout dim"; + if (series !== null && series.metadata_refreshed_at === null) { + if (refreshGaveUp) { + item.append(document.createTextNode("metadata refresh has not landed yet — ")); + const retry = document.createElement("button"); + retry.type = "button"; + retry.className = "control control-quiet"; + retry.textContent = "retry"; + retry.addEventListener("click", () => { + retry.disabled = true; + void retryRefresh(); + }); + item.append(retry); + } else { + const lamp = document.createElement("span"); + lamp.className = "lamp"; + lamp.dataset.state = "probing"; + item.append( + lamp, + document.createTextNode(" refreshing metadata — seasons will appear here"), + ); + } + } else { + item.textContent = "no seasons listed upstream"; + } + return item; + } + function renderSeasons() { seasonsList.replaceChildren(); if (!seasons) { @@ -3465,13 +3513,80 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { seasonsList.append(seasonRow(season)); } if (seasons.length === 0) { - const none = document.createElement("li"); - none.className = "rel rel-none readout dim"; - none.textContent = "no seasons revealed yet — a metadata refresh fills this in"; - seasonsList.append(none); + seasonsList.append(seasonsEmptyRow()); } } + /** + * Poll while a refresh is pending, painting seasons as soon as they land + * with no manual reload. Stops on arrival, on the refresh being recorded + * done with none, or once `SERIES_REFRESH_WAIT_MS` passes — a page left + * open must not poll forever (#177). + */ + function syncRefreshWatch(ticket: number) { + window.clearTimeout(refreshPollTimer); + const id = seriesId; + if (id === null || series === null || seasons === null) { + return; + } + const pending = series.metadata_refreshed_at === null && seasons.length === 0; + if (!pending || refreshGaveUp) { + return; + } + const deadline = Date.now() + SERIES_REFRESH_WAIT_MS; + const tick = async () => { + if (ticket !== sequence || seriesId !== id) { + return; + } + const [detail, seasonsOutcome] = await Promise.all([fetchSeries(id), fetchSeasons(id)]); + if (ticket !== sequence || seriesId !== id) { + return; + } + if (detail && seasonsOutcome.kind === "seasons") { + series = detail; + seasons = seasonsOutcome.seasons; + paintHeader(); + if (series.metadata_refreshed_at !== null || seasons.length > 0) { + renderSeasons(); + return; + } + } + if (Date.now() >= deadline) { + refreshGaveUp = true; + renderSeasons(); + return; + } + refreshPollTimer = window.setTimeout(() => { + void tick(); + }, SERIES_REFRESH_POLL_MS); + }; + refreshPollTimer = window.setTimeout(() => { + void tick(); + }, SERIES_REFRESH_POLL_MS); + } + + /** The empty state's retry control: resend the on-demand command by hand. */ + async function retryRefresh() { + const id = seriesId; + if (id === null) { + return; + } + const ticket = sequence; + refreshGaveUp = false; + renderSeasons(); + const outcome = await refreshSeriesMetadata(id); + if (ticket !== sequence || seriesId !== id) { + return; + } + if (outcome.kind === "error") { + refreshGaveUp = true; + setStatus(`refresh failed — ${outcome.detail}`, "fault"); + renderSeasons(); + return; + } + syncRefreshWatch(ticket); + } + async function load() { const id = seriesId; if (id === null) { @@ -3479,6 +3594,7 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { } sequence += 1; const ticket = sequence; + window.clearTimeout(refreshPollTimer); setStatus("reading series…"); const [detail, seasonsOutcome, filesOutcome, fetchedRoots] = await Promise.all([ fetchSeries(id), @@ -3508,6 +3624,7 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { paintHeader(); clearRichDetail(); renderSeasons(); + syncRefreshWatch(ticket); setStatus(null); void loadMetadata(id, ticket); if (focusKey !== null) { @@ -3529,6 +3646,7 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { parentRoute = parent; expanded = new Set(); focusKey = null; + refreshGaveUp = false; deckEl.hidden = true; view.hidden = false; clearRemove(); @@ -3542,6 +3660,7 @@ function seriesMain(tvDeck: TvReleasesView, views: HideableView[]): SeriesView { seriesId = null; seasons = null; sequence += 1; + window.clearTimeout(refreshPollTimer); clearRemove(); clearRichDetail(); } diff --git a/web/src/series.ts b/web/src/series.ts index f5dbedc..6d1deeb 100644 --- a/web/src/series.ts +++ b/web/src/series.ts @@ -16,6 +16,8 @@ export interface ApiSeries { year: number | null; root_id: number; blocked: boolean; + /** Null until a metadata refresh has stamped it (#160, #177). */ + metadata_refreshed_at: string | null; status: SeriesStatus; wanted_episodes: number; available_episodes: number; @@ -79,6 +81,19 @@ export async function fetchSeasons(seriesId: number): Promise { } } +/** Issue #177: resend the on-demand refresh a page gave up polling for. */ +export async function refreshSeriesMetadata(seriesId: number): Promise { + try { + const response = await fetch(`/api/series/${seriesId}/refresh-metadata`, { method: "POST" }); + if (!response.ok) { + return { kind: "error", detail: await errorDetail(response) }; + } + return { kind: "done" }; + } catch { + return { kind: "error", detail: "daemon unreachable" }; + } +} + /** One imported episode file as `/api/series/{id}/files` reports it (§7.4). */ export interface EpisodeFile { episode_id: number;