diff --git a/.sqlx/query-dc98f1a609e67b642aed635b26239328e6456e69c12dc8561c474fff3dcd14d5.json b/.sqlx/query-09299172474d10a07387b74f4d714bf389b5422334bd1aa2a0e6f2d63ebdd623.json similarity index 57% rename from .sqlx/query-dc98f1a609e67b642aed635b26239328e6456e69c12dc8561c474fff3dcd14d5.json rename to .sqlx/query-09299172474d10a07387b74f4d714bf389b5422334bd1aa2a0e6f2d63ebdd623.json index bc78369..e63bc90 100644 --- a/.sqlx/query-dc98f1a609e67b642aed635b26239328e6456e69c12dc8561c474fff3dcd14d5.json +++ b/.sqlx/query-09299172474d10a07387b74f4d714bf389b5422334bd1aa2a0e6f2d63ebdd623.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT folder_id FROM folders WHERE folder_id = $1 AND owner_id = $2", + "query": "SELECT folder_id FROM files WHERE file_id = $1", "describe": { "columns": [ { @@ -11,13 +11,12 @@ ], "parameters": { "Left": [ - "Uuid", - "Int4" + "Uuid" ] }, "nullable": [ false ] }, - "hash": "dc98f1a609e67b642aed635b26239328e6456e69c12dc8561c474fff3dcd14d5" + "hash": "09299172474d10a07387b74f4d714bf389b5422334bd1aa2a0e6f2d63ebdd623" } diff --git a/.sqlx/query-39b78c7f3266bea5e3e44aa372574319cb74dea6b3d0bc16d25e29ca28803317.json b/.sqlx/query-39b78c7f3266bea5e3e44aa372574319cb74dea6b3d0bc16d25e29ca28803317.json new file mode 100644 index 0000000..3c788cc --- /dev/null +++ b/.sqlx/query-39b78c7f3266bea5e3e44aa372574319cb74dea6b3d0bc16d25e29ca28803317.json @@ -0,0 +1,39 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n username,\n permission_type as \"permission_type: PermissionRaw\"\nFROM\n permissions\n INNER JOIN users ON permissions.user_id = users.user_id\nWHERE\n folder_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "permission_type: PermissionRaw", + "type_info": { + "Custom": { + "name": "permission", + "kind": { + "Enum": [ + "read", + "write", + "manage" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "39b78c7f3266bea5e3e44aa372574319cb74dea6b3d0bc16d25e29ca28803317" +} diff --git a/.sqlx/query-4c7c701a22c49eebb4ba31a21dab15a5dbe2eaba99036932790c7c09599d4cd7.json b/.sqlx/query-4c7c701a22c49eebb4ba31a21dab15a5dbe2eaba99036932790c7c09599d4cd7.json new file mode 100644 index 0000000..21d9a5b --- /dev/null +++ b/.sqlx/query-4c7c701a22c49eebb4ba31a21dab15a5dbe2eaba99036932790c7c09599d4cd7.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE files SET (sha512, file_size, updated_at) = ($2, $3, NOW()) WHERE file_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Bytea", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "4c7c701a22c49eebb4ba31a21dab15a5dbe2eaba99036932790c7c09599d4cd7" +} diff --git a/.sqlx/query-595739c03acbe706107e34a3ebebec8c8f21f70ccc51b5366eff962d9af391d7.json b/.sqlx/query-595739c03acbe706107e34a3ebebec8c8f21f70ccc51b5366eff962d9af391d7.json new file mode 100644 index 0000000..bdd810c --- /dev/null +++ b/.sqlx/query-595739c03acbe706107e34a3ebebec8c8f21f70ccc51b5366eff962d9af391d7.json @@ -0,0 +1,27 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH RECURSIVE folder_hierarchy AS (\n -- Start with the given directory\n SELECT \n folder_id \n FROM \n folders \n WHERE \n folder_id = $1\n\n UNION ALL\n\n -- Recursively find all subdirectories\n SELECT \n f.folder_id\n FROM \n folders f\n INNER JOIN \n folder_hierarchy fh ON f.parent_folder_id = fh.folder_id\n)\nINSERT INTO permissions(user_id, folder_id, permission_type)\nSELECT $2::integer as user_id, fh.folder_id::UUID as folder_id, $3\nFROM folder_hierarchy fh\nON CONFLICT (user_id, folder_id) DO UPDATE\nSET permission_type = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Int4", + { + "Custom": { + "name": "permission", + "kind": { + "Enum": [ + "read", + "write", + "manage" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "595739c03acbe706107e34a3ebebec8c8f21f70ccc51b5366eff962d9af391d7" +} diff --git a/.sqlx/query-61a26b3321bb5b58a0b90e61b2cdcacfb46a03eb0c0a89839c9b3eff53cb7e56.json b/.sqlx/query-61a26b3321bb5b58a0b90e61b2cdcacfb46a03eb0c0a89839c9b3eff53cb7e56.json new file mode 100644 index 0000000..770dfa2 --- /dev/null +++ b/.sqlx/query-61a26b3321bb5b58a0b90e61b2cdcacfb46a03eb0c0a89839c9b3eff53cb7e56.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n user_id, username, email\nFROM\n users\nORDER BY\n GREATEST (\n similarity (email, $1),\n similarity (username, $1)\n ) DESC", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "user_id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "61a26b3321bb5b58a0b90e61b2cdcacfb46a03eb0c0a89839c9b3eff53cb7e56" +} diff --git a/.sqlx/query-9a70e24a3de68f4a66718124bd3ca959bd0a992e5e0dda3baae52b8cb545ce66.json b/.sqlx/query-6b58c84cdc19cea97ef025211a98879bb5cc80a934490125a19c960133f6d93d.json similarity index 50% rename from .sqlx/query-9a70e24a3de68f4a66718124bd3ca959bd0a992e5e0dda3baae52b8cb545ce66.json rename to .sqlx/query-6b58c84cdc19cea97ef025211a98879bb5cc80a934490125a19c960133f6d93d.json index 9b26c9a..d38cfd4 100644 --- a/.sqlx/query-9a70e24a3de68f4a66718124bd3ca959bd0a992e5e0dda3baae52b8cb545ce66.json +++ b/.sqlx/query-6b58c84cdc19cea97ef025211a98879bb5cc80a934490125a19c960133f6d93d.json @@ -1,13 +1,12 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO files(file_id, folder_id, owner_id, file_name, file_size, sha512) VALUES ($1, $2, $3, $4, $5, $6)", + "query": "INSERT INTO files(file_id, folder_id, file_name, file_size, sha512) VALUES ($1, $2, $3, $4, $5)", "describe": { "columns": [], "parameters": { "Left": [ "Uuid", "Uuid", - "Int4", "Varchar", "Int8", "Bytea" @@ -15,5 +14,5 @@ }, "nullable": [] }, - "hash": "9a70e24a3de68f4a66718124bd3ca959bd0a992e5e0dda3baae52b8cb545ce66" + "hash": "6b58c84cdc19cea97ef025211a98879bb5cc80a934490125a19c960133f6d93d" } diff --git a/.sqlx/query-87f7df91208438a35516604f57f0443e0f12db718e23acd374f6f7ace65f467d.json b/.sqlx/query-87f7df91208438a35516604f57f0443e0f12db718e23acd374f6f7ace65f467d.json new file mode 100644 index 0000000..d7d9844 --- /dev/null +++ b/.sqlx/query-87f7df91208438a35516604f57f0443e0f12db718e23acd374f6f7ace65f467d.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n permission_type as \"permission_type: PermissionRaw\"\nFROM\n permissions\nWHERE\n folder_id = $1\n AND user_id = $2\nUNION\nSELECT\n 'manage' as \"permission_type: PermissionRaw\"\nFROM\n folders\nWHERE\n folder_id = $1\n AND owner_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "permission_type: PermissionRaw", + "type_info": { + "Custom": { + "name": "permission", + "kind": { + "Enum": [ + "read", + "write", + "manage" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Uuid", + "Int4" + ] + }, + "nullable": [ + null + ] + }, + "hash": "87f7df91208438a35516604f57f0443e0f12db718e23acd374f6f7ace65f467d" +} diff --git a/.sqlx/query-948f13b631bcc7df1919a9639443f0ed932c4cb37f2ba5bf6f000eb84b265ae2.json b/.sqlx/query-948f13b631bcc7df1919a9639443f0ed932c4cb37f2ba5bf6f000eb84b265ae2.json new file mode 100644 index 0000000..0c874ba --- /dev/null +++ b/.sqlx/query-948f13b631bcc7df1919a9639443f0ed932c4cb37f2ba5bf6f000eb84b265ae2.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH RECURSIVE folder_hierarchy AS (\n -- Start with the given directory\n SELECT \n folder_id \n FROM \n folders \n WHERE \n folder_id = $1\n\n UNION ALL\n\n -- Recursively find all subdirectories\n SELECT \n f.folder_id\n FROM \n folders f\n INNER JOIN \n folder_hierarchy fh ON f.parent_folder_id = fh.folder_id\n)\nDELETE FROM permissions WHERE user_id = $2 AND folder_id IN (SELECT folder_id FROM folder_hierarchy)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "948f13b631bcc7df1919a9639443f0ed932c4cb37f2ba5bf6f000eb84b265ae2" +} diff --git a/.sqlx/query-9a26dab9efbbbb92b7be27792b581a0156210fdc0aadd3756f7003186f428374.json b/.sqlx/query-9a26dab9efbbbb92b7be27792b581a0156210fdc0aadd3756f7003186f428374.json deleted file mode 100644 index 66af126..0000000 --- a/.sqlx/query-9a26dab9efbbbb92b7be27792b581a0156210fdc0aadd3756f7003186f428374.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT file_id FROM files WHERE file_id = $1 AND owner_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "file_id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Int4" - ] - }, - "nullable": [ - false - ] - }, - "hash": "9a26dab9efbbbb92b7be27792b581a0156210fdc0aadd3756f7003186f428374" -} diff --git a/.sqlx/query-a54829e9cd90e55022c2f6dd413b797efaafd1c4793b60886140bfe9ea6df592.json b/.sqlx/query-a54829e9cd90e55022c2f6dd413b797efaafd1c4793b60886140bfe9ea6df592.json new file mode 100644 index 0000000..5c5ef5d --- /dev/null +++ b/.sqlx/query-a54829e9cd90e55022c2f6dd413b797efaafd1c4793b60886140bfe9ea6df592.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH\n deleted_files AS (\n DELETE FROM files USING folders\n WHERE\n files.folder_id = folders.folder_id\n AND folders.owner_id = $1 RETURNING files.file_id\n ),\n deleted_users AS (\n DELETE FROM users\n WHERE\n user_id = $1\n )\nSELECT\n *\nFROM\n deleted_files;", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "file_id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "a54829e9cd90e55022c2f6dd413b797efaafd1c4793b60886140bfe9ea6df592" +} diff --git a/.sqlx/query-5a51ab540453327bdd75f49991f402fac6b1d8fb0a760d420236e2b41d3e7fcf.json b/.sqlx/query-e125c9f06cb89c6ddd2653ed45c576da3aecfb9fb74aabf202e83406fc8c8fff.json similarity index 66% rename from .sqlx/query-5a51ab540453327bdd75f49991f402fac6b1d8fb0a760d420236e2b41d3e7fcf.json rename to .sqlx/query-e125c9f06cb89c6ddd2653ed45c576da3aecfb9fb74aabf202e83406fc8c8fff.json index 312c93f..f558664 100644 --- a/.sqlx/query-5a51ab540453327bdd75f49991f402fac6b1d8fb0a760d420236e2b41d3e7fcf.json +++ b/.sqlx/query-e125c9f06cb89c6ddd2653ed45c576da3aecfb9fb74aabf202e83406fc8c8fff.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT file_id, owner_id, file_name, file_size, sha512, created_at, updated_at FROM files WHERE folder_id = $1", + "query": "SELECT file_id, file_name, file_size, encode(sha512, 'base64') as \"sha512!\", created_at, updated_at FROM files WHERE folder_id = $1", "describe": { "columns": [ { @@ -10,31 +10,26 @@ }, { "ordinal": 1, - "name": "owner_id", - "type_info": "Int4" - }, - { - "ordinal": 2, "name": "file_name", "type_info": "Varchar" }, { - "ordinal": 3, + "ordinal": 2, "name": "file_size", "type_info": "Int8" }, { - "ordinal": 4, - "name": "sha512", - "type_info": "Bytea" + "ordinal": 3, + "name": "sha512!", + "type_info": "Text" }, { - "ordinal": 5, + "ordinal": 4, "name": "created_at", "type_info": "Timestamp" }, { - "ordinal": 6, + "ordinal": 5, "name": "updated_at", "type_info": "Timestamp" } @@ -48,11 +43,10 @@ false, false, false, - false, - false, + null, false, false ] }, - "hash": "5a51ab540453327bdd75f49991f402fac6b1d8fb0a760d420236e2b41d3e7fcf" + "hash": "e125c9f06cb89c6ddd2653ed45c576da3aecfb9fb74aabf202e83406fc8c8fff" } diff --git a/.sqlx/query-f9e36f45f25dd2439a7a0b16b6df356a0a2a47e70b6e031ea5a0442adc86725b.json b/.sqlx/query-f9e36f45f25dd2439a7a0b16b6df356a0a2a47e70b6e031ea5a0442adc86725b.json new file mode 100644 index 0000000..27dfc7c --- /dev/null +++ b/.sqlx/query-f9e36f45f25dd2439a7a0b16b6df356a0a2a47e70b6e031ea5a0442adc86725b.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH\n permitted as (\n SELECT\n folder_id\n FROM\n permissions\n WHERE\n user_id = $1\n )\nSELECT\n folder_id\nFROM\n folders\nWHERE\n folder_id IN (\n SELECT\n folder_id\n FROM\n permitted\n )\n AND parent_folder_id NOT IN (\n SELECT\n folder_id\n FROM\n permitted\n )", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "folder_id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "f9e36f45f25dd2439a7a0b16b6df356a0a2a47e70b6e031ea5a0442adc86725b" +} diff --git a/Cargo.lock b/Cargo.lock index e7f1f35..a175989 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,7 +107,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -220,7 +220,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -663,7 +663,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -1079,6 +1079,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -1419,7 +1420,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -1523,7 +1524,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -1581,6 +1582,30 @@ dependencies = [ "zerocopy-derive", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -1612,6 +1637,8 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "utoipa", + "utoipauto", "uuid", ] @@ -2060,7 +2087,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2274,7 +2301,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.72", ] [[package]] @@ -2297,7 +2324,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.72", "tempfile", "tokio", "url", @@ -2429,6 +2456,16 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.72" @@ -2502,7 +2539,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2585,7 +2622,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2713,7 +2750,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] @@ -2819,6 +2856,64 @@ dependencies = [ "serde", ] +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf0e16c02bc4bf5322ab65f10ab1149bdbcaa782cba66dc7057370a3f8190be" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.72", + "uuid", +] + +[[package]] +name = "utoipauto" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4713aabc5ed18aabcd594345b48983b112c0b5dab3d24754352e7f5cf924da03" +dependencies = [ + "utoipauto-macro", +] + +[[package]] +name = "utoipauto-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e82ab96c5a55263b5bed151b8426410d93aa909a453acdbd4b6792b5af7d64" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "utoipauto-macro" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8338dc3c9526011ffaa2aa6bd60ddfda9d49d2123108690755c6e34844212" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "utoipauto-core", +] + [[package]] name = "uuid" version = "1.10.0" @@ -2889,7 +2984,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -2923,7 +3018,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3177,7 +3272,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.72", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 317e363..d020e7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,4 +51,6 @@ tracing-subscriber = { version = "0.3", features = [ "parking_lot", "env-filter", ] } +utoipa = { version = "4", features = ["axum_extras", "uuid", "chrono"] } +utoipauto = "0.1" uuid = { version = "1", features = ["serde", "v4"] } diff --git a/migrations/0001_initial.down.sql b/migrations/0001_initial.down.sql index 9204d0d..85d8222 100644 --- a/migrations/0001_initial.down.sql +++ b/migrations/0001_initial.down.sql @@ -6,4 +6,6 @@ DROP TABLE folders; DROP TABLE users; -DROP TYPE permission; \ No newline at end of file +DROP TYPE permission; + +DROP EXTENSION pg_trgm; \ No newline at end of file diff --git a/migrations/0001_initial.up.sql b/migrations/0001_initial.up.sql index 0a744e9..9bc2e2d 100644 --- a/migrations/0001_initial.up.sql +++ b/migrations/0001_initial.up.sql @@ -1,3 +1,5 @@ +CREATE EXTENSION IF NOT EXISTS pg_trgm; + CREATE TABLE users ( user_id SERIAL PRIMARY KEY, diff --git a/sql/delete_user.sql b/sql/delete_user.sql new file mode 100644 index 0000000..ea55f20 --- /dev/null +++ b/sql/delete_user.sql @@ -0,0 +1,16 @@ +WITH + deleted_files AS ( + DELETE FROM files USING folders + WHERE + files.folder_id = folders.folder_id + AND folders.owner_id = $1 RETURNING files.file_id + ), + deleted_users AS ( + DELETE FROM users + WHERE + user_id = $1 + ) +SELECT + * +FROM + deleted_files; \ No newline at end of file diff --git a/sql/search_for_user.sql b/sql/search_for_user.sql new file mode 100644 index 0000000..433831a --- /dev/null +++ b/sql/search_for_user.sql @@ -0,0 +1,9 @@ +SELECT + user_id, username, email +FROM + users +ORDER BY + GREATEST ( + similarity (email, $1), + similarity (username, $1) + ) DESC \ No newline at end of file diff --git a/src/db/folder.rs b/src/db/folder.rs index c3ad2dc..55e0afe 100644 --- a/src/db/folder.rs +++ b/src/db/folder.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use futures::TryStreamExt; +use futures::{Stream, TryStreamExt}; use uuid::Uuid; use crate::{db::permissions::PermissionRaw, Pool}; @@ -101,10 +101,8 @@ pub async fn insert( .map(|record| record.folder_id) } -pub async fn delete(folder_id: Uuid, pool: &Pool) -> sqlx::Result> { +pub fn delete(folder_id: Uuid, pool: &Pool) -> impl Stream> + '_ { sqlx::query_file!("sql/delete_folder.sql", folder_id) .fetch(pool) .map_ok(|row| row.file_id) - .try_collect() - .await } diff --git a/src/db/mod.rs b/src/db/mod.rs index a47f6a0..7e45ca5 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,3 +1,4 @@ pub mod file; pub mod folder; pub mod permissions; +pub mod users; diff --git a/src/db/users.rs b/src/db/users.rs new file mode 100644 index 0000000..7805363 --- /dev/null +++ b/src/db/users.rs @@ -0,0 +1,73 @@ +use futures::{stream::BoxStream, Stream, TryStreamExt}; +use serde::Serialize; +use uuid::Uuid; + +use crate::Pool; + +/// Creates user and returns its id +pub async fn create_user(user_name: &str, user_email: &str, pool: &Pool) -> sqlx::Result { + let id = sqlx::query!( + "INSERT INTO users(username, email) VALUES ($1, $2) RETURNING user_id", + user_name, + user_email + ) + .fetch_one(pool) + .await? + .user_id; + sqlx::query!( + "INSERT INTO folders(owner_id, folder_name) VALUES ($1, $2)", + id, + "ROOT" + ) + .execute(pool) + .await?; + Ok(id) +} + +/// Deletes the user and returns the files that must be deleted +pub fn delete_user(user_id: i32, pool: &Pool) -> impl Stream> + '_ { + sqlx::query_file!("sql/delete_user.sql", user_id) + .fetch(pool) + .map_ok(|record| record.file_id) +} + +#[derive(Serialize, Debug)] +pub struct UserInfo { + user_id: i32, + username: String, + email: String, +} + +pub async fn update( + user_id: i32, + username: &str, + email: &str, + pool: &Pool, +) -> sqlx::Result { + sqlx::query_as!( + UserInfo, + "UPDATE users SET username = $2, email = $3 WHERE user_id = $1 RETURNING *", + user_id, + username, + email + ) + .fetch_one(pool) + .await +} + +pub async fn get(user_id: i32, pool: &Pool) -> sqlx::Result { + sqlx::query_as!( + UserInfo, + "SELECT user_id, username, email FROM users WHERE user_id = $1", + user_id + ) + .fetch_one(pool) + .await +} + +pub fn search_for_user<'a>( + search_string: &str, + pool: &'a Pool, +) -> BoxStream<'a, sqlx::Result> { + sqlx::query_file_as!(UserInfo, "sql/search_for_user.sql", search_string).fetch(pool) +} diff --git a/src/endpoints/folder/delete.rs b/src/endpoints/folder/delete.rs index ec8cd87..781897e 100644 --- a/src/endpoints/folder/delete.rs +++ b/src/endpoints/folder/delete.rs @@ -1,3 +1,5 @@ +use futures::TryStreamExt; + use crate::prelude::*; #[derive(Deserialize, Debug)] @@ -22,14 +24,12 @@ pub async fn delete( .handle_internal()? .can_write_guard()?; - let files_to_delete = db::folder::delete(params.folder_id, &state.pool) - .await - .handle_internal()?; let storage = &state.storage; - futures::stream::iter(files_to_delete) - .for_each_concurrent(5, |file| async move { + db::folder::delete(params.folder_id, &state.pool) + .try_for_each_concurrent(5, |file| async move { let _ = storage.delete(file).await; + Ok(()) }) - .await; - Ok(()) + .await + .handle_internal() } diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index a47f6a0..7e45ca5 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -1,3 +1,4 @@ pub mod file; pub mod folder; pub mod permissions; +pub mod users; diff --git a/src/endpoints/permissions/get_top_level_permitted_folders.rs b/src/endpoints/permissions/get_top_level.rs similarity index 100% rename from src/endpoints/permissions/get_top_level_permitted_folders.rs rename to src/endpoints/permissions/get_top_level.rs diff --git a/src/endpoints/permissions/mod.rs b/src/endpoints/permissions/mod.rs index f040219..226959c 100644 --- a/src/endpoints/permissions/mod.rs +++ b/src/endpoints/permissions/mod.rs @@ -1,4 +1,4 @@ pub mod delete; pub mod get; -pub mod get_top_level_permitted_folders; +pub mod get_top_level; pub mod set; diff --git a/src/endpoints/users/create.rs b/src/endpoints/users/create.rs new file mode 100644 index 0000000..8b94ca3 --- /dev/null +++ b/src/endpoints/users/create.rs @@ -0,0 +1,17 @@ +use crate::prelude::*; + +#[derive(Deserialize, Debug)] +pub struct Params { + username: String, + email: String, +} + +pub async fn create( + State(state): State, + Json(params): Json, +) -> Result, StatusCode> { + let id = db::users::create_user(¶ms.username, ¶ms.email, &state.pool) + .await + .handle_internal()?; + Ok(Json(id)) +} diff --git a/src/endpoints/users/delete.rs b/src/endpoints/users/delete.rs new file mode 100644 index 0000000..0f87088 --- /dev/null +++ b/src/endpoints/users/delete.rs @@ -0,0 +1,14 @@ +use futures::TryStreamExt; + +use crate::prelude::*; + +pub async fn delete(State(state): State, claims: Claims) -> Result<(), StatusCode> { + let storage = &state.storage; + db::users::delete_user(claims.user_id, &state.pool) + .try_for_each_concurrent(5, |file_id| async move { + let _ = storage.delete(file_id).await; + Ok(()) + }) + .await + .handle_internal() +} diff --git a/src/endpoints/users/get.rs b/src/endpoints/users/get.rs new file mode 100644 index 0000000..e389202 --- /dev/null +++ b/src/endpoints/users/get.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; + +#[derive(Deserialize, Debug)] +pub struct Params { + user_id: i32, +} + +pub async fn get( + State(state): State, + Query(params): Query, +) -> Result, StatusCode> { + let info = db::users::get(params.user_id, &state.pool) + .await + .handle_internal()?; + Ok(Json(info)) +} diff --git a/src/endpoints/users/mod.rs b/src/endpoints/users/mod.rs new file mode 100644 index 0000000..cb22467 --- /dev/null +++ b/src/endpoints/users/mod.rs @@ -0,0 +1,5 @@ +pub mod create; +pub mod delete; +pub mod get; +pub mod put; +pub mod search; diff --git a/src/endpoints/users/put.rs b/src/endpoints/users/put.rs new file mode 100644 index 0000000..44d28b6 --- /dev/null +++ b/src/endpoints/users/put.rs @@ -0,0 +1,18 @@ +use crate::prelude::*; + +#[derive(Deserialize, Debug)] +pub struct Params { + username: String, + email: String, +} + +pub async fn put( + State(state): State, + claims: Claims, + Json(params): Json, +) -> Result, StatusCode> { + let info = db::users::update(claims.user_id, ¶ms.username, ¶ms.email, &state.pool) + .await + .handle_internal()?; + Ok(Json(info)) +} diff --git a/src/endpoints/users/search.rs b/src/endpoints/users/search.rs new file mode 100644 index 0000000..701ab05 --- /dev/null +++ b/src/endpoints/users/search.rs @@ -0,0 +1,20 @@ +use futures::TryStreamExt; + +use crate::prelude::*; + +#[derive(Deserialize, Debug)] +pub struct Params { + search_string: String, +} + +pub async fn search( + State(state): State, + Query(params): Query, +) -> sqlx::Result>, StatusCode> { + let users = db::users::search_for_user(¶ms.search_string, &state.pool) + .take(5) + .try_collect() + .await + .handle_internal()?; + Ok(Json(users)) +} diff --git a/src/main.rs b/src/main.rs index f6f9ace..683f98e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,25 +19,6 @@ struct AppState { storage: FileStorage, } -async fn create_user(user_name: &str, user_email: &str, pool: &Pool) -> anyhow::Result { - let id = sqlx::query!( - "INSERT INTO users(username, email) VALUES ($1, $2) RETURNING user_id", - user_name, - user_email - ) - .fetch_one(pool) - .await? - .user_id; - sqlx::query!( - "INSERT INTO folders(owner_id, folder_name) VALUES ($1, $2)", - id, - "ROOT" - ) - .execute(pool) - .await?; - Ok(id) -} - async fn create_debug_users(pool: &Pool) -> anyhow::Result<()> { let count = sqlx::query!("SELECT count(user_id) FROM users") .fetch_one(pool) @@ -49,8 +30,8 @@ async fn create_debug_users(pool: &Pool) -> anyhow::Result<()> { } tokio::try_join!( - create_user("Test1", "test1@example.com", pool), - create_user("Test2", "test2@example.com", pool) + db::users::create_user("Test1", "test1@example.com", pool), + db::users::create_user("Test2", "test2@example.com", pool) )?; Ok(()) @@ -87,7 +68,8 @@ fn app(state: AppState) -> Router { use axum::{http::header, routing::get}; use endpoints::{ file, folder, - permissions::{self, get_top_level_permitted_folders::get_top_level}, + permissions::{self, get_top_level::get_top_level}, + users, }; use tower_http::ServiceBuilderExt as _; @@ -123,6 +105,14 @@ fn app(state: AppState) -> Router { "/permissions/get_top_level_permitted_folders", get(get_top_level), ) + .route( + "/users", + get(users::get::get) + .post(users::create::create) + .delete(users::delete::delete) + .put(users::put::put), + ) + .route("/users/search", get(users::search::search)) .layer(middleware) .with_state(state) }