From 06830bfd2c945c1e229638415e46008dc9ea1a97 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Wed, 16 Feb 2022 17:13:12 +0530 Subject: [PATCH] feat: DB: visibility filtered gist retrieval SUMMARY GistDatabase::get_user_gists retrieved all gists that were created by the user. GistDatabase::get_user_public_gists and GistDatabase::get_user_public_unlisted_gists produce visibility-filtered results DESCRIPTION Gist visibility levels are ordered in the following order: --------------------------------- | Public < Unlisted < Private | --------------------------------- A user with permissions to access a visibility-level will also have access to the levels lower levels to it. Accessibility filters use this mechanism to filter gists: a higher visibility-level request will also return gists with lower visibility-levels. GistDatabase::get_user_public_unlisted_gists: Return all gists that belong to a user with public(GistVisibility::Public) and unlisted(GistVisibility::UnliUnlisted) visibility levels. Gists with private(GistVisibility::Private) visibility levels are ignored. GistDatabase::get_user_public_gists: Return all gists that belong to a user with public(GistVisibility::Public) only is returned. Private and Unlisted resources are ignored. GistDatabase::get_user_gists: Returns all gists belonging to a user --- database/db-core/src/lib.rs | 15 ++++ database/db-core/src/tests.rs | 43 ++++++++++ database/db-sqlx-postgres/sqlx-data.json | 102 +++++++++++++++++++++++ database/db-sqlx-postgres/src/lib.rs | 72 ++++++++++++++++ database/db-sqlx-sqlite/sqlx-data.json | 96 +++++++++++++++++++++ database/db-sqlx-sqlite/src/lib.rs | 72 ++++++++++++++++ 6 files changed, 400 insertions(+) diff --git a/database/db-core/src/lib.rs b/database/db-core/src/lib.rs index bf24ea7..893f58e 100644 --- a/database/db-core/src/lib.rs +++ b/database/db-core/src/lib.rs @@ -236,6 +236,13 @@ pub trait GistDatabase: std::marker::Send + std::marker::Sync + CloneGistDatabas /// Retrieve gists belonging to user async fn get_user_gists(&self, owner: &str) -> DBResult>; + /// Retrieve gists belonging to user that are [GistVisibility::Public] + async fn get_user_public_gists(&self, owner: &str) -> DBResult>; + + /// Retrieve gists belonging to user that are [GistVisibility::Public] and + /// [GistVisibility::UnliUnlisted] + async fn get_user_public_unlisted_gists(&self, owner: &str) -> DBResult>; + /// Delete gist async fn delete_gist(&self, owner: &str, public_id: &str) -> DBResult<()>; @@ -323,6 +330,14 @@ impl GistDatabase for Box { (**self).get_user_gists(owner).await } + async fn get_user_public_gists(&self, owner: &str) -> DBResult> { + (**self).get_user_public_gists(owner).await + } + + async fn get_user_public_unlisted_gists(&self, owner: &str) -> DBResult> { + (**self).get_user_public_unlisted_gists(owner).await + } + async fn delete_gist(&self, owner: &str, public_id: &str) -> DBResult<()> { (**self).delete_gist(owner, public_id).await } diff --git a/database/db-core/src/tests.rs b/database/db-core/src/tests.rs index 1a65d40..71555be 100644 --- a/database/db-core/src/tests.rs +++ b/database/db-core/src/tests.rs @@ -142,6 +142,49 @@ pub async fn gists_work( DBError::CommentNotFound )); + // visibility filters + let create_unlisted_gist = CreateGist { + owner: username.into(), + description: Some("foo"), + public_id: &format!("{}unlisted", public_id), + visibility: &GistVisibility::Unlisted, + }; + db.new_gist(&create_unlisted_gist).await.unwrap(); + let create_private_gist = CreateGist { + owner: username.into(), + description: Some("foo"), + public_id: &format!("{}private", public_id), + visibility: &GistVisibility::Private, + }; + db.new_gist(&create_private_gist).await.unwrap(); + + let public_gists = db.get_user_public_gists(username).await.unwrap(); + assert_eq!(public_gists.len(), 1); + assert_gists(&create_gist, &public_gists[0]); + + let public_unlisted_gists = db.get_user_public_unlisted_gists(username).await.unwrap(); + assert_eq!(public_unlisted_gists.len(), 2); + for gist in public_unlisted_gists { + assert_ne!(gist.visibility, GistVisibility::Private); + if gist.visibility == GistVisibility::Public { + assert_gists(&create_gist, &gist); + } else { + assert_gists(&create_unlisted_gist, &gist); + } + } + + let all_gists = db.get_user_gists(username).await.unwrap(); + assert_eq!(all_gists.len(), 3); + for gist in all_gists { + if gist.visibility == GistVisibility::Public { + assert_gists(&create_gist, &gist); + } else if gist.visibility == GistVisibility::Unlisted { + assert_gists(&create_unlisted_gist, &gist); + } else { + assert_gists(&create_private_gist, &gist); + } + } + // delete gist db.delete_gist(username, &create_gist.public_id) .await diff --git a/database/db-sqlx-postgres/sqlx-data.json b/database/db-sqlx-postgres/sqlx-data.json index cf4ca22..584dba1 100644 --- a/database/db-sqlx-postgres/sqlx-data.json +++ b/database/db-sqlx-postgres/sqlx-data.json @@ -72,6 +72,57 @@ "nullable": [] } }, + "36dec233992fb484e0ee86dac49197ddcea4e2ea47bb98a3aee89f857e13cc80": { + "query": "SELECT\n owner,\n visibility,\n created,\n updated,\n public_id,\n description\n FROM\n gists_gists_view\n WHERE \n owner = $1\n AND\n visibility = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "owner", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "visibility", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "created", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "public_id", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "description", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true, + true, + true, + true, + true, + true + ] + } + }, "38409cc4e1e1edf0a5f15160d54a59b741fc668795bcdc11570e3963661c77e0": { "query": "SELECT\n owner,\n visibility,\n created,\n updated,\n public_id,\n description\n FROM\n gists_gists_view\n WHERE public_id = $1\n ", "describe": { @@ -465,6 +516,57 @@ ] } }, + "f8f0c9da439206cfc4df5f916d9c4cf731c19cbf6c005a5e7f56dac5d3b90b8e": { + "query": "SELECT\n owner,\n visibility,\n created,\n updated,\n public_id,\n description\n FROM\n gists_gists_view\n WHERE \n owner = $1\n AND\n visibility <> $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "owner", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "visibility", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "created", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "public_id", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "description", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + true, + true, + true, + true, + true, + true + ] + } + }, "fbeb7f634647d0082b9a083590b027ba668e59f2173e0699b78c2261ace2638f": { "query": "INSERT INTO gists_gists \n (owner_id , public_id, visibility, created, updated)\n VALUES (\n (SELECT ID FROM gists_users WHERE username = $1),\n $2, (SELECT ID FROM gists_visibility WHERE name = $3), $4, $5\n )", "describe": { diff --git a/database/db-sqlx-postgres/src/lib.rs b/database/db-sqlx-postgres/src/lib.rs index 61b335e..83d421d 100644 --- a/database/db-sqlx-postgres/src/lib.rs +++ b/database/db-sqlx-postgres/src/lib.rs @@ -398,6 +398,78 @@ impl GistDatabase for Database { Ok(gists) } + /// Retrieve gists belonging to user from database + async fn get_user_public_gists(&self, owner: &str) -> DBResult> { + const PUBLIC: &str = GistVisibility::Public.to_str(); + let mut res = sqlx::query_as!( + InnerGist, + "SELECT + owner, + visibility, + created, + updated, + public_id, + description + FROM + gists_gists_view + WHERE + owner = $1 + AND + visibility = $2 + ", + owner, + PUBLIC + ) + .fetch_all(&self.pool) + .await + .map_err(|e| match e { + Error::RowNotFound => DBError::GistNotFound, + e => DBError::DBError(Box::new(e)), + })?; + + let mut gists = Vec::with_capacity(res.len()); + for r in res.drain(..) { + gists.push(r.to_gist()?); + } + Ok(gists) + } + + /// Retrieve gists belonging to user from database + async fn get_user_public_unlisted_gists(&self, owner: &str) -> DBResult> { + const PRIVATE: &str = GistVisibility::Private.to_str(); + let mut res = sqlx::query_as!( + InnerGist, + "SELECT + owner, + visibility, + created, + updated, + public_id, + description + FROM + gists_gists_view + WHERE + owner = $1 + AND + visibility <> $2 + ", + owner, + PRIVATE + ) + .fetch_all(&self.pool) + .await + .map_err(|e| match e { + Error::RowNotFound => DBError::GistNotFound, + e => DBError::DBError(Box::new(e)), + })?; + + let mut gists = Vec::with_capacity(res.len()); + for r in res.drain(..) { + gists.push(r.to_gist()?); + } + Ok(gists) + } + async fn delete_gist(&self, owner: &str, public_id: &str) -> DBResult<()> { sqlx::query!( "DELETE FROM gists_gists diff --git a/database/db-sqlx-sqlite/sqlx-data.json b/database/db-sqlx-sqlite/sqlx-data.json index 335c237..d4b0b92 100644 --- a/database/db-sqlx-sqlite/sqlx-data.json +++ b/database/db-sqlx-sqlite/sqlx-data.json @@ -72,6 +72,54 @@ "nullable": [] } }, + "36dec233992fb484e0ee86dac49197ddcea4e2ea47bb98a3aee89f857e13cc80": { + "query": "SELECT\n owner,\n visibility,\n created,\n updated,\n public_id,\n description\n FROM\n gists_gists_view\n WHERE \n owner = $1\n AND\n visibility = $2\n ", + "describe": { + "columns": [ + { + "name": "owner", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "visibility", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "created", + "ordinal": 2, + "type_info": "Int64" + }, + { + "name": "updated", + "ordinal": 3, + "type_info": "Int64" + }, + { + "name": "public_id", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 5, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false, + false, + false, + true + ] + } + }, "38409cc4e1e1edf0a5f15160d54a59b741fc668795bcdc11570e3963661c77e0": { "query": "SELECT\n owner,\n visibility,\n created,\n updated,\n public_id,\n description\n FROM\n gists_gists_view\n WHERE public_id = $1\n ", "describe": { @@ -422,6 +470,54 @@ ] } }, + "f8f0c9da439206cfc4df5f916d9c4cf731c19cbf6c005a5e7f56dac5d3b90b8e": { + "query": "SELECT\n owner,\n visibility,\n created,\n updated,\n public_id,\n description\n FROM\n gists_gists_view\n WHERE \n owner = $1\n AND\n visibility <> $2\n ", + "describe": { + "columns": [ + { + "name": "owner", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "visibility", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "created", + "ordinal": 2, + "type_info": "Int64" + }, + { + "name": "updated", + "ordinal": 3, + "type_info": "Int64" + }, + { + "name": "public_id", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "description", + "ordinal": 5, + "type_info": "Text" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false, + false, + false, + true + ] + } + }, "fbeb7f634647d0082b9a083590b027ba668e59f2173e0699b78c2261ace2638f": { "query": "INSERT INTO gists_gists \n (owner_id , public_id, visibility, created, updated)\n VALUES (\n (SELECT ID FROM gists_users WHERE username = $1),\n $2, (SELECT ID FROM gists_visibility WHERE name = $3), $4, $5\n )", "describe": { diff --git a/database/db-sqlx-sqlite/src/lib.rs b/database/db-sqlx-sqlite/src/lib.rs index 6eccab2..48ebc41 100644 --- a/database/db-sqlx-sqlite/src/lib.rs +++ b/database/db-sqlx-sqlite/src/lib.rs @@ -361,6 +361,78 @@ impl GistDatabase for Database { Ok(gists) } + /// Retrieve gists belonging to user from database + async fn get_user_public_gists(&self, owner: &str) -> DBResult> { + const PUBLIC: &str = GistVisibility::Public.to_str(); + let mut res = sqlx::query_as!( + InnerGist, + "SELECT + owner, + visibility, + created, + updated, + public_id, + description + FROM + gists_gists_view + WHERE + owner = $1 + AND + visibility = $2 + ", + owner, + PUBLIC + ) + .fetch_all(&self.pool) + .await + .map_err(|e| match e { + Error::RowNotFound => DBError::GistNotFound, + e => DBError::DBError(Box::new(e)), + })?; + + let mut gists = Vec::with_capacity(res.len()); + for r in res.drain(..) { + gists.push(r.to_gist()?); + } + Ok(gists) + } + + /// Retrieve gists belonging to user from database + async fn get_user_public_unlisted_gists(&self, owner: &str) -> DBResult> { + const PRIVATE: &str = GistVisibility::Private.to_str(); + let mut res = sqlx::query_as!( + InnerGist, + "SELECT + owner, + visibility, + created, + updated, + public_id, + description + FROM + gists_gists_view + WHERE + owner = $1 + AND + visibility <> $2 + ", + owner, + PRIVATE + ) + .fetch_all(&self.pool) + .await + .map_err(|e| match e { + Error::RowNotFound => DBError::GistNotFound, + e => DBError::DBError(Box::new(e)), + })?; + + let mut gists = Vec::with_capacity(res.len()); + for r in res.drain(..) { + gists.push(r.to_gist()?); + } + Ok(gists) + } + async fn delete_gist(&self, owner: &str, public_id: &str) -> DBResult<()> { sqlx::query!( "DELETE FROM gists_gists