diff --git a/crates/adapters/sqlite/.sqlx/query-01a08873b7fa815ad98a56a0902b60414cfcdc2c7a8570351320c4bc425347c6.json b/crates/adapters/sqlite/.sqlx/query-01a08873b7fa815ad98a56a0902b60414cfcdc2c7a8570351320c4bc425347c6.json new file mode 100644 index 0000000..27ed241 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-01a08873b7fa815ad98a56a0902b60414cfcdc2c7a8570351320c4bc425347c6.json @@ -0,0 +1,92 @@ +{ + "db_name": "SQLite", + "query": "SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,\n r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at\n FROM reviews r\n INNER JOIN movies m ON m.id = r.movie_id\n WHERE r.movie_id = ?\n ORDER BY r.watched_at ASC\n LIMIT ? OFFSET ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "external_metadata_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "release_year", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "director", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "review_id", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "movie_id", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "user_id", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "rating", + "ordinal": 9, + "type_info": "Integer" + }, + { + "name": "comment", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "watched_at", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 12, + "type_info": "Text" + } + ], + "parameters": { + "Right": 3 + }, + "nullable": [ + false, + true, + false, + false, + true, + true, + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "01a08873b7fa815ad98a56a0902b60414cfcdc2c7a8570351320c4bc425347c6" +} diff --git a/crates/adapters/sqlite/.sqlx/query-026e2afeb573707cb360fcdab8f6137aabfaf603b5ed57b98ac2888b4a0389ff.json b/crates/adapters/sqlite/.sqlx/query-026e2afeb573707cb360fcdab8f6137aabfaf603b5ed57b98ac2888b4a0389ff.json new file mode 100644 index 0000000..58175b3 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-026e2afeb573707cb360fcdab8f6137aabfaf603b5ed57b98ac2888b4a0389ff.json @@ -0,0 +1,92 @@ +{ + "db_name": "SQLite", + "query": "SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,\n r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at\n FROM reviews r\n INNER JOIN movies m ON m.id = r.movie_id\n ORDER BY r.watched_at DESC\n LIMIT ? OFFSET ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "external_metadata_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "release_year", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "director", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "review_id", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "movie_id", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "user_id", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "rating", + "ordinal": 9, + "type_info": "Integer" + }, + { + "name": "comment", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "watched_at", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 12, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + true, + false, + false, + true, + true, + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "026e2afeb573707cb360fcdab8f6137aabfaf603b5ed57b98ac2888b4a0389ff" +} diff --git a/crates/adapters/sqlite/.sqlx/query-0963b9661182e139cd760bbabb0d6ea3a301a2a3adbdfdda4a88f333a1144c77.json b/crates/adapters/sqlite/.sqlx/query-0963b9661182e139cd760bbabb0d6ea3a301a2a3adbdfdda4a88f333a1144c77.json new file mode 100644 index 0000000..2fb8300 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-0963b9661182e139cd760bbabb0d6ea3a301a2a3adbdfdda4a88f333a1144c77.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT COUNT(*) FROM reviews", + "describe": { + "columns": [ + { + "name": "COUNT(*)", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "0963b9661182e139cd760bbabb0d6ea3a301a2a3adbdfdda4a88f333a1144c77" +} diff --git a/crates/adapters/sqlite/.sqlx/query-167481bb1692cc81531d9a5cd85425e43d09a6df97c335ac347f7cfd61acd171.json b/crates/adapters/sqlite/.sqlx/query-167481bb1692cc81531d9a5cd85425e43d09a6df97c335ac347f7cfd61acd171.json new file mode 100644 index 0000000..80aefc3 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-167481bb1692cc81531d9a5cd85425e43d09a6df97c335ac347f7cfd61acd171.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, email, password_hash FROM users WHERE email = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "email", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "password_hash", + "ordinal": 2, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "167481bb1692cc81531d9a5cd85425e43d09a6df97c335ac347f7cfd61acd171" +} diff --git a/crates/adapters/sqlite/.sqlx/query-18de90feb13b9f467f06d0ce25332d9ea7eabc99d9f1a44694e5d10762606f82.json b/crates/adapters/sqlite/.sqlx/query-18de90feb13b9f467f06d0ce25332d9ea7eabc99d9f1a44694e5d10762606f82.json new file mode 100644 index 0000000..78a50ea --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-18de90feb13b9f467f06d0ce25332d9ea7eabc99d9f1a44694e5d10762606f82.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT OR IGNORE INTO users (id, email, password_hash, created_at) VALUES (?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "18de90feb13b9f467f06d0ce25332d9ea7eabc99d9f1a44694e5d10762606f82" +} diff --git a/crates/adapters/sqlite/.sqlx/query-1bc5a51762717e45292626052f0a65ac0b8a001798a2476ea86143c5565df399.json b/crates/adapters/sqlite/.sqlx/query-1bc5a51762717e45292626052f0a65ac0b8a001798a2476ea86143c5565df399.json new file mode 100644 index 0000000..56618eb --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-1bc5a51762717e45292626052f0a65ac0b8a001798a2476ea86143c5565df399.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, email, password_hash FROM users WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "email", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "password_hash", + "ordinal": 2, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "1bc5a51762717e45292626052f0a65ac0b8a001798a2476ea86143c5565df399" +} diff --git a/crates/adapters/sqlite/.sqlx/query-3047579c6ed13ce87aad9b9ce6300c02f0df3516979518976e13f9d9abc6a403.json b/crates/adapters/sqlite/.sqlx/query-3047579c6ed13ce87aad9b9ce6300c02f0df3516979518976e13f9d9abc6a403.json new file mode 100644 index 0000000..54b4a4e --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-3047579c6ed13ce87aad9b9ce6300c02f0df3516979518976e13f9d9abc6a403.json @@ -0,0 +1,50 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, external_metadata_id, title, release_year, director, poster_path\n FROM movies WHERE title = ? AND release_year = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "external_metadata_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "release_year", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "director", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 5, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + true, + false, + false, + true, + true + ] + }, + "hash": "3047579c6ed13ce87aad9b9ce6300c02f0df3516979518976e13f9d9abc6a403" +} diff --git a/crates/adapters/sqlite/.sqlx/query-33d0dae7d16b0635c1c7eb5afd10824bb55af7cc7a854f590d326622863759d1.json b/crates/adapters/sqlite/.sqlx/query-33d0dae7d16b0635c1c7eb5afd10824bb55af7cc7a854f590d326622863759d1.json new file mode 100644 index 0000000..5666885 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-33d0dae7d16b0635c1c7eb5afd10824bb55af7cc7a854f590d326622863759d1.json @@ -0,0 +1,50 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, external_metadata_id, title, release_year, director, poster_path\n FROM movies WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "external_metadata_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "release_year", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "director", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 5, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true, + false, + false, + true, + true + ] + }, + "hash": "33d0dae7d16b0635c1c7eb5afd10824bb55af7cc7a854f590d326622863759d1" +} diff --git a/crates/adapters/sqlite/.sqlx/query-47f7cf95ce3450635b643ab710cadba96f40319140834d510bc5207b2552e055.json b/crates/adapters/sqlite/.sqlx/query-47f7cf95ce3450635b643ab710cadba96f40319140834d510bc5207b2552e055.json new file mode 100644 index 0000000..8db3f5e --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-47f7cf95ce3450635b643ab710cadba96f40319140834d510bc5207b2552e055.json @@ -0,0 +1,92 @@ +{ + "db_name": "SQLite", + "query": "SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,\n r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at\n FROM reviews r\n INNER JOIN movies m ON m.id = r.movie_id\n WHERE r.movie_id = ?\n ORDER BY r.watched_at DESC\n LIMIT ? OFFSET ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "external_metadata_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "release_year", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "director", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "review_id", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "movie_id", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "user_id", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "rating", + "ordinal": 9, + "type_info": "Integer" + }, + { + "name": "comment", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "watched_at", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 12, + "type_info": "Text" + } + ], + "parameters": { + "Right": 3 + }, + "nullable": [ + false, + true, + false, + false, + true, + true, + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "47f7cf95ce3450635b643ab710cadba96f40319140834d510bc5207b2552e055" +} diff --git a/crates/adapters/sqlite/.sqlx/query-4b3074b532342c6356ee0e8e4d8c4a830f016234bb690e1f6240f02824d6d84f.json b/crates/adapters/sqlite/.sqlx/query-4b3074b532342c6356ee0e8e4d8c4a830f016234bb690e1f6240f02824d6d84f.json new file mode 100644 index 0000000..1089c99 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-4b3074b532342c6356ee0e8e4d8c4a830f016234bb690e1f6240f02824d6d84f.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT COUNT(*) FROM reviews WHERE movie_id = ?", + "describe": { + "columns": [ + { + "name": "COUNT(*)", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "4b3074b532342c6356ee0e8e4d8c4a830f016234bb690e1f6240f02824d6d84f" +} diff --git a/crates/adapters/sqlite/.sqlx/query-630e092fcd33bc312befef352a98225e6e18e6079644b949258a39bf4b0fe3e5.json b/crates/adapters/sqlite/.sqlx/query-630e092fcd33bc312befef352a98225e6e18e6079644b949258a39bf4b0fe3e5.json new file mode 100644 index 0000000..8b41a06 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-630e092fcd33bc312befef352a98225e6e18e6079644b949258a39bf4b0fe3e5.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO reviews (id, movie_id, user_id, rating, comment, watched_at, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 7 + }, + "nullable": [] + }, + "hash": "630e092fcd33bc312befef352a98225e6e18e6079644b949258a39bf4b0fe3e5" +} diff --git a/crates/adapters/sqlite/.sqlx/query-7bc4aebcb94547976d3d7e063e4e908fc22b977b3cbf063ee93ffe4648c42011.json b/crates/adapters/sqlite/.sqlx/query-7bc4aebcb94547976d3d7e063e4e908fc22b977b3cbf063ee93ffe4648c42011.json new file mode 100644 index 0000000..a23bd20 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-7bc4aebcb94547976d3d7e063e4e908fc22b977b3cbf063ee93ffe4648c42011.json @@ -0,0 +1,50 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, external_metadata_id, title, release_year, director, poster_path\n FROM movies WHERE external_metadata_id = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "external_metadata_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "release_year", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "director", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 5, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true, + false, + false, + true, + true + ] + }, + "hash": "7bc4aebcb94547976d3d7e063e4e908fc22b977b3cbf063ee93ffe4648c42011" +} diff --git a/crates/adapters/sqlite/.sqlx/query-7d7e23355ee0e442f2aa27e898dcfa40bdc4b09391afe04325f076157d9d84aa.json b/crates/adapters/sqlite/.sqlx/query-7d7e23355ee0e442f2aa27e898dcfa40bdc4b09391afe04325f076157d9d84aa.json new file mode 100644 index 0000000..1967c47 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-7d7e23355ee0e442f2aa27e898dcfa40bdc4b09391afe04325f076157d9d84aa.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT INTO movies (id, external_metadata_id, title, release_year, director, poster_path)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n external_metadata_id = excluded.external_metadata_id,\n title = excluded.title,\n release_year = excluded.release_year,\n director = excluded.director,\n poster_path = excluded.poster_path", + "describe": { + "columns": [], + "parameters": { + "Right": 6 + }, + "nullable": [] + }, + "hash": "7d7e23355ee0e442f2aa27e898dcfa40bdc4b09391afe04325f076157d9d84aa" +} diff --git a/crates/adapters/sqlite/.sqlx/query-af883f8b78f185077e2d3dcfaa0a6e62fbdfbf00c97c9b33b699dc631476181d.json b/crates/adapters/sqlite/.sqlx/query-af883f8b78f185077e2d3dcfaa0a6e62fbdfbf00c97c9b33b699dc631476181d.json new file mode 100644 index 0000000..4b94231 --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-af883f8b78f185077e2d3dcfaa0a6e62fbdfbf00c97c9b33b699dc631476181d.json @@ -0,0 +1,56 @@ +{ + "db_name": "SQLite", + "query": "SELECT id, movie_id, user_id, rating, comment, watched_at, created_at\n FROM reviews WHERE movie_id = ? ORDER BY watched_at ASC", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "movie_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "user_id", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "rating", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "comment", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "watched_at", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 6, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "af883f8b78f185077e2d3dcfaa0a6e62fbdfbf00c97c9b33b699dc631476181d" +} diff --git a/crates/adapters/sqlite/.sqlx/query-affe1eb261283c09d4b1ce6e684681755f079a044ffec8ff2bd79cfd8efe16b8.json b/crates/adapters/sqlite/.sqlx/query-affe1eb261283c09d4b1ce6e684681755f079a044ffec8ff2bd79cfd8efe16b8.json new file mode 100644 index 0000000..65a123c --- /dev/null +++ b/crates/adapters/sqlite/.sqlx/query-affe1eb261283c09d4b1ce6e684681755f079a044ffec8ff2bd79cfd8efe16b8.json @@ -0,0 +1,92 @@ +{ + "db_name": "SQLite", + "query": "SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,\n r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.watched_at, r.created_at\n FROM reviews r\n INNER JOIN movies m ON m.id = r.movie_id\n ORDER BY r.watched_at ASC\n LIMIT ? OFFSET ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "external_metadata_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "release_year", + "ordinal": 3, + "type_info": "Integer" + }, + { + "name": "director", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "poster_path", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "review_id", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "movie_id", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "user_id", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "rating", + "ordinal": 9, + "type_info": "Integer" + }, + { + "name": "comment", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "watched_at", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 12, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + true, + false, + false, + true, + true, + false, + false, + false, + false, + true, + false, + false + ] + }, + "hash": "affe1eb261283c09d4b1ce6e684681755f079a044ffec8ff2bd79cfd8efe16b8" +} diff --git a/crates/adapters/sqlite/src/users.rs b/crates/adapters/sqlite/src/users.rs index 02235fb..4c48f60 100644 --- a/crates/adapters/sqlite/src/users.rs +++ b/crates/adapters/sqlite/src/users.rs @@ -73,4 +73,79 @@ impl UserRepository for SqliteUserRepository { Ok(()) } + + async fn find_by_id(&self, id: &UserId) -> Result, DomainError> { + let id_str = id.value().to_string(); + let row = sqlx::query!( + "SELECT id, email, password_hash FROM users WHERE id = ?", + id_str + ) + .fetch_optional(&self.pool) + .await + .map_err(Self::map_err)?; + + match row { + None => Ok(None), + Some(r) => { + let uuid = uuid::Uuid::parse_str(&r.id) + .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + let email = Email::new(r.email) + .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + let hash = PasswordHash::new(r.password_hash) + .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + Ok(Some(User::from_persistence(UserId::from_uuid(uuid), email, hash))) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sqlx::SqlitePool; + + async fn setup() -> (SqlitePool, SqliteUserRepository) { + let pool = SqlitePool::connect(":memory:").await.unwrap(); + sqlx::query( + "CREATE TABLE users (id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at TEXT NOT NULL)" + ) + .execute(&pool) + .await + .unwrap(); + let repo = SqliteUserRepository::new(pool.clone()); + (pool, repo) + } + + #[tokio::test] + async fn find_by_id_returns_none_when_not_found() { + let (_, repo) = setup().await; + let result = repo + .find_by_id(&UserId::from_uuid(uuid::Uuid::new_v4())) + .await + .unwrap(); + assert!(result.is_none()); + } + + #[tokio::test] + async fn find_by_id_returns_user_when_found() { + let (pool, repo) = setup().await; + let id = uuid::Uuid::new_v4(); + sqlx::query( + "INSERT INTO users (id, email, password_hash, created_at) VALUES (?, ?, ?, ?)" + ) + .bind(id.to_string()) + .bind("test@example.com") + .bind("$argon2id$v=19$m=65536,t=2,p=1$fakesalt$fakehash") + .bind("2026-01-01T00:00:00Z") + .execute(&pool) + .await + .unwrap(); + + let result = repo + .find_by_id(&UserId::from_uuid(id)) + .await + .unwrap(); + assert!(result.is_some()); + assert_eq!(result.unwrap().email().value(), "test@example.com"); + } } diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index f408c01..72b58e2 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -82,6 +82,7 @@ pub trait AuthService: Send + Sync { pub trait UserRepository: Send + Sync { async fn find_by_email(&self, email: &Email) -> Result, DomainError>; async fn save(&self, user: &User) -> Result<(), DomainError>; + async fn find_by_id(&self, id: &UserId) -> Result, DomainError>; } #[async_trait]