Make a number of changes after understanding the spec better

These include:

- enable querying activities and objects by ActivityPub id
- GET the outbox
This commit is contained in:
Jeremy Dormitzer 2018-09-18 22:55:47 -04:00
parent fdbe64c136
commit 1e808f3eca
8 changed files with 135 additions and 74 deletions

View File

@ -12,7 +12,20 @@ function get_activity( $id ) {
);
}
$activity = json_decode( $activity_json, true );
$activity['id'] = get_activity_url( $id );
return $activity;
}
function get_activity_by_activitypub_id( $activitypub_id ) {
global $wpdb;
$activity_json = $wpdb->get_var( $wpdb->prepare(
'SELECT activity FROM activitypub_activities WHERE id = %s', $activitypub_id
) );
if ( is_null( $activity_json ) ) {
return new \WP_Error(
'not_found', __( 'Activity not found', 'activitypub' ), array( 'status' => 404 )
);
}
$activity = json_decode( $activity_json, true );
return $activity;
}
@ -28,26 +41,37 @@ function strip_private_fields( $activity ) {
function persist_activity( $activity ) {
global $wpdb;
$wpdb->insert(
'activitypub_activities', array( 'activity' => wp_json_encode( $activity ) )
);
$activity["id"] = get_activity_url( $wpdb->insert_id );
if ( !array_key_exists( 'id', $activity ) ) {
return new \WP_Error(
'invalid_activity',
__( 'Activity must have an "id" field', 'activitypub' ),
array( 'status' => 400 )
);
}
$activitypub_id = $activity['id'];
$wpdb->insert( 'activitypub_activities', array(
'activitypub_id' => $activitypub_id,
'activity' => wp_json_encode( $activity )
) );
return $activity;
}
function get_activity_url( $id ) {
return get_rest_url( null, sprintf( '/activitypub/v1/activity/%d', $id ) );
}
function create_activities_table() {
global $wpdb;
$wpdb->query(
"
CREATE TABLE IF NOT EXISTS activitypub_activities (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
activitypub_id TEXT UNIQUE NOT NULL,
activity TEXT NOT NULL
);
"
);
$wpdb->query(
"
CREATE UNIQUE INDEX ACTIVITYPUB_ID_INDEX
ON activitypub_activities (activitypub_id);
"
);
}
?>

View File

@ -29,10 +29,10 @@ function handle_outbox( $actor, $activity ) {
);
}
$object = $activity['object'];
$actor_id = $activity['actor'];
$object['attributedTo'] = $actor_id;
$attributed_actor = $activity['actor'];
$object['attributedTo'] = $attributed_actor;
reconcile_receivers( $object, $activity );
scrub_object( $object );
$object = scrub_object( $object );
$object = \objects\create_object( $object );
if ( is_wp_error( $object ) ) {
return $object;
@ -68,8 +68,9 @@ function copy_field_value( $field, $from, &$to ) {
}
}
function scrub_object( &$object ) {
function scrub_object( $object ) {
unset( $object['bcc'] );
unset( $object['bto'] );
return $object;
}
?>

View File

@ -26,7 +26,7 @@ function handle_outbox( $actor, $activity ) {
array( 'status' => 400 )
);
}
$existing_object = \objects\get_object_from_url( $update_object['id'] );
$existing_object = \objects\get_object_by_actvitypub_id( $update_object['id'] );
if ( is_wp_error( $existing_object ) ) {
return $existing_object;
}

View File

