Add some routing, start working on actually searching webmentions
This commit is contained in:
parent
15d03799bf
commit
45024faa5f
12
index.php
12
index.php
@ -2,10 +2,20 @@
|
|||||||
|
|
||||||
require_once __DIR__ . "/vendor/autoload.php";
|
require_once __DIR__ . "/vendor/autoload.php";
|
||||||
|
|
||||||
|
use Lewisdale\Webmentions\Router\Router;
|
||||||
|
use Lewisdale\Webmentions\Router\Response;
|
||||||
use Lewisdale\Webmentions\Webmention;
|
use Lewisdale\Webmentions\Webmention;
|
||||||
|
|
||||||
$mentioner = new Webmention();
|
$mentioner = new Webmention();
|
||||||
|
|
||||||
$mentioner->sendForPage("https://lewisdale.dev/post/bringing-my-omg-lol-now-page-into-eleventy/");
|
// $mentioner->sendForPage("https://lewisdale.dev/post/bringing-my-omg-lol-now-page-into-eleventy/");
|
||||||
|
|
||||||
|
$router = new Router();
|
||||||
|
|
||||||
|
$router->get("/send", function($req, Response $response) {
|
||||||
|
return "<h1>Hello world</h1>";
|
||||||
|
});
|
||||||
|
|
||||||
|
$router->dispatch();
|
||||||
|
|
||||||
?>
|
?>
|
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace Lewisdale\Webmentions;
|
namespace Lewisdale\Webmentions;
|
||||||
|
|
||||||
|
use Lewisdale\Webmentions\Exceptions\InvalidTargetException;
|
||||||
|
use Lewisdale\Webmentions\Exceptions\InvalidUrlException;
|
||||||
use Lewisdale\Webmentions\Gateways\WebmentionGatewayInterface;
|
use Lewisdale\Webmentions\Gateways\WebmentionGatewayInterface;
|
||||||
|
use Lewisdale\Webmentions\Models\Webmention;
|
||||||
|
use Symfony\Component\HttpClient\HttpClient;
|
||||||
|
|
||||||
class Endpoint {
|
class Endpoint {
|
||||||
private readonly WebmentionGatewayInterface $gateway;
|
private readonly WebmentionGatewayInterface $gateway;
|
||||||
@ -16,19 +20,35 @@ class Endpoint {
|
|||||||
// Validate that both source and target are actual domains
|
// Validate that both source and target are actual domains
|
||||||
if (!$this->validateUrl($source) || !$this->validateUrl($target))
|
if (!$this->validateUrl($source) || !$this->validateUrl($target))
|
||||||
{
|
{
|
||||||
return;
|
throw new InvalidUrlException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the webmention target is a domain I care about
|
// Validate that the webmention target is a domain I care about
|
||||||
if (!str_contains($target, "https://lewisdale.dev")) {
|
if (!str_contains($target, "https://lewisdale.dev")) {
|
||||||
// Should throw a Bad Request error
|
throw new InvalidTargetException();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse content from the source
|
// Parse content from the source
|
||||||
|
$client = HttpClient::create();
|
||||||
|
$response = $client->request('GET', $source);
|
||||||
|
|
||||||
// Store the webmention
|
if ($response->getStatusCode() === 200)
|
||||||
|
{
|
||||||
|
$content = $this->parseContent($response->getContent());
|
||||||
|
$author = $this->parseAuthor($response->getContent());
|
||||||
|
|
||||||
// Send the appropriate response
|
$webmention = new Webmention(null, $target, $source, $content, $author);
|
||||||
|
$this->gateway->save($webmention);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseContent(string $content) : ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseAuthor(string $author) : ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
6
src/Exceptions/InvalidTargetException.php
Normal file
6
src/Exceptions/InvalidTargetException.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Exceptions;
|
||||||
|
|
||||||
|
class InvalidTargetException extends \Exception
|
||||||
|
{}
|
6
src/Exceptions/InvalidUrlException.php
Normal file
6
src/Exceptions/InvalidUrlException.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Exceptions;
|
||||||
|
|
||||||
|
class InvalidUrlException extends \Exception
|
||||||
|
{}
|
@ -97,5 +97,21 @@ class SqliteGateway extends WebmentionGatewayInterface {
|
|||||||
$statement->execute(["id" => $webmention->id]);
|
$statement->execute(["id" => $webmention->id]);
|
||||||
$statement->closeCursor();
|
$statement->closeCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function find(array $values) : array
|
||||||
|
{
|
||||||
|
$keys = implode(" AND ", array_map(function($v) {
|
||||||
|
return "$v=:$v";
|
||||||
|
}, array_keys($values)));
|
||||||
|
$sql = <<<SQL
|
||||||
|
SELECT * FROM webmentions
|
||||||
|
WHERE {$keys}
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$statement = $this->connection->prepare($sql);
|
||||||
|
$statement->execute($values);
|
||||||
|
|
||||||
|
return $statement->fetchAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
@ -10,6 +10,7 @@ abstract class WebmentionGatewayInterface {
|
|||||||
abstract public function save(Webmention $webmention) : ?int;
|
abstract public function save(Webmention $webmention) : ?int;
|
||||||
abstract public function delete(Webmention $webmention) : void;
|
abstract public function delete(Webmention $webmention) : void;
|
||||||
abstract public function get(int $id) : ?Webmention;
|
abstract public function get(int $id) : ?Webmention;
|
||||||
|
abstract public function find(array $values) : array;
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
11
src/Router/Method.php
Normal file
11
src/Router/Method.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Router;
|
||||||
|
|
||||||
|
enum Method {
|
||||||
|
case GET;
|
||||||
|
case POST;
|
||||||
|
|
||||||
|
// Fallback method
|
||||||
|
case UNKNOWN;
|
||||||
|
};
|
14
src/Router/Request.php
Normal file
14
src/Router/Request.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Router;
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
public function __construct(
|
||||||
|
public array $params,
|
||||||
|
public readonly string $uri,
|
||||||
|
public readonly Method $method,
|
||||||
|
public readonly array $post = [],
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
37
src/Router/Response.php
Normal file
37
src/Router/Response.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Router;
|
||||||
|
|
||||||
|
class Response {
|
||||||
|
public StatusCode $status_code = StatusCode::Ok;
|
||||||
|
public string $body = "";
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function json(mixed $obj): string {
|
||||||
|
return json_encode($obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect the user to a new page.
|
||||||
|
*
|
||||||
|
* Unlike other response methods, this is evaulated immediately, and takes precedence over other routes.
|
||||||
|
*
|
||||||
|
* @param url - the URL to redirect to
|
||||||
|
*/
|
||||||
|
public function redirect(string $url) {
|
||||||
|
header("Location: $url", true, 301);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Render a template, with an array of values to provide to the template
|
||||||
|
// */
|
||||||
|
// public function render(string $template, array $values = []) {
|
||||||
|
// $view = new View($template);
|
||||||
|
|
||||||
|
// return $view->render($values);
|
||||||
|
// }
|
||||||
|
}
|
14
src/Router/Route.php
Normal file
14
src/Router/Route.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Router;
|
||||||
|
|
||||||
|
class Route {
|
||||||
|
public function __construct(
|
||||||
|
public readonly Method $type,
|
||||||
|
public readonly string $pattern,
|
||||||
|
public readonly mixed $fn
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
102
src/Router/Router.php
Normal file
102
src/Router/Router.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Router;
|
||||||
|
|
||||||
|
class Router {
|
||||||
|
private array $routes;
|
||||||
|
|
||||||
|
private function trim_route(string $route): string {
|
||||||
|
$route = $route == "/" ? $route : rtrim($route, "/");
|
||||||
|
$route = str_replace("/", "\/", $route);
|
||||||
|
|
||||||
|
return $route;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function format_route(string $route): string {
|
||||||
|
$formatted = $this->trim_route($route);
|
||||||
|
return "#^$formatted$#";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $route, callable $fn) {
|
||||||
|
$this->routes[] = new Route(Method::GET, $this->format_route($route), $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post(string $route, callable $fn) {
|
||||||
|
$this->routes[] = new Route(Method::POST, $this->format_route($route), $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function method_to_enum(string $method): Method {
|
||||||
|
switch ($method) {
|
||||||
|
case "GET":
|
||||||
|
return Method::GET;
|
||||||
|
case "POST":
|
||||||
|
return Method::POST;
|
||||||
|
default:
|
||||||
|
return Method::UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_params(array $matches): array {
|
||||||
|
array_shift($matches);
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$results[] = $match[0];
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch() {
|
||||||
|
$uri = $_SERVER['REQUEST_URI'];
|
||||||
|
$method = $this->method_to_enum($_SERVER['REQUEST_METHOD']);
|
||||||
|
|
||||||
|
$num_matched = 0;
|
||||||
|
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
foreach($this->routes as $route) {
|
||||||
|
if ($method == $route->type) {
|
||||||
|
$matches = array();
|
||||||
|
if (preg_match_all($route->pattern, $uri, $matches)) {
|
||||||
|
$num_matched++;
|
||||||
|
$fn = $route->fn;
|
||||||
|
$params = $this->get_params($matches);
|
||||||
|
$response->status_code = StatusCode::Ok;
|
||||||
|
$response->body .= $fn(new Request($params, $uri, $method, $_POST), $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$num_matched) {
|
||||||
|
// Handle 404
|
||||||
|
$response->status_code = StatusCode::NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->respond($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function map_status_code(StatusCode $code): int {
|
||||||
|
switch ($code) {
|
||||||
|
case StatusCode::NotFound:
|
||||||
|
return 404;
|
||||||
|
case StatusCode::Ok:
|
||||||
|
return 200;
|
||||||
|
case StatusCode::InternalError:
|
||||||
|
return 500;
|
||||||
|
case StatusCode::Redirect:
|
||||||
|
return 300;
|
||||||
|
case StatusCode::BadRequest:
|
||||||
|
return 400;
|
||||||
|
default:
|
||||||
|
return 501;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function respond(Response $response) {
|
||||||
|
http_response_code($this->map_status_code($response->status_code));
|
||||||
|
|
||||||
|
echo $response->body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
13
src/Router/StatusCode.php
Normal file
13
src/Router/StatusCode.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lewisdale\Webmentions\Router;
|
||||||
|
|
||||||
|
enum StatusCode {
|
||||||
|
case NotFound;
|
||||||
|
case Ok;
|
||||||
|
case Redirect;
|
||||||
|
case InternalError;
|
||||||
|
case BadRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -34,8 +34,6 @@ class SqliteGatewayTest extends TestCase
|
|||||||
|
|
||||||
public function testCanRetrieveAWebmention()
|
public function testCanRetrieveAWebmention()
|
||||||
{
|
{
|
||||||
$this->gateway = new SqliteGateway(":memory:");
|
|
||||||
|
|
||||||
$webmention = new Webmention(
|
$webmention = new Webmention(
|
||||||
null,
|
null,
|
||||||
"https://lewisdale.dev/post/a-post",
|
"https://lewisdale.dev/post/a-post",
|
||||||
@ -53,8 +51,6 @@ class SqliteGatewayTest extends TestCase
|
|||||||
|
|
||||||
public function testCanDeleteAWebmention()
|
public function testCanDeleteAWebmention()
|
||||||
{
|
{
|
||||||
$this->gateway = new SqliteGateway(":memory:");
|
|
||||||
|
|
||||||
$webmention = new Webmention(
|
$webmention = new Webmention(
|
||||||
null,
|
null,
|
||||||
"https://lewisdale.dev/post/a-post",
|
"https://lewisdale.dev/post/a-post",
|
||||||
@ -73,8 +69,6 @@ class SqliteGatewayTest extends TestCase
|
|||||||
|
|
||||||
public function testCanGetByPost()
|
public function testCanGetByPost()
|
||||||
{
|
{
|
||||||
$this->gateway = new SqliteGateway(":memory:");
|
|
||||||
|
|
||||||
foreach(range(0, 4) as $_) {
|
foreach(range(0, 4) as $_) {
|
||||||
$this->gateway->save(new Webmention(
|
$this->gateway->save(new Webmention(
|
||||||
null,
|
null,
|
||||||
@ -99,4 +93,56 @@ class SqliteGatewayTest extends TestCase
|
|||||||
|
|
||||||
$this->assertCount(5, $mentions);
|
$this->assertCount(5, $mentions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCanFindByParams()
|
||||||
|
{
|
||||||
|
$this->gateway->save(new Webmention(
|
||||||
|
null,
|
||||||
|
"https://lewisdale.dev/post/a-new-post",
|
||||||
|
"https://a-source.url",
|
||||||
|
"No content",
|
||||||
|
"Some Author Name"
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->gateway->save(new Webmention(
|
||||||
|
null,
|
||||||
|
"https://lewisdale.dev/post/a-new-post",
|
||||||
|
"https://a-different-source.url",
|
||||||
|
"No content",
|
||||||
|
"Some Author Name"
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->gateway->save(new Webmention(
|
||||||
|
null,
|
||||||
|
"https://lewisdale.dev/post/a-new-post",
|
||||||
|
"https://a-source.url",
|
||||||
|
"Some content",
|
||||||
|
"Some Author Name"
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertCount(
|
||||||
|
2,
|
||||||
|
$this->gateway->find([
|
||||||
|
"target" => "https://lewisdale.dev/post/a-new-post",
|
||||||
|
"source" => "https://a-source.url"
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertCount(
|
||||||
|
1,
|
||||||
|
$this->gateway->find([
|
||||||
|
"target" => "https://lewisdale.dev/post/a-new-post",
|
||||||
|
"source" => "https://a-different-source.url"
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertCount(
|
||||||
|
1,
|
||||||
|
$this->gateway->find([
|
||||||
|
"target" => "https://lewisdale.dev/post/a-new-post",
|
||||||
|
"source" => "https://a-source.url",
|
||||||
|
"content" => "Some content"
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user