Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevented duplicate followers from being added to the list of followers #29

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions features/accept-follows.feature
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,17 @@ Feature: We automatically accept Follow requests
Then an "Accept(F)" Activity "A" is created by "Us"
And Activity "A" is sent to "Alice"
And "Alice" is in our Followers

Rule: We can be followed multiple times by the same actor, but we only record them once

Example: An actor attempts to follow us multiple times
Given an Actor "Alice"
And a "Follow(Us)" Activity "F1" by "Alice"
And a "Follow(Us)" Activity "F2" by "Alice"
When "Alice" sends "F1" to the Inbox
And "Alice" sends "F2" to the Inbox
Then an "Accept(F1)" Activity "A1" is created by "Us"
And an "Accept(F2)" Activity "A2" is created by "Us"
And Activity "A1" is sent to "Alice"
And Activity "A2" is sent to "Alice"
And "Alice" is in our Followers once only
16 changes: 15 additions & 1 deletion features/step_definitions/stepdefs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from 'assert';
import Knex from 'knex';
import { BeforeAll, AfterAll, Before, After, Given, When, Then } from '@cucumber/cucumber';
import { v4 as uuidv4 } from 'uuid';
import { WireMock } from 'wiremock-captain';

async function createActivity(activityType, object, actor, remote = true) {
Expand All @@ -11,7 +12,7 @@ async function createActivity(activityType, object, actor, remote = true) {
'https://w3id.org/security/data-integrity/v1',
],
'type': 'Follow',
'id': 'http://wiremock:8080/follow/1',
'id': `http://wiremock:8080/follow/${uuidv4()}`,
'to': 'as:Public',
'object': object,
actor: actor,
Expand Down Expand Up @@ -318,3 +319,16 @@ Then('{string} is in our Followers', async function (actorName) {

assert(found);
});

Then('{string} is in our Followers once only', async function (actorName) {
const response = await fetch('http://activitypub-testing:8083/.ghost/activitypub/followers/index', {
headers: {
Accept: 'application/ld+json'
}
});
const followers = await response.json();
const actor = this.actors[actorName];
const found = followers.orderedItems.filter(item => item === actor.id);

assert.equal(found.length, 1);
});
24 changes: 20 additions & 4 deletions src/dispatchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,28 @@ export async function handleFollow(
if (sender === null || sender.id === null) {
return;
}
const senderJson = await sender.toJsonLd();

const currentFollowers = await ctx.data.db.get<string[]>(['followers']) ?? [];
let shouldRecordFollower = currentFollowers.includes(sender.id.href) === false;

// Add follow activity to inbox
const followJson = await follow.toJsonLd();

ctx.data.globaldb.set([follow.id.href], followJson);
ctx.data.globaldb.set([sender.id.href], senderJson);
await addToList(ctx.data.db, ['inbox'], follow.id.href);
await addToList(ctx.data.db, ['followers'], sender.id.href);
await addToList(ctx.data.db, ['followers', 'expanded'], senderJson);

// Record follower in followers list
const senderJson = await sender.toJsonLd();

if (shouldRecordFollower) {
await addToList(ctx.data.db, ['followers'], sender.id.href);
await addToList(ctx.data.db, ['followers', 'expanded'], senderJson);
}

// Store or update sender in global db
ctx.data.globaldb.set([sender.id.href], senderJson);

// Add accept activity to outbox
const acceptId = ctx.getObjectUri(Accept, { id: uuidv4() });
const accept = new Accept({
id: acceptId,
Expand All @@ -80,6 +94,8 @@ export async function handleFollow(

await ctx.data.globaldb.set([accept.id!.href], acceptJson);
await addToList(ctx.data.db, ['outbox'], accept.id!.href);

// Send accept activity to sender
await ctx.sendActivity({ handle: parsed.handle }, sender, accept);
}

Expand Down
Loading