init
This commit is contained in:
commit
f1669d6e83
|
@ -0,0 +1,4 @@
|
|||
etc/keys/*
|
||||
etc/nginx.conf
|
||||
|
||||
public/config.php
|
|
@ -0,0 +1,30 @@
|
|||
# hostas2
|
||||
|
||||
A backend for your ActivityPub-enabled website
|
||||
|
||||
## Setup
|
||||
|
||||
For Nginx:
|
||||
|
||||
```bash
|
||||
export HOSTAS_ROOT=/path/to/root
|
||||
|
||||
# Create the database
|
||||
sudo php init.php
|
||||
|
||||
# Link the necessary components around the file system
|
||||
ln -s $HOSTAS_ROOT/etc /etc/hostas
|
||||
ln -s $HOSTAS_ROOT/etc/nginx.conf /etc/nginx/sites-enabled/example.net
|
||||
ln -s $HOSTAS_ROOT/public /var/www/example.net
|
||||
|
||||
# Restart Nginx
|
||||
sudo service nginx restart
|
||||
|
||||
# Generate the signing keys for activities
|
||||
openssl genrsa -out $HOSTAS_ROOT/etc/keys/private.pem 2048
|
||||
openssl rsa -in $HOSTAS_ROOT/etc/keys/private.pem -outform PEM -pubout -out $HOSTAS_ROOT/etc/keys/public.pem
|
||||
|
||||
# Create and set up the config
|
||||
cp $HOSTAS_ROOT/etc/config.example.php $HOSTAS_ROOT/public/config.php
|
||||
vim $HOSTAS_ROOT/public/config.php
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
<?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: ');
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
require_once('./public/config.php');
|
||||
|
||||
echo 'Creating new actor. Please fill out the following prompts:' . PHP_EOL;
|
||||
|
||||
$name = readline('Name: ');
|
||||
$preferred_username = readline('Username: ');
|
||||
$summary = readline('Summary: ');
|
||||
|
||||
// We use this link as the user's ID because in ActivityStreams, all IDs
|
||||
// must be dereferenceable
|
||||
$id = "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username";
|
||||
|
||||
$url = readline('Canonical URL (hit enter to generate one): ');
|
||||
if ($url === '') {
|
||||
$url = $id;
|
||||
echo 'Canonical URL: ' . $url . PHP_EOL;
|
||||
}
|
||||
|
||||
$icon_url = readline('Icon URL (hit enter for none): ');
|
||||
$icon_url = $icon_url === '' ? null : $icon_url;
|
||||
|
||||
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||
|
||||
$object_creation_stmt = $conn->prepare("
|
||||
insert into object(id, type, name, summary, url, icon)
|
||||
values (:id, 'Person', :name, :summary, :url, :icon);
|
||||
");
|
||||
|
||||
$person_creation_stmt = $conn->prepare("
|
||||
insert into actor(objectId, preferredUsername)
|
||||
values (:objectId, :preferredUsername);
|
||||
");
|
||||
|
||||
$object_creation_stmt->bindValue(':id', $id);
|
||||
$object_creation_stmt->bindValue(':name', $name);
|
||||
$object_creation_stmt->bindValue(':summary', $summary);
|
||||
$object_creation_stmt->bindValue(':url', $url);
|
||||
$object_creation_stmt->bindValue(':icon', $icon_url);
|
||||
|
||||
$object_creation_result = $object_creation_stmt->execute();
|
||||
|
||||
if (!$object_creation_result) {
|
||||
echo 'Error: failed to insert object.' . PHP_EOL;
|
||||
die();
|
||||
}
|
||||
|
||||
$person_creation_stmt->bindValue(':objectId', $id);
|
||||
$person_creation_stmt->bindValue(':preferredUsername', $preferred_username);
|
||||
|
||||
$person_creation_result = $person_creation_stmt->execute();
|
||||
|
||||
if (!$person_creation_result) {
|
||||
echo 'Error: failed to insert person.' . PHP_EOL;
|
||||
die();
|
||||
}
|
||||
|
||||
echo "Actor with ID $id created successfully!" . PHP_EOL;
|
||||
|
||||
$conn->close();
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
require_once('./public/config.php');
|
||||
|
||||
$preferred_username = readline('Username: ');
|
||||
|
||||
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||
|
||||
$person_query = $conn->prepare("
|
||||
select * from actor where preferredUsername = :preferredUsername
|
||||
");
|
||||
$person_query->bindValue(':preferredUsername', $preferred_username);
|
||||
$person = ($person_query->execute())->fetchArray();
|
||||
|
||||
if ($person === null) {
|
||||
echo "No user by the name $preferred_username was found. Aborting" . PHP_EOL;
|
||||
die();
|
||||
}
|
||||
|
||||
$objectId = $person['objectId'];
|
||||
|
||||
$person_drop_stmt = $conn->prepare("
|
||||
delete from actor where objectId = :objectId
|
||||
");
|
||||
|
||||
$object_drop_stmt = $conn->prepare("
|
||||
delete from object where id = :objectId
|
||||
");
|
||||
|
||||
$person_drop_stmt->bindValue(':objectId', $objectId);
|
||||
$object_drop_stmt->bindValue(':objectId', $objectId);
|
||||
|
||||
$person_drop_result = $object_drop_stmt->execute();
|
||||
|
||||
if (!$person_drop_stmt->execute()) {
|
||||
echo "Error: failed to drop person $objectId. No changes made. Aborting." . PHP_EOL;
|
||||
die();
|
||||
}
|
||||
|
||||
if (!$object_drop_stmt->execute()) {
|
||||
echo "Error: failed to drop object $objectId. This object has been left without a corresponding actor. Aborting" . PHP_EOL;
|
||||
die();
|
||||
}
|
||||
|
||||
echo "Successfully dropped actor $preferred_username!" . PHP_EOL;
|
||||
|
||||
$conn->close();
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if ($argc === 1) {
|
||||
echo 'Error: No sub-command provided. Please specify a sub-command' . PHP_EOL;
|
||||
die();
|
||||
}
|
||||
|
||||
try {
|
||||
require_once(dirname($_SERVER['SCRIPT_NAME']) . "/{$argv[1]}.php");
|
||||
} catch (err) {
|
||||
echo "Unknown sub-command '$argv[1]'" . PHP_EOL;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
require_once('./public/config.php');
|
||||
require_once('./public/api/v1/database.php');
|
||||
|
||||
$note_id = 'https://' . HOSTAS_DOMAIN . '/api/v1/object/' . exec('uuidgen');
|
||||
$activity_id= 'https://' . HOSTAS_DOMAIN . '/api/v1/object/' . exec('uuidgen');
|
||||
$published = time();
|
||||
|
||||
$preferred_username = readline('Username: ');
|
||||
|
||||
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||
$actor_query = $conn->prepare("
|
||||
select * from actor
|
||||
join object on actor.objectId = object.id
|
||||
where preferredUsername = :preferred_username
|
||||
limit 1;
|
||||
");
|
||||
|
||||
$actor_query->bindValue(':preferred_username', $preferred_username);
|
||||
$actor_result = $actor_query->execute();
|
||||
$actor = $actor_result->fetchArray();
|
||||
|
||||
if(!$actor) {
|
||||
echo "No actor by the preferred username $preferred_username. Exiting" . PHP_EOL;
|
||||
$conn->close();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$content = readline('Content: ');
|
||||
|
||||
$note_result = prepare_and_execute($conn,
|
||||
"insert into object(id, type, published, attributedTo, content)
|
||||
values (:id, :type, :published, :attributedTo, :content);",
|
||||
array(
|
||||
':id' => $note_id,
|
||||
':type' => 'Note',
|
||||
':published' => $published,
|
||||
':attributedTo' => $actor['id'],
|
||||
':content' => $content
|
||||
)
|
||||
);
|
||||
|
||||
if (!$note_result) {
|
||||
echo 'Failed to create note. Exiting' . PHP_EOL;
|
||||
$conn->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
echo "Note created at $note_id" . PHP_EOL;
|
||||
|
||||
$activity_object_result = prepare_and_execute($conn,
|
||||
"insert into object(id, type, published)
|
||||
values(:id, :type, :published);",
|
||||
array(
|
||||
':id' => $activity_id,
|
||||
':type' => 'Create',
|
||||
':published' => $published,
|
||||
)
|
||||
);
|
||||
|
||||
if (!$activity_object_result) {
|
||||
echo 'Failed to create the activity\'s object. Exiting' . PHP_EOL;
|
||||
$conn->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
echo "Object created for Create activity at $activity_id" . PHP_EOL;
|
||||
|
||||
$activity_result = prepare_and_execute($conn,
|
||||
"insert into activity(objectId, actor, object)
|
||||
values(:objectId, :actor, :object);",
|
||||
array(
|
||||
':objectId' => $activity_id,
|
||||
':actor' => $actor['id'],
|
||||
':object' => $note_id,
|
||||
)
|
||||
);
|
||||
|
||||
if (!$activity_result) {
|
||||
echo 'Failed to create the activity record. Exiting' . PHP_EOL;
|
||||
$conn->close();
|
||||
exit();
|
||||
}
|
||||
|
||||
echo "Successfully created activity." . PHP_EOL;
|
||||
$conn->close();
|
|
@ -0,0 +1,89 @@
|
|||
CREATE TABLE IF NOT EXISTS actor (
|
||||
objectId TEXT, -- UUIDv4 of the corresponding object
|
||||
preferredUsername TEXT,
|
||||
|
||||
FOREIGN KEY (objectId) REFERENCES object(id),
|
||||
PRIMARY KEY (objectId)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object (
|
||||
id TEXT, -- Dereferenceable link to the object
|
||||
type TEXT, -- Object type, see <https://www.w3.org/TR/activitystreams-vocabulary/#object-types>
|
||||
|
||||
name TEXT, -- Title of the object
|
||||
attributedTo TEXT, -- object ID of the actor to whom the object is attributed
|
||||
summary TEXT, -- Natural language summary of the object
|
||||
content TEXT, -- Text content or representation
|
||||
url TEXT, -- A URL that represents the object
|
||||
mediaType TEXT, -- MIME type of `content`
|
||||
icon TEXT, -- URL of a 1:1 image that represents the object
|
||||
|
||||
-- These four columns are unix timestamps. AP expects ISO timestamps
|
||||
startTime INTEGER, -- When the object is said to have "begun," in some context
|
||||
endTime INTEGER, -- When the object is said to "end," in some context
|
||||
published INTEGER, -- When the object was first created
|
||||
updated INTEGER, -- When the object was last updated
|
||||
|
||||
FOREIGN KEY (attributedTo) REFERENCES object(id),
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS activity (
|
||||
objectId TEXT,
|
||||
|
||||
-- All of these are object IDs, all of them are optional
|
||||
actor TEXT, -- Actor "behind" the activity
|
||||
object TEXT, -- Object encapsulated by the activity, if applicable
|
||||
origin TEXT, -- Object the activity is "from"
|
||||
target TEXT, -- Object the activity is "going to", or the encapsulated object is
|
||||
result TEXT, -- Object describing the outcome
|
||||
instrument TEXT, -- Object with which the activity was performed
|
||||
|
||||
FOREIGN KEY (objectId) REFERENCES object(id),
|
||||
PRIMARY KEY (objectId)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object_contentMap (
|
||||
objectId INTEGER, -- The object attaching `attachedId`
|
||||
key TEXT, -- Langauge code
|
||||
content TEXT, -- The content being mapped to by the key
|
||||
|
||||
FOREIGN KEY(objectId) REFERENCES object(id),
|
||||
PRIMARY KEY(objectId, key)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object_attachment (
|
||||
objectId INTEGER, -- The object attaching `attachedId`
|
||||
attachedId INTEGER, -- The object attached by `objectId`
|
||||
|
||||
FOREIGN KEY(objectId) REFERENCES object(id),
|
||||
FOREIGN KEY(attachedId) REFERENCES object(id),
|
||||
PRIMARY KEY(objectId, attachedId)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object_audience (
|
||||
objectId INTEGER, -- Object identifying an audience
|
||||
audienceId INTEGER, -- Audience being identified
|
||||
|
||||
FOREIGN KEY(objectId) REFERENCES object(id),
|
||||
FOREIGN KEY(audienceId) REFERENCES object(id),
|
||||
PRIMARY KEY(objectId, audienceId)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object_replies (
|
||||
objectId INTEGER, -- "original post," or whatever is being replied to
|
||||
responseId INTEGER, -- The response to the original object
|
||||
|
||||
FOREIGN KEY(objectId) REFERENCES object(id),
|
||||
FOREIGN KEY(responseId) REFERENCES object(id),
|
||||
PRIMARY KEY(objectId, responseId)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS object_tags (
|
||||
taggerId INTEGER,
|
||||
taggedId INTEGER,
|
||||
|
||||
FOREIGN KEY(taggerId) REFERENCES object(id),
|
||||
FOREIGN KEY(taggedId) REFERENCES object(id),
|
||||
PRIMARY KEY(taggerId, taggedId)
|
||||
);
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
require_once('/etc/hostas/definitions.php');
|
||||
|
||||
define('HOSTAS_DATABASE_PATH', '/var/db/hostas2.db');
|
||||
define('HOSTAS_INSTALL_PATH', '/path/to/hostas2/website');
|
||||
define('HOSTAS_DOMAIN', 'example.com');
|
||||
define('HOSTAS_UNIX_USER', 'www-data');
|
||||
|
||||
define('HOSTAS_ACCESS_LIST', array(
|
||||
'example.org' => HOSTAS_ACCESS_LEVEL->trusted
|
||||
));
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
define('HOSTAS_ACCESS_LEVEL', (object) array(
|
||||
'default' => 0, // Can view, cannot respond
|
||||
'trusted' => 1, // Can view, can respond
|
||||
'blocked' => 2, // Cannot view or respond
|
||||
));
|
||||
|
||||
define('HOSTAS_CONTEXT', 'https://www.w3.org/ns/activitystreams');
|
||||
|
||||
|
||||
define('HOSTAS_PUBKEY_PATH', '/etc/hostas/keys/public.pem');
|
||||
define('HOSTAS_PRIVKEY_PATH', '/etc/hostas/keys/public.pem');
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
server {
|
||||
# SSL configuration
|
||||
#
|
||||
# listen 443 ssl default_server;
|
||||
# listen [::]:443 ssl default_server;
|
||||
#
|
||||
# Note: You should disable gzip for SSL traffic.
|
||||
# See: https://bugs.debian.org/773332
|
||||
#
|
||||
# Read up on ssl_ciphers to ensure a secure configuration.
|
||||
# See: https://bugs.debian.org/765782
|
||||
#
|
||||
# Self signed certs generated by the ssl-cert package
|
||||
# Don't use them in a production server!
|
||||
#
|
||||
# include snippets/snakeoil.conf;
|
||||
|
||||
root /rootdir/of/hostas2/;
|
||||
|
||||
# Add index.php to the list if you are using PHP
|
||||
index index.php index.html index.htm index.nginx-debian.html;
|
||||
|
||||
server_name example.net;
|
||||
|
||||
# For WebFinger lookup
|
||||
location /.well-known/webfinger {
|
||||
rewrite ^/.well-known/webfinger /api/webfinger-lookup.php;
|
||||
}
|
||||
|
||||
# API
|
||||
location /api/v1/ {
|
||||
index router.php;
|
||||
rewrite ^/api/v1/(.*)$ /api/v1/router.php?$args;
|
||||
}
|
||||
|
||||
# pass PHP scripts to FastCGI server
|
||||
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
|
||||
# With php-fpm (or other unix sockets):
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
fastcgi_param DOCUMENT_ROOT $realpath_root;
|
||||
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
|
||||
# With php-cgi (or other tcp sockets):
|
||||
# fastcgi_pass 127.0.0.1:9000;
|
||||
}
|
||||
|
||||
location / {
|
||||
autoindex on;
|
||||
# First attempt to serve request as file, then
|
||||
# as directory, then fall back to displaying a 404.
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# deny access to .htaccess files, if Apache's document root
|
||||
# concurs with nginx's one
|
||||
#
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
|
||||
location /config.php {
|
||||
deny all;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/example.net/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.net/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
}
|
||||
server {
|
||||
if ($host = example.net) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name example.net;
|
||||
return 404;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
include_once('./public/config.php');
|
||||
|
||||
// Create the database if it doesn't already exist
|
||||
if (!file_exists(dirname(HOSTAS_DATABASE_PATH))) {
|
||||
mkdir(
|
||||
directory: dirname(HOSTAS_DATABASE_PATH),
|
||||
recursive: true
|
||||
);
|
||||
|
||||
// The SQLite driver can't write to a database unless its directory is
|
||||
// writeable.
|
||||
//
|
||||
// See <https://stackoverflow.com/questions/3319112/sqlite-error-attempt-to-write-a-readonly-database-during-insert>
|
||||
exec('chown ' . HOSTAS_UNIX_USER . ' ' . dirname(HOSTAS_DATABASE_PATH));
|
||||
}
|
||||
|
||||
if (!file_exists(HOSTAS_DATABASE_PATH)) {
|
||||
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||
echo "Creating the database..." . PHP_EOL;
|
||||
$ddl = file_get_contents('./database.ddl');
|
||||
$conn->exec($ddl);
|
||||
|
||||
// Make sure the right permissions are set
|
||||
exec('chown ' . HOSTAS_UNIX_USER . ' ' . HOSTAS_DATABASE_PATH);
|
||||
exec('chgrp ' . HOSTAS_UNIX_USER . ' ' . HOSTAS_DATABASE_PATH);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/api/v1/database.php');
|
||||
|
||||
function get_actor_or_exit($conn, string $preferred_username) {
|
||||
$actor = sql_fetch_actor($conn, $preferred_username);
|
||||
|
||||
if (!$actor) {
|
||||
http_response_code(404);
|
||||
die();
|
||||
}
|
||||
|
||||
return $actor;
|
||||
}
|
||||
|
||||
function get_actor(string $preferred_username) {
|
||||
$public_key = file_get_contents(HOSTAS_PUBKEY_PATH);
|
||||
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||
|
||||
$actor = get_actor_or_exit($conn, $preferred_username);
|
||||
|
||||
$activity_representation = array(
|
||||
'@context' => HOSTAS_CONTEXT,
|
||||
'id' => $actor['id'],
|
||||
'type' => 'Person',
|
||||
'preferredUsername' => $actor['preferredUsername'],
|
||||
'name' => $actor['name'],
|
||||
'summary' => $actor['summary'],
|
||||
'url' => $actor['url'],
|
||||
'icon' => array(
|
||||
'type' => 'Image',
|
||||
'url' => $actor['icon'],
|
||||
),
|
||||
'publicKey' => array(
|
||||
'id' => "{$actor['id']}#main-key",
|
||||
'owner' => $actor['id'],
|
||||
'publicKeyPem' => $public_key,
|
||||
),
|
||||
'inbox' => "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/inbox",
|
||||
'outbox' => "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/outbox",
|
||||
);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($activity_representation);
|
||||
|
||||
$conn->close();
|
||||
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);
|
||||
|
||||
$create_activities_result = prepare_and_execute($conn,
|
||||
"select object.id as object_id, object.type as object_type,
|
||||
activity.actor as activity_actor, post.id as post_id,
|
||||
post.published as post_published, post.type as post_type,
|
||||
post.content as post_content
|
||||
from object
|
||||
join activity on activity.objectId = object.id
|
||||
join object as post on activity.object = post.id
|
||||
where activity.actor = :actor_id
|
||||
order by object.published",
|
||||
/*
|
||||
"select object.id as object_id, object.type as object_type,
|
||||
post.id as post_id, post.type as post.type, post.published as post_published,
|
||||
post.url as post_url, post.content as post_content
|
||||
from object
|
||||
join activity on activity.objectId = object.id
|
||||
join object as post on activity.object = post.id
|
||||
where activity.actor = :actor_id
|
||||
order by object.published",
|
||||
*/
|
||||
array(':actor_id' => $actor['id'])
|
||||
);
|
||||
|
||||
$total_items = 0;
|
||||
$ordered_items = array();
|
||||
|
||||
while ($entry = $create_activities_result->fetchArray()) {
|
||||
$total_items += 1;
|
||||
array_push($ordered_items, array(
|
||||
'id' => $entry['object_id'],
|
||||
'type' => $entry['object_type'],
|
||||
'actor' => $entry['activity_actor'],
|
||||
'published' => date(DATE_ISO8601, $entry['post_published']),
|
||||
'cc' => array('https://www.w3.org/ns/activitystreams#Public'),
|
||||
'object' => array(
|
||||
'id' => $entry['post_id'],
|
||||
'type'=> $entry['post_type'],
|
||||
'published' => date(DATE_ISO8601, $entry['post_published']),
|
||||
'url' => $entry['post_id'],
|
||||
'attributedTo' => $actor['id'],
|
||||
'cc' => array('https://www.w3.org/ns/activitystreams#Public'),
|
||||
'content' => $entry['post_content'],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
echo json_encode(array(
|
||||
'@context' => HOSTAS_CONTEXT,
|
||||
'id' => "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/outbox",
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $total_items,
|
||||
'orderedItems' => $ordered_items,
|
||||
));
|
||||
|
||||
die();
|
||||
}
|
||||
|
||||
function get_actor_inbox(string $objectId) {
|
||||
die();
|
||||
}
|
||||
|
||||
switch ($_SERVER['REQUEST_METHOD']) {
|
||||
case 'GET':
|
||||
if (sizeof(REQUEST_PATH) === 3) {
|
||||
if (REQUEST_PATH[2] === 'inbox') get_actor_inbox(REQUEST_PATH[1]);
|
||||
else if (REQUEST_PATH[2] === 'outbox') get_actor_outbox(REQUEST_PATH[1]);
|
||||
}
|
||||
get_actor(REQUEST_PATH[1]);
|
||||
break;
|
||||
default:
|
||||
http_response_code(405);
|
||||
die();
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
function prepare_and_execute($conn, $stmt_string, $parameters) {
|
||||
$stmt = $conn->prepare($stmt_string);
|
||||
|
||||
foreach ($parameters as $k => $v) {
|
||||
$stmt->bindValue($k, $v);
|
||||
}
|
||||
|
||||
return $stmt->execute();
|
||||
}
|
||||
|
||||
function sql_fetch_actor($conn, string $preferred_username) {
|
||||
$object_id = 'https://' . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username";
|
||||
$result = prepare_and_execute($conn,
|
||||
"select * from actor
|
||||
join object on actor.objectId = object.id
|
||||
where object.id = :object_id",
|
||||
array(':object_id' => $object_id)
|
||||
);
|
||||
|
||||
return $result->fetchArray(SQLITE3_ASSOC);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
|
||||
|
||||
// e.g. /api/v1/test/request/ -> ['', 'api', 'v1', 'test', 'request'].
|
||||
// For convenience later, we take a slice that excludes ['', 'api', 'v1'].
|
||||
define('REQUEST_PATH', array_slice(explode('/', $_SERVER['REQUEST_URI']), offset: 3));
|
||||
|
||||
switch (REQUEST_PATH[0]) {
|
||||
case 'actor': // /api/v1/actor
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/api/v1/actor.php');
|
||||
break;
|
||||
default:
|
||||
http_response_code(404);
|
||||
die();
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
|
||||
|
||||
$resource = $_GET['resource'];
|
||||
|
||||
$canonical_username = substr($resource, offset: 5); // Drop the 'acct:'
|
||||
$username_parts = explode(separator: '@', string: $canonical_username, limit: 2);
|
||||
|
||||
$local_username = $username_parts[0];
|
||||
$given_domain = $username_parts[1];
|
||||
|
||||
// We're only returning results at our domain
|
||||
if ($given_domain !== HOSTAS_DOMAIN) {
|
||||
http_response_code(404);
|
||||
die();
|
||||
}
|
||||
|
||||
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
|
||||
|
||||
$people_query = $conn->prepare("
|
||||
select * from actor
|
||||
join object on actor.objectId = object.id
|
||||
where actor.preferredUsername = :preferredUsername
|
||||
");
|
||||
|
||||
$people_query->bindValue(':preferredUsername', $local_username);
|
||||
$people_query_results = $people_query->execute();
|
||||
$person = $people_query_results->fetchArray();
|
||||
|
||||
if (!$person) {
|
||||
http_response_code(404);
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
header('Content-Type: application/jrd+json; charset=utf8');
|
||||
echo json_encode(array(
|
||||
'subject' => "acct:$local_username@$given_domain",
|
||||
'links' => array(
|
||||
array(
|
||||
'rel'=> 'self',
|
||||
'href'=> $person['url'],
|
||||
'type'=> 'application/activity+json'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
$conn->close();
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
echo 'hi';
|
Loading…
Reference in New Issue