Create profile_emoji database table
This commit is contained in:
parent
2787efc83f
commit
70c2d2aa25
16 changed files with 123 additions and 53 deletions
5
migrations/V0045__profile_emoji.sql
Normal file
5
migrations/V0045__profile_emoji.sql
Normal 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)
|
||||
);
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))?;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
"
|
||||
|
|
Loading…
Reference in a new issue