Add hostname column to actor_profile table and replace acct with generated column
This commit is contained in:
parent
0c00dca7d3
commit
a6c525f35d
5 changed files with 79 additions and 10 deletions
19
migrations/V0034__actor_profile__add_hostname.sql
Normal file
19
migrations/V0034__actor_profile__add_hostname.sql
Normal 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;
|
|
@ -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 (
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue