summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/activitypub/README.md2
-rw-r--r--src/activitypub/federation/OrderedCollection.ts6
-rw-r--r--src/activitypub/federation/inbox/index.ts2
-rw-r--r--src/activitypub/federation/transforms.ts17
-rw-r--r--src/activitypub/federation/utils.ts6
-rw-r--r--src/activitypub/routes/guilds/#guild_id/followers.ts41
-rw-r--r--src/util/entities/FederationKeys.ts20
7 files changed, 79 insertions, 15 deletions
diff --git a/src/activitypub/README.md b/src/activitypub/README.md
index 836741ba..14451b39 100644
--- a/src/activitypub/README.md
+++ b/src/activitypub/README.md
@@ -97,7 +97,7 @@ Follows/is followed by it's corresponding Guild, if applicable.
 
 ## Guild Federation
 
-An automated actor. Follows and is followed by it's corresponding Channels.
+An automated actor. Follows its Channels. Is followed by guild members.
 Also contains a collection of [roles](#role-federation).
 
 ### Supported Activities
diff --git a/src/activitypub/federation/OrderedCollection.ts b/src/activitypub/federation/OrderedCollection.ts
index f3b8feea..aa462e64 100644
--- a/src/activitypub/federation/OrderedCollection.ts
+++ b/src/activitypub/federation/OrderedCollection.ts
@@ -1,7 +1,9 @@
-import { APOrderedCollection, AnyAPObject } from "activitypub-types";
+import { APOrderedCollection, CollectionCurrentField } from "activitypub-types";
 import { ACTIVITYSTREAMS_CONTEXT } from "./utils";
 
-export const makeOrderedCollection = async <T extends AnyAPObject>(opts: {
+export const makeOrderedCollection = async <
+	T extends CollectionCurrentField,
+>(opts: {
 	page: boolean;
 	min_id?: string;
 	max_id?: string;
diff --git a/src/activitypub/federation/inbox/index.ts b/src/activitypub/federation/inbox/index.ts
index 079c13de..3fbdfc4a 100644
--- a/src/activitypub/federation/inbox/index.ts
+++ b/src/activitypub/federation/inbox/index.ts
@@ -192,7 +192,7 @@ const addRemoteUserToGuild = async (
 		},
 	});
 
-	const { entity, keys } = await fetchFederatedUser(actor);
+	const { entity } = await fetchFederatedUser(actor);
 
 	await Member.addToGuild(entity.id, guild.actorId);
 
diff --git a/src/activitypub/federation/transforms.ts b/src/activitypub/federation/transforms.ts
index a11cc62b..b11005eb 100644
--- a/src/activitypub/federation/transforms.ts
+++ b/src/activitypub/federation/transforms.ts
@@ -211,13 +211,13 @@ export const transformUserToPerson = async (user: User): Promise<APPerson> => {
 	const { host, accountDomain } = Config.get().federation;
 
 	const keys = await FederationKey.findOneOrFail({
-		where: { actorId: user.id, domain: accountDomain },
+		where: { actorId: user.id },
 	});
 
 	return {
 		"@context": ACTIVITYSTREAMS_CONTEXT,
 		type: "Person",
-		id: `https://${host}/federation/users/${user.id}`,
+		id: keys.federatedId,
 
 		name: user.username,
 		preferredUsername: user.id,
@@ -232,9 +232,10 @@ export const transformUserToPerson = async (user: User): Promise<APPerson> => {
 			  ]
 			: undefined,
 
-		inbox: `https://${host}/federation/users/${user.id}/inbox`,
-		outbox: `https://${host}/federation/users/${user.id}/outbox`,
-		followers: `https://${host}/federation/users/${user.id}/followers`,
+		inbox: keys.inbox,
+		outbox: keys.outbox,
+		followers: keys.followers,
+		following: keys.following,
 		publicKey: {
 			id: `https://${host}/federation/users/${user.id}#main-key`,
 			owner: `https://${host}/federation/users/${user.id}`,
@@ -266,6 +267,8 @@ export const transformPersonToUser = async (person: APPerson) => {
 		type: ActorType.USER,
 		inbox: person.inbox.toString(),
 		outbox: person.outbox.toString(),
+		followers: person.followers?.toString(),
+		following: person.following?.toString(),
 	}).save();
 
 	return await User.create({
@@ -332,6 +335,8 @@ export const transformOrganisationToGuild = async (org: APOrganization) => {
 		type: ActorType.GUILD,
 		inbox: org.inbox.toString(),
 		outbox: org.outbox.toString(),
+		followers: org.followers?.toString(),
+		following: org.following?.toString(),
 	});
 
 	if (typeof org.attributedTo != "string")
@@ -423,6 +428,8 @@ export const transformGroupToChannel = async (
 		type: ActorType.CHANNEL,
 		inbox: group.inbox.toString(),
 		outbox: group.outbox.toString(),
+		followers: group.followers?.toString(),
+		following: group.following?.toString(),
 	});
 
 	const channel = Channel.create({
diff --git a/src/activitypub/federation/utils.ts b/src/activitypub/federation/utils.ts
index e879e863..4a56c67d 100644
--- a/src/activitypub/federation/utils.ts
+++ b/src/activitypub/federation/utils.ts
@@ -191,8 +191,10 @@ export const fetchFederatedUser = async (
 		domain: mention.domain,
 		publicKey: remoteActor.publicKey?.publicKeyPem,
 		type,
-		inbox: remoteActor.inbox,
-		outbox: remoteActor.outbox,
+		inbox: remoteActor.inbox?.toString(),
+		outbox: remoteActor.outbox?.toString(),
+		following: remoteActor.following?.toString(),
+		followers: remoteActor.followers?.toString(),
 	});
 
 	let entity: BaseClass | undefined = undefined;
diff --git a/src/activitypub/routes/guilds/#guild_id/followers.ts b/src/activitypub/routes/guilds/#guild_id/followers.ts
new file mode 100644
index 00000000..7f3b9e31
--- /dev/null
+++ b/src/activitypub/routes/guilds/#guild_id/followers.ts
@@ -0,0 +1,41 @@
+import { makeOrderedCollection } from "@spacebar/ap";
+import { route } from "@spacebar/api";
+import {
+	Channel,
+	Config,
+	Datasource,
+	FederationKey,
+	Member,
+} from "@spacebar/util";
+import { Request, Response, Router } from "express";
+const router = Router();
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+	const { guild_id } = req.params;
+	const { page, min_id, max_id } = req.query;
+
+	const { host } = Config.get().federation;
+
+	const ret = await makeOrderedCollection({
+		page: page != undefined,
+		min_id: min_id?.toString(),
+		max_id: max_id?.toString(),
+		id: `https://${host}/federation/guilds/${guild_id}/following`,
+		getTotalElements: () => Channel.count({ where: { guild_id } }),
+		getElements: async (before, after): Promise<string[]> => {
+			const members = await Datasource.getRepository(FederationKey)
+				.createQueryBuilder("key")
+				.leftJoin(Member, "member", "member.id == key.actorId")
+				.where("member.guild_id = :guild_id", { guild_id })
+				.getMany();
+
+			// TODO: actual pagination
+
+			return members.map((x) => x.federatedId);
+		},
+	});
+
+	return res.json(ret);
+});
+
+export default router;
diff --git a/src/util/entities/FederationKeys.ts b/src/util/entities/FederationKeys.ts
index 332e13e1..86e72bd2 100644
--- a/src/util/entities/FederationKeys.ts
+++ b/src/util/entities/FederationKeys.ts
@@ -34,12 +34,20 @@ export class FederationKey extends BaseClassWithoutId {
 	federatedId: string;
 
 	/** The inbox of the remote user */
-	@Column({ nullable: true, type: String })
-	inbox: string | null;
+	@Column()
+	inbox: string;
 
 	/** The outbox of the remote user */
-	@Column({ nullable: true, type: String })
-	outbox: string | null;
+	@Column()
+	outbox: string;
+
+	/** The following collection of this user */
+	@Column()
+	following: string;
+
+	/** The followers collection of this user */
+	@Column()
+	followers: string;
 
 	/** The public key of this actor. Public keys of remote actors are cached. */
 	@Column()
@@ -63,6 +71,10 @@ export class FederationKey extends BaseClassWithoutId {
 			actorId,
 			type,
 			federatedId: `https://${host}/federation/${type}/${actorId}`,
+			inbox: `https://${host}/federation/${type}/${actorId}/inbox`,
+			outbox: `https://${host}/federation/${type}/${actorId}/outbox`,
+			followers: `https://${host}/federation/${type}/${actorId}/followers`,
+			following: `https://${host}/federation/${type}/${actorId}/following`,
 			domain: accountDomain,
 			...(await generateKeyPair("rsa", {
 				modulusLength: 4096,