Squashed commit of the following:
commit b87b6b2a96de870f1782b00cfe3f393cc79b7d3b Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Mon Dec 18 14:05:11 2017 +0000 Even more tests for this micropub refactor commit 2d967f33c3abeea8fc89f91e1764e970681dc58f Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Sat Dec 16 22:19:53 2017 +0000 Fill out token endpoint tests commit 440dcbe3e53f058060c918429bea75911ddafdc1 Merge: 02a25b0 f60164f Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Fri Dec 15 14:32:50 2017 +0000 Merge pull request #77 from jonnybarnes/analysis-8KABW6 Apply fixes from StyleCI commit f60164fe81dbcc1d2343704145d26c6d6412579a Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Fri Dec 15 14:27:40 2017 +0000 Apply fixes from StyleCI commit 02a25b083a0305f73d715feb3f9d34f9de8f67d4 Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Fri Dec 15 14:17:43 2017 +0000 phpcs fix commit 144998de0866bf11f235847d7edc076235294545 Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Fri Dec 15 14:16:59 2017 +0000 Don’t pass the request object to service files, pass request()->all() commit dd5e52010c51a359665efa349ff8c13d4d6dbf57 Merge: 97b270a 23b145e Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Sun Dec 10 20:01:03 2017 +0000 Merge pull request #76 from jonnybarnes/analysis-86AVg6 Apply fixes from StyleCI commit 23b145e7bf67a358b3cb894ea0793984b65ecab5 Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Sun Dec 10 19:50:53 2017 +0000 Apply fixes from StyleCI commit 97b270a89abe92e167e0d363029ae0b86608bbc9 Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Sun Dec 10 19:43:38 2017 +0000 Improve test coverage of the refactor commit 244102264559e4fb0b0614d1738c0283703a71dc Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Fri Dec 8 13:31:13 2017 +0000 Refactor the note creation code commit 22b4786cbd7ae508b51a47f0c8cf9a15535edbb1 Author: Jonny Barnes <jonny@jonnybarnes.uk> Date: Thu Dec 7 17:39:41 2017 +0000 Remove ununsed importsed classes in the controller
This commit is contained in:
parent
d409098efb
commit
43beae2f82
15 changed files with 875 additions and 286 deletions
|
@ -2,41 +2,35 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Storage;
|
||||
use Monolog\Logger;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use App\Jobs\ProcessMedia;
|
||||
use App\Services\TokenService;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use App\{Like, Media, Note, Place};
|
||||
use Intervention\Image\ImageManager;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\{Request, Response};
|
||||
use App\Exceptions\InvalidTokenException;
|
||||
use Phaza\LaravelPostgis\Geometries\Point;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use App\Services\{NoteService, PlaceService, TokenService};
|
||||
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
|
||||
|
||||
class MicropubController extends Controller
|
||||
{
|
||||
protected $tokenService;
|
||||
protected $noteService;
|
||||
protected $placeService;
|
||||
protected $hentryService;
|
||||
protected $hcardService;
|
||||
protected $updateService;
|
||||
|
||||
public function __construct(
|
||||
TokenService $tokenService,
|
||||
NoteService $noteService,
|
||||
PlaceService $placeService,
|
||||
HEntryService $hentryService,
|
||||
HCardService $hcardService,
|
||||
UpdateService $updateService
|
||||
) {
|
||||
$this->tokenService = $tokenService;
|
||||
$this->noteService = $noteService;
|
||||
$this->placeService = $placeService;
|
||||
$this->hentryService = $hentryService;
|
||||
$this->hcardService = $hcardService;
|
||||
$this->updateService = $updateService;
|
||||
|
@ -46,13 +40,12 @@ class MicropubController extends Controller
|
|||
* This function receives an API request, verifies the authenticity
|
||||
* then passes over the info to the relavent Service class.
|
||||
*
|
||||
* @param \Illuminate\Http\Request request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function post(Request $request)
|
||||
public function post()
|
||||
{
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
$tokenData = $this->tokenService->validateToken(request()->bearerToken());
|
||||
} catch (InvalidTokenException $e) {
|
||||
return $this->invalidTokenResponse();
|
||||
}
|
||||
|
@ -61,13 +54,13 @@ class MicropubController extends Controller
|
|||
return $this->tokenHasNoScopeResponse();
|
||||
}
|
||||
|
||||
$this->logMicropubRequest($request);
|
||||
$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) {
|
||||
return $this->insufficientScopeResponse();
|
||||
}
|
||||
$location = $this->hentryService->process($request);
|
||||
$location = $this->hentryService->process(request()->all(), $this->getCLientId());
|
||||
|
||||
return response()->json([
|
||||
'response' => 'created',
|
||||
|
@ -75,11 +68,11 @@ class MicropubController extends Controller
|
|||
], 201)->header('Location', $location);
|
||||
}
|
||||
|
||||
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) {
|
||||
return $this->insufficientScopeResponse();
|
||||
}
|
||||
$location = $this->hcardService->process($request);
|
||||
$location = $this->hcardService->process(request()->all());
|
||||
|
||||
return response()->json([
|
||||
'response' => 'created',
|
||||
|
@ -87,13 +80,18 @@ class MicropubController extends Controller
|
|||
], 201)->header('Location', $location);
|
||||
}
|
||||
|
||||
if ($request->input('action') == 'update') {
|
||||
if (request()->input('action') == 'update') {
|
||||
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
|
||||
return $this->returnInsufficientScopeResponse();
|
||||
return $this->insufficientScopeResponse();
|
||||
}
|
||||
|
||||
return $this->updateService->process($request);
|
||||
return $this->updateService->process(request()->all());
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error_description' => 'unsupported_request_type',
|
||||
], 500);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,34 +100,33 @@ class MicropubController extends Controller
|
|||
* appropriately. Further if the request has the query parameter
|
||||
* synidicate-to we respond with the known syndication endpoints.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function get(Request $request)
|
||||
public function get()
|
||||
{
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
$tokenData = $this->tokenService->validateToken(request()->bearerToken());
|
||||
} catch (InvalidTokenException $e) {
|
||||
return $this->invalidTokenResponse();
|
||||
}
|
||||
|
||||
if ($request->input('q') === 'syndicate-to') {
|
||||
if (request()->input('q') === 'syndicate-to') {
|
||||
return response()->json([
|
||||
'syndicate-to' => config('syndication.targets'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->input('q') == 'config') {
|
||||
if (request()->input('q') == 'config') {
|
||||
return response()->json([
|
||||
'syndicate-to' => config('syndication.targets'),
|
||||
'media-endpoint' => route('media-endpoint'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (substr($request->input('q'), 0, 4) === 'geo:') {
|
||||
if (substr(request()->input('q'), 0, 4) === 'geo:') {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$request->input('q'),
|
||||
request()->input('q'),
|
||||
$matches
|
||||
);
|
||||
$distance = (count($matches[0]) == 3) ? 100 * $matches[0][2] : 1000;
|
||||
|
@ -155,13 +152,12 @@ class MicropubController extends Controller
|
|||
/**
|
||||
* Process a media item posted to the media endpoint.
|
||||
*
|
||||
* @param Illuminate\Http\Request $request
|
||||
* @return Illuminate\Http\Response
|
||||
*/
|
||||
public function media(Request $request)
|
||||
public function media()
|
||||
{
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
$tokenData = $this->tokenService->validateToken(request()->bearerToken());
|
||||
} catch (InvalidTokenException $e) {
|
||||
return $this->invalidTokenResponse();
|
||||
}
|
||||
|
@ -174,7 +170,7 @@ class MicropubController extends Controller
|
|||
return $this->insufficientScopeResponse();
|
||||
}
|
||||
|
||||
if (($request->hasFile('file') && $request->file('file')->isValid()) === false) {
|
||||
if ((request()->hasFile('file') && request()->file('file')->isValid()) === false) {
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'invalid_request',
|
||||
|
@ -182,13 +178,13 @@ class MicropubController extends Controller
|
|||
], 400);
|
||||
}
|
||||
|
||||
$this->logMicropubRequest($request);
|
||||
$this->logMicropubRequest(request()->all());
|
||||
|
||||
$filename = $this->saveFile($request->file('file'));
|
||||
$filename = $this->saveFile(request()->file('file'));
|
||||
|
||||
$manager = resolve(ImageManager::class);
|
||||
try {
|
||||
$image = $manager->make($request->file('file'));
|
||||
$image = $manager->make(request()->file('file'));
|
||||
$width = $image->width();
|
||||
} catch (NotReadableException $exception) {
|
||||
// not an image
|
||||
|
@ -196,9 +192,9 @@ class MicropubController extends Controller
|
|||
}
|
||||
|
||||
$media = Media::create([
|
||||
'token' => $request->bearerToken(),
|
||||
'token' => request()->bearerToken(),
|
||||
'path' => 'media/' . $filename,
|
||||
'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()),
|
||||
'type' => $this->getFileTypeFromMimeType(request()->file('file')->getMimeType()),
|
||||
'image_widths' => $width,
|
||||
]);
|
||||
|
||||
|
@ -234,6 +230,7 @@ class MicropubController extends Controller
|
|||
$videoMimeTypes = [
|
||||
'video/mp4',
|
||||
'video/mpeg',
|
||||
'video/ogg',
|
||||
'video/quicktime',
|
||||
'video/webm',
|
||||
];
|
||||
|
@ -254,18 +251,24 @@ class MicropubController extends Controller
|
|||
return 'download';
|
||||
}
|
||||
|
||||
private function logMicropubRequest(Request $request)
|
||||
private function getClientId(): string
|
||||
{
|
||||
return resolve(TokenService::class)
|
||||
->validateToken(request()->bearerToken())
|
||||
->getClaim('client_id');
|
||||
}
|
||||
|
||||
private function logMicropubRequest(array $request)
|
||||
{
|
||||
$logger = new Logger('micropub');
|
||||
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')), Logger::DEBUG);
|
||||
$logger->debug('MicropubLog', $request->all());
|
||||
$logger->debug('MicropubLog', $request);
|
||||
}
|
||||
|
||||
private function saveFile(UploadedFile $file)
|
||||
{
|
||||
$filename = Uuid::uuid4() . '.' . $file->extension();
|
||||
$size = $file->getClientSize();
|
||||
Storage::disk('local')->put($filename, $file->openFile()->fread($size));
|
||||
Storage::disk('local')->put($filename, $file);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
|
|
@ -136,6 +136,9 @@ class Place extends Model
|
|||
|
||||
public function setExternalUrlsAttribute($url)
|
||||
{
|
||||
if ($url === null) {
|
||||
return;
|
||||
}
|
||||
$type = $this->getType($url);
|
||||
$already = [];
|
||||
if (array_key_exists('external_urls', $this->attributes)) {
|
||||
|
|
|
@ -21,25 +21,27 @@ class BookmarkService
|
|||
/**
|
||||
* Create a new Bookmark.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param array $request
|
||||
* @return Bookmark $bookmark
|
||||
*/
|
||||
public function createBookmark(Request $request): Bookmark
|
||||
public function createBookmark(array $request): Bookmark
|
||||
{
|
||||
if ($request->header('Content-Type') == 'application/json') {
|
||||
if (array_get($request, 'properties.bookmark-of.0')) {
|
||||
//micropub request
|
||||
$url = normalize_url($request->input('properties.bookmark-of.0'));
|
||||
$name = $request->input('properties.name.0');
|
||||
$content = $request->input('properties.content.0');
|
||||
$categories = $request->input('properties.category');
|
||||
$url = normalize_url(array_get($request, 'properties.bookmark-of.0'));
|
||||
$name = array_get($request, 'properties.name.0');
|
||||
$content = array_get($request, 'properties.content.0');
|
||||
$categories = array_get($request, 'properties.category');
|
||||
}
|
||||
if (($request->header('Content-Type') == 'application/x-www-form-urlencoded')
|
||||
||
|
||||
(str_contains($request->header('Content-Type'), 'multipart/form-data'))
|
||||
) {
|
||||
$url = normalize_url($request->input('bookmark-of'));
|
||||
$name = $request->input('name');
|
||||
$content = $request->input('content');
|
||||
$categories = $request->input('category');
|
||||
if (array_get($request, 'bookmark-of')) {
|
||||
$url = normalize_url(array_get($request, 'bookmark-of'));
|
||||
$name = array_get($request, 'name');
|
||||
$content = array_get($request, 'content');
|
||||
$categories = array_get($request, 'category');
|
||||
}
|
||||
|
||||
if (! isset($url)) {
|
||||
throw new \Exception;
|
||||
}
|
||||
|
||||
$bookmark = Bookmark::create([
|
||||
|
@ -55,11 +57,11 @@ class BookmarkService
|
|||
|
||||
$targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
|
||||
$mpSyndicateTo = null;
|
||||
if ($request->has('mp-syndicate-to')) {
|
||||
$mpSyndicateTo = $request->input('mp-syndicate-to');
|
||||
if (array_get($request, 'mp-syndicate-to')) {
|
||||
$mpSyndicateTo = array_get($request, 'mp-syndicate-to');
|
||||
}
|
||||
if ($request->has('properties.mp-syndicate-to')) {
|
||||
$mpSyndicateTo = $request->input('properties.mp-syndicate-to');
|
||||
if (array_get($request, 'properties.mp-syndicate-to')) {
|
||||
$mpSyndicateTo = array_get($request, 'properties.mp-syndicate-to');
|
||||
}
|
||||
if (is_string($mpSyndicateTo)) {
|
||||
$service = array_search($mpSyndicateTo, $targets);
|
||||
|
|
|
@ -6,26 +6,27 @@ namespace App\Services;
|
|||
|
||||
use App\Like;
|
||||
use App\Jobs\ProcessLike;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LikeService
|
||||
{
|
||||
/**
|
||||
* Create a new Like.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param array $request
|
||||
* @return Like $like
|
||||
*/
|
||||
public function createLike(Request $request): Like
|
||||
public function createLike(array $request): Like
|
||||
{
|
||||
if ($request->header('Content-Type') == 'application/json') {
|
||||
if (array_get($request, 'properties.like-of.0')) {
|
||||
//micropub request
|
||||
$url = normalize_url($request->input('properties.like-of.0'));
|
||||
$url = normalize_url(array_get($request, 'properties.like-of.0'));
|
||||
}
|
||||
if (($request->header('Content-Type') == 'application/x-www-form-urlencoded')
|
||||
||
|
||||
($request->header('Content-Type') == 'multipart/form-data')
|
||||
) {
|
||||
$url = normalize_url($request->input('like-of'));
|
||||
if (array_get($request, 'like-of')) {
|
||||
$url = normalize_url(array_get($request, 'like-of'));
|
||||
}
|
||||
|
||||
if (! isset($url)) {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
$like = Like::create(['url' => $url]);
|
||||
|
|
|
@ -2,30 +2,23 @@
|
|||
|
||||
namespace App\Services\Micropub;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\PlaceService;
|
||||
|
||||
class HCardService
|
||||
{
|
||||
public function process(Request $request)
|
||||
public function process(array $request)
|
||||
{
|
||||
$data = [];
|
||||
if ($request->header('Content-Type') == 'application/json') {
|
||||
$data['name'] = $request->input('properties.name');
|
||||
$data['description'] = $request->input('properties.description') ?? null;
|
||||
if ($request->has('properties.geo')) {
|
||||
$data['geo'] = $request->input('properties.geo');
|
||||
}
|
||||
if (array_get($request, 'properties.name')) {
|
||||
$data['name'] = array_get($request, 'properties.name');
|
||||
$data['description'] = array_get($request, 'properties.description');
|
||||
$data['geo'] = array_get($request, 'properties.geo');
|
||||
} else {
|
||||
$data['name'] = $request->input('name');
|
||||
$data['description'] = $request->input('description');
|
||||
if ($request->has('geo')) {
|
||||
$data['geo'] = $request->input('geo');
|
||||
}
|
||||
if ($request->has('latitude')) {
|
||||
$data['latitude'] = $request->input('latitude');
|
||||
$data['longitude'] = $request->input('longitude');
|
||||
}
|
||||
$data['name'] = array_get($request, 'name');
|
||||
$data['description'] = array_get($request, 'description');
|
||||
$data['geo'] = array_get($request, 'geo');
|
||||
$data['latitude'] = array_get($request, 'latitude');
|
||||
$data['longitude'] = array_get($request, 'longitude');
|
||||
}
|
||||
$place = resolve(PlaceService::class)->createPlace($data);
|
||||
|
||||
|
|
|
@ -2,26 +2,25 @@
|
|||
|
||||
namespace App\Services\Micropub;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\{BookmarkService, LikeService, NoteService};
|
||||
|
||||
class HEntryService
|
||||
{
|
||||
public function process(Request $request)
|
||||
public function process(array $request, string $client = null)
|
||||
{
|
||||
if ($request->has('properties.like-of') || $request->has('like-of')) {
|
||||
if (array_get($request, 'properties.like-of') || array_get($request, 'like-of')) {
|
||||
$like = resolve(LikeService::class)->createLike($request);
|
||||
|
||||
return $like->longurl;
|
||||
}
|
||||
|
||||
if ($request->has('properties.bookmark-of') || $request->has('bookmark-of')) {
|
||||
if (array_get($request, 'properties.bookmark-of') || array_get($request, 'bookmark-of')) {
|
||||
$bookmark = resolve(BookmarkService::class)->createBookmark($request);
|
||||
|
||||
return $bookmark->longurl;
|
||||
}
|
||||
|
||||
$note = resolve(NoteService::class)->createNote($request);
|
||||
$note = resolve(NoteService::class)->createNote($request, $client);
|
||||
|
||||
return $note->longurl;
|
||||
}
|
||||
|
|
|
@ -4,14 +4,13 @@ namespace App\Services\Micropub;
|
|||
|
||||
use App\Note;
|
||||
use App\Media;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
class UpdateService
|
||||
{
|
||||
public function process(Request $request)
|
||||
public function process(array $request)
|
||||
{
|
||||
$urlPath = parse_url($request->input('url'), PHP_URL_PATH);
|
||||
$urlPath = parse_url(array_get($request, 'url'), PHP_URL_PATH);
|
||||
|
||||
//is it a note we are updating?
|
||||
if (mb_substr($urlPath, 1, 5) !== 'notes') {
|
||||
|
@ -31,8 +30,8 @@ class UpdateService
|
|||
}
|
||||
|
||||
//got the note, are we dealing with a “replace” request?
|
||||
if ($request->has('replace')) {
|
||||
foreach ($request->input('replace') as $property => $value) {
|
||||
if (array_get($request, 'replace')) {
|
||||
foreach (array_get($request, 'replace') as $property => $value) {
|
||||
if ($property == 'content') {
|
||||
$note->note = $value[0];
|
||||
}
|
||||
|
@ -58,8 +57,8 @@ class UpdateService
|
|||
}
|
||||
|
||||
//how about “add”
|
||||
if ($request->has('add')) {
|
||||
foreach ($request->input('add') as $property => $value) {
|
||||
if (array_get($request, 'add')) {
|
||||
foreach (array_get($request, 'add') as $property => $value) {
|
||||
if ($property == 'syndication') {
|
||||
foreach ($value as $syndicationURL) {
|
||||
if (starts_with($syndicationURL, 'https://www.facebook.com')) {
|
||||
|
@ -75,7 +74,7 @@ class UpdateService
|
|||
}
|
||||
if ($property == 'photo') {
|
||||
foreach ($value as $photoURL) {
|
||||
if (start_with($photo, 'https://')) {
|
||||
if (starts_with($photoURL, 'https://')) {
|
||||
$media = new Media();
|
||||
$media->path = $photoURL;
|
||||
$media->type = 'image';
|
||||
|
@ -91,5 +90,10 @@ class UpdateService
|
|||
'response' => 'updated',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error_description' => 'unsupported request',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\{Media, Note, Place};
|
||||
use App\Jobs\{SendWebMentions, SyndicateNoteToFacebook, SyndicateNoteToTwitter};
|
||||
|
||||
|
@ -13,190 +12,38 @@ class NoteService
|
|||
/**
|
||||
* Create a new note.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param array $request
|
||||
* @param string $client
|
||||
* @return \App\Note $note
|
||||
*/
|
||||
public function createNote(Request $request): Note
|
||||
public function createNote(array $request, string $client = null): Note
|
||||
{
|
||||
//move the request to data code here before refactor
|
||||
$data = [];
|
||||
$data['client-id'] = resolve(TokenService::class)
|
||||
->validateToken($request->bearerToken())
|
||||
->getClaim('client_id');
|
||||
if ($request->header('Content-Type') == 'application/json') {
|
||||
if (is_string($request->input('properties.content.0'))) {
|
||||
$data['content'] = $request->input('properties.content.0'); //plaintext content
|
||||
}
|
||||
if (is_array($request->input('properties.content.0'))
|
||||
&& array_key_exists('html', $request->input('properties.content.0'))
|
||||
) {
|
||||
$data['content'] = $request->input('properties.content.0.html');
|
||||
}
|
||||
$data['in-reply-to'] = $request->input('properties.in-reply-to.0');
|
||||
// check location is geo: string
|
||||
if (is_string($request->input('properties.location.0'))) {
|
||||
$data['location'] = $request->input('properties.location.0');
|
||||
}
|
||||
// check location is h-card
|
||||
if (is_array($request->input('properties.location.0'))) {
|
||||
if ($request->input('properties.location.0.type.0' === 'h-card')) {
|
||||
try {
|
||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||
$request->input('properties.location.0')
|
||||
);
|
||||
$data['checkin'] = $place->longurl;
|
||||
} catch (\Exception $e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
$data['published'] = $request->input('properties.published.0');
|
||||
//create checkin place
|
||||
if (array_key_exists('checkin', $request->input('properties'))) {
|
||||
$data['swarm-url'] = $request->input('properties.syndication.0');
|
||||
try {
|
||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||
$request->input('properties.checkin.0')
|
||||
);
|
||||
$data['checkin'] = $place->longurl;
|
||||
} catch (\Exception $e) {
|
||||
$data['checkin'] = null;
|
||||
$data['swarm-url'] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$data['content'] = $request->input('content');
|
||||
$data['in-reply-to'] = $request->input('in-reply-to');
|
||||
$data['location'] = $request->input('location');
|
||||
$data['published'] = $request->input('published');
|
||||
}
|
||||
$data['syndicate'] = [];
|
||||
$targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
|
||||
$mpSyndicateTo = null;
|
||||
if ($request->has('mp-syndicate-to')) {
|
||||
$mpSyndicateTo = $request->input('mp-syndicate-to');
|
||||
}
|
||||
if ($request->has('properties.mp-syndicate-to')) {
|
||||
$mpSyndicateTo = $request->input('properties.mp-syndicate-to');
|
||||
}
|
||||
if (is_string($mpSyndicateTo)) {
|
||||
$service = array_search($mpSyndicateTo, $targets);
|
||||
if ($service == 'Twitter') {
|
||||
$data['syndicate'][] = 'twitter';
|
||||
}
|
||||
if ($service == 'Facebook') {
|
||||
$data['syndicate'][] = 'facebook';
|
||||
}
|
||||
}
|
||||
if (is_array($mpSyndicateTo)) {
|
||||
foreach ($mpSyndicateTo as $uid) {
|
||||
$service = array_search($uid, $targets);
|
||||
if ($service == 'Twitter') {
|
||||
$data['syndicate'][] = 'twitter';
|
||||
}
|
||||
if ($service == 'Facebook') {
|
||||
$data['syndicate'][] = 'facebook';
|
||||
}
|
||||
}
|
||||
}
|
||||
$data['photo'] = [];
|
||||
$photos = null;
|
||||
if ($request->has('photo')) {
|
||||
$photos = $request->input('photo');
|
||||
}
|
||||
if ($request->has('properties.photo')) {
|
||||
$photos = $request->input('properties.photo');
|
||||
}
|
||||
if ($photos !== null) {
|
||||
foreach ($photos as $photo) {
|
||||
if (is_string($photo)) {
|
||||
//only supporting media URLs for now
|
||||
$data['photo'][] = $photo;
|
||||
}
|
||||
}
|
||||
if (starts_with($request->input('properties.syndication.0'), 'https://www.instagram.com')) {
|
||||
$data['instagram-url'] = $request->input('properties.syndication.0');
|
||||
}
|
||||
}
|
||||
//check the input
|
||||
if (array_key_exists('content', $data) === false) {
|
||||
$data['content'] = null;
|
||||
}
|
||||
if (array_key_exists('in-reply-to', $data) === false) {
|
||||
$data['in-reply-to'] = null;
|
||||
}
|
||||
if (array_key_exists('client-id', $data) === false) {
|
||||
$data['client-id'] = null;
|
||||
}
|
||||
$note = Note::create(
|
||||
[
|
||||
'note' => $data['content'],
|
||||
'in_reply_to' => $data['in-reply-to'],
|
||||
'client_id' => $data['client-id'],
|
||||
'note' => $this->getContent($request),
|
||||
'in_reply_to' => $this->getInReplyTo($request),
|
||||
'client_id' => $client,
|
||||
]
|
||||
);
|
||||
|
||||
if (array_key_exists('published', $data) && empty($data['published']) === false) {
|
||||
$note->created_at = $note->updated_at = carbon($data['published'])
|
||||
->toDateTimeString();
|
||||
if ($this->getPublished($request)) {
|
||||
$note->created_at = $note->updated_at = $this->getPublished($request);
|
||||
}
|
||||
|
||||
if (array_key_exists('location', $data) && $data['location'] !== null && $data['location'] !== 'no-location') {
|
||||
if (starts_with($data['location'], config('app.url'))) {
|
||||
//uri of form http://host/places/slug, we want slug
|
||||
//get the URL path, then take last part, we can hack with basename
|
||||
//as path looks like file path.
|
||||
$place = Place::where('slug', basename(parse_url($data['location'], PHP_URL_PATH)))->first();
|
||||
$note->place()->associate($place);
|
||||
}
|
||||
if (substr($data['location'], 0, 4) == 'geo:') {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$data['location'],
|
||||
$matches
|
||||
);
|
||||
$note->location = $matches[0][0] . ', ' . $matches[0][1];
|
||||
$note->location = $this->getLocation($request);
|
||||
|
||||
if ($this->getCheckin($request)) {
|
||||
$note->place()->associate($this->getCheckin($request));
|
||||
$note->swarm_url = $this->getSwarmUrl($request);
|
||||
if ($note->note === null || $note->note == '') {
|
||||
$note->note = 'I’ve just checked in with Swarm';
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('checkin', $data) && $data['checkin'] !== null) {
|
||||
$place = Place::where('slug', basename(parse_url($data['checkin'], PHP_URL_PATH)))->first();
|
||||
if ($place !== null) {
|
||||
$note->place()->associate($place);
|
||||
$note->swarm_url = $data['swarm-url'];
|
||||
if ($note->note === null || $note->note == '') {
|
||||
$note->note = 'I’ve just checked in with Swarm';
|
||||
}
|
||||
}
|
||||
}
|
||||
$note->instagram_url = $this->getInstagramUrl($request);
|
||||
|
||||
/* drop image support for now
|
||||
//add images to media library
|
||||
if ($request->hasFile('photo')) {
|
||||
$files = $request->file('photo');
|
||||
foreach ($files as $file) {
|
||||
$note->addMedia($file)->toCollectionOnDisk('images', 's3');
|
||||
}
|
||||
}
|
||||
*/
|
||||
//add support for media uploaded as URLs
|
||||
if (array_key_exists('photo', $data)) {
|
||||
foreach ((array) $data['photo'] as $photo) {
|
||||
// check the media was uploaded to my endpoint, and use path
|
||||
if (starts_with($photo, config('filesystems.disks.s3.url'))) {
|
||||
$path = substr($photo, strlen(config('filesystems.disks.s3.url')));
|
||||
$media = Media::where('path', ltrim($path, '/'))->firstOrFail();
|
||||
} else {
|
||||
$media = Media::firstOrNew(['path' => $photo]);
|
||||
// currently assuming this is a photo from Swarm or OwnYourGram
|
||||
$media->type = 'image';
|
||||
$media->save();
|
||||
}
|
||||
$note->media()->save($media);
|
||||
}
|
||||
if (array_key_exists('instagram-url', $data)) {
|
||||
$note->instagram_url = $data['instagram-url'];
|
||||
}
|
||||
foreach ($this->getMedia($request) as $media) {
|
||||
$note->media()->save($media);
|
||||
}
|
||||
|
||||
$note->save();
|
||||
|
@ -204,15 +51,175 @@ class NoteService
|
|||
dispatch(new SendWebMentions($note));
|
||||
|
||||
//syndication targets
|
||||
if (array_key_exists('syndicate', $data)) {
|
||||
if (in_array('twitter', $data['syndicate'])) {
|
||||
if (count($this->getSyndicationTargets($request)) > 0) {
|
||||
if (in_array('twitter', $this->getSyndicationTargets($request))) {
|
||||
dispatch(new SyndicateNoteToTwitter($note));
|
||||
}
|
||||
if (in_array('facebook', $data['syndicate'])) {
|
||||
if (in_array('facebook', $this->getSyndicationTargets($request))) {
|
||||
dispatch(new SyndicateNoteToFacebook($note));
|
||||
}
|
||||
}
|
||||
|
||||
return $note;
|
||||
}
|
||||
|
||||
private function getContent(array $request): ?string
|
||||
{
|
||||
if (array_get($request, 'properties.content.0.html')) {
|
||||
return array_get($request, 'properties.content.0.html');
|
||||
}
|
||||
if (is_string(array_get($request, 'properties.content.0'))) {
|
||||
return array_get($request, 'properties.content.0');
|
||||
}
|
||||
|
||||
return array_get($request, 'content');
|
||||
}
|
||||
|
||||
private function getInReplyTo(array $request): ?string
|
||||
{
|
||||
if (array_get($request, 'properties.in-reply-to.0')) {
|
||||
return array_get($request, 'properties.in-reply-to.0');
|
||||
}
|
||||
|
||||
return array_get($request, 'in-reply-to');
|
||||
}
|
||||
|
||||
private function getPublished(array $request): ?string
|
||||
{
|
||||
if (array_get($request, 'properties.published.0')) {
|
||||
return carbon(array_get($request, 'properties.published.0'))
|
||||
->toDateTimeString();
|
||||
}
|
||||
if (array_get($request, 'published')) {
|
||||
return carbon(array_get($request, 'published'))->toDateTimeString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getLocation(array $request): ?string
|
||||
{
|
||||
$location = array_get($request, 'properties.location.0') ?? array_get($request, 'location');
|
||||
if (is_string($location) && substr($location, 0, 4) == 'geo:') {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$location,
|
||||
$matches
|
||||
);
|
||||
|
||||
return $matches[0][0] . ', ' . $matches[0][1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getCheckin(array $request): ?Place
|
||||
{
|
||||
if (array_get($request, 'properties.location.0.type.0') === 'h-card') {
|
||||
try {
|
||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||
array_get($request, 'properties.location.0')
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $place;
|
||||
}
|
||||
if (starts_with(array_get($request, 'properties.location.0'), config('app.url'))) {
|
||||
return Place::where(
|
||||
'slug',
|
||||
basename(
|
||||
parse_url(
|
||||
array_get($request, 'properties.location.0'),
|
||||
PHP_URL_PATH
|
||||
)
|
||||
)
|
||||
)->first();
|
||||
}
|
||||
if (array_get($request, 'properties.checkin')) {
|
||||
try {
|
||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||
array_get($request, 'properties.checkin.0')
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $place;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getSwarmUrl(array $request): ?string
|
||||
{
|
||||
if (stristr(array_get($request, 'properties.syndication.0', ''), 'swarmapp')) {
|
||||
return array_get($request, 'properties.syndication.0');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getSyndicationTargets(array $request): array
|
||||
{
|
||||
$syndication = [];
|
||||
$targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
|
||||
$mpSyndicateTo = array_get($request, 'mp-syndicate-to') ?? array_get($request, 'properties.mp-syndicate-to');
|
||||
if (is_string($mpSyndicateTo)) {
|
||||
$service = array_search($mpSyndicateTo, $targets);
|
||||
if ($service == 'Twitter') {
|
||||
$syndication[] = 'twitter';
|
||||
}
|
||||
if ($service == 'Facebook') {
|
||||
$syndication[] = 'facebook';
|
||||
}
|
||||
}
|
||||
if (is_array($mpSyndicateTo)) {
|
||||
foreach ($mpSyndicateTo as $uid) {
|
||||
$service = array_search($uid, $targets);
|
||||
if ($service == 'Twitter') {
|
||||
$syndication[] = 'twitter';
|
||||
}
|
||||
if ($service == 'Facebook') {
|
||||
$syndication[] = 'facebook';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $syndication;
|
||||
}
|
||||
|
||||
private function getMedia(array $request): array
|
||||
{
|
||||
$media = [];
|
||||
$photos = array_get($request, 'photo') ?? array_get($request, 'properties.photo');
|
||||
|
||||
if (isset($photos)) {
|
||||
foreach ((array) $photos as $photo) {
|
||||
// check the media was uploaded to my endpoint, and use path
|
||||
if (starts_with($photo, config('filesystems.disks.s3.url'))) {
|
||||
$path = substr($photo, strlen(config('filesystems.disks.s3.url')));
|
||||
$media[] = Media::where('path', ltrim($path, '/'))->firstOrFail();
|
||||
} else {
|
||||
$newMedia = Media::firstOrNew(['path' => $photo]);
|
||||
// currently assuming this is a photo from Swarm or OwnYourGram
|
||||
$newMedia->type = 'image';
|
||||
$newMedia->save();
|
||||
$media[] = $newMedia;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
private function getInstagramUrl(array $request): ?string
|
||||
{
|
||||
if (starts_with(array_get($request, 'properties.syndication.0'), 'https://www.instagram.com')) {
|
||||
return array_get($request, 'properties.syndication.0');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class PlaceService
|
|||
{
|
||||
//obviously a place needs a lat/lng, but this could be sent in a geo-url
|
||||
//if no geo array key, we assume the array already has lat/lng values
|
||||
if (array_key_exists('geo', $data)) {
|
||||
if (array_key_exists('geo', $data) && $data['geo'] !== null) {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$data['geo'],
|
||||
|
@ -47,7 +47,7 @@ class PlaceService
|
|||
{
|
||||
//check if the place exists if from swarm
|
||||
if (array_has($checkin, 'properties.url')) {
|
||||
$place = Place::whereExternalURL($checkin['properties']['url'][0])->get();
|
||||
$place = Place::whereExternalURL(array_get($checkin, 'properties.url.0'))->get();
|
||||
if (count($place) === 1) {
|
||||
return $place->first();
|
||||
}
|
||||
|
@ -59,11 +59,11 @@ class PlaceService
|
|||
throw new \InvalidArgumentException('Missing required longitude/latitude');
|
||||
}
|
||||
$place = new Place();
|
||||
$place->name = $checkin['properties']['name'][0];
|
||||
$place->external_urls = $checkin['properties']['url'][0];
|
||||
$place->name = array_get($checkin, 'properties.name.0');
|
||||
$place->external_urls = array_get($checkin, 'properties.url.0');
|
||||
$place->location = new Point(
|
||||
(float) $checkin['properties']['latitude'][0],
|
||||
(float) $checkin['properties']['longitude'][0]
|
||||
(float) array_get($checkin, 'properties.latitude.0'),
|
||||
(float) array_get($checkin, 'properties.longitude.0')
|
||||
);
|
||||
$place->save();
|
||||
|
||||
|
|
|
@ -2,14 +2,21 @@
|
|||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Media;
|
||||
use App\Place;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
use Tests\TestToken;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use App\Jobs\ProcessMedia;
|
||||
use App\Jobs\SendWebMentions;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use App\Jobs\SyndicateNoteToTwitter;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use App\Jobs\SyndicateNoteToFacebook;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Phaza\LaravelPostgis\Geometries\Point;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class MicropubControllerTest extends TestCase
|
||||
|
@ -124,7 +131,9 @@ class MicropubControllerTest extends TestCase
|
|||
'/api/post',
|
||||
[
|
||||
'h' => 'entry',
|
||||
'content' => $note
|
||||
'content' => $note,
|
||||
'published' => Carbon::now()->toW3CString(),
|
||||
'location' => 'geo:1.23,4.56',
|
||||
],
|
||||
[],
|
||||
[],
|
||||
|
@ -134,6 +143,60 @@ class MicropubControllerTest extends TestCase
|
|||
$this->assertDatabaseHas('notes', ['note' => $note]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid micropub requests creates a new note and syndicates to Twitter.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_post_request_creates_new_note_sends_to_twitter()
|
||||
{
|
||||
Queue::fake();
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = $faker->text;
|
||||
$response = $this->call(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'h' => 'entry',
|
||||
'content' => $note,
|
||||
'mp-syndicate-to' => 'https://twitter.com/jonnybarnes'
|
||||
],
|
||||
[],
|
||||
[],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response->assertJson(['response' => 'created']);
|
||||
$this->assertDatabaseHas('notes', ['note' => $note]);
|
||||
Queue::assertPushed(SyndicateNoteToTwitter::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid micropub requests creates a new note and syndicates to Facebook.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_post_request_creates_new_note_sends_to_facebook()
|
||||
{
|
||||
Queue::fake();
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = $faker->text;
|
||||
$response = $this->call(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'h' => 'entry',
|
||||
'content' => $note,
|
||||
'mp-syndicate-to' => 'https://facebook.com/jonnybarnes'
|
||||
],
|
||||
[],
|
||||
[],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response->assertJson(['response' => 'created']);
|
||||
$this->assertDatabaseHas('notes', ['note' => $note]);
|
||||
Queue::assertPushed(SyndicateNoteToFacebook::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid micropub requests creates a new place.
|
||||
*
|
||||
|
@ -182,12 +245,135 @@ class MicropubControllerTest extends TestCase
|
|||
$this->assertDatabaseHas('places', ['slug' => 'the-barton-arms']);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_invalid_token_returns_expected_error_response()
|
||||
{
|
||||
$response = $this->call(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'h' => 'entry',
|
||||
'content' => 'A random note',
|
||||
],
|
||||
[],
|
||||
[],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()]
|
||||
);
|
||||
$response->assertStatus(400);
|
||||
$response->assertJson(['error' => 'invalid_token']);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_scopeless_token_returns_expected_error_response()
|
||||
{
|
||||
$response = $this->call(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'h' => 'entry',
|
||||
'content' => 'A random note',
|
||||
],
|
||||
[],
|
||||
[],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithNoScope()]
|
||||
);
|
||||
$response->assertStatus(400);
|
||||
$response->assertJson(['error_description' => 'The provided token has no scopes']);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_for_place_without_create_scope_errors()
|
||||
{
|
||||
$response = $this->call(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'h' => 'card',
|
||||
'name' => 'The Barton Arms',
|
||||
'geo' => 'geo:53.4974,-2.3768'
|
||||
],
|
||||
[],
|
||||
[],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
||||
);
|
||||
$response->assertStatus(401);
|
||||
$response->assertJson(['error' => 'insufficient_scope']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid micropub requests using JSON syntax creates a new note.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_post_request_with_json_syntax_creates_new_note()
|
||||
{
|
||||
Queue::fake();
|
||||
Media::create([
|
||||
'path' => 'test-photo.jpg',
|
||||
'type' => 'image',
|
||||
]);
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = $faker->text;
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'type' => ['h-entry'],
|
||||
'properties' => [
|
||||
'content' => [$note],
|
||||
'in-reply-to' => ['https://aaronpk.localhost'],
|
||||
'mp-syndicate-to' => [
|
||||
'https://twitter.com/jonnybarnes',
|
||||
'https://facebook.com/jonnybarnes',
|
||||
],
|
||||
'photo' => [config('filesystems.disks.s3.url') . '/test-photo.jpg'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertStatus(201)
|
||||
->assertJson(['response' => 'created']);
|
||||
Queue::assertPushed(SendWebMentions::class);
|
||||
Queue::assertPushed(SyndicateNoteToTwitter::class);
|
||||
Queue::assertPushed(SyndicateNoteToFacebook::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid micropub requests using JSON syntax creates a new note with
|
||||
* existing self-created place.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_post_request_with_json_syntax_creates_new_note_with_existing_place_in_location()
|
||||
{
|
||||
$place = new Place();
|
||||
$place->name = 'Test Place';
|
||||
$place->location = new Point((float) 1.23, (float) 4.56);
|
||||
$place->save();
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = $faker->text;
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'type' => ['h-entry'],
|
||||
'properties' => [
|
||||
'content' => [$note],
|
||||
'location' => [$place->longurl],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertStatus(201)
|
||||
->assertJson(['response' => 'created']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid micropub requests using JSON syntax creates a new note with
|
||||
* a new place defined in the location block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_post_request_with_json_syntax_creates_new_note_with_new_place_in_location()
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = $faker->text;
|
||||
|
@ -198,6 +384,14 @@ class MicropubControllerTest extends TestCase
|
|||
'type' => ['h-entry'],
|
||||
'properties' => [
|
||||
'content' => [$note],
|
||||
'location' => [[
|
||||
'type' => ['h-card'],
|
||||
'properties' => [
|
||||
'name' => ['Awesome Venue'],
|
||||
'latitude' => ['1.23'],
|
||||
'longitude' => ['4.56'],
|
||||
],
|
||||
]],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
|
@ -205,6 +399,44 @@ class MicropubControllerTest extends TestCase
|
|||
$response
|
||||
->assertStatus(201)
|
||||
->assertJson(['response' => 'created']);
|
||||
$this->assertDatabaseHas('places', [
|
||||
'name' => 'Awesome Venue',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid micropub requests using JSON syntax creates a new note without
|
||||
* a new place defined in the location block if there is missing data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_post_request_with_json_syntax_creates_new_note_without_new_place_in_location()
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = $faker->text;
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'type' => ['h-entry'],
|
||||
'properties' => [
|
||||
'content' => [$note],
|
||||
'location' => [[
|
||||
'type' => ['h-card'],
|
||||
'properties' => [
|
||||
'name' => ['Awesome Venue'],
|
||||
],
|
||||
]],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertStatus(201)
|
||||
->assertJson(['response' => 'created']);
|
||||
$this->assertDatabaseMissing('places', [
|
||||
'name' => 'Awesome Venue',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,12 +468,12 @@ class MicropubControllerTest extends TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* Test a micropub requests using JSON syntax without a valis token returns
|
||||
* Test a micropub requests using JSON syntax without a valid token returns
|
||||
* an error. Also check the message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_micropub_post_request_with_json_syntax_with_invalid_token_returns_error()
|
||||
public function test_micropub_post_request_with_json_syntax_with_insufficient_token_returns_error()
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
$note = $faker->text;
|
||||
|
@ -254,7 +486,7 @@ class MicropubControllerTest extends TestCase
|
|||
'content' => [$note],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()]
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
||||
);
|
||||
$response
|
||||
->assertJson([
|
||||
|
@ -264,6 +496,27 @@ class MicropubControllerTest extends TestCase
|
|||
->assertStatus(401);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_for_unsupported_type_returns_error()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'type' => ['h-unsopported'], // a request type I don’t support
|
||||
'properties' => [
|
||||
'content' => ['Some content'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson([
|
||||
'response' => 'error',
|
||||
'error_description' => 'unsupported_request_type'
|
||||
])
|
||||
->assertStatus(500);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_creates_new_place()
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
|
@ -332,7 +585,10 @@ class MicropubControllerTest extends TestCase
|
|||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/A',
|
||||
'add' => [
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/123'],
|
||||
'syndication' => [
|
||||
'https://www.swarmapp.com/checkin/123',
|
||||
'https://www.facebook.com/checkin/123',
|
||||
],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
|
@ -341,7 +597,132 @@ class MicropubControllerTest extends TestCase
|
|||
->assertJson(['response' => 'updated'])
|
||||
->assertStatus(200);
|
||||
$this->assertDatabaseHas('notes', [
|
||||
'swarm_url' => 'https://www.swarmapp.com/checkin/123'
|
||||
'swarm_url' => 'https://www.swarmapp.com/checkin/123',
|
||||
'facebook_url' => 'https://www.facebook.com/checkin/123',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_update_add_image_to_post()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/A',
|
||||
'add' => [
|
||||
'photo' => ['https://example.org/photo.jpg'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson(['response' => 'updated'])
|
||||
->assertStatus(200);
|
||||
$this->assertDatabaseHas('media_endpoint', [
|
||||
'path' => 'https://example.org/photo.jpg',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_update_add_post_errors_for_non_note()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/blog/A',
|
||||
'add' => [
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/123'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson(['error' => 'invalid'])
|
||||
->assertStatus(500);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_update_add_post_errors_for_note_not_found()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/ZZZZ',
|
||||
'add' => [
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/123'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson(['error' => 'invalid_request'])
|
||||
->assertStatus(404);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_update_add_post_errors_for_unsupported_request()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/A',
|
||||
'morph' => [ // or any other unsupported update type
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/123'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson(['response' => 'error'])
|
||||
->assertStatus(500);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_update_errors_for_insufficient_scope()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/B',
|
||||
'add' => [
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/123'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
||||
);
|
||||
$response
|
||||
->assertStatus(401)
|
||||
->assertJson(['error' => 'insufficient_scope']);
|
||||
}
|
||||
|
||||
public function test_micropub_post_request_with_json_syntax_update_replace_post_syndication()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'/api/post',
|
||||
[
|
||||
'action' => 'update',
|
||||
'url' => config('app.url') . '/notes/L',
|
||||
'replace' => [
|
||||
'syndication' => [
|
||||
'https://www.swarmapp.com/checkin/the-id',
|
||||
'https://www.facebook.com/post/the-id',
|
||||
],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertJson(['response' => 'updated'])
|
||||
->assertStatus(200);
|
||||
$this->assertDatabaseHas('notes', [
|
||||
'swarm_url' => 'https://www.swarmapp.com/checkin/the-id',
|
||||
'facebook_url' => 'https://www.facebook.com/post/the-id',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -359,6 +740,20 @@ class MicropubControllerTest extends TestCase
|
|||
$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->call(
|
||||
'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->call(
|
||||
|
@ -367,7 +762,7 @@ class MicropubControllerTest extends TestCase
|
|||
[],
|
||||
[],
|
||||
[],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()]
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
|
||||
);
|
||||
$response->assertStatus(401);
|
||||
$response->assertJsonFragment(['error_description' => 'The token’s scope does not have the necessary requirements.']);
|
||||
|
@ -394,4 +789,91 @@ class MicropubControllerTest extends TestCase
|
|||
Queue::assertPushed(ProcessMedia::class);
|
||||
Storage::disk('local')->assertExists($filename);
|
||||
}
|
||||
|
||||
public function test_media_endpoint_upload_an_audio_file()
|
||||
{
|
||||
Queue::fake();
|
||||
Storage::fake('local');
|
||||
$file = __DIR__ . '/../audio.mp3';
|
||||
|
||||
$response = $this->call(
|
||||
'POST',
|
||||
'/api/media',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
'file' => new UploadedFile($file, 'audio.mp3', 'audio/mpeg', filesize($file), 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);
|
||||
}
|
||||
|
||||
public function test_media_endpoint_upload_a_video_file()
|
||||
{
|
||||
Queue::fake();
|
||||
Storage::fake('local');
|
||||
$file = __DIR__ . '/../video.ogv';
|
||||
|
||||
$response = $this->call(
|
||||
'POST',
|
||||
'/api/media',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
'file' => new UploadedFile($file, 'video.ogv', 'video/ogg', filesize($file), 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);
|
||||
}
|
||||
|
||||
public function test_media_endpoint_upload_a_document_file()
|
||||
{
|
||||
Queue::fake();
|
||||
Storage::fake('local');
|
||||
|
||||
$response = $this->call(
|
||||
'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);
|
||||
}
|
||||
|
||||
public function test_media_endpoint_upload_an_invalid_file_return_error()
|
||||
{
|
||||
Queue::fake();
|
||||
Storage::fake('local');
|
||||
|
||||
$response = $this->call(
|
||||
'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']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,4 +153,37 @@ class SwarmTest extends TestCase
|
|||
'swarm_url' => 'https://www.swarmapp.com/checkin/def'
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_faked_ownyourswarm_request_saves_just_post_when_error_in_checkin_data()
|
||||
{
|
||||
$response = $this->json(
|
||||
'POST',
|
||||
'api/post',
|
||||
[
|
||||
'type' => ['h-entry'],
|
||||
'properties' => [
|
||||
'published' => [\Carbon\Carbon::now()->toDateTimeString()],
|
||||
'syndication' => ['https://www.swarmapp.com/checkin/abc'],
|
||||
'content' => [[
|
||||
'value' => 'My first #checkin using Example Product',
|
||||
'html' => 'My first #checkin using <a href="http://example.org">Example Product</a>',
|
||||
]],
|
||||
'checkin' => [[
|
||||
'type' => ['h-card'],
|
||||
'properties' => [
|
||||
'name' => ['Awesome Venue'],
|
||||
'url' => ['https://foursquare.com/v/123456'],
|
||||
],
|
||||
]],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
|
||||
);
|
||||
$response
|
||||
->assertStatus(201)
|
||||
->assertJson(['response' => 'created']);
|
||||
$this->assertDatabaseMissing('places', [
|
||||
'name' => 'Awesome Venue',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,4 +32,46 @@ class TokenEndpointTest extends TestCase
|
|||
$this->assertEquals(config('app.url'), $output['me']);
|
||||
$this->assertTrue(array_key_exists('access_token', $output));
|
||||
}
|
||||
|
||||
public function test_token_endpoint_returns_error_when_auth_endpoint_lacks_me_data()
|
||||
{
|
||||
$mockClient = Mockery::mock(Client::class);
|
||||
$mockClient->shouldReceive('discoverAuthorizationEndpoint')
|
||||
->with(normalize_url(config('app.url')))
|
||||
->once()
|
||||
->andReturn('https://indieauth.com/auth');
|
||||
$mockClient->shouldReceive('verifyIndieAuthCode')
|
||||
->andReturn([
|
||||
'error' => 'error_message',
|
||||
]);
|
||||
$this->app->instance(Client::class, $mockClient);
|
||||
$response = $this->post('/api/token', [
|
||||
'me' => config('app.url'),
|
||||
'code' => 'abc123',
|
||||
'redirect_uri' => config('app.url') . '/indieauth-callback',
|
||||
'client_id' => config('app.url') . '/micropub-client',
|
||||
'state' => mt_rand(1000, 10000),
|
||||
]);
|
||||
$response->assertStatus(400);
|
||||
$response->assertSeeText('There was an error verifying the authorisation code.');
|
||||
}
|
||||
|
||||
public function test_token_endpoint_returns_error_when_no_auth_endpoint_found()
|
||||
{
|
||||
$mockClient = Mockery::mock(Client::class);
|
||||
$mockClient->shouldReceive('discoverAuthorizationEndpoint')
|
||||
->with(normalize_url(config('app.url')))
|
||||
->once()
|
||||
->andReturn(null);
|
||||
$this->app->instance(Client::class, $mockClient);
|
||||
$response = $this->post('/api/token', [
|
||||
'me' => config('app.url'),
|
||||
'code' => 'abc123',
|
||||
'redirect_uri' => config('app.url') . '/indieauth-callback',
|
||||
'client_id' => config('app.url') . '/micropub-client',
|
||||
'state' => mt_rand(1000, 10000),
|
||||
]);
|
||||
$response->assertStatus(400);
|
||||
$response->assertSeeText('Can’t determine the authorisation endpoint.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ trait TestToken
|
|||
return $token;
|
||||
}
|
||||
|
||||
public function getInvalidToken()
|
||||
public function getTokenWithIncorrectScope()
|
||||
{
|
||||
$signer = new Sha256();
|
||||
$token = (new Builder())
|
||||
|
@ -34,4 +34,24 @@ trait TestToken
|
|||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function getTokenWithNoScope()
|
||||
{
|
||||
$signer = new Sha256();
|
||||
$token = (new Builder())
|
||||
->set('client_id', 'https://quill.p3k.io')
|
||||
->set('me', 'https://jonnybarnes.localhost')
|
||||
->set('issued_at', time())
|
||||
->sign($signer, env('APP_KEY'))
|
||||
->getToken();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function getInvalidToken()
|
||||
{
|
||||
$token = $this->getToken();
|
||||
|
||||
return substr($token, 0, -5);
|
||||
}
|
||||
}
|
||||
|
|
BIN
tests/audio.mp3
Normal file
BIN
tests/audio.mp3
Normal file
Binary file not shown.
BIN
tests/video.ogv
Normal file
BIN
tests/video.ogv
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue