2022-11-17 21:40:07 +00:00
|
|
|
use chrono::{DateTime, Utc};
|
2021-04-09 00:22:17 +00:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2023-02-18 22:25:49 +00:00
|
|
|
use mitra_utils::{
|
|
|
|
currencies::Currency,
|
2023-03-16 17:59:45 +00:00
|
|
|
did::Did,
|
|
|
|
did_pkh::DidPkh,
|
2023-02-18 22:25:49 +00:00
|
|
|
id::generate_ulid,
|
|
|
|
};
|
|
|
|
|
2023-03-30 20:27:17 +00:00
|
|
|
use crate::cleanup::{
|
|
|
|
find_orphaned_files,
|
|
|
|
find_orphaned_ipfs_objects,
|
|
|
|
DeletionQueue,
|
|
|
|
};
|
2022-12-03 22:09:42 +00:00
|
|
|
use crate::database::{
|
|
|
|
catch_unique_violation,
|
|
|
|
query_macro::query,
|
2023-01-17 23:14:18 +00:00
|
|
|
DatabaseClient,
|
2022-12-03 22:09:42 +00:00
|
|
|
DatabaseError,
|
|
|
|
};
|
2023-03-30 20:27:17 +00:00
|
|
|
use crate::emojis::types::DbEmoji;
|
|
|
|
use crate::instances::queries::create_instance;
|
|
|
|
use crate::relationships::types::RelationshipType;
|
|
|
|
|
2021-09-16 14:34:24 +00:00
|
|
|
use super::types::{
|
2023-03-16 22:33:33 +00:00
|
|
|
Aliases,
|
2021-09-16 14:34:24 +00:00
|
|
|
DbActorProfile,
|
2022-04-26 14:12:26 +00:00
|
|
|
ExtraFields,
|
|
|
|
IdentityProofs,
|
2022-07-23 15:47:53 +00:00
|
|
|
PaymentOptions,
|
2021-09-16 14:34:24 +00:00
|
|
|
ProfileCreateData,
|
|
|
|
ProfileUpdateData,
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2023-01-21 00:23:15 +00:00
|
|
|
async fn create_profile_emojis(
|
|
|
|
db_client: &impl DatabaseClient,
|
|
|
|
profile_id: &Uuid,
|
|
|
|
emojis: Vec<Uuid>,
|
|
|
|
) -> Result<Vec<DbEmoji>, DatabaseError> {
|
|
|
|
let emojis_rows = db_client.query(
|
|
|
|
"
|
|
|
|
INSERT INTO profile_emoji (profile_id, emoji_id)
|
|
|
|
SELECT $1, emoji.id FROM emoji WHERE id = ANY($2)
|
|
|
|
RETURNING (
|
|
|
|
SELECT emoji FROM emoji
|
|
|
|
WHERE emoji.id = emoji_id
|
|
|
|
)
|
|
|
|
",
|
|
|
|
&[&profile_id, &emojis],
|
|
|
|
).await?;
|
|
|
|
if emojis_rows.len() != emojis.len() {
|
|
|
|
return Err(DatabaseError::NotFound("emoji"));
|
|
|
|
};
|
|
|
|
let emojis = emojis_rows.iter()
|
|
|
|
.map(|row| row.try_get("emoji"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(emojis)
|
|
|
|
}
|
|
|
|
|
2023-03-04 22:35:49 +00:00
|
|
|
async fn update_emoji_cache(
|
|
|
|
db_client: &impl DatabaseClient,
|
|
|
|
profile_id: &Uuid,
|
|
|
|
) -> Result<DbActorProfile, DatabaseError> {
|
|
|
|
let maybe_row = db_client.query_opt(
|
|
|
|
"
|
|
|
|
WITH profile_emojis AS (
|
|
|
|
SELECT
|
|
|
|
actor_profile.id AS profile_id,
|
|
|
|
COALESCE(
|
|
|
|
jsonb_agg(emoji) FILTER (WHERE emoji.id IS NOT NULL),
|
|
|
|
'[]'
|
|
|
|
) AS emojis
|
|
|
|
FROM actor_profile
|
|
|
|
LEFT JOIN profile_emoji ON (profile_emoji.profile_id = actor_profile.id)
|
|
|
|
LEFT JOIN emoji ON (emoji.id = profile_emoji.emoji_id)
|
|
|
|
WHERE actor_profile.id = $1
|
|
|
|
GROUP BY actor_profile.id
|
|
|
|
)
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET emojis = profile_emojis.emojis
|
|
|
|
FROM profile_emojis
|
|
|
|
WHERE actor_profile.id = profile_emojis.profile_id
|
|
|
|
RETURNING actor_profile
|
|
|
|
",
|
|
|
|
&[&profile_id],
|
|
|
|
).await?;
|
|
|
|
let row = maybe_row.ok_or(DatabaseError::NotFound("profile"))?;
|
|
|
|
let profile: DbActorProfile = row.try_get("actor_profile")?;
|
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
2023-01-21 00:23:15 +00:00
|
|
|
pub async fn update_emoji_caches(
|
|
|
|
db_client: &impl DatabaseClient,
|
|
|
|
emoji_id: &Uuid,
|
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
db_client.execute(
|
|
|
|
"
|
|
|
|
WITH profile_emojis AS (
|
|
|
|
SELECT
|
|
|
|
actor_profile.id AS profile_id,
|
|
|
|
COALESCE(
|
|
|
|
jsonb_agg(emoji) FILTER (WHERE emoji.id IS NOT NULL),
|
|
|
|
'[]'
|
|
|
|
) AS emojis
|
|
|
|
FROM actor_profile
|
|
|
|
CROSS JOIN jsonb_array_elements(actor_profile.emojis) AS cached_emoji
|
|
|
|
LEFT JOIN profile_emoji ON (profile_emoji.profile_id = actor_profile.id)
|
|
|
|
LEFT JOIN emoji ON (emoji.id = profile_emoji.emoji_id)
|
|
|
|
WHERE CAST(cached_emoji ->> 'id' AS UUID) = $1
|
|
|
|
GROUP BY actor_profile.id
|
|
|
|
)
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET emojis = profile_emojis.emojis
|
|
|
|
FROM profile_emojis
|
|
|
|
WHERE actor_profile.id = profile_emojis.profile_id
|
|
|
|
",
|
|
|
|
&[&emoji_id],
|
|
|
|
).await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
/// Create new profile using given Client or Transaction.
|
|
|
|
pub async fn create_profile(
|
2023-01-21 00:23:15 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2022-01-08 11:20:48 +00:00
|
|
|
profile_data: ProfileCreateData,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<DbActorProfile, DatabaseError> {
|
2023-01-21 00:23:15 +00:00
|
|
|
let transaction = db_client.transaction().await?;
|
2023-02-12 22:04:31 +00:00
|
|
|
let profile_id = generate_ulid();
|
2022-10-04 19:18:05 +00:00
|
|
|
if let Some(ref hostname) = profile_data.hostname {
|
2023-01-21 00:23:15 +00:00
|
|
|
create_instance(&transaction, hostname).await?;
|
2022-10-03 20:36:17 +00:00
|
|
|
};
|
2023-03-04 22:35:49 +00:00
|
|
|
transaction.execute(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
|
|
|
INSERT INTO actor_profile (
|
2023-03-16 22:04:36 +00:00
|
|
|
id,
|
|
|
|
username,
|
|
|
|
hostname,
|
|
|
|
display_name,
|
|
|
|
bio,
|
|
|
|
bio_source,
|
|
|
|
avatar,
|
|
|
|
banner,
|
|
|
|
manually_approves_followers,
|
|
|
|
identity_proofs,
|
|
|
|
payment_options,
|
|
|
|
extra_fields,
|
2023-03-16 22:33:33 +00:00
|
|
|
aliases,
|
2021-04-09 00:22:17 +00:00
|
|
|
actor_json
|
|
|
|
)
|
2023-03-16 22:33:33 +00:00
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
2021-04-09 00:22:17 +00:00
|
|
|
RETURNING actor_profile
|
|
|
|
",
|
|
|
|
&[
|
|
|
|
&profile_id,
|
|
|
|
&profile_data.username,
|
2022-10-04 19:18:05 +00:00
|
|
|
&profile_data.hostname,
|
2021-04-09 00:22:17 +00:00
|
|
|
&profile_data.display_name,
|
|
|
|
&profile_data.bio,
|
|
|
|
&profile_data.bio,
|
|
|
|
&profile_data.avatar,
|
|
|
|
&profile_data.banner,
|
2023-03-16 22:04:36 +00:00
|
|
|
&profile_data.manually_approves_followers,
|
2022-04-26 14:12:26 +00:00
|
|
|
&IdentityProofs(profile_data.identity_proofs),
|
2022-07-23 15:47:53 +00:00
|
|
|
&PaymentOptions(profile_data.payment_options),
|
2022-04-26 14:12:26 +00:00
|
|
|
&ExtraFields(profile_data.extra_fields),
|
2023-03-16 22:33:33 +00:00
|
|
|
&Aliases::new(profile_data.aliases),
|
2021-12-31 12:01:08 +00:00
|
|
|
&profile_data.actor_json,
|
2021-04-09 00:22:17 +00:00
|
|
|
],
|
2021-11-12 23:10:20 +00:00
|
|
|
).await.map_err(catch_unique_violation("profile"))?;
|
2023-01-21 00:23:15 +00:00
|
|
|
|
|
|
|
// Create related objects
|
|
|
|
create_profile_emojis(
|
|
|
|
&transaction,
|
|
|
|
&profile_id,
|
|
|
|
profile_data.emojis,
|
|
|
|
).await?;
|
2023-03-04 22:35:49 +00:00
|
|
|
let profile = update_emoji_cache(&transaction, &profile_id).await?;
|
2023-01-21 00:23:15 +00:00
|
|
|
|
|
|
|
transaction.commit().await?;
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_profile(
|
2023-01-21 00:23:15 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
profile_id: &Uuid,
|
2023-01-21 00:23:15 +00:00
|
|
|
profile_data: ProfileUpdateData,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<DbActorProfile, DatabaseError> {
|
2023-01-21 00:23:15 +00:00
|
|
|
let transaction = db_client.transaction().await?;
|
2023-03-04 22:35:49 +00:00
|
|
|
transaction.execute(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET
|
|
|
|
display_name = $1,
|
|
|
|
bio = $2,
|
|
|
|
bio_source = $3,
|
2023-01-06 21:49:15 +00:00
|
|
|
avatar = $4,
|
|
|
|
banner = $5,
|
2023-03-16 22:04:36 +00:00
|
|
|
manually_approves_followers = $6,
|
|
|
|
identity_proofs = $7,
|
|
|
|
payment_options = $8,
|
|
|
|
extra_fields = $9,
|
2023-03-16 22:33:33 +00:00
|
|
|
aliases = $10,
|
|
|
|
actor_json = $11,
|
2022-07-08 19:54:12 +00:00
|
|
|
updated_at = CURRENT_TIMESTAMP
|
2023-03-16 22:33:33 +00:00
|
|
|
WHERE id = $12
|
2021-04-09 00:22:17 +00:00
|
|
|
RETURNING actor_profile
|
|
|
|
",
|
|
|
|
&[
|
2023-01-21 00:23:15 +00:00
|
|
|
&profile_data.display_name,
|
|
|
|
&profile_data.bio,
|
|
|
|
&profile_data.bio_source,
|
|
|
|
&profile_data.avatar,
|
|
|
|
&profile_data.banner,
|
2023-03-16 22:04:36 +00:00
|
|
|
&profile_data.manually_approves_followers,
|
2023-01-21 00:23:15 +00:00
|
|
|
&IdentityProofs(profile_data.identity_proofs),
|
|
|
|
&PaymentOptions(profile_data.payment_options),
|
|
|
|
&ExtraFields(profile_data.extra_fields),
|
2023-03-16 22:33:33 +00:00
|
|
|
&Aliases::new(profile_data.aliases),
|
2023-01-21 00:23:15 +00:00
|
|
|
&profile_data.actor_json,
|
2021-04-09 00:22:17 +00:00
|
|
|
&profile_id,
|
|
|
|
],
|
|
|
|
).await?;
|
2023-01-21 00:23:15 +00:00
|
|
|
|
|
|
|
// Delete and re-create related objects
|
|
|
|
transaction.execute(
|
|
|
|
"DELETE FROM profile_emoji WHERE profile_id = $1",
|
2023-03-04 22:35:49 +00:00
|
|
|
&[profile_id],
|
2023-01-21 00:23:15 +00:00
|
|
|
).await?;
|
|
|
|
create_profile_emojis(
|
|
|
|
&transaction,
|
2023-03-04 22:35:49 +00:00
|
|
|
profile_id,
|
2023-01-21 00:23:15 +00:00
|
|
|
profile_data.emojis,
|
|
|
|
).await?;
|
2023-03-04 22:35:49 +00:00
|
|
|
let profile = update_emoji_cache(&transaction, profile_id).await?;
|
2023-01-21 00:23:15 +00:00
|
|
|
|
|
|
|
transaction.commit().await?;
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_profile_by_id(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-10-15 12:32:27 +00:00
|
|
|
pub async fn get_profile_by_remote_actor_id(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
actor_id: &str,
|
|
|
|
) -> Result<DbActorProfile, DatabaseError> {
|
2023-01-11 20:04:47 +00:00
|
|
|
let maybe_row = db_client.query_opt(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM actor_profile
|
2022-04-20 13:47:04 +00:00
|
|
|
WHERE actor_id = $1
|
2021-04-09 00:22:17 +00:00
|
|
|
",
|
|
|
|
&[&actor_id],
|
|
|
|
).await?;
|
2023-01-11 20:04:47 +00:00
|
|
|
let row = maybe_row.ok_or(DatabaseError::NotFound("profile"))?;
|
|
|
|
let profile: DbActorProfile = row.try_get("actor_profile")?;
|
|
|
|
profile.check_remote()?;
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_profile_by_acct(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-05-06 15:03:43 +00:00
|
|
|
acct: &str,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<DbActorProfile, DatabaseError> {
|
|
|
|
let result = db_client.query_opt(
|
|
|
|
"
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM actor_profile
|
|
|
|
WHERE actor_profile.acct = $1
|
|
|
|
",
|
2022-05-06 15:03:43 +00:00
|
|
|
&[&acct],
|
2021-04-09 00:22:17 +00:00
|
|
|
).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(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-10-02 20:43:51 +00:00
|
|
|
only_local: bool,
|
2022-09-29 21:36:55 +00:00
|
|
|
offset: u16,
|
|
|
|
limit: u16,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
2022-10-02 20:43:51 +00:00
|
|
|
let condition = if only_local { "WHERE actor_id IS NULL" } else { "" };
|
|
|
|
let statement = format!(
|
2021-04-09 00:22:17 +00:00
|
|
|
"
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM actor_profile
|
2022-10-02 20:43:51 +00:00
|
|
|
{condition}
|
2021-04-09 00:22:17 +00:00
|
|
|
ORDER BY username
|
2021-12-16 23:00:52 +00:00
|
|
|
LIMIT $1 OFFSET $2
|
2021-04-09 00:22:17 +00:00
|
|
|
",
|
2022-10-02 20:43:51 +00:00
|
|
|
condition=condition,
|
|
|
|
);
|
|
|
|
let rows = db_client.query(
|
|
|
|
&statement,
|
2022-09-29 21:36:55 +00:00
|
|
|
&[&i64::from(limit), &i64::from(offset)],
|
2021-04-09 00:22:17 +00:00
|
|
|
).await?;
|
|
|
|
let profiles = rows.iter()
|
|
|
|
.map(|row| row.try_get("actor_profile"))
|
2022-10-02 20:43:51 +00:00
|
|
|
.collect::<Result<_, _>>()?;
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(profiles)
|
|
|
|
}
|
|
|
|
|
2021-11-10 17:00:39 +00:00
|
|
|
pub async fn get_profiles_by_accts(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2021-11-10 17:00:39 +00:00
|
|
|
accts: Vec<String>,
|
|
|
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
|
|
|
let rows = db_client.query(
|
|
|
|
"
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM actor_profile
|
|
|
|
WHERE acct = ANY($1)
|
|
|
|
",
|
|
|
|
&[&accts],
|
|
|
|
).await?;
|
|
|
|
let profiles = rows.iter()
|
|
|
|
.map(|row| row.try_get("actor_profile"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(profiles)
|
|
|
|
}
|
|
|
|
|
2021-09-29 11:43:45 +00:00
|
|
|
/// Deletes profile from database and returns collection of orphaned objects.
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn delete_profile(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &mut impl DatabaseClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
profile_id: &Uuid,
|
2021-09-29 11:43:45 +00:00
|
|
|
) -> Result<DeletionQueue, DatabaseError> {
|
2021-09-26 00:11:37 +00:00
|
|
|
let transaction = db_client.transaction().await?;
|
2022-02-19 23:43:18 +00:00
|
|
|
// Select all posts authored by given actor,
|
|
|
|
// their descendants and reposts.
|
|
|
|
let posts_rows = transaction.query(
|
|
|
|
"
|
|
|
|
WITH RECURSIVE context (post_id) AS (
|
|
|
|
SELECT post.id FROM post
|
|
|
|
WHERE post.author_id = $1
|
|
|
|
UNION
|
|
|
|
SELECT post.id FROM post
|
|
|
|
JOIN context ON (
|
|
|
|
post.in_reply_to_id = context.post_id
|
|
|
|
OR post.repost_of_id = context.post_id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
SELECT post_id FROM context
|
|
|
|
",
|
|
|
|
&[&profile_id],
|
|
|
|
).await?;
|
|
|
|
let posts: Vec<Uuid> = posts_rows.iter()
|
|
|
|
.map(|row| row.try_get("post_id"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
// Get list of media files
|
2021-09-26 00:11:37 +00:00
|
|
|
let files_rows = transaction.query(
|
|
|
|
"
|
2023-01-06 21:49:15 +00:00
|
|
|
SELECT unnest(array_remove(
|
|
|
|
ARRAY[
|
|
|
|
avatar ->> 'file_name',
|
|
|
|
banner ->> 'file_name'
|
|
|
|
],
|
|
|
|
NULL
|
|
|
|
)) AS file_name
|
2021-09-26 00:11:37 +00:00
|
|
|
FROM actor_profile WHERE id = $1
|
2022-02-19 23:43:18 +00:00
|
|
|
UNION ALL
|
|
|
|
SELECT file_name
|
|
|
|
FROM media_attachment WHERE post_id = ANY($2)
|
2021-09-26 00:11:37 +00:00
|
|
|
",
|
2022-02-19 23:43:18 +00:00
|
|
|
&[&profile_id, &posts],
|
2021-09-26 00:11:37 +00:00
|
|
|
).await?;
|
|
|
|
let files: Vec<String> = files_rows.iter()
|
|
|
|
.map(|row| row.try_get("file_name"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
2022-02-19 23:43:18 +00:00
|
|
|
// Get list of IPFS objects
|
2021-09-29 11:43:45 +00:00
|
|
|
let ipfs_objects_rows = transaction.query(
|
|
|
|
"
|
|
|
|
SELECT ipfs_cid
|
|
|
|
FROM media_attachment
|
2022-02-19 23:43:18 +00:00
|
|
|
WHERE post_id = ANY($1) AND ipfs_cid IS NOT NULL
|
2021-09-29 11:43:45 +00:00
|
|
|
UNION ALL
|
|
|
|
SELECT ipfs_cid
|
|
|
|
FROM post
|
2022-02-19 23:43:18 +00:00
|
|
|
WHERE id = ANY($1) AND ipfs_cid IS NOT NULL
|
2021-09-29 11:43:45 +00:00
|
|
|
",
|
2022-02-19 23:43:18 +00:00
|
|
|
&[&posts],
|
2021-09-29 11:43:45 +00:00
|
|
|
).await?;
|
|
|
|
let ipfs_objects: Vec<String> = ipfs_objects_rows.iter()
|
|
|
|
.map(|row| row.try_get("ipfs_cid"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
2022-02-19 23:43:18 +00:00
|
|
|
// Update post counters
|
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET post_count = post_count - post.count
|
|
|
|
FROM (
|
|
|
|
SELECT post.author_id, count(*) FROM post
|
|
|
|
WHERE post.id = ANY($1)
|
|
|
|
GROUP BY post.author_id
|
|
|
|
) AS post
|
|
|
|
WHERE actor_profile.id = post.author_id
|
|
|
|
",
|
|
|
|
&[&posts],
|
|
|
|
).await?;
|
2021-09-26 00:11:37 +00:00
|
|
|
// Update counters
|
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET follower_count = follower_count - 1
|
|
|
|
FROM relationship
|
|
|
|
WHERE
|
2022-02-03 00:15:45 +00:00
|
|
|
relationship.source_id = $1
|
|
|
|
AND relationship.target_id = actor_profile.id
|
|
|
|
AND relationship.relationship_type = $2
|
2021-09-26 00:11:37 +00:00
|
|
|
",
|
2022-02-03 00:15:45 +00:00
|
|
|
&[&profile_id, &RelationshipType::Follow],
|
2021-09-26 00:11:37 +00:00
|
|
|
).await?;
|
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET following_count = following_count - 1
|
|
|
|
FROM relationship
|
|
|
|
WHERE
|
2022-02-03 00:15:45 +00:00
|
|
|
relationship.source_id = actor_profile.id
|
2021-09-26 00:11:37 +00:00
|
|
|
AND relationship.target_id = $1
|
2022-02-03 00:15:45 +00:00
|
|
|
AND relationship.relationship_type = $2
|
2021-09-26 00:11:37 +00:00
|
|
|
",
|
2022-02-03 00:15:45 +00:00
|
|
|
&[&profile_id, &RelationshipType::Follow],
|
2021-09-29 12:12:45 +00:00
|
|
|
).await?;
|
2022-09-13 12:41:15 +00:00
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET subscriber_count = subscriber_count - 1
|
|
|
|
FROM relationship
|
|
|
|
WHERE
|
|
|
|
relationship.source_id = $1
|
|
|
|
AND relationship.target_id = actor_profile.id
|
|
|
|
AND relationship.relationship_type = $2
|
|
|
|
",
|
|
|
|
&[&profile_id, &RelationshipType::Subscription],
|
|
|
|
).await?;
|
2021-09-29 12:12:45 +00:00
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
UPDATE post
|
|
|
|
SET reply_count = reply_count - reply.count
|
|
|
|
FROM (
|
|
|
|
SELECT in_reply_to_id, count(*) FROM post
|
|
|
|
WHERE author_id = $1 AND in_reply_to_id IS NOT NULL
|
|
|
|
GROUP BY in_reply_to_id
|
|
|
|
) AS reply
|
|
|
|
WHERE post.id = reply.in_reply_to_id
|
|
|
|
",
|
|
|
|
&[&profile_id],
|
2021-10-17 16:55:33 +00:00
|
|
|
).await?;
|
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
UPDATE post
|
|
|
|
SET reaction_count = reaction_count - 1
|
|
|
|
FROM post_reaction
|
|
|
|
WHERE
|
|
|
|
post_reaction.post_id = post.id
|
|
|
|
AND post_reaction.author_id = $1
|
|
|
|
",
|
|
|
|
&[&profile_id],
|
2021-11-24 16:39:30 +00:00
|
|
|
).await?;
|
|
|
|
transaction.execute(
|
|
|
|
"
|
|
|
|
UPDATE post
|
2021-12-04 01:27:47 +00:00
|
|
|
SET repost_count = post.repost_count - 1
|
2021-11-24 16:39:30 +00:00
|
|
|
FROM post AS repost
|
|
|
|
WHERE
|
|
|
|
repost.repost_of_id = post.id
|
|
|
|
AND repost.author_id = $1
|
|
|
|
",
|
|
|
|
&[&profile_id],
|
2021-09-26 00:11:37 +00:00
|
|
|
).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"));
|
|
|
|
}
|
2021-09-26 00:11:37 +00:00
|
|
|
let orphaned_files = find_orphaned_files(&transaction, files).await?;
|
2021-09-29 11:43:45 +00:00
|
|
|
let orphaned_ipfs_objects = find_orphaned_ipfs_objects(&transaction, ipfs_objects).await?;
|
2021-09-26 00:11:37 +00:00
|
|
|
transaction.commit().await?;
|
2021-09-29 11:43:45 +00:00
|
|
|
Ok(DeletionQueue {
|
|
|
|
files: orphaned_files,
|
|
|
|
ipfs_objects: orphaned_ipfs_objects,
|
|
|
|
})
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 21:36:55 +00:00
|
|
|
pub async fn search_profiles(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2021-11-07 08:52:57 +00:00
|
|
|
username: &str,
|
2022-10-09 14:26:58 +00:00
|
|
|
maybe_hostname: Option<&String>,
|
2022-09-29 21:36:55 +00:00
|
|
|
limit: u16,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
2022-10-09 14:26:58 +00:00
|
|
|
let db_search_query = match maybe_hostname {
|
|
|
|
Some(hostname) => {
|
2022-01-31 00:35:10 +00:00
|
|
|
// Search for exact actor address
|
2022-10-09 14:26:58 +00:00
|
|
|
format!("{}@{}", username, hostname)
|
2021-04-09 00:22:17 +00:00
|
|
|
},
|
|
|
|
None => {
|
2022-01-31 00:35:10 +00:00
|
|
|
// Fuzzy search for username
|
2021-04-09 00:22:17 +00:00
|
|
|
format!("%{}%", username)
|
|
|
|
},
|
|
|
|
};
|
|
|
|
let rows = db_client.query(
|
|
|
|
"
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM actor_profile
|
2022-01-02 12:50:17 +00:00
|
|
|
WHERE acct ILIKE $1
|
2022-05-27 22:07:03 +00:00
|
|
|
LIMIT $2
|
2021-04-09 00:22:17 +00:00
|
|
|
",
|
2022-09-29 21:36:55 +00:00
|
|
|
&[&db_search_query, &i64::from(limit)],
|
2021-04-09 00:22:17 +00:00
|
|
|
).await?;
|
|
|
|
let profiles: Vec<DbActorProfile> = rows.iter()
|
|
|
|
.map(|row| row.try_get("actor_profile"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(profiles)
|
|
|
|
}
|
|
|
|
|
2022-11-02 13:07:18 +00:00
|
|
|
pub async fn search_profiles_by_did_only(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-11-08 21:59:45 +00:00
|
|
|
did: &Did,
|
2022-01-31 00:35:10 +00:00
|
|
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
2022-11-02 13:07:18 +00:00
|
|
|
let rows = db_client.query(
|
2022-01-31 00:35:10 +00:00
|
|
|
"
|
2022-11-02 13:07:18 +00:00
|
|
|
SELECT actor_profile
|
2022-05-25 23:36:41 +00:00
|
|
|
FROM actor_profile
|
2022-01-31 00:35:10 +00:00
|
|
|
WHERE
|
2022-05-25 23:36:41 +00:00
|
|
|
EXISTS (
|
2022-04-27 11:05:46 +00:00
|
|
|
SELECT 1
|
|
|
|
FROM jsonb_array_elements(actor_profile.identity_proofs) AS proof
|
2022-11-02 13:07:18 +00:00
|
|
|
WHERE proof ->> 'issuer' = $1
|
2022-01-31 00:35:10 +00:00
|
|
|
)
|
2022-11-02 13:07:18 +00:00
|
|
|
",
|
|
|
|
&[&did.to_string()],
|
|
|
|
).await?;
|
|
|
|
let profiles: Vec<DbActorProfile> = rows.iter()
|
|
|
|
.map(|row| row.try_get("actor_profile"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(profiles)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn search_profiles_by_did(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-11-08 21:59:45 +00:00
|
|
|
did: &Did,
|
2022-11-02 13:07:18 +00:00
|
|
|
prefer_verified: bool,
|
|
|
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
|
|
|
let verified = search_profiles_by_did_only(db_client, did).await?;
|
2022-11-08 21:59:45 +00:00
|
|
|
let maybe_currency_address = match did {
|
|
|
|
Did::Pkh(did_pkh) => {
|
|
|
|
did_pkh.currency()
|
|
|
|
.map(|currency| (currency, did_pkh.address.clone()))
|
|
|
|
},
|
2022-11-09 01:51:44 +00:00
|
|
|
_ => None,
|
2022-11-08 21:59:45 +00:00
|
|
|
};
|
|
|
|
let unverified = if let Some((currency, address)) = maybe_currency_address {
|
2022-08-04 17:51:42 +00:00
|
|
|
// If currency is Ethereum,
|
|
|
|
// search over extra fields must be case insensitive.
|
|
|
|
let value_op = match currency {
|
|
|
|
Currency::Ethereum => "ILIKE",
|
2022-08-28 19:10:49 +00:00
|
|
|
Currency::Monero => "LIKE",
|
2022-08-04 17:51:42 +00:00
|
|
|
};
|
|
|
|
// This query does not scan user_account.wallet_address because
|
2022-08-15 21:54:46 +00:00
|
|
|
// login addresses are private.
|
2022-08-04 17:51:42 +00:00
|
|
|
let statement = format!(
|
|
|
|
"
|
2022-11-02 13:07:18 +00:00
|
|
|
SELECT actor_profile
|
2022-08-04 17:51:42 +00:00
|
|
|
FROM actor_profile
|
|
|
|
WHERE
|
|
|
|
EXISTS (
|
|
|
|
SELECT 1
|
|
|
|
FROM jsonb_array_elements(actor_profile.extra_fields) AS field
|
|
|
|
WHERE
|
|
|
|
field ->> 'name' ILIKE $field_name
|
|
|
|
AND field ->> 'value' {value_op} $field_value
|
|
|
|
)
|
|
|
|
",
|
|
|
|
value_op=value_op,
|
|
|
|
);
|
|
|
|
let field_name = currency.field_name();
|
|
|
|
let query = query!(
|
|
|
|
&statement,
|
|
|
|
field_name=field_name,
|
2022-11-08 21:59:45 +00:00
|
|
|
field_value=address,
|
2022-08-04 17:51:42 +00:00
|
|
|
)?;
|
2022-11-02 13:07:18 +00:00
|
|
|
let rows = db_client.query(query.sql(), query.parameters()).await?;
|
|
|
|
let unverified = rows.iter()
|
|
|
|
.map(|row| row.try_get("actor_profile"))
|
|
|
|
.collect::<Result<Vec<DbActorProfile>, _>>()?
|
|
|
|
.into_iter()
|
|
|
|
// Exclude verified
|
|
|
|
.filter(|profile| !verified.iter().any(|item| item.id == profile.id))
|
|
|
|
.collect();
|
|
|
|
unverified
|
2022-08-04 17:51:42 +00:00
|
|
|
} else {
|
2022-11-02 13:07:18 +00:00
|
|
|
vec![]
|
2022-04-27 22:15:17 +00:00
|
|
|
};
|
|
|
|
let results = if prefer_verified && verified.len() > 0 {
|
|
|
|
verified
|
|
|
|
} else {
|
|
|
|
[verified, unverified].concat()
|
|
|
|
};
|
|
|
|
Ok(results)
|
2022-01-31 00:35:10 +00:00
|
|
|
}
|
|
|
|
|
2022-09-29 21:36:55 +00:00
|
|
|
pub async fn search_profiles_by_wallet_address(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-08-04 17:51:42 +00:00
|
|
|
currency: &Currency,
|
|
|
|
wallet_address: &str,
|
|
|
|
prefer_verified: bool,
|
|
|
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
2022-11-08 21:59:45 +00:00
|
|
|
let did_pkh = DidPkh::from_address(currency, wallet_address);
|
|
|
|
let did = Did::Pkh(did_pkh);
|
2022-09-29 21:36:55 +00:00
|
|
|
search_profiles_by_did(db_client, &did, prefer_verified).await
|
2022-08-04 17:51:42 +00:00
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn update_follower_count(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
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(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
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")?;
|
2022-09-13 12:41:15 +00:00
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_subscriber_count(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-09-13 12:41:15 +00:00
|
|
|
profile_id: &Uuid,
|
|
|
|
change: i32,
|
|
|
|
) -> Result<DbActorProfile, DatabaseError> {
|
|
|
|
let maybe_row = db_client.query_opt(
|
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET subscriber_count = subscriber_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")?;
|
2021-04-09 00:22:17 +00:00
|
|
|
Ok(profile)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_post_count(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
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)
|
|
|
|
}
|
2022-01-06 11:09:45 +00:00
|
|
|
|
2022-12-03 21:06:15 +00:00
|
|
|
// Doesn't return error if profile doesn't exist
|
|
|
|
pub async fn set_reachability_status(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-12-03 21:06:15 +00:00
|
|
|
actor_id: &str,
|
|
|
|
is_reachable: bool,
|
|
|
|
) -> Result<(), DatabaseError> {
|
|
|
|
if !is_reachable {
|
|
|
|
// Don't update profile if unreachable_since is already set
|
|
|
|
db_client.execute(
|
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET unreachable_since = CURRENT_TIMESTAMP
|
|
|
|
WHERE actor_id = $1 AND unreachable_since IS NULL
|
|
|
|
",
|
|
|
|
&[&actor_id],
|
|
|
|
).await?;
|
|
|
|
} else {
|
|
|
|
// Remove status (if set)
|
|
|
|
db_client.execute(
|
|
|
|
"
|
|
|
|
UPDATE actor_profile
|
|
|
|
SET unreachable_since = NULL
|
|
|
|
WHERE actor_id = $1
|
|
|
|
",
|
|
|
|
&[&actor_id],
|
|
|
|
).await?;
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-03-04 00:35:04 +00:00
|
|
|
pub async fn find_unreachable(
|
|
|
|
db_client: &impl DatabaseClient,
|
|
|
|
unreachable_since: &DateTime<Utc>,
|
|
|
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
|
|
|
let rows = db_client.query(
|
|
|
|
"
|
|
|
|
SELECT actor_profile
|
|
|
|
FROM actor_profile
|
|
|
|
WHERE unreachable_since < $1
|
|
|
|
ORDER BY hostname, username
|
|
|
|
",
|
|
|
|
&[&unreachable_since],
|
|
|
|
).await?;
|
|
|
|
let profiles = rows.iter()
|
|
|
|
.map(|row| row.try_get("actor_profile"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(profiles)
|
|
|
|
}
|
|
|
|
|
2022-11-17 21:40:07 +00:00
|
|
|
/// Finds all empty remote profiles
|
|
|
|
/// (without any posts, reactions, relationships)
|
|
|
|
/// updated before the specified date
|
|
|
|
pub async fn find_empty_profiles(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-11-17 21:40:07 +00:00
|
|
|
updated_before: &DateTime<Utc>,
|
|
|
|
) -> Result<Vec<Uuid>, DatabaseError> {
|
|
|
|
let rows = db_client.query(
|
|
|
|
"
|
|
|
|
SELECT actor_profile.id
|
|
|
|
FROM actor_profile
|
|
|
|
WHERE
|
|
|
|
actor_profile.hostname IS NOT NULL
|
|
|
|
AND actor_profile.updated_at < $1
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM relationship
|
|
|
|
WHERE
|
|
|
|
source_id = actor_profile.id
|
|
|
|
OR target_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM follow_request
|
|
|
|
WHERE
|
|
|
|
source_id = actor_profile.id
|
|
|
|
OR target_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM post
|
|
|
|
WHERE author_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM post_reaction
|
|
|
|
WHERE author_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM media_attachment
|
|
|
|
WHERE owner_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM mention
|
|
|
|
WHERE profile_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM notification
|
|
|
|
WHERE sender_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM invoice
|
|
|
|
WHERE sender_id = actor_profile.id
|
|
|
|
)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM subscription
|
|
|
|
WHERE sender_id = actor_profile.id
|
|
|
|
)
|
|
|
|
",
|
|
|
|
&[&updated_before],
|
|
|
|
).await?;
|
|
|
|
let ids: Vec<Uuid> = rows.iter()
|
|
|
|
.map(|row| row.try_get("id"))
|
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
Ok(ids)
|
|
|
|
}
|
|
|
|
|
2022-01-06 11:09:45 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use serial_test::serial;
|
|
|
|
use crate::database::test_utils::create_test_database;
|
2023-03-30 20:27:17 +00:00
|
|
|
use crate::emojis::{
|
|
|
|
queries::create_emoji,
|
|
|
|
types::EmojiImage,
|
|
|
|
};
|
|
|
|
use crate::profiles::{
|
|
|
|
queries::create_profile,
|
|
|
|
types::{
|
2023-03-16 20:09:13 +00:00
|
|
|
DbActor,
|
2023-03-04 22:35:49 +00:00
|
|
|
ExtraField,
|
|
|
|
IdentityProof,
|
2023-03-13 19:31:31 +00:00
|
|
|
IdentityProofType,
|
2023-03-04 22:35:49 +00:00
|
|
|
ProfileCreateData,
|
|
|
|
},
|
2023-03-30 20:27:17 +00:00
|
|
|
};
|
|
|
|
use crate::users::{
|
|
|
|
queries::create_user,
|
|
|
|
types::UserCreateData,
|
2022-04-27 11:05:46 +00:00
|
|
|
};
|
2022-01-06 11:09:45 +00:00
|
|
|
use super::*;
|
|
|
|
|
2023-03-16 20:09:13 +00:00
|
|
|
fn create_test_actor(actor_id: &str) -> DbActor {
|
|
|
|
DbActor { id: actor_id.to_string(), ..Default::default() }
|
2022-10-03 20:36:17 +00:00
|
|
|
}
|
|
|
|
|
2022-01-06 11:09:45 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
2022-10-03 20:36:17 +00:00
|
|
|
async fn test_create_profile_local() {
|
2022-01-06 11:09:45 +00:00
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
username: "test".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2023-01-21 00:23:15 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
2022-01-08 11:20:48 +00:00
|
|
|
assert_eq!(profile.username, "test");
|
2022-10-03 20:36:17 +00:00
|
|
|
assert_eq!(profile.hostname, None);
|
|
|
|
assert_eq!(profile.acct, "test");
|
2022-04-26 14:12:26 +00:00
|
|
|
assert_eq!(profile.identity_proofs.into_inner().len(), 0);
|
|
|
|
assert_eq!(profile.extra_fields.into_inner().len(), 0);
|
2022-10-03 20:36:17 +00:00
|
|
|
assert_eq!(profile.actor_id, None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_create_profile_remote() {
|
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
username: "test".to_string(),
|
2022-10-04 19:18:05 +00:00
|
|
|
hostname: Some("example.com".to_string()),
|
2022-10-03 20:36:17 +00:00
|
|
|
actor_json: Some(create_test_actor("https://example.com/users/test")),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2023-01-21 00:23:15 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
2022-10-03 20:36:17 +00:00
|
|
|
assert_eq!(profile.username, "test");
|
|
|
|
assert_eq!(profile.hostname.unwrap(), "example.com");
|
|
|
|
assert_eq!(profile.acct, "test@example.com");
|
|
|
|
assert_eq!(
|
|
|
|
profile.actor_id.unwrap(),
|
|
|
|
"https://example.com/users/test",
|
|
|
|
);
|
2022-01-06 11:09:45 +00:00
|
|
|
}
|
|
|
|
|
2023-03-04 22:35:49 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_create_profile_with_emoji() {
|
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let image = EmojiImage::default();
|
|
|
|
let emoji = create_emoji(
|
|
|
|
db_client,
|
|
|
|
"testemoji",
|
|
|
|
None,
|
|
|
|
image,
|
|
|
|
None,
|
|
|
|
&Utc::now(),
|
|
|
|
).await.unwrap();
|
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
username: "test".to_string(),
|
|
|
|
emojis: vec![emoji.id.clone()],
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
|
|
|
let profile_emojis = profile.emojis.into_inner();
|
|
|
|
assert_eq!(profile_emojis.len(), 1);
|
|
|
|
assert_eq!(profile_emojis[0].id, emoji.id);
|
|
|
|
}
|
|
|
|
|
2022-04-20 13:47:04 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_actor_id_unique() {
|
2023-01-21 00:23:15 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
2022-04-20 13:47:04 +00:00
|
|
|
let actor_id = "https://example.com/users/test";
|
|
|
|
let profile_data_1 = ProfileCreateData {
|
|
|
|
username: "test-1".to_string(),
|
2022-10-04 19:18:05 +00:00
|
|
|
hostname: Some("example.com".to_string()),
|
2022-10-03 20:36:17 +00:00
|
|
|
actor_json: Some(create_test_actor(actor_id)),
|
2022-04-20 13:47:04 +00:00
|
|
|
..Default::default()
|
|
|
|
};
|
2023-01-21 00:23:15 +00:00
|
|
|
create_profile(db_client, profile_data_1).await.unwrap();
|
2022-04-20 13:47:04 +00:00
|
|
|
let profile_data_2 = ProfileCreateData {
|
|
|
|
username: "test-2".to_string(),
|
2022-10-04 19:18:05 +00:00
|
|
|
hostname: Some("example.com".to_string()),
|
2022-10-03 20:36:17 +00:00
|
|
|
actor_json: Some(create_test_actor(actor_id)),
|
2022-04-20 13:47:04 +00:00
|
|
|
..Default::default()
|
|
|
|
};
|
2023-01-21 00:23:15 +00:00
|
|
|
let error = create_profile(db_client, profile_data_2).await.err().unwrap();
|
2022-04-20 14:05:09 +00:00
|
|
|
assert_eq!(error.to_string(), "profile already exists");
|
2022-04-20 13:47:04 +00:00
|
|
|
}
|
|
|
|
|
2022-07-08 19:54:12 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_update_profile() {
|
2023-01-21 00:23:15 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
2022-07-08 19:54:12 +00:00
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
username: "test".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2023-01-21 00:23:15 +00:00
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
2022-07-08 19:54:12 +00:00
|
|
|
let mut profile_data = ProfileUpdateData::from(&profile);
|
|
|
|
let bio = "test bio";
|
|
|
|
profile_data.bio = Some(bio.to_string());
|
|
|
|
let profile_updated = update_profile(
|
2023-01-21 00:23:15 +00:00
|
|
|
db_client,
|
2022-07-08 19:54:12 +00:00
|
|
|
&profile.id,
|
|
|
|
profile_data,
|
|
|
|
).await.unwrap();
|
|
|
|
assert_eq!(profile_updated.username, profile.username);
|
|
|
|
assert_eq!(profile_updated.bio.unwrap(), bio);
|
|
|
|
assert!(profile_updated.updated_at != profile.updated_at);
|
|
|
|
}
|
|
|
|
|
2022-01-06 11:09:45 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_delete_profile() {
|
|
|
|
let profile_data = ProfileCreateData::default();
|
2023-01-21 00:23:15 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
|
|
|
let deletion_queue = delete_profile(db_client, &profile.id).await.unwrap();
|
2022-01-06 11:09:45 +00:00
|
|
|
assert_eq!(deletion_queue.files.len(), 0);
|
|
|
|
assert_eq!(deletion_queue.ipfs_objects.len(), 0);
|
|
|
|
}
|
2022-04-12 15:09:58 +00:00
|
|
|
|
2022-04-27 18:14:15 +00:00
|
|
|
const ETHEREUM: Currency = Currency::Ethereum;
|
|
|
|
|
2022-04-12 15:09:58 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
2022-09-29 21:36:55 +00:00
|
|
|
async fn test_search_profiles_by_wallet_address_local() {
|
2022-04-12 15:09:58 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let wallet_address = "0x1234abcd";
|
|
|
|
let user_data = UserCreateData {
|
|
|
|
wallet_address: Some(wallet_address.to_string()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
2022-05-25 23:36:41 +00:00
|
|
|
let _user = create_user(db_client, user_data).await.unwrap();
|
2022-09-29 21:36:55 +00:00
|
|
|
let profiles = search_profiles_by_wallet_address(
|
2022-04-27 22:15:17 +00:00
|
|
|
db_client, ÐEREUM, wallet_address, false).await.unwrap();
|
2022-04-12 15:09:58 +00:00
|
|
|
|
2022-05-25 23:36:41 +00:00
|
|
|
// Login address must not be exposed
|
|
|
|
assert_eq!(profiles.len(), 0);
|
2022-04-12 15:09:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
2022-09-29 21:36:55 +00:00
|
|
|
async fn test_search_profiles_by_wallet_address_remote() {
|
2022-04-12 15:09:58 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let extra_field = ExtraField {
|
|
|
|
name: "$eth".to_string(),
|
|
|
|
value: "0x1234aBcD".to_string(),
|
|
|
|
value_source: None,
|
|
|
|
};
|
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
extra_fields: vec![extra_field],
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
2022-09-29 21:36:55 +00:00
|
|
|
let profiles = search_profiles_by_wallet_address(
|
2022-04-27 22:15:17 +00:00
|
|
|
db_client, ÐEREUM, "0x1234abcd", false).await.unwrap();
|
2022-04-12 15:09:58 +00:00
|
|
|
|
|
|
|
assert_eq!(profiles.len(), 1);
|
|
|
|
assert_eq!(profiles[0].id, profile.id);
|
|
|
|
}
|
2022-04-27 11:05:46 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
2022-09-29 21:36:55 +00:00
|
|
|
async fn test_search_profiles_by_wallet_address_identity_proof() {
|
2022-04-27 11:05:46 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let identity_proof = IdentityProof {
|
2022-11-08 21:59:45 +00:00
|
|
|
issuer: Did::Pkh(DidPkh::from_address(ÐEREUM, "0x1234abcd")),
|
2023-03-13 19:31:31 +00:00
|
|
|
proof_type: IdentityProofType::LegacyEip191IdentityProof,
|
2022-04-27 11:05:46 +00:00
|
|
|
value: "13590013185bdea963".to_string(),
|
|
|
|
};
|
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
identity_proofs: vec![identity_proof],
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
2022-09-29 21:36:55 +00:00
|
|
|
let profiles = search_profiles_by_wallet_address(
|
2022-04-27 22:15:17 +00:00
|
|
|
db_client, ÐEREUM, "0x1234abcd", false).await.unwrap();
|
2022-04-27 11:05:46 +00:00
|
|
|
|
|
|
|
assert_eq!(profiles.len(), 1);
|
|
|
|
assert_eq!(profiles[0].id, profile.id);
|
|
|
|
}
|
2022-11-17 21:40:07 +00:00
|
|
|
|
2022-12-03 21:06:15 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_set_reachability_status() {
|
2023-01-21 00:23:15 +00:00
|
|
|
let db_client = &mut create_test_database().await;
|
2022-12-03 21:06:15 +00:00
|
|
|
let actor_id = "https://example.com/users/test";
|
|
|
|
let profile_data = ProfileCreateData {
|
|
|
|
username: "test".to_string(),
|
|
|
|
hostname: Some("example.com".to_string()),
|
|
|
|
actor_json: Some(create_test_actor(actor_id)),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let profile = create_profile(db_client, profile_data).await.unwrap();
|
|
|
|
set_reachability_status(db_client, actor_id, false).await.unwrap();
|
|
|
|
let profile = get_profile_by_id(db_client, &profile.id).await.unwrap();
|
|
|
|
assert_eq!(profile.unreachable_since.is_some(), true);
|
|
|
|
}
|
|
|
|
|
2022-11-17 21:40:07 +00:00
|
|
|
#[tokio::test]
|
|
|
|
#[serial]
|
|
|
|
async fn test_find_empty_profiles() {
|
|
|
|
let db_client = &mut create_test_database().await;
|
|
|
|
let updated_before = Utc::now();
|
|
|
|
let profiles = find_empty_profiles(db_client, &updated_before).await.unwrap();
|
|
|
|
assert_eq!(profiles.is_empty(), true);
|
|
|
|
}
|
2022-01-06 11:09:45 +00:00
|
|
|
}
|