Media Endpoint improvements
For the code, media related stuff is now in its own controller Added support for querying the most recent file uploaded
This commit is contained in:
parent
2193e96274
commit
4f2d3b7c2b
6 changed files with 496 additions and 338 deletions
|
@ -5,20 +5,14 @@ declare(strict_types=1);
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Exceptions\InvalidTokenException;
|
use App\Exceptions\InvalidTokenException;
|
||||||
use App\Jobs\ProcessMedia;
|
use App\Http\Responses\MicropubResponses;
|
||||||
use App\Models\{Media, Place};
|
use App\Models\Place;
|
||||||
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
|
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
|
||||||
use App\Services\TokenService;
|
use App\Services\TokenService;
|
||||||
use Exception;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
|
||||||
use Illuminate\Http\{File, JsonResponse, Response, UploadedFile};
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Intervention\Image\Exception\NotReadableException;
|
|
||||||
use Intervention\Image\ImageManager;
|
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Handler\StreamHandler;
|
||||||
use Monolog\Logger;
|
use Monolog\Logger;
|
||||||
use MStaack\LaravelPostgis\Geometries\Point;
|
use MStaack\LaravelPostgis\Geometries\Point;
|
||||||
use Ramsey\Uuid\Uuid;
|
|
||||||
|
|
||||||
class MicropubController extends Controller
|
class MicropubController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -44,24 +38,31 @@ class MicropubController extends Controller
|
||||||
* then passes over the info to the relevant Service class.
|
* then passes over the info to the relevant Service class.
|
||||||
*
|
*
|
||||||
* @return JsonResponse
|
* @return JsonResponse
|
||||||
|
* @throws InvalidTokenException
|
||||||
*/
|
*/
|
||||||
public function post(): JsonResponse
|
public function post(): JsonResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
||||||
} catch (InvalidTokenException $e) {
|
} catch (InvalidTokenException $e) {
|
||||||
return $this->invalidTokenResponse();
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->invalidTokenResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($tokenData->hasClaim('scope') === false) {
|
if ($tokenData->hasClaim('scope') === false) {
|
||||||
return $this->tokenHasNoScopeResponse();
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->tokenHasNoScopeResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logMicropubRequest(request()->all());
|
$this->logMicropubRequest(request()->all());
|
||||||
|
|
||||||
if ((request()->input('h') == 'entry') || (request()->input('type.0') == 'h-entry')) {
|
if ((request()->input('h') == 'entry') || (request()->input('type.0') == 'h-entry')) {
|
||||||
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
||||||
return $this->insufficientScopeResponse();
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
}
|
}
|
||||||
$location = $this->hentryService->process(request()->all(), $this->getCLientId());
|
$location = $this->hentryService->process(request()->all(), $this->getCLientId());
|
||||||
|
|
||||||
|
@ -73,7 +74,9 @@ class MicropubController extends Controller
|
||||||
|
|
||||||
if (request()->input('h') == 'card' || request()->input('type.0') == 'h-card') {
|
if (request()->input('h') == 'card' || request()->input('type.0') == 'h-card') {
|
||||||
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
||||||
return $this->insufficientScopeResponse();
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
}
|
}
|
||||||
$location = $this->hcardService->process(request()->all());
|
$location = $this->hcardService->process(request()->all());
|
||||||
|
|
||||||
|
@ -85,7 +88,9 @@ class MicropubController extends Controller
|
||||||
|
|
||||||
if (request()->input('action') == 'update') {
|
if (request()->input('action') == 'update') {
|
||||||
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
|
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
|
||||||
return $this->insufficientScopeResponse();
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->updateService->process(request()->all());
|
return $this->updateService->process(request()->all());
|
||||||
|
@ -112,7 +117,9 @@ class MicropubController extends Controller
|
||||||
try {
|
try {
|
||||||
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
||||||
} catch (InvalidTokenException $e) {
|
} catch (InvalidTokenException $e) {
|
||||||
return $this->invalidTokenResponse();
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->invalidTokenResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request()->input('q') === 'syndicate-to') {
|
if (request()->input('q') === 'syndicate-to') {
|
||||||
|
@ -154,131 +161,11 @@ class MicropubController extends Controller
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a media item posted to the media endpoint.
|
|
||||||
*
|
|
||||||
* @return JsonResponse
|
|
||||||
* @throws BindingResolutionException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function media(): JsonResponse
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
|
||||||
} catch (InvalidTokenException $e) {
|
|
||||||
return $this->invalidTokenResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tokenData->hasClaim('scope') === false) {
|
|
||||||
return $this->tokenHasNoScopeResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
|
||||||
return $this->insufficientScopeResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((request()->hasFile('file') && request()->file('file')->isValid()) === false) {
|
|
||||||
return response()->json([
|
|
||||||
'response' => 'error',
|
|
||||||
'error' => 'invalid_request',
|
|
||||||
'error_description' => 'The uploaded file failed validation',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->logMicropubRequest(request()->all());
|
|
||||||
|
|
||||||
$filename = $this->saveFile(request()->file('file'));
|
|
||||||
|
|
||||||
$manager = resolve(ImageManager::class);
|
|
||||||
try {
|
|
||||||
$image = $manager->make(request()->file('file'));
|
|
||||||
$width = $image->width();
|
|
||||||
} catch (NotReadableException $exception) {
|
|
||||||
// not an image
|
|
||||||
$width = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$media = Media::create([
|
|
||||||
'token' => request()->bearerToken(),
|
|
||||||
'path' => 'media/' . $filename,
|
|
||||||
'type' => $this->getFileTypeFromMimeType(request()->file('file')->getMimeType()),
|
|
||||||
'image_widths' => $width,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// put the file on S3 initially, the ProcessMedia job may edit this
|
|
||||||
Storage::disk('s3')->putFileAs(
|
|
||||||
'media',
|
|
||||||
new File(storage_path('app') . '/' . $filename),
|
|
||||||
$filename
|
|
||||||
);
|
|
||||||
|
|
||||||
ProcessMedia::dispatch($filename);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'response' => 'created',
|
|
||||||
'location' => $media->url,
|
|
||||||
], 201)->header('Location', $media->url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the relevant CORS headers to a pre-flight OPTIONS request.
|
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
|
||||||
public function mediaOptionsResponse(): Response
|
|
||||||
{
|
|
||||||
return response('OK', 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the file type from the mime-type of the uploaded file.
|
|
||||||
*
|
|
||||||
* @param string $mimeType
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function getFileTypeFromMimeType(string $mimeType): string
|
|
||||||
{
|
|
||||||
//try known images
|
|
||||||
$imageMimeTypes = [
|
|
||||||
'image/gif',
|
|
||||||
'image/jpeg',
|
|
||||||
'image/png',
|
|
||||||
'image/svg+xml',
|
|
||||||
'image/tiff',
|
|
||||||
'image/webp',
|
|
||||||
];
|
|
||||||
if (in_array($mimeType, $imageMimeTypes)) {
|
|
||||||
return 'image';
|
|
||||||
}
|
|
||||||
//try known video
|
|
||||||
$videoMimeTypes = [
|
|
||||||
'video/mp4',
|
|
||||||
'video/mpeg',
|
|
||||||
'video/ogg',
|
|
||||||
'video/quicktime',
|
|
||||||
'video/webm',
|
|
||||||
];
|
|
||||||
if (in_array($mimeType, $videoMimeTypes)) {
|
|
||||||
return 'video';
|
|
||||||
}
|
|
||||||
//try known audio types
|
|
||||||
$audioMimeTypes = [
|
|
||||||
'audio/midi',
|
|
||||||
'audio/mpeg',
|
|
||||||
'audio/ogg',
|
|
||||||
'audio/x-m4a',
|
|
||||||
];
|
|
||||||
if (in_array($mimeType, $audioMimeTypes)) {
|
|
||||||
return 'audio';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'download';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the client id from the access token sent with the request.
|
* Determine the client id from the access token sent with the request.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
|
* @throws InvalidTokenException
|
||||||
*/
|
*/
|
||||||
private function getClientId(): string
|
private function getClientId(): string
|
||||||
{
|
{
|
||||||
|
@ -295,64 +182,7 @@ class MicropubController extends Controller
|
||||||
private function logMicropubRequest(array $request)
|
private function logMicropubRequest(array $request)
|
||||||
{
|
{
|
||||||
$logger = new Logger('micropub');
|
$logger = new Logger('micropub');
|
||||||
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')), Logger::DEBUG);
|
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')));
|
||||||
$logger->debug('MicropubLog', $request);
|
$logger->debug('MicropubLog', $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save an uploaded file to the local disk.
|
|
||||||
*
|
|
||||||
* @param UploadedFile $file
|
|
||||||
* @return string
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function saveFile(UploadedFile $file): string
|
|
||||||
{
|
|
||||||
$filename = Uuid::uuid4()->toString() . '.' . $file->extension();
|
|
||||||
Storage::disk('local')->putFileAs('', $file, $filename);
|
|
||||||
|
|
||||||
return $filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a response to be returned when the token has insufficient scope.
|
|
||||||
*
|
|
||||||
* @return JsonResponse
|
|
||||||
*/
|
|
||||||
private function insufficientScopeResponse(): JsonResponse
|
|
||||||
{
|
|
||||||
return response()->json([
|
|
||||||
'response' => 'error',
|
|
||||||
'error' => 'insufficient_scope',
|
|
||||||
'error_description' => 'The token’s scope does not have the necessary requirements.',
|
|
||||||
], 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a response to be returned when the token is invalid.
|
|
||||||
*
|
|
||||||
* @return JsonResponse
|
|
||||||
*/
|
|
||||||
private function invalidTokenResponse(): JsonResponse
|
|
||||||
{
|
|
||||||
return response()->json([
|
|
||||||
'response' => 'error',
|
|
||||||
'error' => 'invalid_token',
|
|
||||||
'error_description' => 'The provided token did not pass validation',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a response to be returned when the token has no scope.
|
|
||||||
*
|
|
||||||
* @return JsonResponse
|
|
||||||
*/
|
|
||||||
private function tokenHasNoScopeResponse(): JsonResponse
|
|
||||||
{
|
|
||||||
return response()->json([
|
|
||||||
'response' => 'error',
|
|
||||||
'error' => 'invalid_request',
|
|
||||||
'error_description' => 'The provided token has no scopes',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
209
app/Http/Controllers/MicropubMediaController.php
Normal file
209
app/Http/Controllers/MicropubMediaController.php
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidTokenException;
|
||||||
|
use App\Http\Responses\MicropubResponses;
|
||||||
|
use App\Jobs\ProcessMedia;
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Services\TokenService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Http\File;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Intervention\Image\Exception\NotReadableException;
|
||||||
|
use Intervention\Image\ImageManager;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
|
class MicropubMediaController extends Controller
|
||||||
|
{
|
||||||
|
protected TokenService $tokenService;
|
||||||
|
|
||||||
|
public function __construct(TokenService $tokenService)
|
||||||
|
{
|
||||||
|
$this->tokenService = $tokenService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHandler(): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
||||||
|
} catch (InvalidTokenException $e) {
|
||||||
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tokenData->hasClaim('scope') === false) {
|
||||||
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->tokenHasNoScopeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Str::contains($tokenData->getClaim('scope'), 'create') === false) {
|
||||||
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request()->input('q') === 'last') {
|
||||||
|
$media = Media::latest()->firstOrFail();
|
||||||
|
|
||||||
|
return response()->json(['url' => $media->url]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a media item posted to the media endpoint.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
* @throws BindingResolutionException
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function media(): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$tokenData = $this->tokenService->validateToken(request()->input('access_token'));
|
||||||
|
} catch (InvalidTokenException $e) {
|
||||||
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tokenData->hasClaim('scope') === false) {
|
||||||
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->tokenHasNoScopeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Str::contains($tokenData->getClaim('scope'), 'create') === false) {
|
||||||
|
$micropubResponses = new MicropubResponses();
|
||||||
|
|
||||||
|
return $micropubResponses->insufficientScopeResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request()->hasFile('file') === false) {
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'error',
|
||||||
|
'error' => 'invalid_request',
|
||||||
|
'error_description' => 'No file was sent with the request',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request()->file('file')->isValid() === false) {
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'error',
|
||||||
|
'error' => 'invalid_request',
|
||||||
|
'error_description' => 'The uploaded file failed validation',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = $this->saveFile(request()->file('file'));
|
||||||
|
|
||||||
|
$manager = resolve(ImageManager::class);
|
||||||
|
try {
|
||||||
|
$image = $manager->make(request()->file('file'));
|
||||||
|
$width = $image->width();
|
||||||
|
} catch (NotReadableException $exception) {
|
||||||
|
// not an image
|
||||||
|
$width = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$media = Media::create([
|
||||||
|
'token' => request()->bearerToken(),
|
||||||
|
'path' => 'media/' . $filename,
|
||||||
|
'type' => $this->getFileTypeFromMimeType(request()->file('file')->getMimeType()),
|
||||||
|
'image_widths' => $width,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// put the file on S3 initially, the ProcessMedia job may edit this
|
||||||
|
Storage::disk('s3')->putFileAs(
|
||||||
|
'media',
|
||||||
|
new File(storage_path('app') . '/' . $filename),
|
||||||
|
$filename
|
||||||
|
);
|
||||||
|
|
||||||
|
ProcessMedia::dispatch($filename);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'created',
|
||||||
|
'location' => $media->url,
|
||||||
|
], 201)->header('Location', $media->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the relevant CORS headers to a pre-flight OPTIONS request.
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function mediaOptionsResponse(): Response
|
||||||
|
{
|
||||||
|
return response('OK', 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file type from the mime-type of the uploaded file.
|
||||||
|
*
|
||||||
|
* @param string $mimeType
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getFileTypeFromMimeType(string $mimeType): string
|
||||||
|
{
|
||||||
|
//try known images
|
||||||
|
$imageMimeTypes = [
|
||||||
|
'image/gif',
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/svg+xml',
|
||||||
|
'image/tiff',
|
||||||
|
'image/webp',
|
||||||
|
];
|
||||||
|
if (in_array($mimeType, $imageMimeTypes)) {
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
//try known video
|
||||||
|
$videoMimeTypes = [
|
||||||
|
'video/mp4',
|
||||||
|
'video/mpeg',
|
||||||
|
'video/ogg',
|
||||||
|
'video/quicktime',
|
||||||
|
'video/webm',
|
||||||
|
];
|
||||||
|
if (in_array($mimeType, $videoMimeTypes)) {
|
||||||
|
return 'video';
|
||||||
|
}
|
||||||
|
//try known audio types
|
||||||
|
$audioMimeTypes = [
|
||||||
|
'audio/midi',
|
||||||
|
'audio/mpeg',
|
||||||
|
'audio/ogg',
|
||||||
|
'audio/x-m4a',
|
||||||
|
];
|
||||||
|
if (in_array($mimeType, $audioMimeTypes)) {
|
||||||
|
return 'audio';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'download';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an uploaded file to the local disk.
|
||||||
|
*
|
||||||
|
* @param UploadedFile $file
|
||||||
|
* @return string
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private function saveFile(UploadedFile $file): string
|
||||||
|
{
|
||||||
|
$filename = Uuid::uuid4()->toString() . '.' . $file->extension();
|
||||||
|
Storage::disk('local')->putFileAs('', $file, $filename);
|
||||||
|
|
||||||
|
return $filename;
|
||||||
|
}
|
||||||
|
}
|
52
app/Http/Responses/MicropubResponses.php
Normal file
52
app/Http/Responses/MicropubResponses.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Responses;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class MicropubResponses
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Generate a response to be returned when the token has insufficient scope.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function insufficientScopeResponse(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'error',
|
||||||
|
'error' => 'insufficient_scope',
|
||||||
|
'error_description' => 'The token’s scope does not have the necessary requirements.',
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a response to be returned when the token is invalid.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function invalidTokenResponse(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'error',
|
||||||
|
'error' => 'invalid_token',
|
||||||
|
'error_description' => 'The provided token did not pass validation',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a response to be returned when the token has no scope.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function tokenHasNoScopeResponse(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'response' => 'error',
|
||||||
|
'error' => 'invalid_request',
|
||||||
|
'error_description' => 'The provided token has no scopes',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,8 +144,11 @@ Route::group(['domain' => config('url.longurl')], function () {
|
||||||
// Micropub Endpoints
|
// Micropub Endpoints
|
||||||
Route::get('api/post', 'MicropubController@get')->middleware('micropub.token');
|
Route::get('api/post', 'MicropubController@get')->middleware('micropub.token');
|
||||||
Route::post('api/post', 'MicropubController@post')->middleware('micropub.token');
|
Route::post('api/post', 'MicropubController@post')->middleware('micropub.token');
|
||||||
Route::post('api/media', 'MicropubController@media')->middleware('micropub.token', 'cors')->name('media-endpoint');
|
Route::get('api/media', 'MicropubMediaController@getHandler')->middleware('micropub.token');
|
||||||
Route::options('/api/media', 'MicropubController@mediaOptionsResponse')->middleware('cors');
|
Route::post('api/media', 'MicropubMediaController@media')
|
||||||
|
->middleware('micropub.token', 'cors')
|
||||||
|
->name('media-endpoint');
|
||||||
|
Route::options('/api/media', 'MicropubMediaController@mediaOptionsResponse')->middleware('cors');
|
||||||
|
|
||||||
// Webmention
|
// Webmention
|
||||||
Route::get('webmention', 'WebMentionsController@get');
|
Route::get('webmention', 'WebMentionsController@get');
|
||||||
|
|
|
@ -655,148 +655,6 @@ class MicropubControllerTest extends TestCase
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_media_endpoint_request_with_invalid_token_return_400_response()
|
|
||||||
{
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[],
|
|
||||||
['HTTP_Authorization' => 'Bearer abc123']
|
|
||||||
);
|
|
||||||
$response->assertStatus(400);
|
|
||||||
$response->assertJsonFragment(['error_description' => 'The provided token did not pass validation']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_media_endpoint_request_with_token_with_no_scope_returns_400_response()
|
|
||||||
{
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithNoScope()]
|
|
||||||
);
|
|
||||||
$response->assertStatus(400);
|
|
||||||
$response->assertJsonFragment(['error_description' => 'The provided token has no scopes']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_media_endpoint_request_with_insufficient_token_scopes_returns_401_response()
|
|
||||||
{
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
|
||||||
);
|
|
||||||
$response->assertStatus(401);
|
|
||||||
$response->assertJsonFragment(['error_description' => 'The token’s scope does not have the necessary requirements.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_media_endpoint_upload_a_file()
|
|
||||||
{
|
|
||||||
Queue::fake();
|
|
||||||
Storage::fake('s3');
|
|
||||||
$file = __DIR__ . '/../aaron.png';
|
|
||||||
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[
|
|
||||||
'file' => new UploadedFile($file, 'aaron.png', 'image/png', null, true),
|
|
||||||
],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
|
||||||
);
|
|
||||||
|
|
||||||
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
|
||||||
$filename = substr($path, 7);
|
|
||||||
Queue::assertPushed(ProcessMedia::class);
|
|
||||||
Storage::disk('local')->assertExists($filename);
|
|
||||||
// now remove file
|
|
||||||
unlink(storage_path('app/') . $filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_media_endpoint_upload_an_audio_file()
|
|
||||||
{
|
|
||||||
Queue::fake();
|
|
||||||
Storage::fake('s3');
|
|
||||||
$file = __DIR__ . '/../audio.mp3';
|
|
||||||
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[
|
|
||||||
'file' => new UploadedFile($file, 'audio.mp3', 'audio/mpeg', null, true),
|
|
||||||
],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
|
||||||
);
|
|
||||||
|
|
||||||
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
|
||||||
$filename = substr($path, 7);
|
|
||||||
Queue::assertPushed(ProcessMedia::class);
|
|
||||||
Storage::disk('local')->assertExists($filename);
|
|
||||||
// now remove file
|
|
||||||
unlink(storage_path('app/') . $filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_media_endpoint_upload_a_video_file()
|
|
||||||
{
|
|
||||||
Queue::fake();
|
|
||||||
Storage::fake('s3');
|
|
||||||
$file = __DIR__ . '/../video.ogv';
|
|
||||||
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[
|
|
||||||
'file' => new UploadedFile($file, 'video.ogv', 'video/ogg', null, true),
|
|
||||||
],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
|
||||||
);
|
|
||||||
|
|
||||||
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
|
||||||
$filename = substr($path, 7);
|
|
||||||
Queue::assertPushed(ProcessMedia::class);
|
|
||||||
Storage::disk('local')->assertExists($filename);
|
|
||||||
// now remove file
|
|
||||||
unlink(storage_path('app/') . $filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_media_endpoint_upload_a_document_file()
|
|
||||||
{
|
|
||||||
Queue::fake();
|
|
||||||
Storage::fake('s3');
|
|
||||||
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[
|
|
||||||
'file' => UploadedFile::fake()->create('document.pdf', 100),
|
|
||||||
],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
|
||||||
);
|
|
||||||
|
|
||||||
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
|
||||||
$filename = substr($path, 7);
|
|
||||||
Queue::assertPushed(ProcessMedia::class);
|
|
||||||
Storage::disk('local')->assertExists($filename);
|
|
||||||
// now remove file
|
|
||||||
unlink(storage_path('app/') . $filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_media_endpoint_upload_an_invalid_file_return_error()
|
|
||||||
{
|
|
||||||
Queue::fake();
|
|
||||||
Storage::fake('local');
|
|
||||||
|
|
||||||
$response = $this->post(
|
|
||||||
'/api/media',
|
|
||||||
[
|
|
||||||
'file' => new UploadedFile(
|
|
||||||
__DIR__ . '/../aaron.png',
|
|
||||||
'aaron.png',
|
|
||||||
'image/png',
|
|
||||||
UPLOAD_ERR_INI_SIZE,
|
|
||||||
true
|
|
||||||
),
|
|
||||||
],
|
|
||||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
|
||||||
);
|
|
||||||
$response->assertStatus(400);
|
|
||||||
$response->assertJson(['error_description' => 'The uploaded file failed validation']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_access_token_form_encoded()
|
public function test_access_token_form_encoded()
|
||||||
{
|
{
|
||||||
$faker = \Faker\Factory::create();
|
$faker = \Faker\Factory::create();
|
||||||
|
|
206
tests/Feature/MicropubMediaTest.php
Normal file
206
tests/Feature/MicropubMediaTest.php
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Jobs\ProcessMedia;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Tests\TestCase;
|
||||||
|
use Tests\TestToken;
|
||||||
|
|
||||||
|
class MicropubMediaTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
use TestToken;
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function clientCanListLastUpload()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
Storage::fake('s3');
|
||||||
|
$file = __DIR__ . '/../aaron.png';
|
||||||
|
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[
|
||||||
|
'file' => new UploadedFile($file, 'aaron.png', 'image/png', null, true),
|
||||||
|
],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
);
|
||||||
|
|
||||||
|
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
||||||
|
$filename = substr($path, 7);
|
||||||
|
|
||||||
|
$lastUploadResponse = $this->get(
|
||||||
|
'/api/media?q=last',
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
);
|
||||||
|
$lastUploadResponse->assertJson(['url' => $response->getData()->location]);
|
||||||
|
|
||||||
|
// now remove file
|
||||||
|
unlink(storage_path('app/') . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function optionsRequestReturnsCorsResponse()
|
||||||
|
{
|
||||||
|
$response = $this->options('/api/media');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertHeader('access-control-allow-origin', '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointRequestWithInvalidTokenReturns400Response()
|
||||||
|
{
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[],
|
||||||
|
['HTTP_Authorization' => 'Bearer abc123']
|
||||||
|
);
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonFragment(['error_description' => 'The provided token did not pass validation']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointRequestWithTokenWithNoScopeReturns400Response()
|
||||||
|
{
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithNoScope()]
|
||||||
|
);
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonFragment(['error_description' => 'The provided token has no scopes']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointRequestWithInsufficientTokenScopesReturns401Response()
|
||||||
|
{
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
||||||
|
);
|
||||||
|
$response->assertStatus(401);
|
||||||
|
$response->assertJsonFragment([
|
||||||
|
'error_description' => 'The token’s scope does not have the necessary requirements.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointUploadFile()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
Storage::fake('s3');
|
||||||
|
$file = __DIR__ . '/../aaron.png';
|
||||||
|
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[
|
||||||
|
'file' => new UploadedFile($file, 'aaron.png', 'image/png', null, true),
|
||||||
|
],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
);
|
||||||
|
|
||||||
|
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
||||||
|
$filename = substr($path, 7);
|
||||||
|
Queue::assertPushed(ProcessMedia::class);
|
||||||
|
Storage::disk('local')->assertExists($filename);
|
||||||
|
// now remove file
|
||||||
|
unlink(storage_path('app/') . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointUploadAudioFile()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
Storage::fake('s3');
|
||||||
|
$file = __DIR__ . '/../audio.mp3';
|
||||||
|
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[
|
||||||
|
'file' => new UploadedFile($file, 'audio.mp3', 'audio/mpeg', null, true),
|
||||||
|
],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
);
|
||||||
|
|
||||||
|
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
||||||
|
$filename = substr($path, 7);
|
||||||
|
Queue::assertPushed(ProcessMedia::class);
|
||||||
|
Storage::disk('local')->assertExists($filename);
|
||||||
|
// now remove file
|
||||||
|
unlink(storage_path('app/') . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointUploadVideoFile()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
Storage::fake('s3');
|
||||||
|
$file = __DIR__ . '/../video.ogv';
|
||||||
|
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[
|
||||||
|
'file' => new UploadedFile($file, 'video.ogv', 'video/ogg', null, true),
|
||||||
|
],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
);
|
||||||
|
|
||||||
|
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
||||||
|
$filename = substr($path, 7);
|
||||||
|
Queue::assertPushed(ProcessMedia::class);
|
||||||
|
Storage::disk('local')->assertExists($filename);
|
||||||
|
// now remove file
|
||||||
|
unlink(storage_path('app/') . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointUploadDocumentFile()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
Storage::fake('s3');
|
||||||
|
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[
|
||||||
|
'file' => UploadedFile::fake()->create('document.pdf', 100),
|
||||||
|
],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
);
|
||||||
|
|
||||||
|
$path = parse_url($response->getData()->location, PHP_URL_PATH);
|
||||||
|
$filename = substr($path, 7);
|
||||||
|
Queue::assertPushed(ProcessMedia::class);
|
||||||
|
Storage::disk('local')->assertExists($filename);
|
||||||
|
// now remove file
|
||||||
|
unlink(storage_path('app/') . $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function mediaEndpointUploadInvalidFileReturnsError()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
Storage::fake('local');
|
||||||
|
|
||||||
|
$response = $this->post(
|
||||||
|
'/api/media',
|
||||||
|
[
|
||||||
|
'file' => new UploadedFile(
|
||||||
|
__DIR__ . '/../aaron.png',
|
||||||
|
'aaron.png',
|
||||||
|
'image/png',
|
||||||
|
UPLOAD_ERR_INI_SIZE,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
],
|
||||||
|
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||||
|
);
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJson(['error_description' => 'The uploaded file failed validation']);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue