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 (
|
||||
id UUID PRIMARY KEY,
|
||||
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),
|
||||
acct VARCHAR(200) UNIQUE NOT NULL,
|
||||
bio TEXT,
|
||||
bio_source TEXT,
|
||||
avatar_file_name VARCHAR(100),
|
||||
|
@ -17,7 +22,8 @@ CREATE TABLE actor_profile (
|
|||
actor_json JSONB,
|
||||
actor_id VARCHAR(200) UNIQUE GENERATED ALWAYS AS (actor_json ->> 'id') STORED,
|
||||
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 (
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use tokio_postgres::GenericClient;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::activitypub::actors::types::ActorAddress;
|
||||
use crate::database::catch_unique_violation;
|
||||
use crate::database::query_macro::query;
|
||||
use crate::errors::DatabaseError;
|
||||
|
@ -28,10 +29,25 @@ pub async fn create_profile(
|
|||
profile_data: ProfileCreateData,
|
||||
) -> Result<DbActorProfile, DatabaseError> {
|
||||
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(
|
||||
"
|
||||
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,
|
||||
identity_proofs, payment_options, extra_fields,
|
||||
actor_json
|
||||
|
@ -42,8 +58,8 @@ pub async fn create_profile(
|
|||
&[
|
||||
&profile_id,
|
||||
&profile_data.username,
|
||||
&hostname,
|
||||
&profile_data.display_name,
|
||||
&profile_data.acct,
|
||||
&profile_data.bio,
|
||||
&profile_data.bio,
|
||||
&profile_data.avatar,
|
||||
|
@ -572,18 +588,46 @@ mod tests {
|
|||
use crate::models::users::types::UserCreateData;
|
||||
use super::*;
|
||||
|
||||
fn create_test_actor(actor_id: &str) -> Actor {
|
||||
Actor { id: actor_id.to_string(), ..Default::default() }
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_profile() {
|
||||
async fn test_create_profile_local() {
|
||||
let profile_data = ProfileCreateData {
|
||||
username: "test".to_string(),
|
||||
acct: "test".to_string(),
|
||||
..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, None);
|
||||
assert_eq!(profile.acct, "test");
|
||||
assert_eq!(profile.identity_proofs.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]
|
||||
|
@ -591,20 +635,17 @@ mod tests {
|
|||
async fn test_actor_id_unique() {
|
||||
let db_client = create_test_database().await;
|
||||
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 {
|
||||
username: "test-1".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()
|
||||
};
|
||||
create_profile(&db_client, profile_data_1).await.unwrap();
|
||||
let profile_data_2 = ProfileCreateData {
|
||||
username: "test-2".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()
|
||||
};
|
||||
let error = create_profile(&db_client, profile_data_2).await.err().unwrap();
|
||||
|
|
|
@ -221,6 +221,7 @@ json_to_sql!(Actor);
|
|||
pub struct DbActorProfile {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub hostname: Option<String>,
|
||||
pub acct: String,
|
||||
pub display_name: Option<String>,
|
||||
pub bio: Option<String>, // html
|
||||
|
@ -296,6 +297,7 @@ impl Default for DbActorProfile {
|
|||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
username: "".to_string(),
|
||||
hostname: None,
|
||||
acct: "".to_string(),
|
||||
display_name: None,
|
||||
bio: None,
|
||||
|
|
|
@ -522,6 +522,7 @@ mod tests {
|
|||
let source = create_user(db_client, source_data).await.unwrap();
|
||||
let target_data = ProfileCreateData {
|
||||
username: "followed".to_string(),
|
||||
acct: "followed@example.org".to_string(),
|
||||
actor_json: Some(Actor::default()),
|
||||
..Default::default()
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue