Refactor of micropub request handling
Trying to organise the code better. It now temporarily doesn’t support update requests. Thought the spec defines them as SHOULD features and not MUST features. So safe for now :)
This commit is contained in:
parent
23c275945a
commit
83d10e1a70
26 changed files with 699 additions and 352 deletions
7
app/Exceptions/InvalidTokenScopeException.php
Normal file
7
app/Exceptions/InvalidTokenScopeException.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
class InvalidTokenScopeException extends \Exception {}
|
7
app/Exceptions/MicropubHandlerException.php
Normal file
7
app/Exceptions/MicropubHandlerException.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
class MicropubHandlerException extends \Exception {}
|
|
@ -4,103 +4,73 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Responses\MicropubResponses;
|
use App\Exceptions\InvalidTokenScopeException;
|
||||||
|
use App\Exceptions\MicropubHandlerException;
|
||||||
|
use App\Http\Requests\MicropubRequest;
|
||||||
use App\Models\Place;
|
use App\Models\Place;
|
||||||
use App\Models\SyndicationTarget;
|
use App\Models\SyndicationTarget;
|
||||||
use App\Services\Micropub\HCardService;
|
use App\Services\Micropub\MicropubHandlerRegistry;
|
||||||
use App\Services\Micropub\HEntryService;
|
|
||||||
use App\Services\Micropub\UpdateService;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Lcobucci\JWT\Token;
|
use Lcobucci\JWT\Token;
|
||||||
use Monolog\Handler\StreamHandler;
|
|
||||||
use Monolog\Logger;
|
|
||||||
|
|
||||||
class MicropubController extends Controller
|
class MicropubController extends Controller
|
||||||
{
|
{
|
||||||
protected HEntryService $hentryService;
|
protected MicropubHandlerRegistry $handlerRegistry;
|
||||||
|
|
||||||
protected HCardService $hcardService;
|
public function __construct(MicropubHandlerRegistry $handlerRegistry)
|
||||||
|
{
|
||||||
protected UpdateService $updateService;
|
$this->handlerRegistry = $handlerRegistry;
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
HEntryService $hentryService,
|
|
||||||
HCardService $hcardService,
|
|
||||||
UpdateService $updateService
|
|
||||||
) {
|
|
||||||
$this->hentryService = $hentryService;
|
|
||||||
$this->hcardService = $hcardService;
|
|
||||||
$this->updateService = $updateService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function receives an API request, verifies the authenticity
|
* Respond to a POST request to the micropub endpoint.
|
||||||
* then passes over the info to the relevant Service class.
|
*
|
||||||
|
* The request is initially processed by the MicropubRequest form request
|
||||||
|
* class. The normalizes the data, so we can pass it into the handlers for
|
||||||
|
* the different micropub requests, h-entry or h-card, for example.
|
||||||
*/
|
*/
|
||||||
public function post(Request $request): JsonResponse
|
public function post(MicropubRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$this->logMicropubRequest($request->except('token_data'));
|
$type = $request->getType();
|
||||||
|
|
||||||
/** @var Token $tokenData */
|
|
||||||
$tokenData = $request->input('token_data');
|
|
||||||
|
|
||||||
if (($request->input('h') === 'entry') || ($request->input('type.0') === 'h-entry')) {
|
|
||||||
$scopes = $tokenData['scope'];
|
|
||||||
if (is_string($scopes)) {
|
|
||||||
$scopes = explode(' ', $scopes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! in_array('create', $scopes, true)) {
|
|
||||||
$micropubResponses = new MicropubResponses;
|
|
||||||
|
|
||||||
return $micropubResponses->insufficientScopeResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
$location = $this->hentryService->process($request->all(), $tokenData['client_id']);
|
|
||||||
|
|
||||||
|
if (! $type) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'response' => 'created',
|
'error' => 'invalid_request',
|
||||||
'location' => $location,
|
'error_description' => 'Microformat object type is missing, for example: h-entry or h-card',
|
||||||
], 201)->header('Location', $location);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->input('h') === 'card' || $request->input('type.0') === 'h-card') {
|
try {
|
||||||
$scopes = $tokenData['scope'];
|
$handler = $this->handlerRegistry->getHandler($type);
|
||||||
if (is_string($scopes)) {
|
$result = $handler->handle($request->getMicropubData());
|
||||||
$scopes = explode(' ', $scopes);
|
|
||||||
}
|
|
||||||
if (! in_array('create', $scopes)) {
|
|
||||||
$micropubResponses = new MicropubResponses;
|
|
||||||
|
|
||||||
return $micropubResponses->insufficientScopeResponse();
|
|
||||||
}
|
|
||||||
$location = $this->hcardService->process($request->all());
|
|
||||||
|
|
||||||
|
// Return appropriate response based on the handler result
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'response' => 'created',
|
'response' => $result['response'],
|
||||||
'location' => $location,
|
'location' => $result['url'] ?? null,
|
||||||
], 201)->header('Location', $location);
|
], 201)->header('Location', $result['url']);
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'invalid_request',
|
||||||
|
'error_description' => $e->getMessage(),
|
||||||
|
], 400);
|
||||||
|
} catch (MicropubHandlerException) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'Unknown Micropub type',
|
||||||
|
'error_description' => 'The request could not be processed by this server',
|
||||||
|
], 500);
|
||||||
|
} catch (InvalidTokenScopeException) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'invalid_scope',
|
||||||
|
'error_description' => 'The token does not have the required scope for this request',
|
||||||
|
], 403);
|
||||||
|
} catch (\Exception) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'error_description' => 'An error occurred processing the request',
|
||||||
|
], 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->input('action') === 'update') {
|
|
||||||
$scopes = $tokenData['scope'];
|
|
||||||
if (is_string($scopes)) {
|
|
||||||
$scopes = explode(' ', $scopes);
|
|
||||||
}
|
|
||||||
if (! in_array('update', $scopes)) {
|
|
||||||
$micropubResponses = new MicropubResponses;
|
|
||||||
|
|
||||||
return $micropubResponses->insufficientScopeResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->updateService->process($request->all());
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'response' => 'error',
|
|
||||||
'error_description' => 'unsupported_request_type',
|
|
||||||
], 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,9 +114,10 @@ class MicropubController extends Controller
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// default response is just to return the token data
|
// the default response is just to return the token data
|
||||||
/** @var Token $tokenData */
|
/** @var Token $tokenData */
|
||||||
$tokenData = $request->input('token_data');
|
$tokenData = $request->input('token_data');
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'response' => 'token',
|
'response' => 'token',
|
||||||
'token' => [
|
'token' => [
|
||||||
|
@ -156,14 +127,4 @@ class MicropubController extends Controller
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the details of the micropub request to a log file.
|
|
||||||
*/
|
|
||||||
private function logMicropubRequest(array $request): void
|
|
||||||
{
|
|
||||||
$logger = new Logger('micropub');
|
|
||||||
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')));
|
|
||||||
$logger->debug('MicropubLog', $request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
24
app/Http/Middleware/LogMicropubRequest.php
Normal file
24
app/Http/Middleware/LogMicropubRequest.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
class LogMicropubRequest
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response|JsonResponse
|
||||||
|
{
|
||||||
|
$logger = new Logger('micropub');
|
||||||
|
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')));
|
||||||
|
$logger->debug('MicropubLog', $request->all());
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,19 +7,19 @@ namespace App\Http\Middleware;
|
||||||
use App\Http\Responses\MicropubResponses;
|
use App\Http\Responses\MicropubResponses;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Lcobucci\JWT\Configuration;
|
||||||
use Lcobucci\JWT\Encoding\CannotDecodeContent;
|
use Lcobucci\JWT\Encoding\CannotDecodeContent;
|
||||||
use Lcobucci\JWT\Token;
|
use Lcobucci\JWT\Token;
|
||||||
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
||||||
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Lcobucci\JWT\Configuration;
|
|
||||||
|
|
||||||
class VerifyMicropubToken
|
class VerifyMicropubToken
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param Closure(Request): (Response) $next
|
* @param Closure(Request): (Response) $next
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
|
|
106
app/Http/Requests/MicropubRequest.php
Normal file
106
app/Http/Requests/MicropubRequest.php
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class MicropubRequest extends FormRequest
|
||||||
|
{
|
||||||
|
protected array $micropubData = [];
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// Validation rules
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMicropubData(): array
|
||||||
|
{
|
||||||
|
return $this->micropubData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): ?string
|
||||||
|
{
|
||||||
|
// Return consistent type regardless of input format
|
||||||
|
return $this->micropubData['type'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
// Normalize the request data based on content type
|
||||||
|
if ($this->isJson()) {
|
||||||
|
$this->normalizeMicropubJson();
|
||||||
|
} else {
|
||||||
|
$this->normalizeMicropubForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeMicropubJson(): void
|
||||||
|
{
|
||||||
|
$json = $this->json();
|
||||||
|
if ($json === null) {
|
||||||
|
throw new \InvalidArgumentException('`isJson()` passed but there is no json data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $json->all();
|
||||||
|
|
||||||
|
// Convert JSON type (h-entry) to simple type (entry)
|
||||||
|
if (isset($data['type']) && is_array($data['type'])) {
|
||||||
|
$type = current($data['type']);
|
||||||
|
if (strpos($type, 'h-') === 0) {
|
||||||
|
$this->micropubData['type'] = substr($type, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Or set the type to update
|
||||||
|
elseif (isset($data['action']) && $data['action'] === 'update') {
|
||||||
|
$this->micropubData['type'] = 'update';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add in the token data
|
||||||
|
$this->micropubData['token_data'] = $data['token_data'];
|
||||||
|
|
||||||
|
// Add h-entry values
|
||||||
|
$this->micropubData['content'] = Arr::get($data, 'properties.content.0');
|
||||||
|
$this->micropubData['in-reply-to'] = Arr::get($data, 'properties.in-reply-to.0');
|
||||||
|
$this->micropubData['published'] = Arr::get($data, 'properties.published.0');
|
||||||
|
$this->micropubData['location'] = Arr::get($data, 'location');
|
||||||
|
$this->micropubData['bookmark-of'] = Arr::get($data, 'properties.bookmark-of.0');
|
||||||
|
$this->micropubData['like-of'] = Arr::get($data, 'properties.like-of.0');
|
||||||
|
$this->micropubData['mp-syndicate-to'] = Arr::get($data, 'properties.mp-syndicate-to');
|
||||||
|
|
||||||
|
// Add h-card values
|
||||||
|
$this->micropubData['name'] = Arr::get($data, 'properties.name.0');
|
||||||
|
$this->micropubData['description'] = Arr::get($data, 'properties.description.0');
|
||||||
|
$this->micropubData['geo'] = Arr::get($data, 'properties.geo.0');
|
||||||
|
|
||||||
|
// Add checkin value
|
||||||
|
$this->micropubData['checkin'] = Arr::get($data, 'checkin');
|
||||||
|
$this->micropubData['syndication'] = Arr::get($data, 'properties.syndication.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeMicropubForm(): void
|
||||||
|
{
|
||||||
|
// Convert form h=entry to type=entry
|
||||||
|
if ($h = $this->input('h')) {
|
||||||
|
$this->micropubData['type'] = $h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some fields to the micropub data with default null values
|
||||||
|
$this->micropubData['in-reply-to'] = null;
|
||||||
|
$this->micropubData['published'] = null;
|
||||||
|
$this->micropubData['location'] = null;
|
||||||
|
$this->micropubData['description'] = null;
|
||||||
|
$this->micropubData['geo'] = null;
|
||||||
|
$this->micropubData['latitude'] = null;
|
||||||
|
$this->micropubData['longitude'] = null;
|
||||||
|
|
||||||
|
// Map form fields to micropub data
|
||||||
|
foreach ($this->except(['h', 'access_token']) as $key => $value) {
|
||||||
|
$this->micropubData[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
app/Providers/MicropubServiceProvider.php
Normal file
26
app/Providers/MicropubServiceProvider.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Services\Micropub\CardHandler;
|
||||||
|
use App\Services\Micropub\EntryHandler;
|
||||||
|
use App\Services\Micropub\MicropubHandlerRegistry;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class MicropubServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->singleton(MicropubHandlerRegistry::class, function () {
|
||||||
|
$registry = new MicropubHandlerRegistry;
|
||||||
|
|
||||||
|
// Register handlers
|
||||||
|
$registry->register('card', new CardHandler);
|
||||||
|
$registry->register('entry', new EntryHandler);
|
||||||
|
|
||||||
|
return $registry;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,13 +6,13 @@ namespace App\Services;
|
||||||
|
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
|
|
||||||
class ArticleService extends Service
|
class ArticleService
|
||||||
{
|
{
|
||||||
public function create(array $request, ?string $client = null): Article
|
public function create(array $data): Article
|
||||||
{
|
{
|
||||||
return Article::create([
|
return Article::create([
|
||||||
'title' => $this->getDataByKey($request, 'name'),
|
'title' => $data['name'],
|
||||||
'main' => $this->getDataByKey($request, 'content'),
|
'main' => $data['content'],
|
||||||
'published' => true,
|
'published' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,28 +10,29 @@ use App\Models\Bookmark;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\Exception\ClientException;
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class BookmarkService extends Service
|
class BookmarkService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create a new Bookmark.
|
* Create a new Bookmark.
|
||||||
*/
|
*/
|
||||||
public function create(array $request, ?string $client = null): Bookmark
|
public function create(array $data): Bookmark
|
||||||
{
|
{
|
||||||
if (Arr::get($request, 'properties.bookmark-of.0')) {
|
if (Arr::get($data, 'properties.bookmark-of.0')) {
|
||||||
// micropub request
|
// micropub request
|
||||||
$url = normalize_url(Arr::get($request, 'properties.bookmark-of.0'));
|
$url = normalize_url(Arr::get($data, 'properties.bookmark-of.0'));
|
||||||
$name = Arr::get($request, 'properties.name.0');
|
$name = Arr::get($data, 'properties.name.0');
|
||||||
$content = Arr::get($request, 'properties.content.0');
|
$content = Arr::get($data, 'properties.content.0');
|
||||||
$categories = Arr::get($request, 'properties.category');
|
$categories = Arr::get($data, 'properties.category');
|
||||||
}
|
}
|
||||||
if (Arr::get($request, 'bookmark-of')) {
|
if (Arr::get($data, 'bookmark-of')) {
|
||||||
$url = normalize_url(Arr::get($request, 'bookmark-of'));
|
$url = normalize_url(Arr::get($data, 'bookmark-of'));
|
||||||
$name = Arr::get($request, 'name');
|
$name = Arr::get($data, 'name');
|
||||||
$content = Arr::get($request, 'content');
|
$content = Arr::get($data, 'content');
|
||||||
$categories = Arr::get($request, 'category');
|
$categories = Arr::get($data, 'category');
|
||||||
}
|
}
|
||||||
|
|
||||||
$bookmark = Bookmark::create([
|
$bookmark = Bookmark::create([
|
||||||
|
@ -54,6 +55,7 @@ class BookmarkService extends Service
|
||||||
* Given a URL, attempt to save it to the Internet Archive.
|
* Given a URL, attempt to save it to the Internet Archive.
|
||||||
*
|
*
|
||||||
* @throws InternetArchiveException
|
* @throws InternetArchiveException
|
||||||
|
* @throws GuzzleException
|
||||||
*/
|
*/
|
||||||
public function getArchiveLink(string $url): string
|
public function getArchiveLink(string $url): string
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,19 +8,19 @@ use App\Jobs\ProcessLike;
|
||||||
use App\Models\Like;
|
use App\Models\Like;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class LikeService extends Service
|
class LikeService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create a new Like.
|
* Create a new Like.
|
||||||
*/
|
*/
|
||||||
public function create(array $request, ?string $client = null): Like
|
public function create(array $data): Like
|
||||||
{
|
{
|
||||||
if (Arr::get($request, 'properties.like-of.0')) {
|
if (Arr::get($data, 'properties.like-of.0')) {
|
||||||
// micropub request
|
// micropub request
|
||||||
$url = normalize_url(Arr::get($request, 'properties.like-of.0'));
|
$url = normalize_url(Arr::get($data, 'properties.like-of.0'));
|
||||||
}
|
}
|
||||||
if (Arr::get($request, 'like-of')) {
|
if (Arr::get($data, 'like-of')) {
|
||||||
$url = normalize_url(Arr::get($request, 'like-of'));
|
$url = normalize_url(Arr::get($data, 'like-of'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$like = Like::create(['url' => $url]);
|
$like = Like::create(['url' => $url]);
|
||||||
|
|
34
app/Services/Micropub/CardHandler.php
Normal file
34
app/Services/Micropub/CardHandler.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Micropub;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidTokenScopeException;
|
||||||
|
use App\Services\PlaceService;
|
||||||
|
|
||||||
|
class CardHandler implements MicropubHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws InvalidTokenScopeException
|
||||||
|
*/
|
||||||
|
public function handle(array $data): array
|
||||||
|
{
|
||||||
|
// Handle h-card requests
|
||||||
|
$scopes = $data['token_data']['scope'];
|
||||||
|
if (is_string($scopes)) {
|
||||||
|
$scopes = explode(' ', $scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array('create', $scopes, true)) {
|
||||||
|
throw new InvalidTokenScopeException;
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = resolve(PlaceService::class)->createPlace($data)->uri;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'response' => 'created',
|
||||||
|
'url' => $location,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
41
app/Services/Micropub/EntryHandler.php
Normal file
41
app/Services/Micropub/EntryHandler.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Micropub;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidTokenScopeException;
|
||||||
|
use App\Services\ArticleService;
|
||||||
|
use App\Services\BookmarkService;
|
||||||
|
use App\Services\LikeService;
|
||||||
|
use App\Services\NoteService;
|
||||||
|
|
||||||
|
class EntryHandler implements MicropubHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws InvalidTokenScopeException
|
||||||
|
*/
|
||||||
|
public function handle(array $data)
|
||||||
|
{
|
||||||
|
$scopes = $data['token_data']['scope'];
|
||||||
|
if (is_string($scopes)) {
|
||||||
|
$scopes = explode(' ', $scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array('create', $scopes, true)) {
|
||||||
|
throw new InvalidTokenScopeException;
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = match (true) {
|
||||||
|
isset($data['like-of']) => resolve(LikeService::class)->create($data)->url,
|
||||||
|
isset($data['bookmark-of']) => resolve(BookmarkService::class)->create($data)->uri,
|
||||||
|
isset($data['name']) => resolve(ArticleService::class)->create($data)->link,
|
||||||
|
default => resolve(NoteService::class)->create($data)->uri,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
'response' => 'created',
|
||||||
|
'url' => $location,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Micropub;
|
|
||||||
|
|
||||||
use App\Services\PlaceService;
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
|
|
||||||
class HCardService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Create a Place from h-card data, return the URL.
|
|
||||||
*/
|
|
||||||
public function process(array $request): string
|
|
||||||
{
|
|
||||||
$data = [];
|
|
||||||
if (Arr::get($request, 'properties.name')) {
|
|
||||||
$data['name'] = Arr::get($request, 'properties.name');
|
|
||||||
$data['description'] = Arr::get($request, 'properties.description');
|
|
||||||
$data['geo'] = Arr::get($request, 'properties.geo');
|
|
||||||
} else {
|
|
||||||
$data['name'] = Arr::get($request, 'name');
|
|
||||||
$data['description'] = Arr::get($request, 'description');
|
|
||||||
$data['geo'] = Arr::get($request, 'geo');
|
|
||||||
$data['latitude'] = Arr::get($request, 'latitude');
|
|
||||||
$data['longitude'] = Arr::get($request, 'longitude');
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolve(PlaceService::class)->createPlace($data)->uri;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Micropub;
|
|
||||||
|
|
||||||
use App\Services\ArticleService;
|
|
||||||
use App\Services\BookmarkService;
|
|
||||||
use App\Services\LikeService;
|
|
||||||
use App\Services\NoteService;
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
|
|
||||||
class HEntryService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Create the relevant model from some h-entry data.
|
|
||||||
*/
|
|
||||||
public function process(array $request, ?string $client = null): ?string
|
|
||||||
{
|
|
||||||
if (Arr::get($request, 'properties.like-of') || Arr::get($request, 'like-of')) {
|
|
||||||
return resolve(LikeService::class)->create($request)->url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Arr::get($request, 'properties.bookmark-of') || Arr::get($request, 'bookmark-of')) {
|
|
||||||
return resolve(BookmarkService::class)->create($request)->uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Arr::get($request, 'properties.name') || Arr::get($request, 'name')) {
|
|
||||||
return resolve(ArticleService::class)->create($request)->link;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolve(NoteService::class)->create($request, $client)->uri;
|
|
||||||
}
|
|
||||||
}
|
|
10
app/Services/Micropub/MicropubHandlerInterface.php
Normal file
10
app/Services/Micropub/MicropubHandlerInterface.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Micropub;
|
||||||
|
|
||||||
|
interface MicropubHandlerInterface
|
||||||
|
{
|
||||||
|
public function handle(array $data);
|
||||||
|
}
|
34
app/Services/Micropub/MicropubHandlerRegistry.php
Normal file
34
app/Services/Micropub/MicropubHandlerRegistry.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Micropub;
|
||||||
|
|
||||||
|
use App\Exceptions\MicropubHandlerException;
|
||||||
|
|
||||||
|
class MicropubHandlerRegistry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var MicropubHandlerInterface[]
|
||||||
|
*/
|
||||||
|
protected array $handlers = [];
|
||||||
|
|
||||||
|
public function register(string $type, MicropubHandlerInterface $handler): self
|
||||||
|
{
|
||||||
|
$this->handlers[$type] = $handler;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws MicropubHandlerException
|
||||||
|
*/
|
||||||
|
public function getHandler(string $type): MicropubHandlerInterface
|
||||||
|
{
|
||||||
|
if (! isset($this->handlers[$type])) {
|
||||||
|
throw new MicropubHandlerException("No handler registered for '{$type}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->handlers[$type];
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,21 +4,33 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Services\Micropub;
|
namespace App\Services\Micropub;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidTokenScopeException;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use App\Models\Note;
|
use App\Models\Note;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class UpdateService
|
/*
|
||||||
|
* @todo Implement this properly
|
||||||
|
*/
|
||||||
|
class UpdateHandler implements MicropubHandlerInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Process a micropub request to update an entry.
|
* @throws InvalidTokenScopeException
|
||||||
*/
|
*/
|
||||||
public function process(array $request): JsonResponse
|
public function handle(array $data)
|
||||||
{
|
{
|
||||||
$urlPath = parse_url(Arr::get($request, 'url'), PHP_URL_PATH);
|
$scopes = $data['token_data']['scope'];
|
||||||
|
if (is_string($scopes)) {
|
||||||
|
$scopes = explode(' ', $scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array('update', $scopes, true)) {
|
||||||
|
throw new InvalidTokenScopeException;
|
||||||
|
}
|
||||||
|
|
||||||
|
$urlPath = parse_url(Arr::get($data, 'url'), PHP_URL_PATH);
|
||||||
|
|
||||||
// is it a note we are updating?
|
// is it a note we are updating?
|
||||||
if (mb_substr($urlPath, 1, 5) !== 'notes') {
|
if (mb_substr($urlPath, 1, 5) !== 'notes') {
|
||||||
|
@ -30,7 +42,7 @@ class UpdateService
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$note = Note::nb60(basename($urlPath))->firstOrFail();
|
$note = Note::nb60(basename($urlPath))->firstOrFail();
|
||||||
} catch (ModelNotFoundException $exception) {
|
} catch (ModelNotFoundException) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'error' => 'invalid_request',
|
'error' => 'invalid_request',
|
||||||
'error_description' => 'No known note with given ID',
|
'error_description' => 'No known note with given ID',
|
||||||
|
@ -38,8 +50,8 @@ class UpdateService
|
||||||
}
|
}
|
||||||
|
|
||||||
// got the note, are we dealing with a “replace” request?
|
// got the note, are we dealing with a “replace” request?
|
||||||
if (Arr::get($request, 'replace')) {
|
if (Arr::get($data, 'replace')) {
|
||||||
foreach (Arr::get($request, 'replace') as $property => $value) {
|
foreach (Arr::get($data, 'replace') as $property => $value) {
|
||||||
if ($property === 'content') {
|
if ($property === 'content') {
|
||||||
$note->note = $value[0];
|
$note->note = $value[0];
|
||||||
}
|
}
|
||||||
|
@ -59,14 +71,14 @@ class UpdateService
|
||||||
}
|
}
|
||||||
$note->save();
|
$note->save();
|
||||||
|
|
||||||
return response()->json([
|
return [
|
||||||
'response' => 'updated',
|
'response' => 'updated',
|
||||||
]);
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// how about “add”
|
// how about “add”
|
||||||
if (Arr::get($request, 'add')) {
|
if (Arr::get($data, 'add')) {
|
||||||
foreach (Arr::get($request, 'add') as $property => $value) {
|
foreach (Arr::get($data, 'add') as $property => $value) {
|
||||||
if ($property === 'syndication') {
|
if ($property === 'syndication') {
|
||||||
foreach ($value as $syndicationURL) {
|
foreach ($value as $syndicationURL) {
|
||||||
if (Str::startsWith($syndicationURL, 'https://www.facebook.com')) {
|
if (Str::startsWith($syndicationURL, 'https://www.facebook.com')) {
|
|
@ -14,49 +14,52 @@ use App\Models\SyndicationTarget;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class NoteService extends Service
|
class NoteService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create a new note.
|
* Create a new note.
|
||||||
*/
|
*/
|
||||||
public function create(array $request, ?string $client = null): Note
|
public function create(array $data): Note
|
||||||
{
|
{
|
||||||
|
// Get the content we want to save
|
||||||
|
if (is_string($data['content'])) {
|
||||||
|
$content = $data['content'];
|
||||||
|
} elseif (isset($data['content']['html'])) {
|
||||||
|
$content = $data['content']['html'];
|
||||||
|
} else {
|
||||||
|
$content = null;
|
||||||
|
}
|
||||||
|
|
||||||
$note = Note::create(
|
$note = Note::create(
|
||||||
[
|
[
|
||||||
'note' => $this->getDataByKey($request, 'content'),
|
'note' => $content,
|
||||||
'in_reply_to' => $this->getDataByKey($request, 'in-reply-to'),
|
'in_reply_to' => $data['in-reply-to'],
|
||||||
'client_id' => $client,
|
'client_id' => $data['token_data']['client_id'],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($this->getPublished($request)) {
|
if ($published = $this->getPublished($data)) {
|
||||||
$note->created_at = $note->updated_at = $this->getPublished($request);
|
$note->created_at = $note->updated_at = $published;
|
||||||
}
|
}
|
||||||
|
|
||||||
$note->location = $this->getLocation($request);
|
$note->location = $this->getLocation($data);
|
||||||
|
|
||||||
if ($this->getCheckin($request)) {
|
if ($this->getCheckin($data)) {
|
||||||
$note->place()->associate($this->getCheckin($request));
|
$note->place()->associate($this->getCheckin($data));
|
||||||
$note->swarm_url = $this->getSwarmUrl($request);
|
$note->swarm_url = $this->getSwarmUrl($data);
|
||||||
}
|
|
||||||
|
|
||||||
$note->instagram_url = $this->getInstagramUrl($request);
|
|
||||||
|
|
||||||
foreach ($this->getMedia($request) as $media) {
|
|
||||||
$note->media()->save($media);
|
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// $note->instagram_url = $this->getInstagramUrl($request);
|
||||||
|
//
|
||||||
|
// foreach ($this->getMedia($request) as $media) {
|
||||||
|
// $note->media()->save($media);
|
||||||
|
// }
|
||||||
|
|
||||||
$note->save();
|
$note->save();
|
||||||
|
|
||||||
dispatch(new SendWebMentions($note));
|
dispatch(new SendWebMentions($note));
|
||||||
|
|
||||||
if (in_array('mastodon', $this->getSyndicationTargets($request), true)) {
|
$this->dispatchSyndicationJobs($note, $data);
|
||||||
dispatch(new SyndicateNoteToMastodon($note));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array('bluesky', $this->getSyndicationTargets($request), true)) {
|
|
||||||
dispatch(new SyndicateNoteToBluesky($note));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $note;
|
return $note;
|
||||||
}
|
}
|
||||||
|
@ -64,14 +67,10 @@ class NoteService extends Service
|
||||||
/**
|
/**
|
||||||
* Get the published time from the request to create a new note.
|
* Get the published time from the request to create a new note.
|
||||||
*/
|
*/
|
||||||
private function getPublished(array $request): ?string
|
private function getPublished(array $data): ?string
|
||||||
{
|
{
|
||||||
if (Arr::get($request, 'properties.published.0')) {
|
if ($data['published']) {
|
||||||
return carbon(Arr::get($request, 'properties.published.0'))
|
return carbon($data['published'])->toDateTimeString();
|
||||||
->toDateTimeString();
|
|
||||||
}
|
|
||||||
if (Arr::get($request, 'published')) {
|
|
||||||
return carbon(Arr::get($request, 'published'))->toDateTimeString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -80,12 +79,13 @@ class NoteService extends Service
|
||||||
/**
|
/**
|
||||||
* Get the location data from the request to create a new note.
|
* Get the location data from the request to create a new note.
|
||||||
*/
|
*/
|
||||||
private function getLocation(array $request): ?string
|
private function getLocation(array $data): ?string
|
||||||
{
|
{
|
||||||
$location = Arr::get($request, 'properties.location.0') ?? Arr::get($request, 'location');
|
$location = Arr::get($data, 'location');
|
||||||
|
|
||||||
if (is_string($location) && str_starts_with($location, 'geo:')) {
|
if (is_string($location) && str_starts_with($location, 'geo:')) {
|
||||||
preg_match_all(
|
preg_match_all(
|
||||||
'/([0-9\.\-]+)/',
|
'/([0-9.\-]+)/',
|
||||||
$location,
|
$location,
|
||||||
$matches
|
$matches
|
||||||
);
|
);
|
||||||
|
@ -99,9 +99,9 @@ class NoteService extends Service
|
||||||
/**
|
/**
|
||||||
* Get the checkin data from the request to create a new note. This will be a Place.
|
* Get the checkin data from the request to create a new note. This will be a Place.
|
||||||
*/
|
*/
|
||||||
private function getCheckin(array $request): ?Place
|
private function getCheckin(array $data): ?Place
|
||||||
{
|
{
|
||||||
$location = Arr::get($request, 'location');
|
$location = Arr::get($data, 'location');
|
||||||
if (is_string($location) && Str::startsWith($location, config('app.url'))) {
|
if (is_string($location) && Str::startsWith($location, config('app.url'))) {
|
||||||
return Place::where(
|
return Place::where(
|
||||||
'slug',
|
'slug',
|
||||||
|
@ -113,12 +113,12 @@ class NoteService extends Service
|
||||||
)
|
)
|
||||||
)->first();
|
)->first();
|
||||||
}
|
}
|
||||||
if (Arr::get($request, 'checkin')) {
|
if (Arr::get($data, 'checkin')) {
|
||||||
try {
|
try {
|
||||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||||
Arr::get($request, 'checkin')
|
Arr::get($data, 'checkin')
|
||||||
);
|
);
|
||||||
} catch (\InvalidArgumentException $e) {
|
} catch (\InvalidArgumentException) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,34 +142,47 @@ class NoteService extends Service
|
||||||
/**
|
/**
|
||||||
* Get the Swarm URL from the syndication data in the request to create a new note.
|
* Get the Swarm URL from the syndication data in the request to create a new note.
|
||||||
*/
|
*/
|
||||||
private function getSwarmUrl(array $request): ?string
|
private function getSwarmUrl(array $data): ?string
|
||||||
{
|
{
|
||||||
if (str_contains(Arr::get($request, 'properties.syndication.0', ''), 'swarmapp')) {
|
$syndication = Arr::get($data, 'syndication');
|
||||||
return Arr::get($request, 'properties.syndication.0');
|
if ($syndication === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($syndication, 'swarmapp')) {
|
||||||
|
return $syndication;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the syndication targets from the request to create a new note.
|
* Dispatch syndication jobs based on the request data.
|
||||||
*/
|
*/
|
||||||
private function getSyndicationTargets(array $request): array
|
private function dispatchSyndicationJobs(Note $note, array $request): void
|
||||||
{
|
{
|
||||||
$syndication = [];
|
// If no syndication targets are specified, return early
|
||||||
$mpSyndicateTo = Arr::get($request, 'mp-syndicate-to') ?? Arr::get($request, 'properties.mp-syndicate-to');
|
if (empty($request['mp-syndicate-to'])) {
|
||||||
$mpSyndicateTo = Arr::wrap($mpSyndicateTo);
|
return;
|
||||||
foreach ($mpSyndicateTo as $uid) {
|
|
||||||
$target = SyndicationTarget::where('uid', $uid)->first();
|
|
||||||
if ($target && $target->service_name === 'Mastodon') {
|
|
||||||
$syndication[] = 'mastodon';
|
|
||||||
}
|
|
||||||
if ($target && $target->service_name === 'Bluesky') {
|
|
||||||
$syndication[] = 'bluesky';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $syndication;
|
// Get the configured syndication targets
|
||||||
|
$syndicationTargets = SyndicationTarget::all();
|
||||||
|
|
||||||
|
foreach ($syndicationTargets as $target) {
|
||||||
|
// Check if the target is in the request data
|
||||||
|
if (in_array($target->uid, $request['mp-syndicate-to'], true)) {
|
||||||
|
// Dispatch the appropriate job based on the target service name
|
||||||
|
switch ($target->service_name) {
|
||||||
|
case 'Mastodon':
|
||||||
|
dispatch(new SyndicateNoteToMastodon($note));
|
||||||
|
break;
|
||||||
|
case 'Bluesky':
|
||||||
|
dispatch(new SyndicateNoteToBluesky($note));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
|
|
||||||
abstract class Service
|
|
||||||
{
|
|
||||||
abstract public function create(array $request, ?string $client = null): Model;
|
|
||||||
|
|
||||||
protected function getDataByKey(array $request, string $key): ?string
|
|
||||||
{
|
|
||||||
if (Arr::get($request, "properties.{$key}.0.html")) {
|
|
||||||
return Arr::get($request, "properties.{$key}.0.html");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string(Arr::get($request, "properties.{$key}.0"))) {
|
|
||||||
return Arr::get($request, "properties.{$key}.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string(Arr::get($request, "properties.{$key}"))) {
|
|
||||||
return Arr::get($request, "properties.{$key}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Arr::get($request, $key);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,4 +3,5 @@
|
||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\HorizonServiceProvider::class,
|
App\Providers\HorizonServiceProvider::class,
|
||||||
|
App\Providers\MicropubServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|
|
@ -49,7 +49,8 @@
|
||||||
"openai-php/client": "^0.10.1",
|
"openai-php/client": "^0.10.1",
|
||||||
"phpunit/php-code-coverage": "^11.0",
|
"phpunit/php-code-coverage": "^11.0",
|
||||||
"phpunit/phpunit": "^11.0",
|
"phpunit/phpunit": "^11.0",
|
||||||
"spatie/laravel-ray": "^1.12"
|
"spatie/laravel-ray": "^1.12",
|
||||||
|
"spatie/x-ray": "^1.2"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|
201
composer.lock
generated
201
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "cd963bfd9cfb41beb4151e73ae98dc98",
|
"content-hash": "1076b46fccbfe2c22f51fa6e904cfedf",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
|
@ -10079,6 +10079,133 @@
|
||||||
],
|
],
|
||||||
"time": "2024-11-12T20:51:16+00:00"
|
"time": "2024-11-12T20:51:16+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "permafrost-dev/code-snippets",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/permafrost-dev/code-snippets.git",
|
||||||
|
"reference": "639827ba7118a6b5521c861a265358ce5bd2b0c5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/permafrost-dev/code-snippets/zipball/639827ba7118a6b5521c861a265358ce5bd2b0c5",
|
||||||
|
"reference": "639827ba7118a6b5521c861a265358ce5bd2b0c5",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.3|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"spatie/phpunit-snapshot-assertions": "^4.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Permafrost\\CodeSnippets\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Patrick Organ",
|
||||||
|
"email": "patrick@permafrost.dev",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Easily work with code snippets in PHP",
|
||||||
|
"homepage": "https://github.com/permafrost-dev/code-snippets",
|
||||||
|
"keywords": [
|
||||||
|
"code",
|
||||||
|
"code-snippets",
|
||||||
|
"permafrost",
|
||||||
|
"snippets"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/permafrost-dev/code-snippets/issues",
|
||||||
|
"source": "https://github.com/permafrost-dev/code-snippets/tree/1.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://permafrost.dev/open-source",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/permafrost-dev",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-07-27T05:15:06+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "permafrost-dev/php-code-search",
|
||||||
|
"version": "1.12.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/permafrost-dev/php-code-search.git",
|
||||||
|
"reference": "dbbca18f7dc2950e88121bb62f8ed2c697df799a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/permafrost-dev/php-code-search/zipball/dbbca18f7dc2950e88121bb62f8ed2c697df799a",
|
||||||
|
"reference": "dbbca18f7dc2950e88121bb62f8ed2c697df799a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"nikic/php-parser": "^5.0",
|
||||||
|
"permafrost-dev/code-snippets": "^1.2.0",
|
||||||
|
"php": "^7.4|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"spatie/phpunit-snapshot-assertions": "^4.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/Support/helpers.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Permafrost\\PhpCodeSearch\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Patrick Organ",
|
||||||
|
"email": "patrick@permafrost.dev",
|
||||||
|
"homepage": "https://permafrost.dev",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Search PHP code for function & method calls, variable assignments, and more",
|
||||||
|
"homepage": "https://github.com/permafrost-dev/php-code-search",
|
||||||
|
"keywords": [
|
||||||
|
"code",
|
||||||
|
"permafrost",
|
||||||
|
"php",
|
||||||
|
"search",
|
||||||
|
"sourcecode"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/permafrost-dev/php-code-search/issues",
|
||||||
|
"source": "https://github.com/permafrost-dev/php-code-search/tree/1.12.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/permafrost-dev",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-03T04:33:45+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phar-io/manifest",
|
"name": "phar-io/manifest",
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
|
@ -12169,6 +12296,78 @@
|
||||||
],
|
],
|
||||||
"time": "2025-03-21T08:56:30+00:00"
|
"time": "2025-03-21T08:56:30+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/x-ray",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/x-ray.git",
|
||||||
|
"reference": "c1d8fe19951b752422d058fc911f14066e4ac346"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/x-ray/zipball/c1d8fe19951b752422d058fc911f14066e4ac346",
|
||||||
|
"reference": "c1d8fe19951b752422d058fc911f14066e4ac346",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"permafrost-dev/code-snippets": "^1.2.0",
|
||||||
|
"permafrost-dev/php-code-search": "^1.10.5",
|
||||||
|
"php": "^8.0",
|
||||||
|
"symfony/console": "^5.3|^6.0|^7.0",
|
||||||
|
"symfony/finder": "^5.3|^6.0|^7.0",
|
||||||
|
"symfony/yaml": "^5.3|^6.0|^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0.0",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"spatie/phpunit-snapshot-assertions": "^4.2"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/x-ray"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\XRay\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Patrick Organ",
|
||||||
|
"email": "patrick@permafrost.dev",
|
||||||
|
"homepage": "https://permafrost.dev",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Quickly scan source code for calls to Ray",
|
||||||
|
"homepage": "https://github.com/spatie/x-ray",
|
||||||
|
"keywords": [
|
||||||
|
"permafrost",
|
||||||
|
"ray",
|
||||||
|
"search",
|
||||||
|
"spatie"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/x-ray/issues",
|
||||||
|
"source": "https://github.com/spatie/x-ray/tree/1.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/permafrost-dev",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-11-12T13:23:31+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "staabm/side-effects-detector",
|
"name": "staabm/side-effects-detector",
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
|
|
|
@ -25,6 +25,7 @@ use App\Http\Controllers\PlacesController;
|
||||||
use App\Http\Controllers\SearchController;
|
use App\Http\Controllers\SearchController;
|
||||||
use App\Http\Controllers\WebMentionsController;
|
use App\Http\Controllers\WebMentionsController;
|
||||||
use App\Http\Middleware\CorsHeaders;
|
use App\Http\Middleware\CorsHeaders;
|
||||||
|
use App\Http\Middleware\LogMicropubRequest;
|
||||||
use App\Http\Middleware\MyAuthMiddleware;
|
use App\Http\Middleware\MyAuthMiddleware;
|
||||||
use App\Http\Middleware\VerifyMicropubToken;
|
use App\Http\Middleware\VerifyMicropubToken;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
@ -197,7 +198,9 @@ Route::post('token', [IndieAuthController::class, 'processTokenRequest'])->name(
|
||||||
|
|
||||||
// Micropub Endpoints
|
// Micropub Endpoints
|
||||||
Route::get('api/post', [MicropubController::class, 'get'])->middleware(VerifyMicropubToken::class);
|
Route::get('api/post', [MicropubController::class, 'get'])->middleware(VerifyMicropubToken::class);
|
||||||
Route::post('api/post', [MicropubController::class, 'post'])->middleware(VerifyMicropubToken::class)->name('micropub-endpoint');
|
Route::post('api/post', [MicropubController::class, 'post'])
|
||||||
|
->middleware([LogMicropubRequest::class, VerifyMicropubToken::class])
|
||||||
|
->name('micropub-endpoint');
|
||||||
Route::get('api/media', [MicropubMediaController::class, 'getHandler'])->middleware(VerifyMicropubToken::class);
|
Route::get('api/media', [MicropubMediaController::class, 'getHandler'])->middleware(VerifyMicropubToken::class);
|
||||||
Route::post('api/media', [MicropubMediaController::class, 'media'])
|
Route::post('api/media', [MicropubMediaController::class, 'media'])
|
||||||
->middleware([VerifyMicropubToken::class, CorsHeaders::class])
|
->middleware([VerifyMicropubToken::class, CorsHeaders::class])
|
||||||
|
|
|
@ -11,9 +11,9 @@ use App\Models\Media;
|
||||||
use App\Models\Note;
|
use App\Models\Note;
|
||||||
use App\Models\Place;
|
use App\Models\Place;
|
||||||
use App\Models\SyndicationTarget;
|
use App\Models\SyndicationTarget;
|
||||||
use Carbon\Carbon;
|
|
||||||
use Faker\Factory;
|
use Faker\Factory;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Queue;
|
use Illuminate\Support\Facades\Queue;
|
||||||
use PHPUnit\Framework\Attributes\Test;
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
@ -106,16 +106,16 @@ class MicropubControllerTest extends TestCase
|
||||||
{
|
{
|
||||||
$faker = Factory::create();
|
$faker = Factory::create();
|
||||||
$note = $faker->text;
|
$note = $faker->text;
|
||||||
|
|
||||||
$response = $this->post(
|
$response = $this->post(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
'h' => 'entry',
|
'h' => 'entry',
|
||||||
'content' => $note,
|
'content' => $note,
|
||||||
'published' => Carbon::now()->toW3CString(),
|
|
||||||
'location' => 'geo:1.23,4.56',
|
|
||||||
],
|
],
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
);
|
);
|
||||||
|
|
||||||
$response->assertJson(['response' => 'created']);
|
$response->assertJson(['response' => 'created']);
|
||||||
$this->assertDatabaseHas('notes', ['note' => $note]);
|
$this->assertDatabaseHas('notes', ['note' => $note]);
|
||||||
}
|
}
|
||||||
|
@ -223,14 +223,13 @@ class MicropubControllerTest extends TestCase
|
||||||
$response = $this->post(
|
$response = $this->post(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
'h' => 'card',
|
'h' => 'entry',
|
||||||
'name' => 'The Barton Arms',
|
'content' => 'A random note',
|
||||||
'geo' => 'geo:53.4974,-2.3768',
|
|
||||||
],
|
],
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
||||||
);
|
);
|
||||||
$response->assertStatus(401);
|
$response->assertStatus(403);
|
||||||
$response->assertJson(['error' => 'insufficient_scope']);
|
$response->assertJson(['error' => 'invalid_scope']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -424,10 +423,10 @@ class MicropubControllerTest extends TestCase
|
||||||
);
|
);
|
||||||
$response
|
$response
|
||||||
->assertJson([
|
->assertJson([
|
||||||
'response' => 'error',
|
'error' => 'invalid_scope',
|
||||||
'error' => 'insufficient_scope',
|
'error_description' => 'The token does not have the required scope for this request',
|
||||||
])
|
])
|
||||||
->assertStatus(401);
|
->assertStatus(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
|
@ -436,7 +435,7 @@ class MicropubControllerTest extends TestCase
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
'type' => ['h-unsopported'], // a request type I don’t support
|
'type' => ['h-unsupported'], // a request type I don’t support
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'content' => ['Some content'],
|
'content' => ['Some content'],
|
||||||
],
|
],
|
||||||
|
@ -445,8 +444,8 @@ class MicropubControllerTest extends TestCase
|
||||||
);
|
);
|
||||||
$response
|
$response
|
||||||
->assertJson([
|
->assertJson([
|
||||||
'response' => 'error',
|
'error' => 'Unknown Micropub type',
|
||||||
'error_description' => 'unsupported_request_type',
|
'error_description' => 'The request could not be processed by this server',
|
||||||
])
|
])
|
||||||
->assertStatus(500);
|
->assertStatus(500);
|
||||||
}
|
}
|
||||||
|
@ -460,8 +459,8 @@ class MicropubControllerTest extends TestCase
|
||||||
[
|
[
|
||||||
'type' => ['h-card'],
|
'type' => ['h-card'],
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'name' => $faker->name,
|
'name' => [$faker->name],
|
||||||
'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude,
|
'geo' => ['geo:' . $faker->latitude . ',' . $faker->longitude],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
@ -480,8 +479,8 @@ class MicropubControllerTest extends TestCase
|
||||||
[
|
[
|
||||||
'type' => ['h-card'],
|
'type' => ['h-card'],
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'name' => $faker->name,
|
'name' => [$faker->name],
|
||||||
'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude . ';u=35',
|
'geo' => ['geo:' . $faker->latitude . ',' . $faker->longitude . ';u=35'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
@ -494,6 +493,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_updates_existing_note(): void
|
public function micropub_client_api_request_updates_existing_note(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$note = Note::factory()->create();
|
$note = Note::factory()->create();
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
|
@ -514,6 +515,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_updates_note_syndication_links(): void
|
public function micropub_client_api_request_updates_note_syndication_links(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$note = Note::factory()->create();
|
$note = Note::factory()->create();
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
|
@ -541,6 +544,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_adds_image_to_note(): void
|
public function micropub_client_api_request_adds_image_to_note(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$note = Note::factory()->create();
|
$note = Note::factory()->create();
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
|
@ -564,6 +569,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_returns_error_trying_to_update_non_note_model(): void
|
public function micropub_client_api_request_returns_error_trying_to_update_non_note_model(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
|
@ -583,6 +590,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_returns_error_trying_to_update_non_existing_note(): void
|
public function micropub_client_api_request_returns_error_trying_to_update_non_existing_note(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
|
@ -602,6 +611,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_returns_error_when_trying_to_update_unsupported_property(): void
|
public function micropub_client_api_request_returns_error_when_trying_to_update_unsupported_property(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$note = Note::factory()->create();
|
$note = Note::factory()->create();
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
|
@ -622,6 +633,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_with_token_with_insufficient_scope_returns_error(): void
|
public function micropub_client_api_request_with_token_with_insufficient_scope_returns_error(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
|
@ -641,6 +654,8 @@ class MicropubControllerTest extends TestCase
|
||||||
#[Test]
|
#[Test]
|
||||||
public function micropub_client_api_request_can_replace_note_syndication_targets(): void
|
public function micropub_client_api_request_can_replace_note_syndication_targets(): void
|
||||||
{
|
{
|
||||||
|
$this->markTestSkipped('Update requests are not supported yet');
|
||||||
|
|
||||||
$note = Note::factory()->create();
|
$note = Note::factory()->create();
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
|
@ -695,8 +710,8 @@ class MicropubControllerTest extends TestCase
|
||||||
[
|
[
|
||||||
'type' => ['h-entry'],
|
'type' => ['h-entry'],
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'name' => $name,
|
'name' => [$name],
|
||||||
'content' => $content,
|
'content' => [$content],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Tests\Feature;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use PHPUnit\Framework\Attributes\Test;
|
|
||||||
use Tests\TestCase;
|
|
||||||
use Tests\TestToken;
|
|
||||||
|
|
||||||
class OwnYourGramTest extends TestCase
|
|
||||||
{
|
|
||||||
use RefreshDatabase;
|
|
||||||
use TestToken;
|
|
||||||
|
|
||||||
#[Test]
|
|
||||||
public function posting_instagram_url_saves_media_path(): void
|
|
||||||
{
|
|
||||||
$response = $this->json(
|
|
||||||
'POST',
|
|
||||||
'/api/post',
|
|
||||||
[
|
|
||||||
'type' => ['h-entry'],
|
|
||||||
'properties' => [
|
|
||||||
'content' => ['How beautiful are the plates and chopsticks'],
|
|
||||||
'published' => [Carbon::now()->toIso8601String()],
|
|
||||||
'location' => ['geo:53.802419075834,-1.5431942917637'],
|
|
||||||
'syndication' => ['https://www.instagram.com/p/BVC_nVTBFfi/'],
|
|
||||||
'photo' => [
|
|
||||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
|
||||||
'https://scontent-sjc2-1.cdninstagram.com/t51.2885-15/e35/18888604_425332491185600_326487281944756224_n.jpg',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
|
||||||
);
|
|
||||||
|
|
||||||
$response->assertStatus(201)->assertJson([
|
|
||||||
'response' => 'created',
|
|
||||||
]);
|
|
||||||
$this->assertDatabaseHas('media_endpoint', [
|
|
||||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
|
||||||
'path' => 'https://scontent-sjc2-1.cdninstagram.com/t51.2885-15/e35/18888604_425332491185600_326487281944756224_n.jpg',
|
|
||||||
]);
|
|
||||||
$this->assertDatabaseHas('notes', [
|
|
||||||
'note' => 'How beautiful are the plates and chopsticks',
|
|
||||||
'instagram_url' => 'https://www.instagram.com/p/BVC_nVTBFfi/',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ use App\Services\TokenService;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Lcobucci\JWT\Configuration;
|
use Lcobucci\JWT\Configuration;
|
||||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||||
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
|
||||||
use PHPUnit\Framework\Attributes\Test;
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ class TokenServiceTest extends TestCase
|
||||||
'me' => $data['me'],
|
'me' => $data['me'],
|
||||||
'client_id' => $data['client_id'],
|
'client_id' => $data['client_id'],
|
||||||
'scope' => $data['scope'],
|
'scope' => $data['scope'],
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue