fedimovies/src/models/profiles/queries.rs

352 lines
9.7 KiB
Rust
Raw Normal View History

2021-04-09 00:22:17 +00:00
use tokio_postgres::GenericClient;
use uuid::Uuid;
use crate::errors::DatabaseError;
use crate::models::cleanup::{
find_orphaned_files,
find_orphaned_ipfs_objects,
DeletionQueue,
};
2021-09-16 14:34:24 +00:00
use super::types::{
ExtraFields,
DbActorProfile,
ProfileCreateData,
ProfileUpdateData,
};
2021-04-09 00:22:17 +00:00
/// Create new profile using given Client or Transaction.
pub async fn create_profile(
db_client: &impl GenericClient,
profile_data: &ProfileCreateData,
) -> Result<DbActorProfile, DatabaseError> {
let profile_id = Uuid::new_v4();
2021-09-17 12:36:24 +00:00
let extra_fields = ExtraFields(profile_data.extra_fields.clone());
2021-04-09 00:22:17 +00:00
let result = db_client.query_one(
"
INSERT INTO actor_profile (
id, username, display_name, acct, bio, bio_source,
2021-09-17 12:36:24 +00:00
avatar_file_name, banner_file_name, extra_fields,
2021-04-09 00:22:17 +00:00
actor_json
)
2021-09-17 12:36:24 +00:00
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
2021-04-09 00:22:17 +00:00
RETURNING actor_profile
",
&[
&profile_id,
&profile_data.username,
&profile_data.display_name,
&profile_data.acct,
&profile_data.bio,
&profile_data.bio,
&profile_data.avatar,
&profile_data.banner,
2021-09-17 12:36:24 +00:00
&extra_fields,
2021-04-09 00:22:17 +00:00
&profile_data.actor,
],
).await;
let profile = match result {
Ok(row) => row.try_get("actor_profile")?,
Err(err) => {
// TODO: catch profile already exists error
log::warn!("{}", err);
return Err(DatabaseError::AlreadyExists("profile"));
},
};
Ok(profile)
}
pub async fn update_profile(
db_client: &impl GenericClient,
profile_id: &Uuid,
data: ProfileUpdateData,
) -> Result<DbActorProfile, DatabaseError> {
let maybe_row = db_client.query_opt(
"
UPDATE actor_profile
SET
display_name = $1,
bio = $2,
bio_source = $3,
avatar_file_name = $4,
2021-09-16 14:34:24 +00:00
banner_file_name = $5,
extra_fields = $6
WHERE id = $7
2021-04-09 00:22:17 +00:00
RETURNING actor_profile
",
&[
&data.display_name,
&data.bio,
&data.bio_source,
&data.avatar,
&data.banner,
2021-09-16 14:34:24 +00:00
&ExtraFields(data.extra_fields),
2021-04-09 00:22:17 +00:00
&profile_id,
],
).await?;
let profile = match maybe_row {
Some(row) => row.try_get("actor_profile")?,
None => return Err(DatabaseError::NotFound("profile")),
};
Ok(profile)
}
pub async fn get_profile_by_id(
db_client: &impl GenericClient,
profile_id: &Uuid,
) -> Result<DbActorProfile, DatabaseError> {
let result = db_client.query_opt(
"
SELECT actor_profile
FROM actor_profile
WHERE id = $1
",
&[&profile_id],
).await?;
let profile = match result {
Some(row) => row.try_get("actor_profile")?,
None => return Err(DatabaseError::NotFound("profile")),
};
Ok(profile)
}
pub async fn get_profile_by_actor_id(
db_client: &impl GenericClient,
actor_id: &str,
) -> Result<DbActorProfile, DatabaseError> {
let result = db_client.query_opt(
"
SELECT actor_profile
FROM actor_profile
WHERE actor_profile.actor_json ->> 'id' = $1
",
&[&actor_id],
).await?;
let profile = match result {
Some(row) => row.try_get("actor_profile")?,
None => return Err(DatabaseError::NotFound("profile")),
};
Ok(profile)
}
pub async fn get_profile_by_acct(
db_client: &impl GenericClient,
account_uri: &str,
) -> Result<DbActorProfile, DatabaseError> {
let result = db_client.query_opt(
"
SELECT actor_profile
FROM actor_profile
WHERE actor_profile.acct = $1
",
&[&account_uri],
).await?;
let profile = match result {
Some(row) => row.try_get("actor_profile")?,
None => return Err(DatabaseError::NotFound("profile")),
};
Ok(profile)
}
pub async fn get_profiles(
db_client: &impl GenericClient,
) -> Result<Vec<DbActorProfile>, DatabaseError> {
let rows = db_client.query(
"
SELECT actor_profile
FROM actor_profile
ORDER BY username
",
&[],
).await?;
let profiles = rows.iter()
.map(|row| row.try_get("actor_profile"))
.collect::<Result<Vec<DbActorProfile>, _>>()?;
Ok(profiles)
}
pub async fn get_followers(
db_client: &impl GenericClient,
profile_id: &Uuid,
) -> Result<Vec<DbActorProfile>, DatabaseError> {
let rows = db_client.query(
"
SELECT actor_profile
FROM actor_profile
JOIN relationship
ON (actor_profile.id = relationship.source_id)
WHERE relationship.target_id = $1
",
&[&profile_id],
).await?;
let profiles = rows.iter()
.map(|row| row.try_get("actor_profile"))
.collect::<Result<Vec<DbActorProfile>, _>>()?;
Ok(profiles)
}
/// Deletes profile from database and returns collection of orphaned objects.
2021-04-09 00:22:17 +00:00
pub async fn delete_profile(
db_client: &mut impl GenericClient,
2021-04-09 00:22:17 +00:00
profile_id: &Uuid,
) -> Result<DeletionQueue, DatabaseError> {
let transaction = db_client.transaction().await?;
// Get list of media files owned by actor
let files_rows = transaction.query(
"
SELECT file_name
FROM media_attachment WHERE owner_id = $1
UNION ALL
SELECT unnest(array_remove(ARRAY[avatar_file_name, banner_file_name], NULL))
FROM actor_profile WHERE id = $1
",
&[&profile_id],
).await?;
let files: Vec<String> = files_rows.iter()
.map(|row| row.try_get("file_name"))
.collect::<Result<_, _>>()?;
// Get list of IPFS objects owned by actor
let ipfs_objects_rows = transaction.query(
"
SELECT ipfs_cid
FROM media_attachment
WHERE owner_id = $1 AND ipfs_cid IS NOT NULL
UNION ALL
SELECT ipfs_cid
FROM post
WHERE author_id = $1 AND ipfs_cid IS NOT NULL
",
&[&profile_id],
).await?;
let ipfs_objects: Vec<String> = ipfs_objects_rows.iter()
.map(|row| row.try_get("ipfs_cid"))
.collect::<Result<_, _>>()?;
// Update counters
transaction.execute(
"
UPDATE actor_profile
SET follower_count = follower_count - 1
FROM relationship
WHERE
actor_profile.id = relationship.target_id
AND relationship.source_id = $1
",
&[&profile_id],
).await?;
transaction.execute(
"
UPDATE actor_profile
SET following_count = following_count - 1
FROM relationship
WHERE
actor_profile.id = relationship.source_id
AND relationship.target_id = $1
",
&[&profile_id],
).await?;
// Delete profile
let deleted_count = transaction.execute(
"
DELETE FROM actor_profile WHERE id = $1
RETURNING actor_profile
",
2021-04-09 00:22:17 +00:00
&[&profile_id],
).await?;
if deleted_count == 0 {
return Err(DatabaseError::NotFound("profile"));
}
let orphaned_files = find_orphaned_files(&transaction, files).await?;
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(&transaction, ipfs_objects).await?;
transaction.commit().await?;
Ok(DeletionQueue {
files: orphaned_files,
ipfs_objects: orphaned_ipfs_objects,
})
2021-04-09 00:22:17 +00:00
}
pub async fn search_profile(
db_client: &impl GenericClient,
username: &String,
instance: &Option<String>,
) -> Result<Vec<DbActorProfile>, DatabaseError> {
let db_search_query = match &instance {
Some(instance) => {
// Search for exact profile name.
// Fetch from remote server if not found
format!("{}@{}", username, instance)
},
None => {
// Search for username
format!("%{}%", username)
},
};
let rows = db_client.query(
"
SELECT actor_profile
FROM actor_profile
WHERE acct LIKE $1
",
&[&db_search_query],
).await?;
let profiles: Vec<DbActorProfile> = rows.iter()
.map(|row| row.try_get("actor_profile"))
.collect::<Result<_, _>>()?;
Ok(profiles)
}
pub async fn update_follower_count(
db_client: &impl GenericClient,
profile_id: &Uuid,
change: i32,
) -> Result<DbActorProfile, DatabaseError> {
let maybe_row = db_client.query_opt(
"
UPDATE actor_profile
SET follower_count = follower_count + $1
WHERE id = $2
RETURNING actor_profile
",
&[&change, &profile_id],
).await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("profile"))?;
let profile = row.try_get("actor_profile")?;
Ok(profile)
}
pub async fn update_following_count(
db_client: &impl GenericClient,
profile_id: &Uuid,
change: i32,
) -> Result<DbActorProfile, DatabaseError> {
let maybe_row = db_client.query_opt(
"
UPDATE actor_profile
SET following_count = following_count + $1
WHERE id = $2
RETURNING actor_profile
",
&[&change, &profile_id],
).await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("profile"))?;
let profile = row.try_get("actor_profile")?;
Ok(profile)
}
pub async fn update_post_count(
db_client: &impl GenericClient,
profile_id: &Uuid,
change: i32,
) -> Result<DbActorProfile, DatabaseError> {
let maybe_row = db_client.query_opt(
"
UPDATE actor_profile
SET post_count = post_count + $1
WHERE id = $2
RETURNING actor_profile
",
&[&change, &profile_id],
).await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("profile"))?;
let profile = row.try_get("actor_profile")?;
Ok(profile)
}