Skip to content

ADD model related code for ActivityPub subscription

kik requested to merge gitlab-community/gitlab:activitypub-inbox-models into master

What does this MR do and why?

This MR has been split off from ADD ActivityPub subscription. See there if you want a complete description and overall look of the feature.

Those changes add everything we need on the database part to implement the subscription of an external user to an internal resource.

We're implementing base architecture for all ActivityPub features through a first used case, being the releases actor. After discussions on the blueprint, the models were migrated from using STI to using an abstract model, so we have here this abstract model (ActivityPub::Subscription) from which all other models will inherit, using their own database table - here activity_pub_releases_subscriptions.

The subscription happens in several steps, which is why there is those status and payload fields:

  1. the record is created with the requested status, we save the request JSON payload of the Follow activity (we're going to need it later) and store the subscriber_url, which is the profile URL for the external subscriber
  2. the background job retrieve the subscriber inbox URL by fetching the profile page, and stores it in the record
  3. the background job sends out an Accept activity, which must contains a copy of the original Follow activity
  4. the record status is changed to accepted.

Related to #423073, &11247

status attribute

Worth noting is that I added a denied status that won't be used in this feature. ActivityPub allows to reply to a Follow request with a Reject activity rather than an Accept one. The main purpose is to notify the user that no, we won't be pushing activities out to let them know of updates (but if the actor is public, they still can poll it to see those updates).

We don't need that for now, but that may happen in the future (to let the subscriber know we don't accept follow on that resource, or because some fields we decided are mandatory are missing, or to blacklist some users, or what have you). So I decided to add that value right now, since it is an enum. It gives us denied = 0, requested = 1, accepted = 2 so we can easily do things like WHERE status > 0 or WHERE status < 2. If we don't add denied now, we would need to rewrite all values in a possibly massive table later to allow the same thing (because we would have requested = 0 and accepted = 1).

queries and query plans

Query and query plan for creating the ActivityPub::ReleasesSubscription record:

INSERT INTO "activity_pub_releases_subscriptions" 
  ("subscriber_url", "status", "project_id", 
  "payload", "created_at", "updated_at") 
VALUES 
  ('http://127.0.0.1:3001/users/admin', 1, 7,
  '{"id":"http://localhost:3001/ef63d04e-e1a8-42c1-a119-327128d1fcca","type":"Follow","actor":"http://localhost:3001/users/admin","object":"http://127.0.0.1:3000/flightjs/Flight/-/releases","@context":"https://www.w3.org/ns/activitystreams"}', '2023-09-27 16:32:43.200625', '2023-09-27 16:32:43.200625')
RETURNING "id"

Insert on activity_pub_releases_subscriptions  (cost=0.00..0.01 rows=1 width=130)
   ->  Result  (cost=0.00..0.01 rows=1 width=130)

For ActivityPub::ReleasesSubscription#find_by_subscriber_url:

SELECT "activity_pub_releases_subscriptions".* 
FROM "activity_pub_releases_subscriptions" 
WHERE "activity_pub_releases_subscriptions"."subscriber_url" = 'http://127.0.0.1:3001/users/admin'
LIMIT 1 

 Limit  (cost=0.15..1.50 rows=1 width=130)
   ->  Index Scan using index_activity_pub_releases_subscriptions_on_subscriber_url on activity_pub_releases_subscriptions  (cost=0.15..4.20 rows=3 width=130)
         Index Cond: (subscriber_url = 'http://127.0.0.1:3001/users/admin'::text)

For updating the subscriber_inbox_url field:

UPDATE "activity_pub_releases_subscriptions" 
SET 
  "subscriber_inbox_url" = 'http://127.0.0.1:3001/users/admin/inbox',
  "updated_at" = '2023-09-27 16:27:56.222183'
WHERE "activity_pub_releases_subscriptions"."id" = 6

Update on activity_pub_releases_subscriptions  (cost=0.15..2.17 rows=0 width=0)
   ->  Index Scan using activity_pub_releases_subscriptions_pkey on activity_pub_releases_subscriptions  (cost=0.15..2.17 rows=1 width=46)
         Index Cond: (id = 6)

For subscription.accepted!:

UPDATE "activity_pub_releases_subscriptions" 
SET 
  "status" = 2,
  "updated_at" = '2023-09-27 16:24:15.007749'
WHERE "activity_pub_releases_subscriptions"."id" = 6

Update on activity_pub_releases_subscriptions  (cost=0.15..2.17 rows=0 width=0)
   ->  Index Scan using activity_pub_releases_subscriptions_pkey on activity_pub_releases_subscriptions  (cost=0.15..2.17 rows=1 width=16)
         Index Cond: (id = 6)

For subscription.destroy:

DELETE FROM "activity_pub_releases_subscriptions" 
WHERE "activity_pub_releases_subscriptions"."id" = 6

Delete on activity_pub_releases_subscriptions  (cost=0.15..2.17 rows=0 width=0)
   ->  Index Scan using activity_pub_releases_subscriptions_pkey on activity_pub_releases_subscriptions  (cost=0.15..2.17 rows=1 width=6)
         Index Cond: (id = 6)
Edited by kik

Merge request reports