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
master
Aravinth Manivannan 2022-02-16 17:13:12 +05:30
parent 7cdfcc47bb
commit 06830bfd2c
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
6 changed files with 400 additions and 0 deletions

View File

@ -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<Vec<Gist>>;
/// Retrieve gists belonging to user that are [GistVisibility::Public]
async fn get_user_public_gists(&self, owner: &str) -> DBResult<Vec<Gist>>;
/// Retrieve gists belonging to user that are [GistVisibility::Public] and
/// [GistVisibility::UnliUnlisted]
async fn get_user_public_unlisted_gists(&self, owner: &str) -> DBResult<Vec<Gist>>;
/// Delete gist
async fn delete_gist(&self, owner: &str, public_id: &str) -> DBResult<()>;
@ -323,6 +330,14 @@ impl GistDatabase for Box<dyn GistDatabase> {
(**self).get_user_gists(owner).await
}
async fn get_user_public_gists(&self, owner: &str) -> DBResult<Vec<Gist>> {
(**self).get_user_public_gists(owner).await
}
async fn get_user_public_unlisted_gists(&self, owner: &str) -> DBResult<Vec<Gist>> {
(**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
}

View File

@ -142,6 +142,49 @@ pub async fn gists_work<T: GistDatabase>(
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

View File

@ -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": {

View File

@ -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<Vec<Gist>> {
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<Vec<Gist>> {
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

View File

@ -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": {

View File

@ -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<Vec<Gist>> {
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<Vec<Gist>> {
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