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