Compare commits
6 Commits
f1669d6e83
...
cce480bad7
Author | SHA1 | Date |
---|---|---|
Nat | cce480bad7 | |
Nat | 54669d2d4b | |
Nat | ba8e95fa4f | |
Nat | 9e7c18afba | |
Nat | c6f9f74be3 | |
Nat | cb803a1c70 |
|
@ -2,3 +2,7 @@ etc/keys/*
|
||||||
etc/nginx.conf
|
etc/nginx.conf
|
||||||
|
|
||||||
public/config.php
|
public/config.php
|
||||||
|
|
||||||
|
# Vim
|
||||||
|
~*
|
||||||
|
.*.sw*
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
require_once('./public/config.php');
|
|
||||||
|
|
||||||
$uuid = shell_exec('uuidgen');
|
|
||||||
|
|
||||||
// Have the user write the content to a temporary file
|
|
||||||
$temp_file_name = sys_get_temp_dir() . '/' . $uuid;
|
|
||||||
shell_exec("vim $temp_file_name");
|
|
||||||
$content = file_get_contents($temp_file_name);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
unlink($temp_file_name);
|
|
||||||
|
|
||||||
|
|
||||||
$object_id = 'https://' . HOSTAS_DOMAIN . "/api/v1/object/$uuid";
|
|
||||||
$preferred_username = readline('Author username: ');
|
|
||||||
|
|
|
@ -40,6 +40,12 @@ CREATE TABLE IF NOT EXISTS activity (
|
||||||
instrument TEXT, -- Object with which the activity was performed
|
instrument TEXT, -- Object with which the activity was performed
|
||||||
|
|
||||||
FOREIGN KEY (objectId) REFERENCES object(id),
|
FOREIGN KEY (objectId) REFERENCES object(id),
|
||||||
|
FOREIGN KEY (actor) REFERENCES object(id),
|
||||||
|
FOREIGN KEY (object) REFERENCES object(id),
|
||||||
|
FOREIGN KEY (origin) REFERENCES object(id),
|
||||||
|
FOREIGN KEY (target) REFERENCES object(id),
|
||||||
|
FOREIGN KEY (result) REFERENCES object(id),
|
||||||
|
FOREIGN KEY (instrument) REFERENCES object(id),
|
||||||
PRIMARY KEY (objectId)
|
PRIMARY KEY (objectId)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -10,3 +10,5 @@ define('HOSTAS_UNIX_USER', 'www-data');
|
||||||
define('HOSTAS_ACCESS_LIST', array(
|
define('HOSTAS_ACCESS_LIST', array(
|
||||||
'example.org' => HOSTAS_ACCESS_LEVEL->trusted
|
'example.org' => HOSTAS_ACCESS_LEVEL->trusted
|
||||||
));
|
));
|
||||||
|
|
||||||
|
define('HOSTAS_MAX_POSTS_PER_PAGE', 50);
|
||||||
|
|
|
@ -47,33 +47,57 @@ function get_actor(string $preferred_username) {
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_actor_outbox(string $preferred_username) {
|
/** /api/v1/actor/:preferred_username/outbox?before=:before_date_iso&limit=:limit
|
||||||
|
* Returns an OrderedCollectionPage of at most `limit` pages published before
|
||||||
|
* `before_date_iso`.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* before ISO timestamp string. Only posts published before this time are included
|
||||||
|
* limit Integer. Number of posts to include in the result. Default: 20. Max: HOSTAS_MAX_POSTS_PER_PAGE
|
||||||
|
*/
|
||||||
|
function get_actor_outbox_paged(string $preferred_username) {
|
||||||
header('Content-Type: application/activity+json');
|
header('Content-Type: application/activity+json');
|
||||||
|
|
||||||
|
$before = array_key_exists('before', $_GET) ? $_GET['before'] : date(DATE_ISO8601, time());
|
||||||
|
$after = array_key_exists('after', $_GET) ? $_GET['after'] : date(DATE_ISO8601, 0);
|
||||||
|
$limit = array_key_exists('limit', $_GET) ? $_GET['limit'] : 20;
|
||||||
|
|
||||||
|
// Parameters in $_GET with a + character will get replaced with whitespace,
|
||||||
|
// so we've got to change it back
|
||||||
|
$before = str_replace(" ", "+", $before);
|
||||||
|
$after = str_replace(" ", "+", $after);
|
||||||
|
|
||||||
|
// Cap the limit at the configured maximum
|
||||||
|
if ($limit > HOSTAS_MAX_POSTS_PER_PAGE) {
|
||||||
|
$limit = HOSTAS_MAX_POSTS_PER_PAGE;
|
||||||
|
}
|
||||||
|
|
||||||
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||||
$actor = get_actor_or_exit($conn, $preferred_username);
|
$actor = get_actor_or_exit($conn, $preferred_username);
|
||||||
|
|
||||||
$create_activities_result = prepare_and_execute($conn,
|
$sql_query = <<<'EOT'
|
||||||
"select object.id as object_id, object.type as object_type,
|
select object.id as object_id, object.type as object_type,
|
||||||
activity.actor as activity_actor, post.id as post_id,
|
activity.actor as activity_actor, post.id as post_id,
|
||||||
post.published as post_published, post.type as post_type,
|
post.published as post_published, post.type as post_type,
|
||||||
post.content as post_content
|
post.content as post_content, post.published as post_published
|
||||||
from object
|
from object
|
||||||
join activity on activity.objectId = object.id
|
join activity on activity.objectId = object.id
|
||||||
join object as post on activity.object = post.id
|
join object as post on activity.object = post.id
|
||||||
where activity.actor = :actor_id
|
where
|
||||||
order by object.published",
|
activity_actor = :actor_id
|
||||||
/*
|
and post_published < :before
|
||||||
"select object.id as object_id, object.type as object_type,
|
and post_published > :after
|
||||||
post.id as post_id, post.type as post.type, post.published as post_published,
|
order by object.published desc
|
||||||
post.url as post_url, post.content as post_content
|
limit :limit
|
||||||
from object
|
EOT;
|
||||||
join activity on activity.objectId = object.id
|
|
||||||
join object as post on activity.object = post.id
|
$create_activities_result = prepare_and_execute($conn, $sql_query,
|
||||||
where activity.actor = :actor_id
|
array(
|
||||||
order by object.published",
|
':actor_id' => $actor['id'],
|
||||||
*/
|
':before' => strtotime($before),
|
||||||
array(':actor_id' => $actor['id'])
|
':after' => strtotime($after),
|
||||||
|
':limit' => $limit
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$total_items = 0;
|
$total_items = 0;
|
||||||
|
@ -99,12 +123,64 @@ function get_actor_outbox(string $preferred_username) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$clean_uri = 'https://' . HOSTAS_DOMAIN . strtok($_SERVER['REQUEST_URI'], '?');
|
||||||
|
|
||||||
|
$response = array(
|
||||||
|
'@context' => HOSTAS_CONTEXT,
|
||||||
|
'id' => 'https://' . HOSTAS_DOMAIN . $_SERVER['REQUEST_URI'],
|
||||||
|
'type' => 'OrderedCollectionPage',
|
||||||
|
'partOf' => 'https://' . HOSTAS_DOMAIN . $clean_uri,
|
||||||
|
'orderedItems' => $ordered_items,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($total_items === 0) {
|
||||||
|
// There may be previous items, so we can reuse the before param. We
|
||||||
|
// need to do some pre-processing to avoid characters getting escaped
|
||||||
|
// from the URL
|
||||||
|
$latest_time = str_replace(" ", "+", $before);
|
||||||
|
$response['prev'] = $clean_uri . "?after=$latest_time";
|
||||||
|
} else {
|
||||||
|
$earliest_time = $ordered_items[array_key_last($ordered_items)]['published'];
|
||||||
|
$latest_time = $ordered_items[0]['published'];
|
||||||
|
|
||||||
|
$response['prev'] = $clean_uri . "?after=$latest_time";
|
||||||
|
$response['next'] = $clean_uri . "?before=$earliest_time";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_actor_outbox(string $preferred_username) {
|
||||||
|
header('Content-Type: application/activity+json');
|
||||||
|
|
||||||
|
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||||
|
$actor = get_actor_or_exit($conn, $preferred_username);
|
||||||
|
|
||||||
|
$sql_query = <<<'EOT'
|
||||||
|
select count(post.id) as post_count, min(post.published)
|
||||||
|
from object
|
||||||
|
join activity on activity.objectId = object.id
|
||||||
|
join object as post on activity.object = post.id
|
||||||
|
where activity.actor = :actor_id
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
$items_count_result = prepare_and_execute($conn, $sql_query,
|
||||||
|
array(':actor_id' => $actor['id'])
|
||||||
|
);
|
||||||
|
$items_count_row = $items_count_result->fetchArray(SQLITE3_ASSOC);
|
||||||
|
$total_items = $items_count_row['post_count'];
|
||||||
|
|
||||||
|
$collection_id = "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/outbox";
|
||||||
|
|
||||||
echo json_encode(array(
|
echo json_encode(array(
|
||||||
'@context' => HOSTAS_CONTEXT,
|
'@context' => HOSTAS_CONTEXT,
|
||||||
'id' => "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/outbox",
|
'id' => $collection_id,
|
||||||
'type' => 'OrderedCollection',
|
'type' => 'OrderedCollection',
|
||||||
'totalItems' => $total_items,
|
'totalItems' => $total_items,
|
||||||
'orderedItems' => $ordered_items,
|
'first' => $collection_id . '?before=' . date(DATE_ISO8601, time()),
|
||||||
|
'last' => $collection_id . '?after=' . date(DATE_ISO8601, 0),
|
||||||
));
|
));
|
||||||
|
|
||||||
die();
|
die();
|
||||||
|
@ -118,8 +194,15 @@ switch ($_SERVER['REQUEST_METHOD']) {
|
||||||
case 'GET':
|
case 'GET':
|
||||||
if (sizeof(REQUEST_PATH) === 3) {
|
if (sizeof(REQUEST_PATH) === 3) {
|
||||||
if (REQUEST_PATH[2] === 'inbox') get_actor_inbox(REQUEST_PATH[1]);
|
if (REQUEST_PATH[2] === 'inbox') get_actor_inbox(REQUEST_PATH[1]);
|
||||||
else if (REQUEST_PATH[2] === 'outbox') get_actor_outbox(REQUEST_PATH[1]);
|
else if (REQUEST_PATH[2] === 'outbox') {
|
||||||
|
if (array_key_exists('before', $_GET) || array_key_exists('after', $_GET)) {
|
||||||
|
get_actor_outbox_paged(REQUEST_PATH[1]);
|
||||||
|
} else {
|
||||||
|
get_actor_outbox(REQUEST_PATH[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_actor(REQUEST_PATH[1]);
|
get_actor(REQUEST_PATH[1]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -19,5 +19,8 @@ function sql_fetch_actor($conn, string $preferred_username) {
|
||||||
array(':object_id' => $object_id)
|
array(':object_id' => $object_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return $result->fetchArray(SQLITE3_ASSOC);
|
// fetchArray returns false when there's no array; we're rather have it be
|
||||||
|
// explicitly null
|
||||||
|
$actor = $result->fetchArray(SQLITE3_ASSOC);
|
||||||
|
return $actor !== false ? $actor : null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
|
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
|
||||||
|
|
||||||
|
// $clean_uri is the request URI with any query strings removed
|
||||||
|
$clean_uri = strtok($_SERVER['REQUEST_URI'], '?');
|
||||||
|
|
||||||
// e.g. /api/v1/test/request/ -> ['', 'api', 'v1', 'test', 'request'].
|
// e.g. /api/v1/test/request/ -> ['', 'api', 'v1', 'test', 'request'].
|
||||||
// For convenience later, we take a slice that excludes ['', 'api', 'v1'].
|
// For convenience later, we take a slice that excludes ['', 'api', 'v1'].
|
||||||
define('REQUEST_PATH', array_slice(explode('/', $_SERVER['REQUEST_URI']), offset: 3));
|
define('REQUEST_PATH', array_slice(explode('/', $clean_uri), offset: 3));
|
||||||
|
|
||||||
switch (REQUEST_PATH[0]) {
|
switch (REQUEST_PATH[0]) {
|
||||||
case 'actor': // /api/v1/actor
|
case 'actor': // /api/v1/actor
|
||||||
|
|
Loading…
Reference in New Issue