Refactor get_relationship function and optimize SQL query

This commit is contained in:
silverpill 2022-02-02 17:51:45 +00:00
parent a81d0ef216
commit 7de7f7a501
2 changed files with 122 additions and 39 deletions

View file

@ -14,40 +14,71 @@ use crate::models::profiles::types::DbActorProfile;
use crate::utils::id::new_uuid;
use super::types::{
DbFollowRequest,
DbRelationship,
FollowRequestStatus,
Relationship,
RelationshipMap,
RelationshipType,
};
async fn get_relationships(
db_client: &impl GenericClient,
source_id: &Uuid,
target_id: &Uuid,
) -> Result<Vec<DbRelationship>, DatabaseError> {
let rows = db_client.query(
"
SELECT source_id, target_id, $4::smallint AS relationship_type
FROM relationship
WHERE
source_id = $1 AND target_id = $2
OR
source_id = $2 AND target_id = $1
UNION ALL
SELECT source_id, target_id, $5 AS relationship_type
FROM follow_request
WHERE
source_id = $1 AND target_id = $2
AND request_status = $3
",
&[
&source_id,
&target_id,
&FollowRequestStatus::Pending,
&RelationshipType::Follow,
&RelationshipType::FollowRequest,
],
).await?;
let relationships = rows.iter()
.map(DbRelationship::try_from)
.collect::<Result<_, _>>()?;
Ok(relationships)
}
pub async fn get_relationship(
db_client: &impl GenericClient,
source_id: &Uuid,
target_id: &Uuid,
) -> Result<Relationship, DatabaseError> {
let maybe_row = db_client.query_opt(
"
SELECT
actor_profile.id AS profile_id,
EXISTS (
SELECT 1 FROM relationship
WHERE source_id = $1 AND target_id = actor_profile.id
) AS following,
EXISTS (
SELECT 1 FROM relationship
WHERE source_id = actor_profile.id AND target_id = $1
) AS followed_by,
EXISTS (
SELECT 1 FROM follow_request
WHERE source_id = $1 AND target_id = actor_profile.id
AND request_status = $3
) AS requested
FROM actor_profile
WHERE actor_profile.id = $2
",
&[&source_id, &target_id, &FollowRequestStatus::Pending],
).await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("profile"))?;
let relationship = Relationship::try_from(&row)?;
Ok(relationship)
) -> Result<RelationshipMap, DatabaseError> {
// NOTE: this method returns relationship map even if target does not exist
let relationships = get_relationships(db_client, source_id, target_id).await?;
let mut relationship_map = RelationshipMap { id: *target_id, ..Default::default() };
for relationship in relationships {
match relationship.relationship_type {
RelationshipType::Follow => {
if relationship.is_direct(source_id, target_id)? {
relationship_map.following = true;
} else {
relationship_map.followed_by = true;
};
},
RelationshipType::FollowRequest => {
if relationship.is_direct(source_id, target_id)? {
relationship_map.requested = true;
};
},
};
};
Ok(relationship_map)
}
pub async fn follow(

View file

@ -8,29 +8,81 @@ use uuid::Uuid;
use crate::database::int_enum::{int_enum_from_sql, int_enum_to_sql};
use crate::errors::ConversionError;
#[derive(Serialize)]
pub struct Relationship {
pub id: Uuid,
pub following: bool,
pub followed_by: bool,
pub requested: bool,
#[derive(Debug)]
pub enum RelationshipType {
Follow,
FollowRequest,
}
impl TryFrom<&Row> for Relationship {
impl From<&RelationshipType> for i16 {
fn from(value: &RelationshipType) -> i16 {
match value {
RelationshipType::Follow => 1,
RelationshipType::FollowRequest => 2,
}
}
}
impl TryFrom<i16> for RelationshipType {
type Error = ConversionError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
let relationship_type = match value {
1 => Self::Follow,
2 => Self::FollowRequest,
_ => return Err(ConversionError),
};
Ok(relationship_type)
}
}
int_enum_from_sql!(RelationshipType);
int_enum_to_sql!(RelationshipType);
pub struct DbRelationship {
pub source_id: Uuid,
pub target_id: Uuid,
pub relationship_type: RelationshipType,
}
impl DbRelationship {
pub fn is_direct(
&self,
source_id: &Uuid,
target_id: &Uuid,
) -> Result<bool, ConversionError> {
if &self.source_id == source_id && &self.target_id == target_id {
Ok(true)
} else if &self.source_id == target_id && &self.target_id == source_id {
Ok(false)
} else {
Err(ConversionError)
}
}
}
impl TryFrom<&Row> for DbRelationship {
type Error = tokio_postgres::Error;
fn try_from(row: &Row) -> Result<Self, Self::Error> {
let relationship = Relationship {
id: row.try_get("profile_id")?,
following: row.try_get("following")?,
followed_by: row.try_get("followed_by")?,
requested: row.try_get("requested")?,
let relationship = Self {
source_id: row.try_get("source_id")?,
target_id: row.try_get("target_id")?,
relationship_type: row.try_get("relationship_type")?,
};
Ok(relationship)
}
}
#[derive(Default, Serialize)]
pub struct RelationshipMap {
pub id: Uuid,
pub following: bool,
pub followed_by: bool,
pub requested: bool,
}
#[derive(Debug)]
pub enum FollowRequestStatus {
Pending,