Add hostname column to actor_profile table and replace acct with generated column

This commit is contained in:
silverpill 2022-10-03 20:36:17 +00:00
parent 0c00dca7d3
commit a6c525f35d
5 changed files with 79 additions and 10 deletions

View file

@ -0,0 +1,19 @@
CREATE TABLE instance (
hostname VARCHAR(100) PRIMARY KEY
);
INSERT INTO instance
SELECT DISTINCT split_part(acct, '@', 2)
FROM actor_profile
WHERE acct <> username;
ALTER TABLE actor_profile
ADD COLUMN hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT;
UPDATE actor_profile
SET hostname = split_part(acct, '@', 2)
WHERE acct <> username;
ALTER TABLE actor_profile
ADD CONSTRAINT actor_profile_hostname_check CHECK ((hostname IS NULL) = (actor_json IS NULL));
ALTER TABLE actor_profile
DROP COLUMN acct;
ALTER TABLE actor_profile
ADD COLUMN acct VARCHAR(200) UNIQUE
GENERATED ALWAYS AS (CASE WHEN hostname IS NULL THEN username ELSE username || '@' || hostname END) STORED;

View file

@ -1,8 +1,13 @@
CREATE TABLE instance (
hostname VARCHAR(100) PRIMARY KEY
);
CREATE TABLE actor_profile ( CREATE TABLE actor_profile (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
username VARCHAR(100) NOT NULL, username VARCHAR(100) NOT NULL,
hostname VARCHAR(100) REFERENCES instance (hostname) ON DELETE RESTRICT,
acct VARCHAR(200) UNIQUE GENERATED ALWAYS AS (CASE WHEN hostname IS NULL THEN username ELSE username || '@' || hostname END) STORED,
display_name VARCHAR(200), display_name VARCHAR(200),
acct VARCHAR(200) UNIQUE NOT NULL,
bio TEXT, bio TEXT,
bio_source TEXT, bio_source TEXT,
avatar_file_name VARCHAR(100), avatar_file_name VARCHAR(100),
@ -17,7 +22,8 @@ CREATE TABLE actor_profile (
actor_json JSONB, actor_json JSONB,
actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED, actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK ((hostname IS NULL) = (actor_json IS NULL))
); );
CREATE TABLE user_invite_code ( CREATE TABLE user_invite_code (

View file

@ -1,6 +1,7 @@
use tokio_postgres::GenericClient; use tokio_postgres::GenericClient;
use uuid::Uuid; use uuid::Uuid;
use crate::activitypub::actors::types::ActorAddress;
use crate::database::catch_unique_violation; use crate::database::catch_unique_violation;
use crate::database::query_macro::query; use crate::database::query_macro::query;
use crate::errors::DatabaseError; use crate::errors::DatabaseError;
@ -28,10 +29,25 @@ pub async fn create_profile(
profile_data: ProfileCreateData, profile_data: ProfileCreateData,
) -> Result<DbActorProfile, DatabaseError> { ) -> Result<DbActorProfile, DatabaseError> {
let profile_id = new_uuid(); let profile_id = new_uuid();
// TODO: replace ProfileCreateData.acct with hostname field
let hostname = if profile_data.actor_json.is_some() {
let actor_address = profile_data.acct.parse::<ActorAddress>().unwrap();
let hostname = actor_address.instance;
db_client.execute(
"
INSERT INTO instance VALUES ($1)
ON CONFLICT DO NOTHING
",
&[&hostname],
).await?;
Some(hostname)
} else {
None
};
let row = db_client.query_one( let row = db_client.query_one(
" "
INSERT INTO actor_profile ( INSERT INTO actor_profile (
id, username, display_name, acct, bio, bio_source, id, username, hostname, display_name, bio, bio_source,
avatar_file_name, banner_file_name, avatar_file_name, banner_file_name,
identity_proofs, payment_options, extra_fields, identity_proofs, payment_options, extra_fields,
actor_json actor_json
@ -42,8 +58,8 @@ pub async fn create_profile(
&[ &[
&profile_id, &profile_id,
&profile_data.username, &profile_data.username,
&hostname,
&profile_data.display_name, &profile_data.display_name,
&profile_data.acct,
&profile_data.bio, &profile_data.bio,
&profile_data.bio, &profile_data.bio,
&profile_data.avatar, &profile_data.avatar,
@ -572,18 +588,46 @@ mod tests {
use crate::models::users::types::UserCreateData; use crate::models::users::types::UserCreateData;
use super::*; use super::*;
fn create_test_actor(actor_id: &str) -> Actor {
Actor { id: actor_id.to_string(), ..Default::default() }
}
#[tokio::test] #[tokio::test]
#[serial] #[serial]
async fn test_create_profile() { async fn test_create_profile_local() {
let profile_data = ProfileCreateData { let profile_data = ProfileCreateData {
username: "test".to_string(), username: "test".to_string(),
acct: "test".to_string(),
..Default::default() ..Default::default()
}; };
let db_client = create_test_database().await; let db_client = create_test_database().await;
let profile = create_profile(&db_client, profile_data).await.unwrap(); let profile = create_profile(&db_client, profile_data).await.unwrap();
assert_eq!(profile.username, "test"); assert_eq!(profile.username, "test");
assert_eq!(profile.hostname, None);
assert_eq!(profile.acct, "test");
assert_eq!(profile.identity_proofs.into_inner().len(), 0); assert_eq!(profile.identity_proofs.into_inner().len(), 0);
assert_eq!(profile.extra_fields.into_inner().len(), 0); assert_eq!(profile.extra_fields.into_inner().len(), 0);
assert_eq!(profile.actor_id, None);
}
#[tokio::test]
#[serial]
async fn test_create_profile_remote() {
let profile_data = ProfileCreateData {
username: "test".to_string(),
acct: "test@example.com".to_string(),
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();
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",
);
} }
#[tokio::test] #[tokio::test]
@ -591,20 +635,17 @@ mod tests {
async fn test_actor_id_unique() { async fn test_actor_id_unique() {
let db_client = create_test_database().await; let db_client = create_test_database().await;
let actor_id = "https://example.com/users/test"; let actor_id = "https://example.com/users/test";
let create_actor = |actor_id: &str| {
Actor { id: actor_id.to_string(), ..Default::default() }
};
let profile_data_1 = ProfileCreateData { let profile_data_1 = ProfileCreateData {
username: "test-1".to_string(), username: "test-1".to_string(),
acct: "test-1@example.com".to_string(), acct: "test-1@example.com".to_string(),
actor_json: Some(create_actor(actor_id)), actor_json: Some(create_test_actor(actor_id)),
..Default::default() ..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 { let profile_data_2 = ProfileCreateData {
username: "test-2".to_string(), username: "test-2".to_string(),
acct: "test-2@example.com".to_string(), acct: "test-2@example.com".to_string(),
actor_json: Some(create_actor(actor_id)), actor_json: Some(create_test_actor(actor_id)),
..Default::default() ..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();

View file

@ -221,6 +221,7 @@ json_to_sql!(Actor);
pub struct DbActorProfile { pub struct DbActorProfile {
pub id: Uuid, pub id: Uuid,
pub username: String, pub username: String,
pub hostname: Option<String>,
pub acct: String, pub acct: String,
pub display_name: Option<String>, pub display_name: Option<String>,
pub bio: Option<String>, // html pub bio: Option<String>, // html
@ -296,6 +297,7 @@ impl Default for DbActorProfile {
Self { Self {
id: Uuid::new_v4(), id: Uuid::new_v4(),
username: "".to_string(), username: "".to_string(),
hostname: None,
acct: "".to_string(), acct: "".to_string(),
display_name: None, display_name: None,
bio: None, bio: None,

View file

@ -522,6 +522,7 @@ mod tests {
let source = create_user(db_client, source_data).await.unwrap(); let source = create_user(db_client, source_data).await.unwrap();
let target_data = ProfileCreateData { let target_data = ProfileCreateData {
username: "followed".to_string(), username: "followed".to_string(),
acct: "followed@example.org".to_string(),
actor_json: Some(Actor::default()), actor_json: Some(Actor::default()),
..Default::default() ..Default::default()
}; };