@ -11,10 +11,15 @@ function get_actor( $request ) {
return \actors\get_actor_by_slug( $actor );
}
function handle_outbox( $request ) {
$actor = $request['actor'];
function post_to_outbox( $request ) {
$actor_slug = $request['actor'];
$activity = json_decode( $request->get_body(), true );
return \outbox\handle_activity( $actor, $activity );
return \outbox\handle_activity( $actor_slug, $activity );
}
function get_outbox( $request ) {
$actor_slug = $request['actor'];
return \outbox\get_outbox( $actor_slug );
}
function get_object( $request ) {
@ -30,7 +35,11 @@ function get_activity( $request ) {
function register_routes() {
register_rest_route( 'activitypub/v1', '/actor/(?P<actor>[a-zA-Z0-9-]+)/outbox', array(
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\handle_outbox',
'callback' => __NAMESPACE__ . '\post_to_outbox',
) );
register_rest_route( 'activitypub/v1', '/actor/(?P<actor>[a-zA-Z0-9-]+/outbox', array(
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\get_outbox',
) );
register_rest_route( 'activitypub/v1', '/actor/(?P<actor>[a-zA-Z0-9-]+)', array(
'methods' => 'GET',

12
inc/collections.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace collections;
function make_ordered_collection( $objects ) {
$ordered_collection = array(
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'OrderedCollection',
'totalItems' => count( $objects ),
'orderedItems' => $objects
);
}
?>

View File

@ -57,6 +57,7 @@ function forward_activity( $activity ) {
}
function persist_activity( $activity ) {
global $wpdb;
}

View File

@ -6,15 +6,22 @@ namespace objects;
function create_object( $object ) {
global $wpdb;
$res = $wpdb->insert(
'activitypub_objects', array( 'object' => wp_json_encode( $object ) )
);
if ( !array_key_exists( 'id', $object ) ) {
return new \WP_Error(
'invalid_object',
__( 'Object must have an "id" field', 'activitypub' ),
array( 'status' => 400 )
);
}
$res = $wpdb->insert( 'activitypub_objects', array(
'activitypub_id' => $object['id'],
'object' => wp_json_encode( $object )
) );
if ( !$res ) {
return new \WP_Error(
'db_error', __( 'Failed to insert object row', 'activitypub' )
);
}
$object['id'] = get_object_url( $wpdb->insert_id );
return $object;
}
@ -23,11 +30,10 @@ function update_object( $object ) {
if ( !array_key_exists( 'id', $object ) ) {
return new \WP_Error(
'invalid_object',
__( 'Object must have an "id" parameter', 'activitypub' ),
__( 'Object must have an "id" field', 'activitypub' ),
array( 'status' => 400 )
);
}
$id = get_id_from_url( $object['id'] );
$object_json = wp_json_encode( $object );
$res = $wpdb->update(
'activitypub_object',
@ -52,9 +58,20 @@ function get_object( $id ) {
'not_found', __( 'Object not found', 'activitypub' ), array( 'status' => 404 )
);
}
$object = json_decode( $object_json, true );
$object['id'] = get_object_url( $id );
return $object;
return json_decode( $object_json, true );
}
function get_object_by_activitypub_id( $activitypub_id ) {
global $wpdb;
$object_json = $wpdb->get_var( $wpdb->prepare(
'SELECT object FROM activitypub_objects WHERE activitypub_id = %s', $activitypub_id
) );
if ( is_null( $object_json ) ) {
return new \WP_Error(
'not_found', __( 'Object not found', 'activitypub' ), array( 'status' => 404 )
);
}
return json_decode( $object_json, true );
}
function delete_object( $object ) {
@ -62,51 +79,34 @@ function delete_object( $object ) {
if ( !array_key_exists( 'id', $object ) ) {
return new \WP_Error(
'invalid_object',
__( 'Object must have an "id" parameter', 'activitypub' ),
__( 'Object must have an "id" field', 'activitypub' ),
array( 'status' => 400 )
);
}
$id = get_id_from_url( $object['id'] );
$res = $wpdb->delete( 'activitypub_objects', array( 'id' => $id ), '%d' );
$activitypub_id = $object['id'];
$res = $wpdb->delete( 'activitypub_objects', array( 'activitypub_id' => $id ), '%s' );
if ( !$res ) {
return new \WP_Error( 'db_error', __( 'Error deleting object', 'activitypub' ) );
}
return $res;
}
function get_id_from_url( $url ) {
global $wpdb;
$matches = array();
$found = preg_match(
get_rest_url( null, '/activitypub/v1/object/(.+)' ), $url, $matches );
if ( $found === 0 || count( $matches ) != 2 ) {
return new \WP_Error(
'invalid_url',
sprintf( '%s %s', $url, __( 'is not a valid object url', 'activitypub' ) ),
array( 'status' => 400 )
);
}
$id = $matches[1];
return $id;
}
function get_object_from_url( $url ) {
return get_object( get_id_from_url( $url ) );
}
function get_object_url( $id ) {
return get_rest_url( null, sprintf( '/activitypub/v1/object/%d', $id ) );
}
function create_object_table() {
global $wpdb;
$wpdb->query(
"
CREATE TABLE IF NOT EXISTS activitypub_objects (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
activitypub_id TEXT UNIQUE NOT NULL,
object TEXT NOT NULL
);
"
);
$wpdb->query(
"
CREATE UNIQUE INDEX ACTIVITYPUB_ID_INDEX
ON activitypub_objects (activitypub_id);
"
);
}
?>

View File

@ -13,6 +13,7 @@ When an Activity is received (i.e. POSTed) to an Actor's outbox, the server must
namespace outbox;
require_once plugin_dir_path( __FILE__ ) . '/activities.php';
require_once plugin_dir_path( __FILE__ ) . '/actors.php';
require_once plugin_dir_path( __FILE__ ) . '/deliver.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/create.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/update.php';
@ -21,7 +22,7 @@ require_once plugin_dir_path( __FILE__ ) . '/activities/like.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/follow.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/block.php';
function handle_activity( $actor, $activity ) {
function handle_activity( $actor_slug, $activity ) {
// TODO handle authentication/authorization
if ( !array_key_exists( 'type', $activity ) ) {
return new \WP_Error(
@ -32,16 +33,16 @@ function handle_activity( $actor, $activity ) {
}
switch ( $activity['type'] ) {
case 'Create':
$activity = \activities\create\handle_outbox( $actor, $activity );
$activity = \activities\create\handle_outbox( $actor_slug, $activity );
break;
case 'Update':
$activity = \activities\update\handle_outbox( $actor, $activity );
$activity = \activities\update\handle_outbox( $actor_slug, $activity );
break;
case 'Delete':
$activity = \activities\delete\handle_outbox( $actor, $activity );
$activity = \activities\delete\handle_outbox( $actor_slug, $activity );
break;
case 'Follow':
$activity = \activities\follow\handle_outbox( $actor, $activity );
$activity = \activities\follow\handle_outbox( $actor_slug, $activity );
break;
case 'Add':
return new \WP_Error(
@ -58,10 +59,10 @@ function handle_activity( $actor, $activity ) {
);
break;
case 'Like':
$activity = \activities\like\handle_outbox( $actor, $activity );
$activity = \activities\like\handle_outbox( $actor_slug, $activity );
break;
case 'Block':
$activity = \activities\block\handle_outbox( $actor, $activity );
$activity = \activities\block\handle_outbox( $actor_slug, $activity );
break;
case 'Undo':
return new \WP_Error(
@ -75,24 +76,37 @@ function handle_activity( $actor, $activity ) {
if ( is_wp_error( $create_activity ) ) {
return $create_activity;
}
$activity = \activities\create\handle_outbox( $actor, $create_activity );
$activity = \activities\create\handle_outbox( $actor_slug, $create_activity );
break;
}
if ( is_wp_error( $activity ) ) {
return $activity;
}
$activity = deliver_activity( $activity );
return persist_activity( $actor, $activity );
return persist_activity( $actor_slug, $activity );
}
function get_outbox( $actor_id ) {
function get_outbox( $actor_slug ) {
global $wpdb;
$activities = $wpdb->get_results( $wpdb->prepare(
"
SELECT * FROM activitypub_outbox WHERE
// TODO what sort of joins should these be?
$results = $wpdb->get_results( $wpdb->prepare(
"
SELECT activitypub_activities.activity FROM activitypub_outbox
JOIN activitypub_actors
ON activitypub_actors.id = activitypub_outbox.actor_id
JOIN activitypub_activities
ON activitypub_activities.id = activitypub_outbox.activity_id
WHERE activitypub_outbox.actor_id = %d
",
$actor_id
) );
// TODO return PagedCollection if $activites is too big
return \collections\make_ordered_collection( array_map(
function ( $result) {
return json_decode( $result->activity, true);
},
$results
) );
// $wpdb->num_rows will hold the number of results, once this implements paging
}
function deliver_activity( $activity ) {
@ -101,15 +115,15 @@ function deliver_activity( $activity ) {
return $activity;
}
function persist_activity( $actor, $activity ) {
function persist_activity( $actor_slug, $activity ) {
global $wpdb;
$activity = \activities\persist_activity( $activity );
$activity_id = $wpdb->insert_id;
$wpdb->insert( 'activitypub_outbox',
array(
'actor' => $actor,
'activity_id' => $activity_id,
) );
$actor_id = \actors\get_actor_id( $actor_slug );
$wpdb->insert( 'activitypub_outbox', array(
'actor_id' => $actor_id,
'activity_id' => $activity_id,
) );
$response = new \WP_REST_Response();
$response->set_status( 201 );
$response->header( 'Location', $activity['id'] );