Create profile_emoji database table

This commit is contained in:
silverpill 2023-01-21 00:23:15 +00:00
parent 2787efc83f
commit 70c2d2aa25
16 changed files with 123 additions and 53 deletions

View file

@ -0,0 +1,5 @@
CREATE TABLE profile_emoji (
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (profile_id, emoji_id)
);

View file

@ -178,6 +178,12 @@ CREATE TABLE post_emoji (
PRIMARY KEY (post_id, emoji_id)
);
CREATE TABLE profile_emoji (
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
emoji_id UUID NOT NULL REFERENCES emoji (id) ON DELETE CASCADE,
PRIMARY KEY (profile_id, emoji_id)
);
CREATE TABLE notification (
id SERIAL PRIMARY KEY,
sender_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,

View file

@ -210,7 +210,7 @@ impl RefetchActor {
pub async fn execute(
&self,
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
) -> Result<(), Error> {
let profile = get_profile_by_remote_actor_id(
db_client,

View file

@ -1,5 +1,7 @@
use std::path::Path;
use uuid::Uuid;
use mitra_config::Instance;
use crate::activitypub::{
@ -86,7 +88,7 @@ async fn parse_tags(
instance: &Instance,
media_dir: &Path,
actor: &Actor,
) -> Result<(), HandlerError> {
) -> Result<Vec<Uuid>, HandlerError> {
let mut emojis = vec![];
for tag_value in actor.tag.clone() {
let tag_type = tag_value["type"].as_str().unwrap_or(HASHTAG);
@ -112,11 +114,11 @@ async fn parse_tags(
log::warn!("skipping actor tag of type {}", tag_type);
};
};
Ok(())
Ok(emojis)
}
pub async fn create_remote_profile(
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
instance: &Instance,
media_dir: &Path,
actor: Actor,
@ -134,7 +136,7 @@ pub async fn create_remote_profile(
).await;
let (identity_proofs, payment_options, extra_fields) =
actor.parse_attachments();
parse_tags(
let emojis = parse_tags(
db_client,
instance,
media_dir,
@ -150,6 +152,7 @@ pub async fn create_remote_profile(
identity_proofs,
payment_options,
extra_fields,
emojis,
actor_json: Some(actor),
};
profile_data.clean()?;
@ -159,7 +162,7 @@ pub async fn create_remote_profile(
/// Updates remote actor's profile
pub async fn update_remote_profile(
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
instance: &Instance,
media_dir: &Path,
profile: DbActorProfile,
@ -189,7 +192,7 @@ pub async fn update_remote_profile(
).await;
let (identity_proofs, payment_options, extra_fields) =
actor.parse_attachments();
parse_tags(
let emojis = parse_tags(
db_client,
instance,
media_dir,
@ -204,6 +207,7 @@ pub async fn update_remote_profile(
identity_proofs,
payment_options,
extra_fields,
emojis,
actor_json: Some(actor),
};
profile_data.clean()?;

View file

@ -73,7 +73,7 @@ fn key_id_to_actor_id(key_id: &str) -> Result<String, AuthenticationError> {
async fn get_signer(
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
signer_id: &str,
no_fetch: bool,
) -> Result<DbActorProfile, AuthenticationError> {
@ -100,7 +100,7 @@ async fn get_signer(
/// Verifies HTTP signature and returns signer
pub async fn verify_signed_request(
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
request: &HttpRequest,
no_fetch: bool,
) -> Result<DbActorProfile, AuthenticationError> {
@ -131,7 +131,7 @@ pub async fn verify_signed_request(
/// Verifies JSON signature and returns signer
pub async fn verify_signed_activity(
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
activity: &Value,
no_fetch: bool,
) -> Result<DbActorProfile, AuthenticationError> {

View file

@ -31,7 +31,7 @@ use super::fetchers::{
};
pub async fn get_or_import_profile_by_actor_id(
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
instance: &Instance,
media_dir: &Path,
actor_id: &str,
@ -107,7 +107,7 @@ pub async fn get_or_import_profile_by_actor_id(
/// Fetches actor profile and saves it into database
pub async fn import_profile_by_actor_address(
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
instance: &Instance,
media_dir: &Path,
actor_address: &ActorAddress,
@ -138,7 +138,7 @@ pub async fn import_profile_by_actor_address(
// Works with local profiles
pub async fn get_or_import_profile_by_actor_address(
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
instance: &Instance,
media_dir: &Path,
actor_address: &ActorAddress,

View file

@ -318,7 +318,7 @@ pub async fn handle_emoji(
pub async fn get_object_tags(
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
object: &Object,
redirects: &HashMap<String, String>,
) -> Result<(Vec<Uuid>, Vec<String>, Vec<Uuid>, Vec<Uuid>), HandlerError> {

View file

@ -95,7 +95,7 @@ struct UpdatePerson {
async fn handle_update_person(
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
activity: Value,
) -> HandlerResult {
let activity: UpdatePerson = serde_json::from_value(activity)

View file

@ -358,6 +358,7 @@ impl AccountUpdateData {
identity_proofs,
payment_options,
extra_fields,
emojis: vec![],
actor_json: None, // always None for local profiles
};
Ok(profile_data)

View file

@ -236,7 +236,7 @@ async fn update_credentials(
db_pool: web::Data<DbPool>,
account_data: web::Json<AccountUpdateData>,
) -> Result<HttpResponse, MastodonError> {
let db_client = &**get_database_client(&db_pool).await?;
let db_client = &mut **get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?;
let mut profile_data = account_data.into_inner()
.into_profile_data(
@ -384,7 +384,7 @@ async fn create_identity_proof(
db_pool: web::Data<DbPool>,
proof_data: web::Json<IdentityProofData>,
) -> Result<HttpResponse, MastodonError> {
let db_client = &**get_database_client(&db_pool).await?;
let db_client = &mut **get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?;
let did = proof_data.did.parse::<Did>()
.map_err(|_| ValidationError("invalid DID"))?;

View file

@ -99,7 +99,7 @@ fn parse_search_query(search_query: &str) -> SearchQuery {
async fn search_profiles_or_import(
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
username: String,
mut maybe_hostname: Option<String>,
limit: u16,
@ -183,7 +183,7 @@ async fn find_post_by_url(
async fn find_profile_by_url(
config: &Config,
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
url: &str,
) -> Result<Option<DbActorProfile>, DatabaseError> {
let profile = match parse_local_actor_id(

View file

@ -107,7 +107,7 @@ pub async fn register_subscription_option(
maybe_blockchain: web::Data<Option<ContractSet>>,
subscription_option: web::Json<SubscriptionOption>,
) -> Result<HttpResponse, MastodonError> {
let db_client = &**get_database_client(&db_pool).await?;
let db_client = &mut **get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?;
if !current_user.role.has_permission(Permission::ManageSubscriptionOptions) {
return Err(MastodonError::PermissionError);

View file

@ -106,7 +106,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_create_attachment() {
let db_client = &create_test_database().await;
let db_client = &mut create_test_database().await;
let profile_data = ProfileCreateData {
username: "test".to_string(),
..Default::default()

View file

@ -19,6 +19,7 @@ use crate::models::{
find_orphaned_ipfs_objects,
DeletionQueue,
},
emojis::types::DbEmoji,
instances::queries::create_instance,
relationships::types::RelationshipType,
};
@ -31,16 +32,42 @@ use super::types::{
ProfileUpdateData,
};
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)
}
/// Create new profile using given Client or Transaction.
pub async fn create_profile(
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
profile_data: ProfileCreateData,
) -> Result<DbActorProfile, DatabaseError> {
let transaction = db_client.transaction().await?;
let profile_id = generate_ulid();
if let Some(ref hostname) = profile_data.hostname {
create_instance(db_client, hostname).await?;
create_instance(&transaction, hostname).await?;
};
let row = db_client.query_one(
let row = transaction.query_one(
"
INSERT INTO actor_profile (
id, username, hostname, display_name, bio, bio_source,
@ -67,15 +94,25 @@ pub async fn create_profile(
],
).await.map_err(catch_unique_violation("profile"))?;
let profile = row.try_get("actor_profile")?;
// Create related objects
create_profile_emojis(
&transaction,
&profile_id,
profile_data.emojis,
).await?;
transaction.commit().await?;
Ok(profile)
}
pub async fn update_profile(
db_client: &impl DatabaseClient,
db_client: &mut impl DatabaseClient,
profile_id: &Uuid,
data: ProfileUpdateData,
profile_data: ProfileUpdateData,
) -> Result<DbActorProfile, DatabaseError> {
let maybe_row = db_client.query_opt(
let transaction = db_client.transaction().await?;
let maybe_row = transaction.query_opt(
"
UPDATE actor_profile
SET
@ -93,22 +130,35 @@ pub async fn update_profile(
RETURNING actor_profile
",
&[
&data.display_name,
&data.bio,
&data.bio_source,
&data.avatar,
&data.banner,
&IdentityProofs(data.identity_proofs),
&PaymentOptions(data.payment_options),
&ExtraFields(data.extra_fields),
&data.actor_json,
&profile_data.display_name,
&profile_data.bio,
&profile_data.bio_source,
&profile_data.avatar,
&profile_data.banner,
&IdentityProofs(profile_data.identity_proofs),
&PaymentOptions(profile_data.payment_options),
&ExtraFields(profile_data.extra_fields),
&profile_data.actor_json,
&profile_id,
],
).await?;
let profile = match maybe_row {
let profile: DbActorProfile = match maybe_row {
Some(row) => row.try_get("actor_profile")?,
None => return Err(DatabaseError::NotFound("profile")),
};
// Delete and re-create related objects
transaction.execute(
"DELETE FROM profile_emoji WHERE profile_id = $1",
&[&profile.id],
).await?;
create_profile_emojis(
&transaction,
&profile.id,
profile_data.emojis,
).await?;
transaction.commit().await?;
Ok(profile)
}
@ -706,8 +756,8 @@ mod tests {
username: "test".to_string(),
..Default::default()
};
let db_client = create_test_database().await;
let profile = create_profile(&db_client, profile_data).await.unwrap();
let db_client = &mut create_test_database().await;
let profile = create_profile(db_client, profile_data).await.unwrap();
assert_eq!(profile.username, "test");
assert_eq!(profile.hostname, None);
assert_eq!(profile.acct, "test");
@ -725,8 +775,8 @@ mod tests {
actor_json: Some(create_test_actor("https://example.com/users/test")),
..Default::default()
};
let db_client = create_test_database().await;
let profile = create_profile(&db_client, profile_data).await.unwrap();
let db_client = &mut create_test_database().await;
let profile = create_profile(db_client, profile_data).await.unwrap();
assert_eq!(profile.username, "test");
assert_eq!(profile.hostname.unwrap(), "example.com");
assert_eq!(profile.acct, "test@example.com");
@ -739,7 +789,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_actor_id_unique() {
let db_client = create_test_database().await;
let db_client = &mut create_test_database().await;
let actor_id = "https://example.com/users/test";
let profile_data_1 = ProfileCreateData {
username: "test-1".to_string(),
@ -747,31 +797,31 @@ mod tests {
actor_json: Some(create_test_actor(actor_id)),
..Default::default()
};
create_profile(&db_client, profile_data_1).await.unwrap();
create_profile(db_client, profile_data_1).await.unwrap();
let profile_data_2 = ProfileCreateData {
username: "test-2".to_string(),
hostname: Some("example.com".to_string()),
actor_json: Some(create_test_actor(actor_id)),
..Default::default()
};
let error = create_profile(&db_client, profile_data_2).await.err().unwrap();
let error = create_profile(db_client, profile_data_2).await.err().unwrap();
assert_eq!(error.to_string(), "profile already exists");
}
#[tokio::test]
#[serial]
async fn test_update_profile() {
let db_client = create_test_database().await;
let db_client = &mut create_test_database().await;
let profile_data = ProfileCreateData {
username: "test".to_string(),
..Default::default()
};
let profile = create_profile(&db_client, profile_data).await.unwrap();
let profile = create_profile(db_client, profile_data).await.unwrap();
let mut profile_data = ProfileUpdateData::from(&profile);
let bio = "test bio";
profile_data.bio = Some(bio.to_string());
let profile_updated = update_profile(
&db_client,
db_client,
&profile.id,
profile_data,
).await.unwrap();
@ -784,9 +834,9 @@ mod tests {
#[serial]
async fn test_delete_profile() {
let profile_data = ProfileCreateData::default();
let mut db_client = create_test_database().await;
let profile = create_profile(&db_client, profile_data).await.unwrap();
let deletion_queue = delete_profile(&mut db_client, &profile.id).await.unwrap();
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();
assert_eq!(deletion_queue.files.len(), 0);
assert_eq!(deletion_queue.ipfs_objects.len(), 0);
}
@ -855,7 +905,7 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_set_reachability_status() {
let db_client = &create_test_database().await;
let db_client = &mut create_test_database().await;
let actor_id = "https://example.com/users/test";
let profile_data = ProfileCreateData {
username: "test".to_string(),

View file

@ -439,6 +439,7 @@ pub struct ProfileCreateData {
pub identity_proofs: Vec<IdentityProof>,
pub payment_options: Vec<PaymentOption>,
pub extra_fields: Vec<ExtraField>,
pub emojis: Vec<Uuid>,
pub actor_json: Option<Actor>,
}
@ -470,6 +471,7 @@ pub struct ProfileUpdateData {
pub identity_proofs: Vec<IdentityProof>,
pub payment_options: Vec<PaymentOption>,
pub extra_fields: Vec<ExtraField>,
pub emojis: Vec<Uuid>,
pub actor_json: Option<Actor>,
}
@ -517,6 +519,7 @@ impl From<&DbActorProfile> for ProfileUpdateData {
identity_proofs: profile.identity_proofs.into_inner(),
payment_options: profile.payment_options.into_inner(),
extra_fields: profile.extra_fields.into_inner(),
emojis: vec![],
actor_json: profile.actor_json,
}
}

View file

@ -71,7 +71,7 @@ pub async fn create_user(
db_client: &mut impl DatabaseClient,
user_data: UserCreateData,
) -> Result<User, DatabaseError> {
let transaction = db_client.transaction().await?;
let mut transaction = db_client.transaction().await?;
// Prevent changes to actor_profile table
transaction.execute(
"LOCK TABLE actor_profile IN EXCLUSIVE MODE",
@ -114,9 +114,10 @@ pub async fn create_user(
identity_proofs: vec![],
payment_options: vec![],
extra_fields: vec![],
emojis: vec![],
actor_json: None,
};
let profile = create_profile(&transaction, profile_data).await?;
let profile = create_profile(&mut transaction, profile_data).await?;
// Create user
let row = transaction.query_one(
"