ActivityPub background actor validation, update and cleanup process (#4765)

* feat(ap): add validation+update+cleanup AP actor job

* fix(test): use time.Time instead of sql.NullTime

* feat(test): add integration test for AP follower cleanup/validation job
This commit is contained in:
Gabe Kangas
2026-02-02 17:34:25 -08:00
committed by GitHub
parent 1a14b266aa
commit 5a4af3a294
16 changed files with 1315 additions and 25 deletions
+13 -11
View File
@@ -18,17 +18,19 @@ type ApAcceptedActivity struct {
}
type ApFollower struct {
Iri string
Inbox string
SharedInbox sql.NullString
Name sql.NullString
Username string
Image sql.NullString
Request string
RequestObject []byte
CreatedAt sql.NullTime
ApprovedAt sql.NullTime
DisabledAt sql.NullTime
Iri string
Inbox string
SharedInbox sql.NullString
Name sql.NullString
Username string
Image sql.NullString
Request string
RequestObject []byte
CreatedAt sql.NullTime
ApprovedAt sql.NullTime
DisabledAt sql.NullTime
LastValidatedAt sql.NullTime
FirstValidationFailureAt sql.NullTime
}
type ApOutbox struct {
+17
View File
@@ -57,6 +57,23 @@ SELECT count(*) FROM ap_accepted_activities WHERE iri = $1 AND actor = $2 AND TY
-- name: UpdateFollowerByIRI :exec
UPDATE ap_followers SET inbox = $1, shared_inbox = $2, name = $3, username = $4, image = $5 WHERE iri = $6;
-- name: GetFollowersToValidate :many
SELECT iri, inbox, shared_inbox, name, username, image, first_validation_failure_at
FROM ap_followers
WHERE approved_at IS NOT NULL AND disabled_at IS NULL
ORDER BY last_validated_at ASC NULLS FIRST
LIMIT $1;
-- name: UpdateFollowerValidationSuccess :exec
UPDATE ap_followers
SET last_validated_at = $1, first_validation_failure_at = NULL
WHERE iri = $2;
-- name: UpdateFollowerValidationFailure :exec
UPDATE ap_followers
SET last_validated_at = $1, first_validation_failure_at = COALESCE(first_validation_failure_at, $1)
WHERE iri = $2;
-- name: GetUniqueDeliveryInboxes :many
SELECT COALESCE(shared_inbox, inbox) as delivery_inbox FROM ap_followers WHERE approved_at is not null GROUP BY delivery_inbox;
+97 -2
View File
@@ -306,9 +306,23 @@ const getFollowerByIRI = `-- name: GetFollowerByIRI :one
SELECT iri, inbox, shared_inbox, name, username, image, request, request_object, created_at, approved_at, disabled_at FROM ap_followers WHERE iri = $1
`
func (q *Queries) GetFollowerByIRI(ctx context.Context, iri string) (ApFollower, error) {
type GetFollowerByIRIRow struct {
Iri string
Inbox string
SharedInbox sql.NullString
Name sql.NullString
Username string
Image sql.NullString
Request string
RequestObject []byte
CreatedAt sql.NullTime
ApprovedAt sql.NullTime
DisabledAt sql.NullTime
}
func (q *Queries) GetFollowerByIRI(ctx context.Context, iri string) (GetFollowerByIRIRow, error) {
row := q.db.QueryRowContext(ctx, getFollowerByIRI, iri)
var i ApFollower
var i GetFollowerByIRIRow
err := row.Scan(
&i.Iri,
&i.Inbox,
@@ -340,6 +354,55 @@ func (q *Queries) GetFollowerCount(ctx context.Context) (int64, error) {
return count, err
}
const getFollowersToValidate = `-- name: GetFollowersToValidate :many
SELECT iri, inbox, shared_inbox, name, username, image, first_validation_failure_at
FROM ap_followers
WHERE approved_at IS NOT NULL AND disabled_at IS NULL
ORDER BY last_validated_at ASC NULLS FIRST
LIMIT $1
`
type GetFollowersToValidateRow struct {
Iri string
Inbox string
SharedInbox sql.NullString
Name sql.NullString
Username string
Image sql.NullString
FirstValidationFailureAt sql.NullTime
}
func (q *Queries) GetFollowersToValidate(ctx context.Context, limit int32) ([]GetFollowersToValidateRow, error) {
rows, err := q.db.QueryContext(ctx, getFollowersToValidate, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetFollowersToValidateRow
for rows.Next() {
var i GetFollowersToValidateRow
if err := rows.Scan(
&i.Iri,
&i.Inbox,
&i.SharedInbox,
&i.Name,
&i.Username,
&i.Image,
&i.FirstValidationFailureAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getIPAddressBans = `-- name: GetIPAddressBans :many
SELECT ip_address, notes, created_at FROM ip_bans
`
@@ -815,3 +878,35 @@ func (q *Queries) UpdateFollowerByIRI(ctx context.Context, arg UpdateFollowerByI
)
return err
}
const updateFollowerValidationFailure = `-- name: UpdateFollowerValidationFailure :exec
UPDATE ap_followers
SET last_validated_at = $1, first_validation_failure_at = COALESCE(first_validation_failure_at, $1)
WHERE iri = $2
`
type UpdateFollowerValidationFailureParams struct {
LastValidatedAt sql.NullTime
Iri string
}
func (q *Queries) UpdateFollowerValidationFailure(ctx context.Context, arg UpdateFollowerValidationFailureParams) error {
_, err := q.db.ExecContext(ctx, updateFollowerValidationFailure, arg.LastValidatedAt, arg.Iri)
return err
}
const updateFollowerValidationSuccess = `-- name: UpdateFollowerValidationSuccess :exec
UPDATE ap_followers
SET last_validated_at = $1, first_validation_failure_at = NULL
WHERE iri = $2
`
type UpdateFollowerValidationSuccessParams struct {
LastValidatedAt sql.NullTime
Iri string
}
func (q *Queries) UpdateFollowerValidationSuccess(ctx context.Context, arg UpdateFollowerValidationSuccessParams) error {
_, err := q.db.ExecContext(ctx, updateFollowerValidationSuccess, arg.LastValidatedAt, arg.Iri)
return err
}
+2
View File
@@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS ap_followers (
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"approved_at" TIMESTAMP,
"disabled_at" TIMESTAMP,
"last_validated_at" TIMESTAMP,
"first_validation_failure_at" TIMESTAMP,
PRIMARY KEY (iri));
CREATE INDEX iri_index ON ap_followers (iri);
CREATE INDEX approved_at_index ON ap_followers (approved_at);