Compare commits
No commits in common. "83d10e1a7077dbcd224a2850ecdc23e64366fcc5" and "70f90dd45669c4d682db6e6514a7b166dfd0e857" have entirely different histories.
83d10e1a70
...
70f90dd456
28 changed files with 480 additions and 792 deletions
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Exceptions;
|
|
||||||
|
|
||||||
class InvalidTokenScopeException extends \Exception {}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Exceptions;
|
|
||||||
|
|
||||||
class MicropubHandlerException extends \Exception {}
|
|
|
@ -4,73 +4,120 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Exceptions\InvalidTokenScopeException;
|
use App\Http\Responses\MicropubResponses;
|
||||||
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\MicropubHandlerRegistry;
|
use App\Services\Micropub\HCardService;
|
||||||
|
use App\Services\Micropub\HEntryService;
|
||||||
|
use App\Services\Micropub\UpdateService;
|
||||||
|
use App\Services\TokenService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Lcobucci\JWT\Token;
|
use Lcobucci\JWT\Encoding\CannotDecodeContent;
|
||||||
|
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
||||||
|
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
|
||||||
class MicropubController extends Controller
|
class MicropubController extends Controller
|
||||||
{
|
{
|
||||||
protected MicropubHandlerRegistry $handlerRegistry;
|
protected TokenService $tokenService;
|
||||||
|
|
||||||
public function __construct(MicropubHandlerRegistry $handlerRegistry)
|
protected HEntryService $hentryService;
|
||||||
{
|
|
||||||
$this->handlerRegistry = $handlerRegistry;
|
protected HCardService $hcardService;
|
||||||
|
|
||||||
|
protected UpdateService $updateService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TokenService $tokenService,
|
||||||
|
HEntryService $hentryService,
|
||||||
|
HCardService $hcardService,
|
||||||
|
UpdateService $updateService
|
||||||
|
) {
|
||||||
|
$this->tokenService = $tokenService;
|
||||||
|
$this->hentryService = $hentryService;
|
||||||
|
$this->hcardService = $hcardService;
|
||||||
|
$this->updateService = $updateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Respond to a POST request to the micropub endpoint.
|
* This function receives an API request, verifies the authenticity
|
||||||
*
|
* 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(MicropubRequest $request): JsonResponse
|
public function post(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$type = $request->getType();
|
|
||||||
|
|
||||||
if (! $type) {
|
|
||||||
return response()->json([
|
|
||||||
'error' => 'invalid_request',
|
|
||||||
'error_description' => 'Microformat object type is missing, for example: h-entry or h-card',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$handler = $this->handlerRegistry->getHandler($type);
|
$tokenData = $this->tokenService->validateToken($request->input('access_token'));
|
||||||
$result = $handler->handle($request->getMicropubData());
|
} catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
// Return appropriate response based on the handler result
|
return $micropubResponses->invalidTokenResponse();
|
||||||
return response()->json([
|
|
||||||
'response' => $result['response'],
|
|
||||||
'location' => $result['url'] ?? null,
|
|
||||||
], 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 ($tokenData->claims()->has('scope') === false) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
|
return $micropubResponses->tokenHasNoScopeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logMicropubRequest($request->all());
|
||||||
|
|
||||||
|
if (($request->input('h') === 'entry') || ($request->input('type.0') === 'h-entry')) {
|
||||||
|
$scopes = $tokenData->claims()->get('scope');
|
||||||
|
if (is_string($scopes)) {
|
||||||
|
$scopes = explode(' ', $scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array('create', $scopes)) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
|
}
|
||||||
|
$location = $this->hentryService->process($request->all(), $this->getCLientId());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'created',
|
||||||
|
'location' => $location,
|
||||||
|
], 201)->header('Location', $location);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('h') === 'card' || $request->input('type.0') === 'h-card') {
|
||||||
|
$scopes = $tokenData->claims()->get('scope');
|
||||||
|
if (is_string($scopes)) {
|
||||||
|
$scopes = explode(' ', $scopes);
|
||||||
|
}
|
||||||
|
if (! in_array('create', $scopes)) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
|
}
|
||||||
|
$location = $this->hcardService->process($request->all());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'created',
|
||||||
|
'location' => $location,
|
||||||
|
], 201)->header('Location', $location);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('action') === 'update') {
|
||||||
|
$scopes = $tokenData->claims()->get('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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,6 +130,12 @@ class MicropubController extends Controller
|
||||||
*/
|
*/
|
||||||
public function get(Request $request): JsonResponse
|
public function get(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$tokenData = $this->tokenService->validateToken($request->input('access_token'));
|
||||||
|
} catch (RequiredConstraintsViolated|InvalidTokenStructure) {
|
||||||
|
return (new MicropubResponses)->invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->input('q') === 'syndicate-to') {
|
if ($request->input('q') === 'syndicate-to') {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'syndicate-to' => SyndicationTarget::all(),
|
'syndicate-to' => SyndicationTarget::all(),
|
||||||
|
@ -114,17 +167,36 @@ class MicropubController extends Controller
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the default response is just to return the token data
|
// default response is just to return the token data
|
||||||
/** @var Token $tokenData */
|
|
||||||
$tokenData = $request->input('token_data');
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'response' => 'token',
|
'response' => 'token',
|
||||||
'token' => [
|
'token' => [
|
||||||
'me' => $tokenData['me'],
|
'me' => $tokenData->claims()->get('me'),
|
||||||
'scope' => $tokenData['scope'],
|
'scope' => $tokenData->claims()->get('scope'),
|
||||||
'client_id' => $tokenData['client_id'],
|
'client_id' => $tokenData->claims()->get('client_id'),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the client id from the access token sent with the request.
|
||||||
|
*
|
||||||
|
* @throws RequiredConstraintsViolated
|
||||||
|
*/
|
||||||
|
private function getClientId(): string
|
||||||
|
{
|
||||||
|
return resolve(TokenService::class)
|
||||||
|
->validateToken(app('request')->input('access_token'))
|
||||||
|
->claims()->get('client_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,10 @@ namespace App\Http\Controllers;
|
||||||
use App\Http\Responses\MicropubResponses;
|
use App\Http\Responses\MicropubResponses;
|
||||||
use App\Jobs\ProcessMedia;
|
use App\Jobs\ProcessMedia;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
|
use App\Services\TokenService;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Http\File;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
@ -16,20 +18,43 @@ use Illuminate\Http\UploadedFile;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Intervention\Image\ImageManager;
|
use Intervention\Image\ImageManager;
|
||||||
|
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
||||||
|
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
class MicropubMediaController extends Controller
|
class MicropubMediaController extends Controller
|
||||||
{
|
{
|
||||||
|
protected TokenService $tokenService;
|
||||||
|
|
||||||
|
public function __construct(TokenService $tokenService)
|
||||||
|
{
|
||||||
|
$this->tokenService = $tokenService;
|
||||||
|
}
|
||||||
|
|
||||||
public function getHandler(Request $request): JsonResponse
|
public function getHandler(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$tokenData = $request->input('token_data');
|
try {
|
||||||
|
$tokenData = $this->tokenService->validateToken($request->input('access_token'));
|
||||||
|
} catch (RequiredConstraintsViolated|InvalidTokenStructure) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
$scopes = $tokenData['scope'];
|
return $micropubResponses->invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tokenData->claims()->has('scope') === false) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
|
return $micropubResponses->tokenHasNoScopeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scopes = $tokenData->claims()->get('scope');
|
||||||
if (is_string($scopes)) {
|
if (is_string($scopes)) {
|
||||||
$scopes = explode(' ', $scopes);
|
$scopes = explode(' ', $scopes);
|
||||||
}
|
}
|
||||||
if (! in_array('create', $scopes, true)) {
|
if (! in_array('create', $scopes)) {
|
||||||
return (new MicropubResponses)->insufficientScopeResponse();
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->input('q') === 'last') {
|
if ($request->input('q') === 'last') {
|
||||||
|
@ -80,14 +105,28 @@ class MicropubMediaController extends Controller
|
||||||
*/
|
*/
|
||||||
public function media(Request $request): JsonResponse
|
public function media(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$tokenData = $request->input('token_data');
|
try {
|
||||||
|
$tokenData = $this->tokenService->validateToken($request->input('access_token'));
|
||||||
|
} catch (RequiredConstraintsViolated|InvalidTokenStructure) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
$scopes = $tokenData['scope'];
|
return $micropubResponses->invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tokenData->claims()->has('scope') === false) {
|
||||||
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
|
return $micropubResponses->tokenHasNoScopeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scopes = $tokenData->claims()->get('scope');
|
||||||
if (is_string($scopes)) {
|
if (is_string($scopes)) {
|
||||||
$scopes = explode(' ', $scopes);
|
$scopes = explode(' ', $scopes);
|
||||||
}
|
}
|
||||||
if (! in_array('create', $scopes, true)) {
|
if (! in_array('create', $scopes)) {
|
||||||
return (new MicropubResponses)->insufficientScopeResponse();
|
$micropubResponses = new MicropubResponses;
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->hasFile('file') === false) {
|
if ($request->hasFile('file') === false) {
|
||||||
|
@ -122,7 +161,7 @@ class MicropubMediaController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$media = Media::create([
|
$media = Media::create([
|
||||||
'token' => $request->input('access_token'),
|
'token' => $request->bearerToken(),
|
||||||
'path' => $filename,
|
'path' => $filename,
|
||||||
'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()),
|
'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()),
|
||||||
'image_widths' => $width,
|
'image_widths' => $width,
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,78 +4,31 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
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\Token;
|
|
||||||
use Lcobucci\JWT\Token\InvalidTokenStructure;
|
|
||||||
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
class VerifyMicropubToken
|
class VerifyMicropubToken
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
|
||||||
* @param Closure(Request): (Response) $next
|
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
$rawToken = null;
|
|
||||||
|
|
||||||
if ($request->input('access_token')) {
|
if ($request->input('access_token')) {
|
||||||
$rawToken = $request->input('access_token');
|
return $next($request);
|
||||||
} elseif ($request->bearerToken()) {
|
|
||||||
$rawToken = $request->bearerToken();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $rawToken) {
|
if ($request->bearerToken()) {
|
||||||
return response()->json([
|
return $next($request->merge([
|
||||||
'response' => 'error',
|
'access_token' => $request->bearerToken(),
|
||||||
'error' => 'unauthorized',
|
]));
|
||||||
'error_description' => 'No access token was provided in the request',
|
|
||||||
], 401);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return response()->json([
|
||||||
$tokenData = $this->validateToken($rawToken);
|
'response' => 'error',
|
||||||
} catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) {
|
'error' => 'unauthorized',
|
||||||
$micropubResponses = new MicropubResponses;
|
'error_description' => 'No access token was provided in the request',
|
||||||
|
], 401);
|
||||||
return $micropubResponses->invalidTokenResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tokenData->claims()->has('scope') === false) {
|
|
||||||
$micropubResponses = new MicropubResponses;
|
|
||||||
|
|
||||||
return $micropubResponses->tokenHasNoScopeResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $next($request->merge([
|
|
||||||
'access_token' => $rawToken,
|
|
||||||
'token_data' => [
|
|
||||||
'me' => $tokenData->claims()->get('me'),
|
|
||||||
'scope' => $tokenData->claims()->get('scope'),
|
|
||||||
'client_id' => $tokenData->claims()->get('client_id'),
|
|
||||||
],
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the token signature is valid.
|
|
||||||
*/
|
|
||||||
private function validateToken(string $bearerToken): Token
|
|
||||||
{
|
|
||||||
$config = resolve(Configuration::class);
|
|
||||||
|
|
||||||
$token = $config->parser()->parse($bearerToken);
|
|
||||||
|
|
||||||
$constraints = $config->validationConstraints();
|
|
||||||
|
|
||||||
$config->validator()->assert($token, ...$constraints);
|
|
||||||
|
|
||||||
return $token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?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
|
class ArticleService extends Service
|
||||||
{
|
{
|
||||||
public function create(array $data): Article
|
public function create(array $request, ?string $client = null): Article
|
||||||
{
|
{
|
||||||
return Article::create([
|
return Article::create([
|
||||||
'title' => $data['name'],
|
'title' => $this->getDataByKey($request, 'name'),
|
||||||
'main' => $data['content'],
|
'main' => $this->getDataByKey($request, 'content'),
|
||||||
'published' => true,
|
'published' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,29 +10,28 @@ 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
|
class BookmarkService extends Service
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create a new Bookmark.
|
* Create a new Bookmark.
|
||||||
*/
|
*/
|
||||||
public function create(array $data): Bookmark
|
public function create(array $request, ?string $client = null): Bookmark
|
||||||
{
|
{
|
||||||
if (Arr::get($data, 'properties.bookmark-of.0')) {
|
if (Arr::get($request, 'properties.bookmark-of.0')) {
|
||||||
// micropub request
|
// micropub request
|
||||||
$url = normalize_url(Arr::get($data, 'properties.bookmark-of.0'));
|
$url = normalize_url(Arr::get($request, 'properties.bookmark-of.0'));
|
||||||
$name = Arr::get($data, 'properties.name.0');
|
$name = Arr::get($request, 'properties.name.0');
|
||||||
$content = Arr::get($data, 'properties.content.0');
|
$content = Arr::get($request, 'properties.content.0');
|
||||||
$categories = Arr::get($data, 'properties.category');
|
$categories = Arr::get($request, 'properties.category');
|
||||||
}
|
}
|
||||||
if (Arr::get($data, 'bookmark-of')) {
|
if (Arr::get($request, 'bookmark-of')) {
|
||||||
$url = normalize_url(Arr::get($data, 'bookmark-of'));
|
$url = normalize_url(Arr::get($request, 'bookmark-of'));
|
||||||
$name = Arr::get($data, 'name');
|
$name = Arr::get($request, 'name');
|
||||||
$content = Arr::get($data, 'content');
|
$content = Arr::get($request, 'content');
|
||||||
$categories = Arr::get($data, 'category');
|
$categories = Arr::get($request, 'category');
|
||||||
}
|
}
|
||||||
|
|
||||||
$bookmark = Bookmark::create([
|
$bookmark = Bookmark::create([
|
||||||
|
@ -55,7 +54,6 @@ class BookmarkService
|
||||||
* 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
|
class LikeService extends Service
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create a new Like.
|
* Create a new Like.
|
||||||
*/
|
*/
|
||||||
public function create(array $data): Like
|
public function create(array $request, ?string $client = null): Like
|
||||||
{
|
{
|
||||||
if (Arr::get($data, 'properties.like-of.0')) {
|
if (Arr::get($request, 'properties.like-of.0')) {
|
||||||
// micropub request
|
// micropub request
|
||||||
$url = normalize_url(Arr::get($data, 'properties.like-of.0'));
|
$url = normalize_url(Arr::get($request, 'properties.like-of.0'));
|
||||||
}
|
}
|
||||||
if (Arr::get($data, 'like-of')) {
|
if (Arr::get($request, 'like-of')) {
|
||||||
$url = normalize_url(Arr::get($data, 'like-of'));
|
$url = normalize_url(Arr::get($request, 'like-of'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$like = Like::create(['url' => $url]);
|
$like = Like::create(['url' => $url]);
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
<?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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
32
app/Services/Micropub/HCardService.php
Normal file
32
app/Services/Micropub/HCardService.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
34
app/Services/Micropub/HEntryService.php
Normal file
34
app/Services/Micropub/HEntryService.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Micropub;
|
|
||||||
|
|
||||||
interface MicropubHandlerInterface
|
|
||||||
{
|
|
||||||
public function handle(array $data);
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?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,33 +4,21 @@ 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
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @throws InvalidTokenScopeException
|
* Process a micropub request to update an entry.
|
||||||
*/
|
*/
|
||||||
public function handle(array $data)
|
public function process(array $request): JsonResponse
|
||||||
{
|
{
|
||||||
$scopes = $data['token_data']['scope'];
|
$urlPath = parse_url(Arr::get($request, 'url'), PHP_URL_PATH);
|
||||||
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') {
|
||||||
|
@ -42,7 +30,7 @@ class UpdateHandler implements MicropubHandlerInterface
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$note = Note::nb60(basename($urlPath))->firstOrFail();
|
$note = Note::nb60(basename($urlPath))->firstOrFail();
|
||||||
} catch (ModelNotFoundException) {
|
} catch (ModelNotFoundException $exception) {
|
||||||
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',
|
||||||
|
@ -50,8 +38,8 @@ class UpdateHandler implements MicropubHandlerInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// got the note, are we dealing with a “replace” request?
|
// got the note, are we dealing with a “replace” request?
|
||||||
if (Arr::get($data, 'replace')) {
|
if (Arr::get($request, 'replace')) {
|
||||||
foreach (Arr::get($data, 'replace') as $property => $value) {
|
foreach (Arr::get($request, 'replace') as $property => $value) {
|
||||||
if ($property === 'content') {
|
if ($property === 'content') {
|
||||||
$note->note = $value[0];
|
$note->note = $value[0];
|
||||||
}
|
}
|
||||||
|
@ -71,14 +59,14 @@ class UpdateHandler implements MicropubHandlerInterface
|
||||||
}
|
}
|
||||||
$note->save();
|
$note->save();
|
||||||
|
|
||||||
return [
|
return response()->json([
|
||||||
'response' => 'updated',
|
'response' => 'updated',
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// how about “add”
|
// how about “add”
|
||||||
if (Arr::get($data, 'add')) {
|
if (Arr::get($request, 'add')) {
|
||||||
foreach (Arr::get($data, 'add') as $property => $value) {
|
foreach (Arr::get($request, '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,52 +14,49 @@ use App\Models\SyndicationTarget;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class NoteService
|
class NoteService extends Service
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Create a new note.
|
* Create a new note.
|
||||||
*/
|
*/
|
||||||
public function create(array $data): Note
|
public function create(array $request, ?string $client = null): 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' => $content,
|
'note' => $this->getDataByKey($request, 'content'),
|
||||||
'in_reply_to' => $data['in-reply-to'],
|
'in_reply_to' => $this->getDataByKey($request, 'in-reply-to'),
|
||||||
'client_id' => $data['token_data']['client_id'],
|
'client_id' => $client,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($published = $this->getPublished($data)) {
|
if ($this->getPublished($request)) {
|
||||||
$note->created_at = $note->updated_at = $published;
|
$note->created_at = $note->updated_at = $this->getPublished($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$note->location = $this->getLocation($data);
|
$note->location = $this->getLocation($request);
|
||||||
|
|
||||||
if ($this->getCheckin($data)) {
|
if ($this->getCheckin($request)) {
|
||||||
$note->place()->associate($this->getCheckin($data));
|
$note->place()->associate($this->getCheckin($request));
|
||||||
$note->swarm_url = $this->getSwarmUrl($data);
|
$note->swarm_url = $this->getSwarmUrl($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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));
|
||||||
|
|
||||||
$this->dispatchSyndicationJobs($note, $data);
|
if (in_array('mastodon', $this->getSyndicationTargets($request), true)) {
|
||||||
|
dispatch(new SyndicateNoteToMastodon($note));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('bluesky', $this->getSyndicationTargets($request), true)) {
|
||||||
|
dispatch(new SyndicateNoteToBluesky($note));
|
||||||
|
}
|
||||||
|
|
||||||
return $note;
|
return $note;
|
||||||
}
|
}
|
||||||
|
@ -67,10 +64,14 @@ class NoteService
|
||||||
/**
|
/**
|
||||||
* 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 $data): ?string
|
private function getPublished(array $request): ?string
|
||||||
{
|
{
|
||||||
if ($data['published']) {
|
if (Arr::get($request, 'properties.published.0')) {
|
||||||
return carbon($data['published'])->toDateTimeString();
|
return carbon(Arr::get($request, 'properties.published.0'))
|
||||||
|
->toDateTimeString();
|
||||||
|
}
|
||||||
|
if (Arr::get($request, 'published')) {
|
||||||
|
return carbon(Arr::get($request, 'published'))->toDateTimeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -79,13 +80,12 @@ class NoteService
|
||||||
/**
|
/**
|
||||||
* 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 $data): ?string
|
private function getLocation(array $request): ?string
|
||||||
{
|
{
|
||||||
$location = Arr::get($data, 'location');
|
$location = Arr::get($request, 'properties.location.0') ?? Arr::get($request, '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
|
||||||
/**
|
/**
|
||||||
* 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 $data): ?Place
|
private function getCheckin(array $request): ?Place
|
||||||
{
|
{
|
||||||
$location = Arr::get($data, 'location');
|
$location = Arr::get($request, '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
|
||||||
)
|
)
|
||||||
)->first();
|
)->first();
|
||||||
}
|
}
|
||||||
if (Arr::get($data, 'checkin')) {
|
if (Arr::get($request, 'checkin')) {
|
||||||
try {
|
try {
|
||||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||||
Arr::get($data, 'checkin')
|
Arr::get($request, 'checkin')
|
||||||
);
|
);
|
||||||
} catch (\InvalidArgumentException) {
|
} catch (\InvalidArgumentException $e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,47 +142,34 @@ class NoteService
|
||||||
/**
|
/**
|
||||||
* 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 $data): ?string
|
private function getSwarmUrl(array $request): ?string
|
||||||
{
|
{
|
||||||
$syndication = Arr::get($data, 'syndication');
|
if (str_contains(Arr::get($request, 'properties.syndication.0', ''), 'swarmapp')) {
|
||||||
if ($syndication === null) {
|
return Arr::get($request, 'properties.syndication.0');
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str_contains($syndication, 'swarmapp')) {
|
|
||||||
return $syndication;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch syndication jobs based on the request data.
|
* Get the syndication targets from the request to create a new note.
|
||||||
*/
|
*/
|
||||||
private function dispatchSyndicationJobs(Note $note, array $request): void
|
private function getSyndicationTargets(array $request): array
|
||||||
{
|
{
|
||||||
// If no syndication targets are specified, return early
|
$syndication = [];
|
||||||
if (empty($request['mp-syndicate-to'])) {
|
$mpSyndicateTo = Arr::get($request, 'mp-syndicate-to') ?? Arr::get($request, 'properties.mp-syndicate-to');
|
||||||
return;
|
$mpSyndicateTo = Arr::wrap($mpSyndicateTo);
|
||||||
}
|
foreach ($mpSyndicateTo as $uid) {
|
||||||
|
$target = SyndicationTarget::where('uid', $uid)->first();
|
||||||
// Get the configured syndication targets
|
if ($target && $target->service_name === 'Mastodon') {
|
||||||
$syndicationTargets = SyndicationTarget::all();
|
$syndication[] = 'mastodon';
|
||||||
|
}
|
||||||
foreach ($syndicationTargets as $target) {
|
if ($target && $target->service_name === 'Bluesky') {
|
||||||
// Check if the target is in the request data
|
$syndication[] = 'bluesky';
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $syndication;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
30
app/Services/Service.php
Normal file
30
app/Services/Service.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ namespace App\Services;
|
||||||
use App\Jobs\AddClientToDatabase;
|
use App\Jobs\AddClientToDatabase;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Lcobucci\JWT\Configuration;
|
use Lcobucci\JWT\Configuration;
|
||||||
|
use Lcobucci\JWT\Token;
|
||||||
|
|
||||||
class TokenService
|
class TokenService
|
||||||
{
|
{
|
||||||
|
@ -29,4 +30,20 @@ class TokenService
|
||||||
|
|
||||||
return $token->toString();
|
return $token->toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the token signature is valid.
|
||||||
|
*/
|
||||||
|
public function validateToken(string $bearerToken): Token
|
||||||
|
{
|
||||||
|
$config = resolve('Lcobucci\JWT\Configuration');
|
||||||
|
|
||||||
|
$token = $config->parser()->parse($bearerToken);
|
||||||
|
|
||||||
|
$constraints = $config->validationConstraints();
|
||||||
|
|
||||||
|
$config->validator()->assert($token, ...$constraints);
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,4 @@
|
||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\HorizonServiceProvider::class,
|
App\Providers\HorizonServiceProvider::class,
|
||||||
App\Providers\MicropubServiceProvider::class,
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -49,8 +49,7 @@
|
||||||
"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": "1076b46fccbfe2c22f51fa6e904cfedf",
|
"content-hash": "cd963bfd9cfb41beb4151e73ae98dc98",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
|
@ -10079,133 +10079,6 @@
|
||||||
],
|
],
|
||||||
"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",
|
||||||
|
@ -12296,78 +12169,6 @@
|
||||||
],
|
],
|
||||||
"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,7 +25,6 @@ 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;
|
||||||
|
@ -198,9 +197,7 @@ 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'])
|
Route::post('api/post', [MicropubController::class, 'post'])->middleware(VerifyMicropubToken::class)->name('micropub-endpoint');
|
||||||
->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,13 +223,14 @@ class MicropubControllerTest extends TestCase
|
||||||
$response = $this->post(
|
$response = $this->post(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
'h' => 'entry',
|
'h' => 'card',
|
||||||
'content' => 'A random note',
|
'name' => 'The Barton Arms',
|
||||||
|
'geo' => 'geo:53.4974,-2.3768',
|
||||||
],
|
],
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
||||||
);
|
);
|
||||||
$response->assertStatus(403);
|
$response->assertStatus(401);
|
||||||
$response->assertJson(['error' => 'invalid_scope']);
|
$response->assertJson(['error' => 'insufficient_scope']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -423,10 +424,10 @@ class MicropubControllerTest extends TestCase
|
||||||
);
|
);
|
||||||
$response
|
$response
|
||||||
->assertJson([
|
->assertJson([
|
||||||
'error' => 'invalid_scope',
|
'response' => 'error',
|
||||||
'error_description' => 'The token does not have the required scope for this request',
|
'error' => 'insufficient_scope',
|
||||||
])
|
])
|
||||||
->assertStatus(403);
|
->assertStatus(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
|
@ -435,7 +436,7 @@ class MicropubControllerTest extends TestCase
|
||||||
$response = $this->postJson(
|
$response = $this->postJson(
|
||||||
'/api/post',
|
'/api/post',
|
||||||
[
|
[
|
||||||
'type' => ['h-unsupported'], // a request type I don’t support
|
'type' => ['h-unsopported'], // a request type I don’t support
|
||||||
'properties' => [
|
'properties' => [
|
||||||
'content' => ['Some content'],
|
'content' => ['Some content'],
|
||||||
],
|
],
|
||||||
|
@ -444,8 +445,8 @@ class MicropubControllerTest extends TestCase
|
||||||
);
|
);
|
||||||
$response
|
$response
|
||||||
->assertJson([
|
->assertJson([
|
||||||
'error' => 'Unknown Micropub type',
|
'response' => 'error',
|
||||||
'error_description' => 'The request could not be processed by this server',
|
'error_description' => 'unsupported_request_type',
|
||||||
])
|
])
|
||||||
->assertStatus(500);
|
->assertStatus(500);
|
||||||
}
|
}
|
||||||
|
@ -459,8 +460,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()]
|
||||||
|
@ -479,8 +480,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()]
|
||||||
|
@ -493,8 +494,6 @@ 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',
|
||||||
|
@ -515,8 +514,6 @@ 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',
|
||||||
|
@ -544,8 +541,6 @@ 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',
|
||||||
|
@ -569,8 +564,6 @@ 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',
|
||||||
[
|
[
|
||||||
|
@ -590,8 +583,6 @@ 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',
|
||||||
[
|
[
|
||||||
|
@ -611,8 +602,6 @@ 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',
|
||||||
|
@ -633,8 +622,6 @@ 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',
|
||||||
[
|
[
|
||||||
|
@ -654,8 +641,6 @@ 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',
|
||||||
|
@ -710,8 +695,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()]
|
||||||
|
|
52
tests/Feature/OwnYourGramTest.php
Normal file
52
tests/Feature/OwnYourGramTest.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?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,6 +8,7 @@ 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;
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class TokenServiceTest extends TestCase
|
||||||
* the APP_KEY, to test, we shall create a token, and then verify it.
|
* the APP_KEY, to test, we shall create a token, and then verify it.
|
||||||
*/
|
*/
|
||||||
#[Test]
|
#[Test]
|
||||||
public function tokenservice_creates_valid_tokens(): void
|
public function tokenservice_creates_and_validates_tokens(): void
|
||||||
{
|
{
|
||||||
$tokenService = new TokenService;
|
$tokenService = new TokenService;
|
||||||
$data = [
|
$data = [
|
||||||
|
@ -27,22 +28,20 @@ class TokenServiceTest extends TestCase
|
||||||
'scope' => 'post',
|
'scope' => 'post',
|
||||||
];
|
];
|
||||||
$token = $tokenService->getNewToken($data);
|
$token = $tokenService->getNewToken($data);
|
||||||
|
$valid = $tokenService->validateToken($token);
|
||||||
$response = $this->get('/api/post', ['HTTP_Authorization' => 'Bearer ' . $token]);
|
$validData = [
|
||||||
|
'me' => $valid->claims()->get('me'),
|
||||||
$response->assertJson([
|
'client_id' => $valid->claims()->get('client_id'),
|
||||||
'response' => 'token',
|
'scope' => $valid->claims()->get('scope'),
|
||||||
'token' => [
|
];
|
||||||
'me' => $data['me'],
|
$this->assertSame($data, $validData);
|
||||||
'client_id' => $data['client_id'],
|
|
||||||
'scope' => $data['scope'],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function tokens_with_different_signing_key_are_not_valid(): void
|
public function tokens_with_different_signing_key_throws_exception(): void
|
||||||
{
|
{
|
||||||
|
$this->expectException(RequiredConstraintsViolated::class);
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'me' => 'https://example.org',
|
'me' => 'https://example.org',
|
||||||
'client_id' => 'https://quill.p3k.io',
|
'client_id' => 'https://quill.p3k.io',
|
||||||
|
@ -60,12 +59,7 @@ class TokenServiceTest extends TestCase
|
||||||
->getToken($config->signer(), InMemory::plainText(random_bytes(32)))
|
->getToken($config->signer(), InMemory::plainText(random_bytes(32)))
|
||||||
->toString();
|
->toString();
|
||||||
|
|
||||||
$response = $this->get('/api/post', ['HTTP_Authorization' => 'Bearer ' . $token]);
|
$service = new TokenService;
|
||||||
|
$service->validateToken($token);
|
||||||
$response->assertJson([
|
|
||||||
'response' => 'error',
|
|
||||||
'error' => 'invalid_token',
|
|
||||||
'error_description' => 'The provided token did not pass validation',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue