Skip to content

Commit

Permalink
ProfileToAgent links
Browse files Browse the repository at this point in the history
  • Loading branch information
guillemcordoba committed Oct 17, 2024
1 parent 3c85429 commit 4889ea1
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 11 deletions.
13 changes: 12 additions & 1 deletion crates/coordinator/src/link_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ pub fn link_agent_with_my_profile(agent_pub_key: AgentPubKey) -> ExternResult<()
))));
};

create_link(agent_pub_key, profile_hash, LinkTypes::AgentToProfile, ())?;
create_link(
agent_pub_key.clone(),
profile_hash.clone(),
LinkTypes::AgentToProfile,
(),
)?;
create_link(
profile_hash.clone(),
agent_pub_key,
LinkTypes::ProfileToAgent,
(),
)?;

Ok(())
}
Expand Down
8 changes: 7 additions & 1 deletion crates/coordinator/src/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ pub fn create_profile(profile: Profile) -> ExternResult<Record> {
LinkTag::new(profile.nickname.to_lowercase().as_bytes().to_vec()),
)?;
create_link(
agent_address,
agent_address.clone(),
action_hash.clone(),
LinkTypes::AgentToProfile,
(),
)?;
create_link(
action_hash.clone(),
agent_address,
LinkTypes::ProfileToAgent,
(),
)?;

let record = get(action_hash, GetOptions::default())?
.ok_or(wasm_error!(WasmErrorInner::Guest("Unreachable".into())))?;
Expand Down
9 changes: 9 additions & 0 deletions crates/coordinator/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ pub fn get_agent_profile(agent_pub_key: AgentPubKey) -> ExternResult<Vec<Link>>
)
}

/// Returns the links targeting the profiles for the given agent, must be one or 0
#[hdk_extern]
pub fn get_agents_for_profile(profile_hash: ActionHash) -> ExternResult<Vec<Link>> {
get_links(
GetLinksInputBuilder::try_new(profile_hash, LinkTypes::ProfileToAgent.try_into_filter()?)?
.build(),
)
}

/// Gets all the agents that have created a profile in this DHT.
#[hdk_extern]
pub fn get_all_profiles(_: ()) -> ExternResult<Vec<Link>> {
Expand Down
31 changes: 31 additions & 0 deletions crates/integrity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ mod path_to_profile;
mod prefix_path;
mod profile;
mod profile_claim;
mod profile_to_agent;
use agent_to_profile::*;
use linking_agents::*;
use path_to_profile::*;
use prefix_path::*;
use profile::*;
use profile_claim::*;
use profile_to_agent::*;

#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
Expand All @@ -39,6 +41,7 @@ pub enum LinkTypes {
PrefixPath,
PathToProfile,
AgentToProfile,
ProfileToAgent,
LinkingAgents,
}

Expand Down Expand Up @@ -163,6 +166,13 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
target_address,
tag,
),
LinkTypes::ProfileToAgent => validate_create_link_profile_to_agent(
action_hash(&op).clone(),
action,
base_address,
target_address,
tag,
),
LinkTypes::PathToProfile => {
validate_create_link_path_to_profile(action, base_address, target_address, tag)
}
Expand All @@ -188,6 +198,13 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
target_address,
tag,
),
LinkTypes::ProfileToAgent => validate_delete_link_profile_to_agent(
action,
original_action,
base_address,
target_address,
tag,
),
LinkTypes::PathToProfile => validate_delete_link_path_to_profile(
action,
original_action,
Expand Down Expand Up @@ -311,6 +328,13 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
target_address,
tag,
),
LinkTypes::ProfileToAgent => validate_create_link_profile_to_agent(
action_hash(&op).clone(),
action,
base_address,
target_address,
tag,
),
LinkTypes::PathToProfile => {
validate_create_link_path_to_profile(action, base_address, target_address, tag)
}
Expand Down Expand Up @@ -350,6 +374,13 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
create_link.target_address,
create_link.tag,
),
LinkTypes::ProfileToAgent => validate_delete_link_profile_to_agent(
action,
create_link.clone(),
base_address,
create_link.target_address,
create_link.tag,
),
LinkTypes::PathToProfile => validate_delete_link_path_to_profile(
action,
create_link.clone(),
Expand Down
54 changes: 54 additions & 0 deletions crates/integrity/src/profile_to_agent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use hdi::prelude::*;
use profiles_types::validate_profile_for_agent_with_zome_index;

pub fn validate_create_link_profile_to_agent(
action_hash: ActionHash,
action: CreateLink,
base_address: AnyLinkableHash,
target_address: AnyLinkableHash,
_tag: LinkTag,
) -> ExternResult<ValidateCallbackResult> {
let _agent = target_address
.into_agent_pub_key()
.ok_or(wasm_error!(WasmErrorInner::Guest(
"No AgentPubKey associated with the base of an AgentToProfile link".to_string()
)))?;
// Check the entry type for the given action hash
let profile_hash =
base_address
.into_action_hash()
.ok_or(wasm_error!(WasmErrorInner::Guest(
"No action hash associated with link".to_string()
)))?;
let record = must_get_valid_record(profile_hash.clone())?;
let _profile: crate::Profile = record
.entry()
.to_app_option()
.map_err(|e| wasm_error!(e))?
.ok_or(wasm_error!(WasmErrorInner::Guest(
"Linked action must reference an entry".to_string()
)))?;

let result = validate_profile_for_agent_with_zome_index(
action.author.clone(),
action_hash,
profile_hash,
zome_info()?.id,
)?;
let ValidateCallbackResult::Valid = result else {
return Ok(result);
};

Ok(ValidateCallbackResult::Valid)
}
pub fn validate_delete_link_profile_to_agent(
_action: DeleteLink,
_original_action: CreateLink,
_base: AnyLinkableHash,
_target: AnyLinkableHash,
_tag: LinkTag,
) -> ExternResult<ValidateCallbackResult> {
Ok(ValidateCallbackResult::Invalid(String::from(
"AgentToProfile links cannot be deleted",
)))
}
51 changes: 49 additions & 2 deletions tests/src/link-agent.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { toPromise, watch } from '@holochain-open-dev/signals';
import { EntryRecord } from '@holochain-open-dev/utils';
import { encodeHashToBase64 } from '@holochain/client';
import { dhtSync, pause, runScenario } from '@holochain/tryorama';
import { assert, test } from 'vitest';

Expand Down Expand Up @@ -31,13 +32,24 @@ test('create Profile and link agent', async () => {
assert.equal(agentsWithProfile.size, 1);

const aliceProfileStatus = await toPromise(alice.store.myProfile);
let agentsForProfile = await toPromise(
alice.store.agentsForProfile.get(profile.actionHash),
);
assert.equal(agentsForProfile.length, 1);
assert.equal(
encodeHashToBase64(agentsForProfile[0]),
encodeHashToBase64(alice.player.agentPubKey),
);

await alice.store.client.linkAgentWithMyProfile(bob.player.agentPubKey);

agentsWithProfile = await toPromise(alice.store.allProfiles);
assert.equal(agentsWithProfile.size, 1);

await pause(30_000); // Difference in time between the create the processing of the signal
await waitUntil(async () => {
const bobProfileStatus = await toPromise(bob.store.myProfile);
return bobProfileStatus !== undefined;
}, 30000); // Difference in time between the create the processing of the signal

const bobProfileStatus = await toPromise(bob.store.myProfile);

Expand All @@ -49,14 +61,28 @@ test('create Profile and link agent', async () => {

assert.deepEqual(aliceLatestProfile, bobLatestProfile);

agentsForProfile = await toPromise(
alice.store.agentsForProfile.get(profile.actionHash),
);
assert.equal(agentsForProfile.length, 2);
assert.ok(
agentsForProfile.find(
a =>
encodeHashToBase64(a) === encodeHashToBase64(bob.player.agentPubKey),
),
);

/** Bob's device now links carol's **/

await bob.store.client.linkAgentWithMyProfile(carol.player.agentPubKey);

agentsWithProfile = await toPromise(alice.store.allProfiles);
assert.equal(agentsWithProfile.size, 1);

await pause(30_000); // Difference in time between the create the processing of the signal
await waitUntil(async () => {
const carolProfileStatus = await toPromise(carol.store.myProfile);
return carolProfileStatus !== undefined;
}, 30000); // Difference in time between the create the processing of the signal

const carolProfileStatus = await toPromise(carol.store.myProfile);

Expand All @@ -65,5 +91,26 @@ test('create Profile and link agent', async () => {
);

assert.deepEqual(carolLatestProfile, bobLatestProfile);

agentsForProfile = await toPromise(
alice.store.agentsForProfile.get(profile.actionHash),
);
assert.equal(agentsForProfile.length, 3);
assert.ok(
agentsForProfile.find(
a =>
encodeHashToBase64(a) ===
encodeHashToBase64(carol.player.agentPubKey),
),
);
});
});

async function waitUntil(condition: () => Promise<boolean>, timeout: number) {
const start = Date.now();
const isDone = await condition();
if (isDone) return;
if (timeout <= 0) throw new Error('timeout');
await pause(1000);
return waitUntil(condition, timeout - (Date.now() - start));
}
12 changes: 11 additions & 1 deletion tests/src/profile.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { toPromise, watch } from '@holochain-open-dev/signals';
import { EntryRecord } from '@holochain-open-dev/utils';
import { EntryRecord, retype } from '@holochain-open-dev/utils';
import { encodeHashToBase64 } from '@holochain/client';
import { dhtSync, pause, runScenario } from '@holochain/tryorama';
import { assert, test } from 'vitest';

Expand Down Expand Up @@ -32,5 +33,14 @@ test('create Profile', async () => {

const aliceProfile = await toPromise(alice.store.myProfile);
assert.ok(aliceProfile);

const agentsForProfile = await toPromise(
alice.store.agentsForProfile.get(profile.actionHash),
);
assert.equal(agentsForProfile.length, 1);
assert.equal(
encodeHashToBase64(agentsForProfile[0]),
encodeHashToBase64(alice.player.agentPubKey),
);
});
});
1 change: 0 additions & 1 deletion tests/src/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ test('create Profile and search', async () => {
assert.equal(agentsWithProfile.size, 0);
watch(alice.store.allProfiles, () => {}); // store keepalive
let myProfile = await toPromise(alice.store.myProfile);
watch(alice.store.myProfile, () => {}); // store keepalive
assert.notOk(myProfile);

// Alice creates a Post
Expand Down
12 changes: 11 additions & 1 deletion ui/src/profiles-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export class ProfilesClient extends ZomeClient<ProfilesSignal> {
return this.callZome('get_agent_profile', agentPubKey);
}

/**
* Get the agents links for the given profile
*
* @param profileHash the profile to get all the agents for
* @returns the links pointing to all the agents for the given profile
*/
async getAgentsForProfile(profileHash: ActionHash): Promise<Array<Link>> {
return this.callZome('get_agents_for_profile', profileHash);
}

/**
* Get the latest version of profile, if they have created it
*
Expand Down Expand Up @@ -132,7 +142,7 @@ export class ProfilesClient extends ZomeClient<ProfilesSignal> {
null,
);

return records.map(r => new EntryRecord(r));
return records ? records.map(r => new EntryRecord(r)) : [];
}

/**
Expand Down
26 changes: 22 additions & 4 deletions ui/src/profiles-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
queryLiveEntriesSignal,
toPromise,
} from '@holochain-open-dev/signals';
import { EntryRecord, LazyHoloHashMap, slice } from '@holochain-open-dev/utils';
import {
EntryRecord,
HashType,
LazyHoloHashMap,
retype,
slice,
} from '@holochain-open-dev/utils';
import { ActionHash, AgentPubKey, encodeHashToBase64 } from '@holochain/client';

import { ProfilesConfig, defaultConfig } from './config.js';
Expand Down Expand Up @@ -95,6 +101,9 @@ export class ProfilesStore {
1000,
);

/**
* Fetches the profile for the given agent
*/
agentProfile = new LazyHoloHashMap(
(agent: AgentPubKey) =>
new AsyncComputed(() => {
Expand Down Expand Up @@ -141,9 +150,18 @@ export class ProfilesStore {
}),
);

/**
* Fetches the profile for the given agent
*/
agentsForProfile = new LazyHoloHashMap((profileHash: ActionHash) =>
mapCompleted(
liveLinksSignal(
this.client,
profileHash,
() => this.client.getAgentsForProfile(profileHash),
'ProfileToAgent',
),
links => links.map(l => retype(l.target, HashType.AGENT)),
),
);

profiles = new LazyHoloHashMap(
(profileHash: ActionHash) => ({
profileHash,
Expand Down

0 comments on commit 4889ea1

Please sign in to comment.