Upgrade to Laravel 13

This commit is contained in:
Jonny Barnes 2026-04-07 09:01:19 +01:00
commit 9f012b01e4
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
117 changed files with 1878 additions and 2215 deletions

View file

@ -70,11 +70,6 @@ ADMIN_USER=admin# pick something better, this is used for `/admin`
ADMIN_PASS=password
DISPLAY_NAME='Joe Bloggs'# This is used for example in the header and titles
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=
SCOUT_DRIVER=database
SCOUT_QUEUE=false

View file

@ -28,10 +28,10 @@ class MentionRenderer implements NodeRendererInterface
// This is not [@]handle@instance, so return a Twitter link
if (count($parts) === 1) {
return new HtmlElement('a', ['href' => 'https://twitter.com/' . $parts[0]], '@' . $mentionText);
return new HtmlElement('a', ['href' => 'https://twitter.com/'.$parts[0]], '@'.$mentionText);
}
// Render the Mastodon profile link
return new HtmlElement('a', ['href' => 'https://' . $parts[1] . '/@' . $parts[0]], '@' . $mentionText);
return new HtmlElement('a', ['href' => 'https://'.$parts[1].'/@'.$parts[0]], '@'.$mentionText);
}
}

View file

@ -34,10 +34,10 @@ class CopyMediaToLocal extends Command
foreach ($media as $mediaItem) {
$filename = $mediaItem->path;
$this->info('Processing: ' . $filename);
$this->info('Processing: '.$filename);
// If the file is already saved locally skip to next one
if (Storage::disk('local')->exists('public/' . $filename)) {
if (Storage::disk('local')->exists('public/'.$filename)) {
$this->info('File already exists locally, skipping');
continue;
@ -50,19 +50,19 @@ class CopyMediaToLocal extends Command
continue;
}
$contents = Storage::disk('s3')->get($filename);
Storage::disk('local')->put('public/' . $filename, $contents);
Storage::disk('local')->put('public/'.$filename, $contents);
// Copy -medium and -small versions if they exist
$filenameParts = explode('.', $filename);
$extension = array_pop($filenameParts);
$basename = trim(implode('.', $filenameParts), '.');
$mediumFilename = $basename . '-medium.' . $extension;
$smallFilename = $basename . '-small.' . $extension;
$mediumFilename = $basename.'-medium.'.$extension;
$smallFilename = $basename.'-small.'.$extension;
if (Storage::disk('s3')->exists($mediumFilename)) {
Storage::disk('local')->put('public/' . $mediumFilename, Storage::disk('s3')->get($mediumFilename));
Storage::disk('local')->put('public/'.$mediumFilename, Storage::disk('s3')->get($mediumFilename));
}
if (Storage::disk('s3')->exists($smallFilename)) {
Storage::disk('local')->put('public/' . $smallFilename, Storage::disk('s3')->get($smallFilename));
Storage::disk('local')->put('public/'.$smallFilename, Storage::disk('s3')->get($smallFilename));
}
}
}

View file

@ -63,7 +63,7 @@ class MigratePlaceDataFromPostgis extends Command
$places = Place::all();
$places->each(function ($place) {
$this->info('Extracting Postgis data for place: ' . $place->name);
$this->info('Extracting Postgis data for place: '.$place->name);
$place->latitude = $place->location->getLat();
$place->longitude = $place->location->getLng();

View file

@ -32,11 +32,11 @@ class ParseCachedWebMentions extends Command
*/
public function handle(FileSystem $filesystem): void
{
$htmlFiles = $filesystem->allFiles(storage_path() . '/HTML');
$htmlFiles = $filesystem->allFiles(storage_path().'/HTML');
foreach ($htmlFiles as $file) {
if ($file->getExtension() !== 'backup') { // we dont want to parse `.backup` files
$filepath = $file->getPathname();
$this->info('Loading HTML from: ' . $filepath);
$this->info('Loading HTML from: '.$filepath);
$html = $filesystem->get($filepath);
$url = $this->urlFromFilename($filepath);
$webmention = WebMention::where('source', $url)->firstOrFail();
@ -53,7 +53,7 @@ class ParseCachedWebMentions extends Command
*/
private function urlFromFilename(string $filepath): string
{
$dir = mb_substr($filepath, mb_strlen(storage_path() . '/HTML/'));
$dir = mb_substr($filepath, mb_strlen(storage_path().'/HTML/'));
$url = str_replace(['http/', 'https/'], ['http://', 'https://'], $dir);
if (mb_substr($url, -10) === 'index.html') {
$url = mb_substr($url, 0, -10);

View file

@ -31,7 +31,7 @@ class ReDownloadWebMentions extends Command
{
$webmentions = WebMention::all();
foreach ($webmentions as $webmention) {
$this->info('Initiation re-download of ' . $webmention->source);
$this->info('Initiation re-download of '.$webmention->source);
dispatch(new DownloadWebMention($webmention->source));
}
}

View file

@ -7,6 +7,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Contact;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;
@ -75,7 +76,7 @@ class ContactsController extends Controller
if (request()->hasFile('avatar') && (request()->input('homepage') != '')) {
$dir = parse_url(request()->input('homepage'), PHP_URL_HOST);
$destination = public_path() . '/assets/profile-images/' . $dir;
$destination = public_path().'/assets/profile-images/'.$dir;
$filesystem = new Filesystem;
if ($filesystem->isDirectory($destination) === false) {
$filesystem->makeDirectory($destination);
@ -103,7 +104,7 @@ class ContactsController extends Controller
* This method attempts to find the microformat marked-up profile image
* from a given homepage and save it accordingly
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
* @return RedirectResponse|View
*/
public function getAvatar(int $contactId)
{
@ -115,8 +116,8 @@ class ContactsController extends Controller
$client = resolve(Client::class);
try {
$response = $client->get($contact->homepage);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
return redirect('/admin/contacts/' . $contactId . '/edit')
} catch (BadResponseException $e) {
return redirect('/admin/contacts/'.$contactId.'/edit')
->with('error', 'Bad resposne from contacts homepage');
}
$mf2 = \Mf2\parse((string) $response->getBody(), $contact->homepage);
@ -129,18 +130,18 @@ class ContactsController extends Controller
if ($avatarURL !== null) {
try {
$avatar = $client->get($avatarURL);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
return redirect('/admin/contacts/' . $contactId . '/edit')
} catch (BadResponseException $e) {
return redirect('/admin/contacts/'.$contactId.'/edit')
->with('error', 'Unable to download avatar');
}
}
if ($avatar !== null) {
$directory = public_path() . '/assets/profile-images/' . parse_url($contact->homepage, PHP_URL_HOST);
$directory = public_path().'/assets/profile-images/'.parse_url($contact->homepage, PHP_URL_HOST);
$filesystem = new Filesystem;
if ($filesystem->isDirectory($directory) === false) {
$filesystem->makeDirectory($directory);
}
$filesystem->put($directory . '/image', $avatar->getBody());
$filesystem->put($directory.'/image', $avatar->getBody());
return view('admin.contacts.getavatarsuccess', [
'homepage' => parse_url($contact->homepage, PHP_URL_HOST),
@ -148,6 +149,6 @@ class ContactsController extends Controller
}
}
return redirect('/admin/contacts/' . $contactId . '/edit');
return redirect('/admin/contacts/'.$contactId.'/edit');
}
}

View file

@ -128,7 +128,7 @@ class PasskeysController extends Controller
// Unset session data to mitigate replay attacks
$request->session()->forget('create_options');
if (empty($publicKeyCredentialCreationOptionsData)) {
throw new WebAuthnException('No public key credential request options found');
throw new WebauthnException('No public key credential request options found');
}
$attestationStatementSupportManager = new AttestationStatementSupportManager;
@ -145,7 +145,7 @@ class PasskeysController extends Controller
);
if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
throw new WebAuthnException('Invalid response type');
throw new WebauthnException('Invalid response type');
}
$algorithmManager = new Manager;

View file

@ -120,6 +120,7 @@ class PlacesController extends Controller
foreach ($place1->notes as $note) {
$note->place()->dissociate();
$note->place()->associate($place2->id);
$note->save();
}
$place1->delete();
}
@ -127,6 +128,7 @@ class PlacesController extends Controller
foreach ($place2->notes as $note) {
$note->place()->dissociate();
$note->place()->associate($place1->id);
$note->save();
}
$place2->delete();
}

View file

@ -38,9 +38,9 @@ class ArticlesController extends Controller
if ($article->updated_at->year != $year || $article->updated_at->month != $month) {
return redirect('/blog/'
. $article->updated_at->year
. '/' . $article->updated_at->format('m')
. '/' . $slug);
.$article->updated_at->year
.'/'.$article->updated_at->format('m')
.'/'.$slug);
}
return view('articles.show', compact('article'));

View file

@ -19,9 +19,9 @@ class ContactsController extends Controller
$contacts = Contact::all();
foreach ($contacts as $contact) {
$contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST);
$file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image';
$file = public_path().'/assets/profile-images/'.$contact->homepageHost.'/image';
$contact->image = ($filesystem->exists($file)) ?
'/assets/profile-images/' . $contact->homepageHost . '/image'
'/assets/profile-images/'.$contact->homepageHost.'/image'
:
'/assets/profile-images/default-image';
}
@ -35,11 +35,11 @@ class ContactsController extends Controller
public function show(Contact $contact): View
{
$contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST);
$file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image';
$file = public_path().'/assets/profile-images/'.$contact->homepageHost.'/image';
$filesystem = new Filesystem;
$image = ($filesystem->exists($file)) ?
'/assets/profile-images/' . $contact->homepageHost . '/image'
'/assets/profile-images/'.$contact->homepageHost.'/image'
:
'/assets/profile-images/default-image';

View file

@ -71,9 +71,9 @@ class FeedsController extends Controller
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
$data = [
'version' => 'https://jsonfeed.org/version/1.1',
'title' => 'The JSON Feed for ' . config('user.display_name') . 's blog',
'home_page_url' => config('app.url') . '/blog',
'feed_url' => config('app.url') . '/blog/feed.json',
'title' => 'The JSON Feed for '.config('user.display_name').'s blog',
'home_page_url' => config('app.url').'/blog',
'feed_url' => config('app.url').'/blog/feed.json',
'authors' => [
[
'name' => config('user.display_name'),
@ -85,9 +85,9 @@ class FeedsController extends Controller
foreach ($articles as $key => $article) {
$data['items'][$key] = [
'id' => config('app.url') . $article->link,
'id' => config('app.url').$article->link,
'title' => $article->title,
'url' => config('app.url') . $article->link,
'url' => config('app.url').$article->link,
'content_html' => $article->main,
'date_published' => $article->created_at->tz('UTC')->toRfc3339String(),
'date_modified' => $article->updated_at->tz('UTC')->toRfc3339String(),
@ -105,9 +105,9 @@ class FeedsController extends Controller
$notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get();
$data = [
'version' => 'https://jsonfeed.org/version/1.1',
'title' => 'The JSON Feed for ' . config('user.display_name') . 's notes',
'home_page_url' => config('app.url') . '/notes',
'feed_url' => config('app.url') . '/notes/feed.json',
'title' => 'The JSON Feed for '.config('user.display_name').'s notes',
'home_page_url' => config('app.url').'/notes',
'feed_url' => config('app.url').'/notes/feed.json',
'authors' => [
[
'name' => config('user.display_name'),
@ -144,8 +144,8 @@ class FeedsController extends Controller
$items[] = [
'type' => 'entry',
'published' => $article->created_at,
'uid' => config('app.url') . $article->link,
'url' => config('app.url') . $article->link,
'uid' => config('app.url').$article->link,
'url' => config('app.url').$article->link,
'content' => [
'text' => $article->main,
'html' => $article->html,
@ -156,7 +156,7 @@ class FeedsController extends Controller
return response()->json([
'type' => 'feed',
'name' => 'Blog feed for ' . config('app.name'),
'name' => 'Blog feed for '.config('app.name'),
'url' => url('/blog'),
'author' => [
'type' => 'card',
@ -192,7 +192,7 @@ class FeedsController extends Controller
return response()->json([
'type' => 'feed',
'name' => 'Notes feed for ' . config('app.name'),
'name' => 'Notes feed for '.config('app.name'),
'url' => url('/notes'),
'author' => [
'type' => 'card',

View file

@ -193,7 +193,7 @@ class MicropubMediaController extends Controller
*/
private function saveFileToLocal(UploadedFile $file): string
{
$filename = Uuid::uuid4()->toString() . '.' . $file->extension();
$filename = Uuid::uuid4()->toString().'.'.$file->extension();
Storage::disk('local')->putFileAs('', $file, $filename);
return $filename;

View file

@ -64,7 +64,7 @@ class NotesController extends Controller
*/
public function redirect(int $decId): RedirectResponse
{
return redirect(config('app.url') . '/notes/' . (new Numbers)->numto60($decId));
return redirect(config('app.url').'/notes/'.(new Numbers)->numto60($decId));
}
/**

View file

@ -14,11 +14,11 @@ class LinkHeadersMiddleware
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->header('Link', '<' . route('indieauth.metadata') . '>; rel="indieauth-metadata"', false);
$response->header('Link', '<' . route('indieauth.start') . '>; rel="authorization_endpoint"', false);
$response->header('Link', '<' . route('indieauth.token') . '>; rel="token_endpoint"', false);
$response->header('Link', '<' . route('micropub-endpoint') . '>; rel="micropub"', false);
$response->header('Link', '<' . route('webmention-endpoint') . '>; rel="webmention"', false);
$response->header('Link', '<'.route('indieauth.metadata').'>; rel="indieauth-metadata"', false);
$response->header('Link', '<'.route('indieauth.start').'>; rel="authorization_endpoint"', false);
$response->header('Link', '<'.route('indieauth.token').'>; rel="token_endpoint"', false);
$response->header('Link', '<'.route('micropub-endpoint').'>; rel="micropub"', false);
$response->header('Link', '<'.route('webmention-endpoint').'>; rel="webmention"', false);
return $response;
}

View file

@ -16,7 +16,7 @@ class RedirectIfAuthenticated
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{

View file

@ -39,9 +39,9 @@ class DownloadWebMention implements ShouldQueue
// Laravel should catch and retry these automatically.
if ($response->getStatusCode() === 200) {
$filesystem = new FileSystem;
$filename = storage_path('HTML') . '/' . $this->createFilenameFromURL($this->source);
$filename = storage_path('HTML').'/'.$this->createFilenameFromURL($this->source);
// backup file first
$filenameBackup = $filename . '.' . date('Y-m-d') . '.backup';
$filenameBackup = $filename.'.'.date('Y-m-d').'.backup';
if ($filesystem->exists($filename)) {
$filesystem->copy($filename, $filenameBackup);
}

View file

@ -5,10 +5,8 @@ declare(strict_types=1);
namespace App\Jobs;
use App\Models\Like;
use Codebird\Codebird;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@ -39,35 +37,6 @@ class ProcessLike implements ShouldQueue
*/
public function handle(Client $client, Authorship $authorship): int
{
if ($this->isTweet($this->like->url)) {
$codebird = resolve(Codebird::class);
$tweet = $codebird->statuses_oembed(['url' => $this->like->url]);
$this->like->author_name = $tweet->author_name;
$this->like->author_url = $tweet->author_url;
$this->like->content = $tweet->html;
$this->like->save();
// POSSE like
try {
$client->request(
'POST',
'https://brid.gy/publish/webmention',
[
'form_params' => [
'source' => $this->like->url,
'target' => 'https://brid.gy/publish/twitter',
],
]
);
} catch (RequestException) {
return 0;
}
return 0;
}
$response = $client->request('GET', $this->like->url);
$mf2 = \Mf2\parse((string) $response->getBody(), $this->like->url);
if (Arr::has($mf2, 'items.0.properties.content')) {
@ -91,15 +60,4 @@ class ProcessLike implements ShouldQueue
return 0;
}
/**
* Determine if a given URL is that of a Tweet.
*/
private function isTweet(string $url): bool
{
$host = parse_url($url, PHP_URL_HOST);
$parts = array_reverse(explode('.', $host));
return $parts[0] === 'com' && $parts[1] === 'twitter';
}
}

View file

@ -57,10 +57,10 @@ class ProcessMedia implements ShouldQueue
$basename = trim(implode('.', $filenameParts), '.');
$medium = $image->resize(width: 1000);
Storage::disk('public')->put($basename . '-medium.' . $extension, (string) $medium->encode());
Storage::disk('public')->put($basename.'-medium.'.$extension, (string) $medium->encode());
$small = $image->resize(width: 500);
Storage::disk('public')->put($basename . '-small.' . $extension, (string) $small->encode());
Storage::disk('public')->put($basename.'-small.'.$extension, (string) $small->encode());
}
// Now we can delete the locally saved image

View file

@ -110,7 +110,7 @@ class ProcessWebMention implements ShouldQueue
if (str_ends_with($url, '/')) {
$filenameFromURL .= 'index.html';
}
$path = storage_path() . '/HTML/' . $filenameFromURL;
$path = storage_path().'/HTML/'.$filenameFromURL;
$parts = explode('/', $path);
$name = array_pop($parts);
$dir = implode('/', $parts);

View file

@ -62,13 +62,13 @@ class SaveProfileImage implements ShouldQueue
$image = $response->getBody();
} catch (RequestException) {
// we are opening and reading the default image so that
$default = public_path() . '/assets/profile-images/default-image';
$default = public_path().'/assets/profile-images/default-image';
$handle = fopen($default, 'rb');
$image = fread($handle, filesize($default));
fclose($handle);
}
$path = public_path() . '/assets/profile-images/' . parse_url($home, PHP_URL_HOST) . '/image';
$path = public_path().'/assets/profile-images/'.parse_url($home, PHP_URL_HOST).'/image';
$parts = explode('/', $path);
$name = array_pop($parts);
$dir = implode('/', $parts);

View file

@ -41,7 +41,7 @@ class SaveScreenshot implements ShouldQueue
// First request that CloudConvert takes a screenshot of the URL
$takeScreenshotJobResponse = $client->request('POST', 'https://api.cloudconvert.com/v2/capture-website', [
'headers' => [
'Authorization' => 'Bearer ' . config('services.cloudconvert.token'),
'Authorization' => 'Bearer '.config('services.cloudconvert.token'),
],
'json' => [
'url' => $this->bookmark->url,
@ -56,9 +56,9 @@ class SaveScreenshot implements ShouldQueue
$taskId = json_decode($takeScreenshotJobResponse->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR)->data->id;
// Now wait till the status job is finished
$screenshotJobStatusResponse = $retryClient->request('GET', 'https://api.cloudconvert.com/v2/tasks/' . $taskId, [
$screenshotJobStatusResponse = $retryClient->request('GET', 'https://api.cloudconvert.com/v2/tasks/'.$taskId, [
'headers' => [
'Authorization' => 'Bearer ' . config('services.cloudconvert.token'),
'Authorization' => 'Bearer '.config('services.cloudconvert.token'),
],
'query' => [
'include' => 'payload',
@ -70,7 +70,7 @@ class SaveScreenshot implements ShouldQueue
// Now we can create a new job to request thst the screenshot is exported to a temporary URL we can download the screenshot from
$exportImageJob = $client->request('POST', 'https://api.cloudconvert.com/v2/export/url', [
'headers' => [
'Authorization' => 'Bearer ' . config('services.cloudconvert.token'),
'Authorization' => 'Bearer '.config('services.cloudconvert.token'),
],
'json' => [
'input' => $finishedCaptureId,
@ -81,9 +81,9 @@ class SaveScreenshot implements ShouldQueue
$exportImageJobId = json_decode($exportImageJob->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR)->data->id;
// Again, wait till the status of this export job is finished
$finalImageUrlResponse = $retryClient->request('GET', 'https://api.cloudconvert.com/v2/tasks/' . $exportImageJobId, [
$finalImageUrlResponse = $retryClient->request('GET', 'https://api.cloudconvert.com/v2/tasks/'.$exportImageJobId, [
'headers' => [
'Authorization' => 'Bearer ' . config('services.cloudconvert.token'),
'Authorization' => 'Bearer '.config('services.cloudconvert.token'),
],
'query' => [
'include' => 'payload',
@ -95,7 +95,7 @@ class SaveScreenshot implements ShouldQueue
$finalImageUrlContent = $client->request('GET', $finalImageUrl);
Storage::disk('public')->put('/assets/img/bookmarks/' . $taskId . '.png', $finalImageUrlContent->getBody()->getContents());
Storage::disk('public')->put('/assets/img/bookmarks/'.$taskId.'.png', $finalImageUrlContent->getBody()->getContents());
$this->bookmark->screenshot = $taskId;
$this->bookmark->save();

View file

@ -15,6 +15,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Mf2\Parser;
class SendWebMentions implements ShouldQueue
{
@ -83,7 +84,11 @@ class SendWebMentions implements ShouldQueue
// failed to find a header so parse HTML
$html = (string) $response->getBody();
$mf2 = new \Mf2\Parser($html, $url);
if ($html === '') {
return null;
}
$mf2 = new Parser($html, $url);
$rels = $mf2->parseRelsAndAlternates();
if (array_key_exists('webmention', $rels[0])) {
$endpoint = $rels[0]['webmention'][0];

View file

@ -42,7 +42,7 @@ class SyndicateNoteToBluesky implements ShouldQueue
'https://brid.gy/micropub',
[
'headers' => [
'Authorization' => 'Bearer ' . config('bridgy.bluesky_token'),
'Authorization' => 'Bearer '.config('bridgy.bluesky_token'),
],
'json' => [
'type' => ['h-entry'],

View file

@ -42,7 +42,7 @@ class SyndicateNoteToMastodon implements ShouldQueue
'https://brid.gy/micropub',
[
'headers' => [
'Authorization' => 'Bearer ' . config('bridgy.mastodon_token'),
'Authorization' => 'Bearer '.config('bridgy.mastodon_token'),
],
'json' => [
'type' => ['h-entry'],

View file

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace App\Models;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -18,23 +20,14 @@ use League\CommonMark\MarkdownConverter;
use Spatie\CommonMarkHighlighter\FencedCodeRenderer;
use Spatie\CommonMarkHighlighter\IndentedCodeRenderer;
#[Table('articles')]
#[Fillable(['url', 'title', 'main', 'published'])]
class Article extends Model
{
use HasFactory;
use Sluggable;
use SoftDeletes;
/** @var string */
protected $table = 'articles';
/** @var array<int, string> */
protected $fillable = [
'url',
'title',
'main',
'published',
];
/** @var array<string, string> */
protected $casts = [
'created_at' => 'datetime',
@ -100,7 +93,7 @@ class Article extends Model
protected function link(): Attribute
{
return Attribute::get(
get: fn () => '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl,
get: fn () => '/blog/'.$this->updated_at->year.'/'.$this->updated_at->format('m').'/'.$this->titleurl,
);
}
@ -112,15 +105,15 @@ class Article extends Model
if ($year === null) {
return $query;
}
$start = $year . '-01-01 00:00:00';
$end = ($year + 1) . '-01-01 00:00:00';
$start = $year.'-01-01 00:00:00';
$end = ($year + 1).'-01-01 00:00:00';
if (($month !== null) && ($month !== 12)) {
$start = $year . '-' . $month . '-01 00:00:00';
$end = $year . '-' . ($month + 1) . '-01 00:00:00';
$start = $year.'-'.$month.'-01 00:00:00';
$end = $year.'-'.($month + 1).'-01 00:00:00';
}
if ($month === 12) {
$start = $year . '-12-01 00:00:00';
$end = ($year + 1) . '-01-01 00:00:00';
$start = $year.'-12-01 00:00:00';
$end = ($year + 1).'-01-01 00:00:00';
}
return $query->where([

View file

@ -4,18 +4,17 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
#[Fillable(['url', 'name', 'content'])]
class Bookmark extends Model
{
use HasFactory;
/** @var array<int, string> */
protected $fillable = ['url', 'name', 'content'];
/** @var array<string, string> */
protected $casts = [
'syndicates' => 'array',
@ -26,10 +25,10 @@ class Bookmark extends Model
return $this->belongsToMany('App\Models\Tag');
}
protected function local_uri(): Attribute
protected function localUri(): Attribute
{
return Attribute::get(
get: fn () => config('app.url') . '/bookmarks/' . $this->id,
get: fn () => config('app.url').'/bookmarks/'.$this->id,
);
}
}

View file

@ -4,28 +4,26 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
#[Table('contacts')]
#[Fillable(['nick', 'name', 'homepage', 'twitter', 'facebook'])]
class Contact extends Model
{
use HasFactory;
/** @var string */
protected $table = 'contacts';
/** @var array<int, string> */
protected $fillable = ['nick', 'name', 'homepage', 'twitter', 'facebook'];
protected function photo(): Attribute
{
$photo = '/assets/profile-images/default-image';
if (array_key_exists('homepage', $this->attributes) && ! empty($this->attributes['homepage'])) {
$host = parse_url($this->attributes['homepage'], PHP_URL_HOST);
if (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
$photo = '/assets/profile-images/' . $host . '/image';
if (file_exists(public_path().'/assets/profile-images/'.$host.'/image')) {
$photo = '/assets/profile-images/'.$host.'/image';
}
}

View file

@ -5,20 +5,19 @@ declare(strict_types=1);
namespace App\Models;
use App\Traits\FilterHtml;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Mf2;
#[Fillable(['url'])]
class Like extends Model
{
use FilterHtml;
use HasFactory;
/** @var array<int, string> */
protected $fillable = ['url'];
protected function url(): Attribute
{
return Attribute::set(

View file

@ -4,22 +4,20 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Str;
#[Table('media_endpoint')]
#[Fillable(['token', 'path', 'type', 'image_widths'])]
class Media extends Model
{
use HasFactory;
/** @var string */
protected $table = 'media_endpoint';
/** @var array<int, string> */
protected $fillable = ['token', 'path', 'type', 'image_widths'];
public function notes(): BelongsToMany
{
return $this->belongsToMany(Note::class)
@ -35,7 +33,7 @@ class Media extends Model
return $attributes['path'];
}
return config('app.url') . '/storage/' . $attributes['path'];
return config('app.url').'/storage/'.$attributes['path'];
}
);
}
@ -80,7 +78,7 @@ class Media extends Model
$basename = $this->getBasename($path);
$extension = $this->getExtension($path);
return config('app.url') . '/storage/' . $basename . '-' . $size . '.' . $extension;
return config('app.url').'/storage/'.$basename.'-'.$size.'.'.$extension;
}
private function getBasename(string $path): string
@ -91,7 +89,7 @@ class Media extends Model
array_pop($filenameParts);
return ltrim(array_reduce($filenameParts, static function ($carry, $item) {
return $carry . '.' . $item;
return $carry.'.'.$item;
}, ''), '.');
}

View file

@ -4,20 +4,18 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Table('clients')]
#[Fillable(['client_url', 'client_name'])]
class MicropubClient extends Model
{
use HasFactory;
/** @var string */
protected $table = 'clients';
/** @var array<int, string> */
protected $fillable = ['client_url', 'client_name'];
public function notes(): HasMany
{
return $this->hasMany('App\Models\Note', 'client_id', 'client_url');

View file

@ -6,9 +6,12 @@ namespace App\Models;
use App\CommonMark\Generators\MentionGenerator;
use App\CommonMark\Renderers\MentionRenderer;
use Codebird\Codebird;
use Exception;
use App\Observers\NoteObserver;
use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -31,6 +34,10 @@ use Normalizer;
use Spatie\CommonMarkHighlighter\FencedCodeRenderer;
use Spatie\CommonMarkHighlighter\IndentedCodeRenderer;
#[Table('notes')]
#[Fillable(['note', 'in_reply_to', 'client_id'])]
#[Hidden(['searchable'])]
#[ObservedBy(NoteObserver::class)]
class Note extends Model
{
use HasFactory;
@ -61,16 +68,6 @@ class Note extends Model
/** @var string */
protected $table = 'notes';
/** @var array<int, string> */
protected $fillable = [
'note',
'in_reply_to',
'client_id',
];
/** @var array<int, string> */
protected $hidden = ['searchable'];
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
@ -126,7 +123,7 @@ class Note extends Model
public function getNoteAttribute(?string $value): ?string
{
if ($value === null && $this->place !== null) {
$value = '📍: <a href="' . $this->place->uri . '">' . $this->place->name . '</a>';
$value = '📍: <a href="'.$this->place->uri.'">'.$this->place->name.'</a>';
}
// if $value is still null, just return null
@ -150,13 +147,13 @@ class Note extends Model
foreach ($this->media as $media) {
if ($media->type === 'image') {
$note .= PHP_EOL . '<img src="' . $media->url . '" alt="">';
$note .= PHP_EOL.'<img src="'.$media->url.'" alt="">';
}
if ($media->type === 'audio') {
$note .= PHP_EOL . '<audio src="' . $media->url . '">';
$note .= PHP_EOL.'<audio src="'.$media->url.'">';
}
if ($media->type === 'video') {
$note .= PHP_EOL . '<video src="' . $media->url . '">';
$note .= PHP_EOL.'<video src="'.$media->url.'">';
}
}
@ -176,7 +173,7 @@ class Note extends Model
public function getUriAttribute(): string
{
return config('app.url') . '/notes/' . $this->nb60id;
return config('app.url').'/notes/'.$this->nb60id;
}
public function getIso8601Attribute(): string
@ -241,43 +238,6 @@ class Note extends Model
return null;
}
/**
* Get the OEmbed html for a tweet the note is a reply to.
*/
public function getTwitterAttribute(): ?object
{
if (
$this->in_reply_to === null ||
! $this->isTwitterLink($this->in_reply_to)
) {
return null;
}
$tweetId = basename($this->in_reply_to);
if (Cache::has($tweetId)) {
return Cache::get($tweetId);
}
try {
$codebird = resolve(Codebird::class);
$oEmbed = $codebird->statuses_oembed([
'url' => $this->in_reply_to,
'dnt' => true,
'align' => 'center',
'maxwidth' => 512,
]);
if ($oEmbed->httpstatus >= 400) {
throw new Exception;
}
} catch (Exception $e) {
return null;
}
Cache::put($tweetId, $oEmbed, ($oEmbed->cache_age));
return $oEmbed;
}
/**
* Scope a query to select a note via a NewBase60 id.
*/
@ -313,14 +273,14 @@ class Note extends Model
self::USERNAMES_REGEX,
function ($matches) {
if (is_null($this->contacts[$matches[1]])) {
return '<a href="https://twitter.com/' . $matches[1] . '">' . $matches[0] . '</a>';
return '<a href="https://twitter.com/'.$matches[1].'">'.$matches[0].'</a>';
}
$contact = $this->contacts[$matches[1]]; // easier to read the following code
$host = parse_url($contact->homepage, PHP_URL_HOST);
$contact->photo = '/assets/profile-images/default-image';
if (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
$contact->photo = '/assets/profile-images/' . $host . '/image';
if (file_exists(public_path().'/assets/profile-images/'.$host.'/image')) {
$contact->photo = '/assets/profile-images/'.$host.'/image';
}
return trim(view('templates.mini-hcard', ['contact' => $contact])->render());
@ -371,8 +331,8 @@ class Note extends Model
'/#([^\s[:punct:]]+)/',
function ($matches) {
return '<a rel="tag" class="p-category" href="/notes/tagged/'
. Tag::normalize($matches[1]) . '">#'
. $matches[1] . '</a>';
.Tag::normalize($matches[1]).'">#'
.$matches[1].'</a>';
},
$note
);
@ -404,7 +364,7 @@ class Note extends Model
public function reverseGeoCode(float $latitude, float $longitude): string
{
$latLng = $latitude . ',' . $longitude;
$latLng = $latitude.','.$longitude;
return Cache::get($latLng, function () use ($latLng, $latitude, $longitude) {
$guzzle = resolve(Client::class);
@ -422,46 +382,41 @@ class Note extends Model
if (isset($json->address->suburb)) {
$locality = $json->address->suburb;
if (isset($json->address->city)) {
$locality .= ', ' . $json->address->city;
$locality .= ', '.$json->address->city;
}
$address = '<span class="p-locality">'
. $locality
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
.$locality
.'</span>, <span class="p-country-name">'
.$json->address->country
.'</span>';
Cache::forever($latLng, $address);
return $address;
}
if (isset($json->address->city)) {
$address = '<span class="p-locality">'
. $json->address->city
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
.$json->address->city
.'</span>, <span class="p-country-name">'
.$json->address->country
.'</span>';
Cache::forever($latLng, $address);
return $address;
}
if (isset($json->address->county)) {
$address = '<span class="p-region">'
. $json->address->county
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
.$json->address->county
.'</span>, <span class="p-country-name">'
.$json->address->country
.'</span>';
Cache::forever($latLng, $address);
return $address;
}
$address = '<span class="p-country-name">' . $json->address->country . '</span>';
$address = '<span class="p-country-name">'.$json->address->country.'</span>';
Cache::forever($latLng, $address);
return $address;
});
}
private function isTwitterLink(string $inReplyTo): bool
{
return str_starts_with($inReplyTo, 'https://twitter.com/');
}
}

View file

@ -4,20 +4,16 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['passkey_id', 'passkey'])]
class Passkey extends Model
{
use HasFactory;
/** @inerhitDoc */
protected $fillable = [
'passkey_id',
'passkey',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Models;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -12,6 +13,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
#[Fillable(['name', 'slug'])]
class Place extends Model
{
use HasFactory;
@ -22,9 +24,6 @@ class Place extends Model
return 'slug';
}
/** @var array<int, string> */
protected $fillable = ['name', 'slug'];
/** @var array<string, string> */
protected $casts = [
'latitude' => 'float',
@ -77,7 +76,7 @@ class Place extends Model
protected function uri(): Attribute
{
return Attribute::get(
get: static fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'],
get: static fn ($value, $attributes) => config('app.url').'/places/'.$attributes['slug'],
);
}

View file

@ -4,40 +4,20 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Appends;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Visible;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
#[Fillable(['uid', 'name', 'service_name', 'service_url', 'service_photo', 'user_name', 'user_url', 'user_photo'])]
#[Visible(['uid', 'name', 'service', 'user'])]
#[Appends(['service', 'user'])]
class SyndicationTarget extends Model
{
use HasFactory;
/** @var array<int, string> */
protected $fillable = [
'uid',
'name',
'service_name',
'service_url',
'service_photo',
'user_name',
'user_url',
'user_photo',
];
/** @var array<int, string> */
protected $visible = [
'uid',
'name',
'service',
'user',
];
/** @var array<int, string> */
protected $appends = [
'service',
'user',
];
protected function service(): Attribute
{
return Attribute::get(

View file

@ -4,19 +4,18 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Str;
#[Guarded(['id'])]
class Tag extends Model
{
use HasFactory;
/** @var array<int, string> */
protected $guarded = ['id'];
public function notes(): BelongsToMany
{
return $this->belongsToMany(Note::class);

View file

@ -4,27 +4,34 @@ declare(strict_types=1);
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
use HasFactory;
use Notifiable;
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/** @var array<int, string> */
protected $fillable = [
'name', 'password',
];
/** @var array<int, string> */
protected $hidden = [
'current_password',
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function passkey(): HasMany
{

View file

@ -5,27 +5,23 @@ declare(strict_types=1);
namespace App\Models;
use App\Traits\FilterHtml;
use Codebird\Codebird;
use Exception;
use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Cache;
use Jonnybarnes\WebmentionsParser\Authorship;
#[Table('webmentions')]
#[Guarded(['id'])]
class WebMention extends Model
{
use FilterHtml;
use HasFactory;
/** @var string */
protected $table = 'webmentions';
/** @var array<int, string> */
protected $guarded = ['id'];
public function commentable(): MorphTo
{
return $this->morphTo();
@ -127,22 +123,9 @@ class WebMention extends Model
return str_replace('http://', 'https://', $url);
}
if ($host === 'twitter.com') {
if (Cache::has($url)) {
return Cache::get($url);
}
$username = ltrim(parse_url($url, PHP_URL_PATH), '/');
$codebird = resolve(Codebird::class);
$info = $codebird->users_show(['screen_name' => $username]);
$profile_image = $info->profile_image_url_https;
Cache::put($url, $profile_image, 10080); // 1 week
return $profile_image;
}
$filesystem = new Filesystem;
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
return '/assets/profile-images/' . $host . '/image';
if ($filesystem->exists(public_path().'/assets/profile-images/'.$host.'/image')) {
return '/assets/profile-images/'.$host.'/image';
}
return $url;

View file

@ -2,15 +2,15 @@
namespace App\Providers;
use App\Models\Note;
use App\Observers\NoteObserver;
use Codebird\Codebird;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;
use Intervention\Image\ImageManager;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
@ -33,33 +33,11 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
Note::observe(NoteObserver::class);
// configure Intervention/Image
$this->app->bind('Intervention\Image\ImageManager', function () {
return \Intervention\Image\ImageManager::withDriver(config('image.driver'));
return ImageManager::withDriver(config('image.driver'));
});
// Bind the Codebird client
// Codebird gets mocked in tests
// @codeCoverageIgnoreStart
$this->app->bind('Codebird\Codebird', function () {
Codebird::setConsumerKey(
env('TWITTER_CONSUMER_KEY'),
env('TWITTER_CONSUMER_SECRET')
);
$cb = Codebird::getInstance();
$cb->setToken(
env('TWITTER_ACCESS_TOKEN'),
env('TWITTER_ACCESS_TOKEN_SECRET')
);
return $cb;
});
// @codeCoverageIgnoreEnd
/**
* Paginate a standard Laravel Collection.
*
@ -106,7 +84,7 @@ class AppServiceProvider extends ServiceProvider
// Configure Guzzle
$this->app->bind('RetryGuzzle', function () {
$handlerStack = \GuzzleHttp\HandlerStack::create();
$handlerStack = HandlerStack::create();
$handlerStack->push(Middleware::retry(
function ($retries, $request, $response, $exception) {
// Limit the number of retries to 5
@ -115,7 +93,7 @@ class AppServiceProvider extends ServiceProvider
}
// Retry connection exceptions
if ($exception instanceof \GuzzleHttp\Exception\ConnectException) {
if ($exception instanceof ConnectException) {
return true;
}

View file

@ -61,7 +61,7 @@ class BookmarkService
{
$client = resolve(Client::class);
try {
$response = $client->request('GET', 'https://web.archive.org/save/' . $url);
$response = $client->request('GET', 'https://web.archive.org/save/'.$url);
} catch (ClientException $e) {
// throw an exception to be caught
throw new InternetArchiveException;

View file

@ -53,9 +53,9 @@ class UpdateHandler implements MicropubHandlerInterface
if ($data->updateReplace !== null) {
foreach ($data->updateReplace as $property => $value) {
match ($property) {
'content' => $note->note = is_array($value[0]) ? ($value[0]['html'] ?? $value[0]['value'] ?? null) : $value[0],
'content' => $note->note = is_array($value[0]) ? ($value[0]['html'] ?? $value[0]['value'] ?? null) : $value[0],
'syndication' => $this->applySyndication($note, $value),
default => null,
default => null,
};
}
}
@ -97,8 +97,8 @@ class UpdateHandler implements MicropubHandlerInterface
foreach ($delete as $property) {
match ($property) {
'syndication' => $this->clearSyndication($note),
'photo' => $note->media()->delete(),
default => null,
'photo' => $note->media()->delete(),
default => null,
};
}

View file

@ -93,7 +93,7 @@ class NoteService
$matches
);
return $matches[0][0] . ', ' . $matches[0][1];
return $matches[0][0].', '.$matches[0][1];
}
return null;

View file

@ -15,14 +15,17 @@ return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware
->append(LinkHeadersMiddleware::class)
->validateCsrfTokens(except: [
'auth', // This is the IndieAuth auth endpoint
'token', // This is the IndieAuth token endpoint
'api/post',
'api/media',
'micropub/places',
'webmention',
]);
->preventRequestForgery(
except: [
'auth', // This is the IndieAuth auth endpoint
'token', // This is the IndieAuth token endpoint
'api/post',
'api/media',
'micropub/places',
'webmention',
],
originOnly: true
);
})
->withExceptions(function (Exceptions $exceptions) {
Flare::handles($exceptions);

View file

@ -1,7 +1,11 @@
<?php
use App\Providers\AppServiceProvider;
use App\Providers\HorizonServiceProvider;
use App\Providers\MicropubServiceProvider;
return [
App\Providers\AppServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\MicropubServiceProvider::class,
AppServiceProvider::class,
HorizonServiceProvider::class,
MicropubServiceProvider::class,
];

View file

@ -6,34 +6,30 @@
"keywords": ["laravel", "framework", "indieweb"],
"license": "CC0-1.0",
"require": {
"php": "^8.2",
"php": "^8.3",
"ext-dom": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-pgsql": "*",
"ext-pdo": "*",
"ext-sodium": "*",
"cviebrock/eloquent-sluggable": "^12.0",
"cviebrock/eloquent-sluggable": "^13.0",
"guzzlehttp/guzzle": "^7.2",
"indieauth/client": "^1.1",
"intervention/image": "^3",
"jonnybarnes/indieweb": "~0.2",
"jonnybarnes/webmentions-parser": "~0.5",
"jublonet/codebird-php": "4.0.0-beta.1",
"laravel/framework": "^12.0",
"laravel/framework": "^13.0",
"laravel/horizon": "^5.0",
"laravel/sanctum": "^4.0",
"laravel/scout": "^10.1",
"laravel/tinker": "^2.8",
"laravel/tinker": "^3.0",
"lcobucci/jwt": "^5.0",
"league/commonmark": "^2.0",
"league/flysystem-aws-s3-v3": "^3.0",
"mf2/mf2": "~0.3",
"phpdocumentor/reflection-docblock": "^5.3",
"spatie/commonmark-highlighter": "^3.0",
"spatie/laravel-flare": "^2.2",
"symfony/html-sanitizer": "^7.0",
"symfony/property-access": "^7.0",
"symfony/serializer": "^7.0",
"symfony/html-sanitizer": "^8.0",
"web-auth/webauthn-lib": "^5.0"
},
"require-dev": {
@ -41,13 +37,13 @@
"barryvdh/laravel-ide-helper": "^3.0",
"fakerphp/faker": "^1.23",
"laravel/dusk": "^8.0",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/pail": "^1.2.5",
"laravel/pint": "^1.27",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.4.4",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/php-code-coverage": "^11.0",
"phpunit/phpunit": "^11.5.50",
"phpunit/php-code-coverage": "^12.0",
"phpunit/phpunit": "^12.5.12",
"spatie/laravel-ray": "^1.12",
"spatie/x-ray": "^1.2"
},
@ -72,7 +68,7 @@
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm install --ignore-scripts",
"npm run build"
],
"dev": [

2251
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,7 @@
<?php
use App\Models\User;
return [
/*
@ -62,7 +64,7 @@ return [
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
'model' => env('AUTH_MODEL', User::class),
],
// 'users' => [

View file

@ -41,9 +41,10 @@ return [
'database' => [
'driver' => 'database',
'table' => env('DB_CACHE_TABLE', 'cache'),
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
@ -111,6 +112,19 @@ return [
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'_cache_'),
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
/*
|--------------------------------------------------------------------------
| Serializable Classes
|--------------------------------------------------------------------------
|
| This value determines the classes that can be unserialized from cache
| storage. By default, no PHP classes will be unserialized from your
| cache to prevent gadget chain attacks if your APP_KEY is leaked.
|
*/
'serializable_classes' => false,
];

View file

@ -1,6 +1,7 @@
<?php
use Illuminate\Support\Str;
use Pdo\Mysql;
return [
@ -59,7 +60,7 @@ return [
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
(PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
@ -79,7 +80,7 @@ return [
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
(PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
@ -95,7 +96,7 @@ return [
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
'sslmode' => env('DB_SSLMODE', 'prefer'),
],
'sqlsrv' => [
@ -148,7 +149,7 @@ return [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'_database_'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],

View file

@ -29,7 +29,7 @@ return [
'storage' => [
'enabled' => true,
'driver' => 'file', // redis, file, pdo
'path' => storage_path() . '/debugbar', // For file driver
'path' => storage_path().'/debugbar', // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
],

View file

@ -24,7 +24,7 @@ return [
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
@ -41,7 +41,7 @@ return [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL'), '/').'/storage',
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,

View file

@ -1,8 +1,10 @@
<?php
use Spatie\FlareClient\Api;
use Spatie\FlareClient\Sampling\RateSampler;
use Spatie\LaravelFlare\AttributesProviders\LaravelUserAttributesProvider;
use Spatie\LaravelFlare\FlareConfig;
use Spatie\LaravelFlare\Senders\LaravelHttpSender;
return [
/*
@ -152,7 +154,7 @@ return [
*/
'sender' => [
'class' => \Spatie\LaravelFlare\Senders\LaravelHttpSender::class,
'class' => LaravelHttpSender::class,
'config' => [
'timeout' => 10,
],
@ -182,7 +184,7 @@ return [
|
*/
'sampler' => [
'class' => \Spatie\FlareClient\Sampling\RateSampler::class,
'class' => RateSampler::class,
'config' => [
'rate' => 0.1,
],

View file

@ -99,7 +99,7 @@ return [
'include_helpers' => false,
'helper_files' => [
base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php',
base_path().'/vendor/laravel/framework/src/Illuminate/Support/helpers.php',
],
/*

View file

@ -1,6 +1,7 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
@ -44,7 +45,7 @@ return [
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
@ -75,7 +76,7 @@ return [
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
@ -96,6 +97,7 @@ return [
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],

View file

@ -112,7 +112,7 @@ return [
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')),
],
];

View file

@ -1,67 +0,0 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. If this value is null, personal access tokens do
| not expire. This won't tweak the lifetime of first-party sessions.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],
];

View file

@ -214,4 +214,20 @@ return [
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
/*
|--------------------------------------------------------------------------
| Session Serialization
|--------------------------------------------------------------------------
|
| This value controls the serialization strategy for session data, which
| is JSON by default. Setting this to "php" allows the storage of PHP
| objects in the session but can make an application vulnerable to
| "gadget chain" serialization attacks if the APP_KEY is leaked.
|
| Supported: "json", "php"
|
*/
'serialization' => 'json',
];

View file

@ -1,18 +0,0 @@
<?php
// You can find the keys here : https://dev.twitter.com/
return [
'API_URL' => 'api.twitter.com',
'API_VERSION' => '1.1',
'AUTHENTICATE_URL' => 'https://api.twitter.com/oauth/authenticate',
'AUTHORIZE_URL' => 'https://api.twitter.com/oauth/authorize',
'ACCESS_TOKEN_URL' => 'oauth/access_token',
'REQUEST_TOKEN_URL' => 'oauth/request_token',
'USE_SSL' => true,
'CONSUMER_KEY' => env('TWITTER_CONSUMER_KEY'),
'CONSUMER_SECRET' => env('TWITTER_CONSUMER_SECRET'),
'ACCESS_TOKEN' => env('TWITTER_ACCESS_TOKEN'),
'ACCESS_TOKEN_SECRET' => env('TWITTER_ACCESS_TOKEN_SECRET'),
];

View file

@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Article>
* @extends Factory<Article>
*/
class ArticleFactory extends Factory
{

View file

@ -2,10 +2,11 @@
namespace Database\Factories;
use App\Models\Bio;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Bio>
* @extends Factory<Bio>
*/
class BioFactory extends Factory
{

View file

@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Bookmark>
* @extends Factory<Bookmark>
*/
class BookmarkFactory extends Factory
{

View file

@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Contact>
* @extends Factory<Contact>
*/
class ContactFactory extends Factory
{

View file

@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Like>
* @extends Factory<Like>
*/
class LikeFactory extends Factory
{
@ -31,7 +31,7 @@ class LikeFactory extends Factory
'url' => $this->faker->url,
'author_name' => $this->faker->name,
'author_url' => $this->faker->url,
'content' => '<html><body><div class="h-entry"><div class="e-content">' . $this->faker->realtext() . '</div></div></body></html>',
'content' => '<html><body><div class="h-entry"><div class="e-content">'.$this->faker->realtext().'</div></div></body></html>',
'created_at' => $now->toDateTimeString(),
'updated_at' => $now->toDateTimeString(),
];

View file

@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Media>
* @extends Factory<Media>
*/
class MediaFactory extends Factory
{
@ -26,7 +26,7 @@ class MediaFactory extends Factory
public function definition(): array
{
return [
'path' => 'media/' . $this->faker->uuid . '.jpg',
'path' => 'media/'.$this->faker->uuid.'.jpg',
'type' => 'image',
'created_at' => Carbon::now()->toDateTimeString(),
'updated_at' => Carbon::now()->toDateTimeString(),

View file

@ -6,7 +6,7 @@ use App\Models\MicropubClient;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\MicropubClient>
* @extends Factory<MicropubClient>
*/
class MicropubClientFactory extends Factory
{

View file

@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Note>
* @extends Factory<Note>
*/
class NoteFactory extends Factory
{

View file

@ -6,7 +6,7 @@ use App\Models\Passkey;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Passkey>
* @extends Factory<Passkey>
*/
class PasskeyFactory extends Factory
{

View file

@ -6,7 +6,7 @@ use App\Models\Place;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Place>
* @extends Factory<Place>
*/
class PlaceFactory extends Factory
{

View file

@ -2,10 +2,11 @@
namespace Database\Factories;
use App\Models\SyndicationTarget;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\SyndicationTarget>
* @extends Factory<SyndicationTarget>
*/
class SyndicationTargetFactory extends Factory
{

View file

@ -6,7 +6,7 @@ use App\Models\Tag;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag>
* @extends Factory<Tag>
*/
class TagFactory extends Factory
{

View file

@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
* @extends Factory<User>
*/
class UserFactory extends Factory
{

View file

@ -6,7 +6,7 @@ use App\Models\WebMention;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\WebMention>
* @extends Factory<WebMention>
*/
class WebMentionFactory extends Factory
{

View file

@ -4,6 +4,9 @@ namespace Database\Seeders;
use App\Models\Like;
use Faker\Generator;
use Faker\Provider\en_US\Person;
use Faker\Provider\Internet;
use Faker\Provider\Lorem;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
@ -19,9 +22,9 @@ class LikesTableSeeder extends Seeder
$now = Carbon::now()->subDays(rand(3, 6));
$faker = new Generator;
$faker->addProvider(new \Faker\Provider\en_US\Person($faker));
$faker->addProvider(new \Faker\Provider\Lorem($faker));
$faker->addProvider(new \Faker\Provider\Internet($faker));
$faker->addProvider(new Person($faker));
$faker->addProvider(new Lorem($faker));
$faker->addProvider(new Internet($faker));
$likeFromAuthor = Like::create([
'url' => $faker->url,
'author_url' => $faker->url,

View file

@ -82,12 +82,12 @@ class NotesTableSeeder extends Seeder
->update(['updated_at' => $now->toDateTimeString()]);
// copy aarons profile pic in place
$spl = new SplFileInfo(public_path() . '/assets/profile-images/aaronparecki.com');
$spl = new SplFileInfo(public_path().'/assets/profile-images/aaronparecki.com');
if ($spl->isDir() === false) {
if (! mkdir($concurrentDirectory = public_path() . '/assets/profile-images/aaronparecki.com', 0755) && ! is_dir($concurrentDirectory)) {
if (! mkdir($concurrentDirectory = public_path().'/assets/profile-images/aaronparecki.com', 0755) && ! is_dir($concurrentDirectory)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
}
copy(base_path() . '/tests/aaron.png', public_path() . '/assets/profile-images/aaronparecki.com/image');
copy(base_path().'/tests/aaron.png', public_path().'/assets/profile-images/aaronparecki.com/image');
}
$now = Carbon::now()->subDays(rand(3, 7));

View file

@ -15,34 +15,34 @@ class WebMentionsTableSeeder extends Seeder
// WebMention reply Aaron
WebMention::create([
'source' => 'https://aaronpk.localhost/reply/1',
'target' => config('app.url') . '/notes/Z',
'target' => config('app.url').'/notes/Z',
'commentable_id' => '5',
'commentable_type' => 'App\Models\Note',
'type' => 'in-reply-to',
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://aaronpk.localhost/reply/1"], "name": ["Hi too"], "author": [{"type": ["h-card"], "value": "Aaron Parecki", "properties": {"url": ["https://aaronpk.localhost"], "name": ["Aaron Parecki"], "photo": ["https://aaronparecki.com/images/profile.jpg"]}}], "content": [{"html": "Hi too", "value": "Hi too"}], "published": ["' . date(DATE_W3C) . '"], "in-reply-to": ["https://aaronpk.loclahost/reply/1", "' . config('app.url') .'/notes/E"]}}]}',
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://aaronpk.localhost/reply/1"], "name": ["Hi too"], "author": [{"type": ["h-card"], "value": "Aaron Parecki", "properties": {"url": ["https://aaronpk.localhost"], "name": ["Aaron Parecki"], "photo": ["https://aaronparecki.com/images/profile.jpg"]}}], "content": [{"html": "Hi too", "value": "Hi too"}], "published": ["'.date(DATE_W3C).'"], "in-reply-to": ["https://aaronpk.loclahost/reply/1", "'.config('app.url').'/notes/E"]}}]}',
]);
// WebMention like Tantek
WebMention::create([
'source' => 'https://tantek.com/likes/1',
'target' => config('app.url') . '/notes/G',
'target' => config('app.url').'/notes/G',
'commentable_id' => '16',
'commentable_type' => 'App\Models\Note',
'type' => 'like-of',
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://tantek.com/likes/1"], "name": ["KUTGW"], "author": [{"type": ["h-card"], "value": "Tantek Celik", "properties": {"url": ["https://tantek.com/"], "name": ["Tantek Celik"], "photo": ["https://tantek.com/photo.jpg"]}}], "content": [{"html": "kutgw", "value": "kutgw"}], "published": ["' . date(DATE_W3C) . '"], "u-like-of": ["' . config('app.url') . '/notes/G"]}}]}',
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://tantek.com/likes/1"], "name": ["KUTGW"], "author": [{"type": ["h-card"], "value": "Tantek Celik", "properties": {"url": ["https://tantek.com/"], "name": ["Tantek Celik"], "photo": ["https://tantek.com/photo.jpg"]}}], "content": [{"html": "kutgw", "value": "kutgw"}], "published": ["'.date(DATE_W3C).'"], "u-like-of": ["'.config('app.url').'/notes/G"]}}]}',
]);
// WebMention repost Barry
WebMention::create([
'source' => 'https://barryfrost.com/reposts/1',
'target' => config('app.url') . '/notes/C',
'target' => config('app.url').'/notes/C',
'commentable_id' => '12',
'commentable_type' => 'App\Models\Note',
'type' => 'repost-of',
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://barryfrost.com/reposts/1"], "name": ["Kagi is the best"], "author": [{"type": ["h-card"], "value": "Barry Frost", "properties": {"url": ["https://barryfrost.com/"], "name": ["Barry Frost"], "photo": ["https://barryfrost.com/barryfrost.jpg"]}}], "content": [{"html": "Kagi is the Best", "value": "Kagi is the Best"}], "published": ["' . date(DATE_W3C) . '"], "u-repost-of": ["' . config('app.url') . '/notes/C"]}}]}',
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://barryfrost.com/reposts/1"], "name": ["Kagi is the best"], "author": [{"type": ["h-card"], "value": "Barry Frost", "properties": {"url": ["https://barryfrost.com/"], "name": ["Barry Frost"], "photo": ["https://barryfrost.com/barryfrost.jpg"]}}], "content": [{"html": "Kagi is the Best", "value": "Kagi is the Best"}], "published": ["'.date(DATE_W3C).'"], "u-repost-of": ["'.config('app.url').'/notes/C"]}}]}',
]);
// WebMention like from Bluesky
WebMention::create([
'source' => 'https://brid.gy/like/bluesky/did:plc:n3jhgiq2ykctnpgzlm6p6b25/at%253A%252F%252Fdid%253Aplc%253An3jhgiq2ykctnpgzlm6p6b25%252Fapp.bsky.feed.post%252F3lalppbcyuc2w/did%253Aplc%253Aia23nh3t37r2lydmmqsixrps',
'target' => config('app.url') . '/notes/B',
'target' => config('app.url').'/notes/B',
'commentable_id' => '11',
'commentable_type' => 'App\Models\Note',
'type' => 'like-of',

View file

@ -68,7 +68,7 @@ if (! function_exists('normalize_url')) {
$url['path'] = preg_replace_callback(
array_map(
function ($str) {
return '/%' . strtoupper($str) . '/x';
return '/%'.strtoupper($str).'/x';
},
$u
),
@ -106,9 +106,9 @@ if (! function_exists('normalize_url')) {
preg_match('!^(/\./)!x', $url['path'], $matches)
|| preg_match('!^(/\.)$!x', $url['path'], $matches)
) {
$url['path'] = preg_replace('!^' . $matches[1] . '!', '/', $url['path']);
$url['path'] = preg_replace('!^'.$matches[1].'!', '/', $url['path']);
} elseif (preg_match('!^(/\.\./|/\.\.)!x', $url['path'], $matches)) {
$url['path'] = preg_replace('!^' . preg_quote($matches[1], '!') . '!x', '/', $url['path']);
$url['path'] = preg_replace('!^'.preg_quote($matches[1], '!').'!x', '/', $url['path']);
$new_path = preg_replace('!/([^/]+)$!x', '', $new_path);
} elseif (preg_match('!^(\.|\.\.)$!x', $url['path'])) {
$url['path'] = preg_replace('!^(\.|\.\.)$!x', '', $url['path']);
@ -116,7 +116,7 @@ if (! function_exists('normalize_url')) {
if (preg_match('!(/*[^/]*)!x', $url['path'], $matches)) {
$first_path_segment = $matches[1];
$url['path'] = preg_replace(
'/^' . preg_quote($first_path_segment, '/') . '/',
'/^'.preg_quote($first_path_segment, '/').'/',
'',
$url['path'],
1
@ -217,9 +217,9 @@ if (! function_exists('prettyPrintJson')) {
$in_escape = true;
}
if ($new_line_level !== null) {
$result .= "\n" . str_repeat("\t", $new_line_level);
$result .= "\n".str_repeat("\t", $new_line_level);
}
$result .= $char . $post;
$result .= $char.$post;
}
return str_replace("\t", ' ', $result);

View file

@ -1,6 +1,3 @@
{
"preset": "laravel",
"rules": {
"concat_space": false
}
"preset": "laravel"
}

View file

@ -3,7 +3,7 @@
@section('title')Merge Places « Admin CP « @stop
@section('content')
<p>We shall be merging {{ $first->name }}. Its location is <code>Point({{ $first->location }})</code>.</p>
<p>We shall be merging {{ $first->name }}. Its location is <code>{{ $first->latitude }}, {{ $first->longitude }}</code>.</p>
<ul>
@foreach($places as $place)
<li>

View file

@ -7,15 +7,15 @@
@foreach($bookmarks as $bookmark)
<div class="h-entry">
<div class="bookmark-link">
<a class="u-bookmark-of<?php if ($bookmark->name !== null) { echo ' h-cite'; } ?>" href="{{ $bookmark->uri }}">
<a class="u-bookmark-of<?php if ($bookmark->name !== null) { echo ' h-cite'; } ?>" href="{{ $bookmark->url }}">
@isset($bookmark->name)
{{ $bookmark->name }}
@endisset
@empty($bookmark->name)
{{ $bookmark->uri }}
{{ $bookmark->url }}
@endempty
</a> &nbsp; <a href="{{ $bookmark->uri }}">🔗</a>
</a> &nbsp; <a href="{{ $bookmark->url }}">🔗</a>
</div>
@isset($bookmark->content)
<p>{{ $bookmark->content }}</p>

View file

@ -8,15 +8,15 @@
@foreach($bookmarks as $bookmark)
<div class="h-entry">
<div class="bookmark-link">
<a class="u-bookmark-of<?php if ($bookmark->name !== null) { echo ' h-cite'; } ?>" href="{{ $bookmark->uri }}">
<a class="u-bookmark-of<?php if ($bookmark->name !== null) { echo ' h-cite'; } ?>" href="{{ $bookmark->url }}">
@isset($bookmark->name)
{{ $bookmark->name }}
@endisset
@empty($bookmark->name)
{{ $bookmark->uri }}
{{ $bookmark->url }}
@endempty
</a> &nbsp; <a href="{{ $bookmark->uri }}">🔗</a>
</a> &nbsp; <a href="{{ $bookmark->url }}">🔗</a>
</div>
@isset($bookmark->content)
<p>{{ $bookmark->content }}</p>

View file

@ -1,7 +1,5 @@
<div class="h-entry">
@if ($note->twitter)
{!! $note->twitter->html !!}
@elseif ($note->in_reply_to)
@if ($note->in_reply_to)
<div class="u-in-reply-to h-cite reply-to">
In reply to <a href="{{ $note->in_reply_to }}" class="u-url">{{ $note->in_reply_to }}</a>
</div>

View file

@ -26,7 +26,7 @@ abstract class DuskTestCase extends BaseTestCase
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
* @return RemoteWebDriver
*/
protected function driver()
{

View file

@ -55,11 +55,11 @@ class ArticlesTest extends TestCase
$user = User::factory()->make();
$faker = Factory::create();
$text = $faker->text;
if ($fh = fopen(sys_get_temp_dir() . '/article.md', 'w')) {
if ($fh = fopen(sys_get_temp_dir().'/article.md', 'w')) {
fwrite($fh, $text);
fclose($fh);
}
$path = sys_get_temp_dir() . '/article.md';
$path = sys_get_temp_dir().'/article.md';
$file = new UploadedFile($path, 'article.md', 'text/plain', null, true);
$this->actingAs($user)
@ -83,7 +83,7 @@ class ArticlesTest extends TestCase
]);
$response = $this->actingAs($user)
->get('/admin/blog/' . $article->id . '/edit');
->get('/admin/blog/'.$article->id.'/edit');
$response->assertSeeText('This is *my* new blog. It uses `Markdown`.');
}
@ -94,7 +94,7 @@ class ArticlesTest extends TestCase
$article = Article::factory()->create();
$this->actingAs($user)
->post('/admin/blog/' . $article->id, [
->post('/admin/blog/'.$article->id, [
'_method' => 'PUT',
'title' => 'My New Blog',
'main' => 'This article has been edited',
@ -112,7 +112,7 @@ class ArticlesTest extends TestCase
$article = Article::factory()->create();
$this->actingAs($user)
->post('/admin/blog/' . $article->id, [
->post('/admin/blog/'.$article->id, [
'_method' => 'DELETE',
]);
$this->assertSoftDeleted('articles', [

View file

@ -59,7 +59,7 @@ class ClientsTest extends TestCase
]);
$response = $this->actingAs($user)
->get('/admin/clients/' . $client->id . '/edit');
->get('/admin/clients/'.$client->id.'/edit');
$response->assertSee('https://jbl5.dev/notes/new');
}
@ -70,7 +70,7 @@ class ClientsTest extends TestCase
$client = MicropubClient::factory()->create();
$this->actingAs($user)
->post('/admin/clients/' . $client->id, [
->post('/admin/clients/'.$client->id, [
'_method' => 'PUT',
'client_url' => 'https://jbl5.dev/notes/new',
'client_name' => 'JBL5dev',
@ -90,7 +90,7 @@ class ClientsTest extends TestCase
]);
$this->actingAs($user)
->post('/admin/clients/' . $client->id, [
->post('/admin/clients/'.$client->id, [
'_method' => 'DELETE',
]);
$this->assertDatabaseMissing('clients', [

View file

@ -21,9 +21,9 @@ class ContactsTest extends TestCase
protected function tearDown(): void
{
if (file_exists(public_path() . '/assets/profile-images/tantek.com/image')) {
unlink(public_path() . '/assets/profile-images/tantek.com/image');
rmdir(public_path() . '/assets/profile-images/tantek.com');
if (file_exists(public_path().'/assets/profile-images/tantek.com/image')) {
unlink(public_path().'/assets/profile-images/tantek.com/image');
rmdir(public_path().'/assets/profile-images/tantek.com');
}
parent::tearDown();
}
@ -69,7 +69,7 @@ class ContactsTest extends TestCase
$user = User::factory()->make();
$contact = Contact::factory()->create();
$response = $this->actingAs($user)->get('/admin/contacts/' . $contact->id . '/edit');
$response = $this->actingAs($user)->get('/admin/contacts/'.$contact->id.'/edit');
$response->assertViewIs('admin.contacts.edit');
}
@ -79,7 +79,7 @@ class ContactsTest extends TestCase
$user = User::factory()->make();
$contact = Contact::factory()->create();
$this->actingAs($user)->post('/admin/contacts/' . $contact->id, [
$this->actingAs($user)->post('/admin/contacts/'.$contact->id, [
'_method' => 'PUT',
'name' => 'Tantek Celik',
'nick' => 'tantek',
@ -95,13 +95,13 @@ class ContactsTest extends TestCase
#[Test]
public function admin_can_edit_contact_and_upload_avatar(): void
{
copy(__DIR__ . '/../../aaron.png', sys_get_temp_dir() . '/tantek.png');
$path = sys_get_temp_dir() . '/tantek.png';
copy(__DIR__.'/../../aaron.png', sys_get_temp_dir().'/tantek.png');
$path = sys_get_temp_dir().'/tantek.png';
$file = new UploadedFile($path, 'tantek.png', 'image/png', null, true);
$user = User::factory()->make();
$contact = Contact::factory()->create();
$this->actingAs($user)->post('/admin/contacts/' . $contact->id, [
$this->actingAs($user)->post('/admin/contacts/'.$contact->id, [
'_method' => 'PUT',
'name' => 'Tantek Celik',
'nick' => 'tantek',
@ -110,8 +110,8 @@ class ContactsTest extends TestCase
'avatar' => $file,
]);
$this->assertFileEquals(
__DIR__ . '/../../aaron.png',
public_path() . '/assets/profile-images/tantek.com/image'
__DIR__.'/../../aaron.png',
public_path().'/assets/profile-images/tantek.com/image'
);
}
@ -125,7 +125,7 @@ class ContactsTest extends TestCase
'nick' => 'tantek',
]);
$this->actingAs($user)->post('/admin/contacts/' . $contact->id, [
$this->actingAs($user)->post('/admin/contacts/'.$contact->id, [
'_method' => 'DELETE',
]);
$this->assertDatabaseMissing('contacts', [
@ -141,7 +141,7 @@ class ContactsTest extends TestCase
<img class="u-photo" alt="" src="http://tantek.com/tantek.png">
</div>
HTML;
$file = fopen(__DIR__ . '/../../aaron.png', 'rb');
$file = fopen(__DIR__.'/../../aaron.png', 'rb');
$mock = new MockHandler([
new Response(200, ['Content-Type' => 'text/html'], $html),
new Response(200, ['Content-Type' => 'image/png'], $file),
@ -154,11 +154,11 @@ class ContactsTest extends TestCase
'homepage' => 'https://tantek.com',
]);
$this->actingAs($user)->get('/admin/contacts/' . $contact->id . '/getavatar');
$this->actingAs($user)->get('/admin/contacts/'.$contact->id.'/getavatar');
$this->assertFileEquals(
__DIR__ . '/../../aaron.png',
public_path() . '/assets/profile-images/tantek.com/image'
__DIR__.'/../../aaron.png',
public_path().'/assets/profile-images/tantek.com/image'
);
}
@ -174,9 +174,9 @@ class ContactsTest extends TestCase
$user = User::factory()->make();
$contact = Contact::factory()->create();
$response = $this->actingAs($user)->get('/admin/contacts/' . $contact->id . '/getavatar');
$response = $this->actingAs($user)->get('/admin/contacts/'.$contact->id.'/getavatar');
$response->assertRedirect('/admin/contacts/' . $contact->id . '/edit');
$response->assertRedirect('/admin/contacts/'.$contact->id.'/edit');
}
#[Test]
@ -197,9 +197,9 @@ class ContactsTest extends TestCase
$user = User::factory()->make();
$contact = Contact::factory()->create();
$response = $this->actingAs($user)->get('/admin/contacts/' . $contact->id . '/getavatar');
$response = $this->actingAs($user)->get('/admin/contacts/'.$contact->id.'/getavatar');
$response->assertRedirect('/admin/contacts/' . $contact->id . '/edit');
$response->assertRedirect('/admin/contacts/'.$contact->id.'/edit');
}
#[Test]
@ -211,8 +211,8 @@ class ContactsTest extends TestCase
]);
$user = User::factory()->make();
$response = $this->actingAs($user)->get('/admin/contacts/' . $contact->id . '/getavatar');
$response = $this->actingAs($user)->get('/admin/contacts/'.$contact->id.'/getavatar');
$response->assertRedirect('/admin/contacts/' . $contact->id . '/edit');
$response->assertRedirect('/admin/contacts/'.$contact->id.'/edit');
}
}

View file

@ -59,7 +59,7 @@ class LikesTest extends TestCase
$like = Like::factory()->create();
$response = $this->actingAs($user)
->get('/admin/likes/' . $like->id . '/edit');
->get('/admin/likes/'.$like->id.'/edit');
$response->assertSee('Edit Like');
}
@ -71,7 +71,7 @@ class LikesTest extends TestCase
$like = Like::factory()->create();
$this->actingAs($user)
->post('/admin/likes/' . $like->id, [
->post('/admin/likes/'.$like->id, [
'_method' => 'PUT',
'like_url' => 'https://example.com',
]);
@ -89,7 +89,7 @@ class LikesTest extends TestCase
$user = User::factory()->make();
$this->actingAs($user)
->post('/admin/likes/' . $like->id, [
->post('/admin/likes/'.$like->id, [
'_method' => 'DELETE',
]);
$this->assertDatabaseMissing('likes', [

View file

@ -54,7 +54,7 @@ class NotesTest extends TestCase
$user = User::factory()->make();
$note = Note::factory()->create();
$response = $this->actingAs($user)->get('/admin/notes/' . $note->id . '/edit');
$response = $this->actingAs($user)->get('/admin/notes/'.$note->id.'/edit');
$response->assertViewIs('admin.notes.edit');
}
@ -65,7 +65,7 @@ class NotesTest extends TestCase
$user = User::factory()->make();
$note = Note::factory()->create();
$this->actingAs($user)->post('/admin/notes/' . $note->id, [
$this->actingAs($user)->post('/admin/notes/'.$note->id, [
'_method' => 'PUT',
'content' => 'An edited note',
'webmentions' => true,
@ -83,7 +83,7 @@ class NotesTest extends TestCase
$user = User::factory()->make();
$note = Note::factory()->create();
$this->actingAs($user)->post('/admin/notes/' . $note->id, [
$this->actingAs($user)->post('/admin/notes/'.$note->id, [
'_method' => 'DELETE',
]);
$this->assertSoftDeleted('notes', [

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests\Feature\Admin;
use App\Models\Note;
use App\Models\Place;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
@ -55,7 +56,7 @@ class PlacesTest extends TestCase
$user = User::factory()->make();
$place = Place::factory()->create();
$response = $this->actingAs($user)->get('/admin/places/' . $place->id . '/edit');
$response = $this->actingAs($user)->get('/admin/places/'.$place->id.'/edit');
$response->assertViewIs('admin.places.edit');
}
@ -67,7 +68,7 @@ class PlacesTest extends TestCase
'name' => 'The Bridgewater Pub',
]);
$this->actingAs($user)->post('/admin/places/' . $place->id, [
$this->actingAs($user)->post('/admin/places/'.$place->id, [
'_method' => 'PUT',
'name' => 'The Bridgewater',
'description' => 'Who uses “Pub” anyway',
@ -78,4 +79,62 @@ class PlacesTest extends TestCase
'name' => 'The Bridgewater',
]);
}
#[Test]
public function merge_index_page_loads(): void
{
$user = User::factory()->make();
// Use specific coordinates to avoid haversine acos overflow with extreme faker values
$place = Place::factory()->create(['latitude' => 53.48, 'longitude' => -2.24]);
$response = $this->actingAs($user)->get('/admin/places/'.$place->id.'/merge');
$response->assertViewIs('admin.places.merge.index');
}
#[Test]
public function merge_edit_page_loads(): void
{
$user = User::factory()->make();
$place1 = Place::factory()->create();
$place2 = Place::factory()->create();
$response = $this->actingAs($user)->get('/admin/places/'.$place1->id.'/merge/'.$place2->id);
$response->assertViewIs('admin.places.merge.edit');
}
#[Test]
public function merge_store_with_delete_one_moves_notes_and_deletes_place1(): void
{
$user = User::factory()->make();
$place1 = Place::factory()->create();
$place2 = Place::factory()->create();
$note = Note::factory()->create(['place_id' => $place1->id]);
$this->actingAs($user)->post('/admin/places/merge', [
'place1' => $place1->id,
'place2' => $place2->id,
'delete' => '1',
]);
$this->assertDatabaseMissing('places', ['id' => $place1->id]);
$this->assertDatabaseHas('notes', ['id' => $note->id, 'place_id' => $place2->id]);
}
#[Test]
public function merge_store_with_delete_two_moves_notes_and_deletes_place2(): void
{
$user = User::factory()->make();
$place1 = Place::factory()->create();
$place2 = Place::factory()->create();
$note = Note::factory()->create(['place_id' => $place2->id]);
$this->actingAs($user)->post('/admin/places/merge', [
'place1' => $place1->id,
'place2' => $place2->id,
'delete' => '2',
]);
$this->assertDatabaseMissing('places', ['id' => $place2->id]);
$this->assertDatabaseHas('notes', ['id' => $note->id, 'place_id' => $place1->id]);
}
}

View file

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\Admin;
use App\Models\SyndicationTarget;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class SyndicationTargetsTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function index_requires_authentication(): void
{
$response = $this->get('/admin/syndication');
$response->assertRedirect();
}
#[Test]
public function index_lists_syndication_targets(): void
{
$user = User::factory()->make();
$target = SyndicationTarget::factory()->create();
$response = $this->actingAs($user)->get('/admin/syndication');
$response->assertOk();
$response->assertSeeText($target->uid);
}
#[Test]
public function create_page_loads(): void
{
$user = User::factory()->make();
$response = $this->actingAs($user)->get('/admin/syndication/create');
$response->assertOk();
}
#[Test]
public function store_creates_a_new_syndication_target(): void
{
$user = User::factory()->make();
$this->actingAs($user)->post('/admin/syndication', [
'uid' => 'https://mastodon.social/users/me',
'name' => 'Mastodon',
]);
$this->assertDatabaseHas('syndication_targets', [
'uid' => 'https://mastodon.social/users/me',
'name' => 'Mastodon',
]);
}
#[Test]
public function store_redirects_to_index_after_creation(): void
{
$user = User::factory()->make();
$response = $this->actingAs($user)->post('/admin/syndication', [
'uid' => 'https://mastodon.social/users/me',
'name' => 'Mastodon',
]);
$response->assertRedirect('/admin/syndication');
}
#[Test]
public function edit_page_loads_with_target_data(): void
{
$user = User::factory()->make();
$target = SyndicationTarget::factory()->create(['name' => 'My Mastodon']);
$response = $this->actingAs($user)->get("/admin/syndication/{$target->id}/edit");
$response->assertOk();
$response->assertSee('value="My Mastodon"', false);
}
#[Test]
public function update_modifies_syndication_target(): void
{
$user = User::factory()->make();
$target = SyndicationTarget::factory()->create(['name' => 'Old Name']);
$this->actingAs($user)->put("/admin/syndication/{$target->id}", [
'uid' => $target->uid,
'name' => 'New Name',
]);
$this->assertDatabaseHas('syndication_targets', [
'id' => $target->id,
'name' => 'New Name',
]);
}
#[Test]
public function update_redirects_to_index(): void
{
$user = User::factory()->make();
$target = SyndicationTarget::factory()->create();
$response = $this->actingAs($user)->put("/admin/syndication/{$target->id}", [
'uid' => $target->uid,
'name' => 'Updated Name',
]);
$response->assertRedirect('/admin/syndication');
}
#[Test]
public function destroy_deletes_syndication_target(): void
{
$user = User::factory()->make();
$target = SyndicationTarget::factory()->create();
$this->actingAs($user)->delete("/admin/syndication/{$target->id}");
$this->assertDatabaseMissing('syndication_targets', ['id' => $target->id]);
}
#[Test]
public function destroy_redirects_to_index(): void
{
$user = User::factory()->make();
$target = SyndicationTarget::factory()->create();
$response = $this->actingAs($user)->delete("/admin/syndication/{$target->id}");
$response->assertRedirect('/admin/syndication');
}
}

View file

@ -33,8 +33,8 @@ class ArticlesTest extends TestCase
public function wrong_date_in_url_redirects_to_correct_date()
{
$article = Article::factory()->create();
$response = $this->get('/blog/1900/01/' . $article->titleurl);
$response->assertRedirect('/blog/' . date('Y') . '/' . date('m') . '/' . $article->titleurl);
$response = $this->get('/blog/1900/01/'.$article->titleurl);
$response->assertRedirect('/blog/'.date('Y').'/'.date('m').'/'.$article->titleurl);
}
#[Test]
@ -42,14 +42,14 @@ class ArticlesTest extends TestCase
{
$article = Article::factory()->create();
$num60Id = resolve(Numbers::class)->numto60($article->id);
$response = $this->get('/blog/s/' . $num60Id);
$response = $this->get('/blog/s/'.$num60Id);
$response->assertRedirect($article->link);
}
#[Test]
public function unknown_slug_gets_not_found_response()
{
$response = $this->get('/blog/' . date('Y') . '/' . date('m') . '/unknown-slug');
$response = $this->get('/blog/'.date('Y').'/'.date('m').'/unknown-slug');
$response->assertNotFound();
}

View file

@ -6,6 +6,7 @@ namespace Tests\Feature;
use App\Jobs\ProcessBookmark;
use App\Models\Bookmark;
use App\Models\Tag;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use PHPUnit\Framework\Attributes\Test;
@ -27,7 +28,7 @@ class BookmarksTest extends TestCase
public function single_bookmark_page_loads_without_error(): void
{
$bookmark = Bookmark::factory()->create();
$response = $this->get('/bookmarks/' . $bookmark->id);
$response = $this->get('/bookmarks/'.$bookmark->id);
$response->assertViewIs('bookmarks.show');
}
@ -37,7 +38,7 @@ class BookmarksTest extends TestCase
Queue::fake();
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->getToken(),
'Authorization' => 'Bearer '.$this->getToken(),
])->post('/api/post', [
'h' => 'entry',
'bookmark-of' => 'https://example.org/blog-post',
@ -55,7 +56,7 @@ class BookmarksTest extends TestCase
Queue::fake();
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->getToken(),
'Authorization' => 'Bearer '.$this->getToken(),
])->json('POST', '/api/post', [
'type' => ['h-entry'],
'properties' => [
@ -69,13 +70,34 @@ class BookmarksTest extends TestCase
$this->assertDatabaseHas('bookmarks', ['url' => 'https://example.org/blog-post']);
}
#[Test]
public function tagged_bookmarks_page_only_shows_bookmarks_with_that_tag(): void
{
$tag = Tag::factory()->create(['tag' => 'php']);
$tagged = Bookmark::factory()->create();
$tagged->tags()->attach($tag);
$untagged = Bookmark::factory()->create();
$response = $this->get('/bookmarks/tagged/php');
$response->assertViewIs('bookmarks.tagged');
$response->assertSee($tagged->url);
$response->assertDontSee($untagged->url);
}
#[Test]
public function bookmark_local_uri_attribute_returns_correct_url(): void
{
$bookmark = Bookmark::factory()->create();
$this->assertEquals(config('app.url').'/bookmarks/'.$bookmark->id, $bookmark->local_uri);
}
#[Test]
public function when_the_bookmark_is_created_check_necessary_tags_are_also_created(): void
{
Queue::fake();
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->getToken(),
'Authorization' => 'Bearer '.$this->getToken(),
])->json('POST', '/api/post', [
'type' => ['h-entry'],
'properties' => [

View file

@ -21,7 +21,7 @@ class CorsHeadersTest extends TestCase
[],
[],
[],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertHeader('Access-Control-Allow-Origin', '*');
}

View file

@ -59,7 +59,7 @@ class FeedsTest extends TestCase
$response->assertHeader('Content-Type', 'application/jf2feed+json');
$response->assertJson([
'type' => 'feed',
'name' => 'Blog feed for ' . config('app.name'),
'name' => 'Blog feed for '.config('app.name'),
'url' => url('/blog'),
'author' => [
'type' => 'card',
@ -117,7 +117,7 @@ class FeedsTest extends TestCase
$response->assertHeader('Content-Type', 'application/jf2feed+json');
$response->assertJson([
'type' => 'feed',
'name' => 'Notes feed for ' . config('app.name'),
'name' => 'Notes feed for '.config('app.name'),
'url' => url('/notes'),
'author' => [
'type' => 'card',

View file

@ -16,10 +16,10 @@ class HeaderLinkTest extends TestCase
$linkHeaders = $response->headers->allPreserveCaseWithoutCookies()['Link'];
$this->assertSame('<' . config('app.url') . '/.well-known/indieauth-server>; rel="indieauth-metadata"', $linkHeaders[0]);
$this->assertSame('<' . config('app.url') . '/auth>; rel="authorization_endpoint"', $linkHeaders[1]);
$this->assertSame('<' . config('app.url') . '/token>; rel="token_endpoint"', $linkHeaders[2]);
$this->assertSame('<' . config('app.url') . '/api/post>; rel="micropub"', $linkHeaders[3]);
$this->assertSame('<' . config('app.url') . '/webmention>; rel="webmention"', $linkHeaders[4]);
$this->assertSame('<'.config('app.url').'/.well-known/indieauth-server>; rel="indieauth-metadata"', $linkHeaders[0]);
$this->assertSame('<'.config('app.url').'/auth>; rel="authorization_endpoint"', $linkHeaders[1]);
$this->assertSame('<'.config('app.url').'/token>; rel="token_endpoint"', $linkHeaders[2]);
$this->assertSame('<'.config('app.url').'/api/post>; rel="micropub"', $linkHeaders[3]);
$this->assertSame('<'.config('app.url').'/webmention>; rel="webmention"', $linkHeaders[4]);
}
}

View file

@ -369,7 +369,7 @@ class IndieAuthTest extends TestCase
$this->assertCount(3, $parts);
$this->assertStringContainsString('code=', $parts[0]);
$this->assertSame('state=123456', $parts[1]);
$this->assertSame('iss=' . config('app.url'), $parts[2]);
$this->assertSame('iss='.config('app.url'), $parts[2]);
}
#[Test]

View file

@ -6,7 +6,6 @@ namespace Tests\Feature;
use App\Jobs\ProcessLike;
use App\Models\Like;
use Codebird\Codebird;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
@ -34,7 +33,7 @@ class LikesTest extends TestCase
public function single_like_page_has_correct_view(): void
{
$like = Like::factory()->create();
$response = $this->get('/likes/' . $like->id);
$response = $this->get('/likes/'.$like->id);
$response->assertViewIs('likes.show');
}
@ -44,7 +43,7 @@ class LikesTest extends TestCase
Queue::fake();
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->getToken(),
'Authorization' => 'Bearer '.$this->getToken(),
])->json('POST', '/api/post', [
'type' => ['h-entry'],
'properties' => [
@ -64,7 +63,7 @@ class LikesTest extends TestCase
Queue::fake();
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->getToken(),
'Authorization' => 'Bearer '.$this->getToken(),
])->post('/api/post', [
'h' => 'entry',
'like-of' => 'https://example.org/blog-post',
@ -194,82 +193,6 @@ class LikesTest extends TestCase
$this->assertNull(Like::find($id)->author_name);
}
#[Test]
public function like_that_is_a_tweet(): void
{
$like = new Like;
$like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200';
$like->save();
$id = $like->id;
$job = new ProcessLike($like);
$mock = new MockHandler([
new Response(201, [], json_encode([
'url' => 'https://twitter.com/likes/id',
])),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$this->app->bind(Client::class, function () use ($client) {
return $client;
});
$info = (object) [
'author_name' => 'Jonny Barnes',
'author_url' => 'https://twitter.com/jonnybarnes',
'html' => '<div>HTML of the tweet embed</div>',
];
$codebirdMock = $this->createPartialMock(Codebird::class, ['__call']);
$codebirdMock->method('__call')
->with('statuses_oembed', $this->anything())
->willReturn($info);
$this->app->instance(Codebird::class, $codebirdMock);
$authorship = new Authorship;
$job->handle($client, $authorship);
$this->assertEquals('Jonny Barnes', Like::find($id)->author_name);
}
#[Test]
public function no_error_for_failure_to_posse_with_bridgy(): void
{
$like = new Like;
$like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200';
$like->save();
$id = $like->id;
$job = new ProcessLike($like);
$mock = new MockHandler([
new Response(404, [], 'Not found'),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$this->app->bind(Client::class, function () use ($client) {
return $client;
});
$info = (object) [
'author_name' => 'Jonny Barnes',
'author_url' => 'https://twitter.com/jonnybarnes',
'html' => '<div>HTML of the tweet embed</div>',
];
$codebirdMock = $this->createPartialMock(Codebird::class, ['__call']);
$codebirdMock->method('__call')
->with('statuses_oembed', $this->anything())
->willReturn($info);
$this->app->instance(Codebird::class, $codebirdMock);
$authorship = new Authorship;
$job->handle($client, $authorship);
$this->assertEquals('Jonny Barnes', Like::find($id)->author_name);
}
#[Test]
public function unknown_like_gives_not_found_response(): void
{

View file

@ -47,7 +47,7 @@ class MicropubControllerTest extends TestCase
#[Test]
public function micropub_get_request_with_valid_token_returns_ok_response(): void
{
$response = $this->get('/api/post', ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
$response = $this->get('/api/post', ['HTTP_Authorization' => 'Bearer '.$this->getToken()]);
$response->assertStatus(200);
$response->assertJsonFragment(['response' => 'token']);
}
@ -55,7 +55,7 @@ class MicropubControllerTest extends TestCase
#[Test]
public function micropub_clients_can_request_syndication_targets_can_be_empty(): void
{
$response = $this->get('/api/post?q=syndicate-to', ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
$response = $this->get('/api/post?q=syndicate-to', ['HTTP_Authorization' => 'Bearer '.$this->getToken()]);
$response->assertJsonFragment(['syndicate-to' => []]);
}
@ -63,7 +63,7 @@ class MicropubControllerTest extends TestCase
public function micropub_clients_can_request_syndication_targets_populates_from_model(): void
{
$syndicationTarget = SyndicationTarget::factory()->create();
$response = $this->get('/api/post?q=syndicate-to', ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
$response = $this->get('/api/post?q=syndicate-to', ['HTTP_Authorization' => 'Bearer '.$this->getToken()]);
$response->assertJsonFragment(['uid' => $syndicationTarget->uid]);
}
@ -75,7 +75,7 @@ class MicropubControllerTest extends TestCase
'latitude' => '53.5',
'longitude' => '-2.38',
]);
$response = $this->get('/api/post?q=geo:53.5,-2.38', ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
$response = $this->get('/api/post?q=geo:53.5,-2.38', ['HTTP_Authorization' => 'Bearer '.$this->getToken()]);
$response->assertJson(['places' => [['slug' => 'the-bridgewater-pub']]]);
}
@ -90,14 +90,14 @@ class MicropubControllerTest extends TestCase
#[Test]
public function return_empty_result_when_micropub_client_requests_known_nearby_places(): void
{
$response = $this->get('/api/post?q=geo:1.23,4.56', ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
$response = $this->get('/api/post?q=geo:1.23,4.56', ['HTTP_Authorization' => 'Bearer '.$this->getToken()]);
$response->assertJson(['places' => []]);
}
#[Test]
public function micropub_client_can_request_endpoint_config(): void
{
$response = $this->get('/api/post?q=config', ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
$response = $this->get('/api/post?q=config', ['HTTP_Authorization' => 'Bearer '.$this->getToken()]);
$response->assertJsonFragment(['media-endpoint' => route('media-endpoint')]);
}
@ -113,7 +113,7 @@ class MicropubControllerTest extends TestCase
'h' => 'entry',
'content' => $note,
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertJson(['response' => 'created']);
@ -146,7 +146,7 @@ class MicropubControllerTest extends TestCase
'https://bsky.app/profile/jonnybarnes.uk',
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertJson(['response' => 'created']);
$this->assertDatabaseHas('notes', ['note' => $note]);
@ -164,7 +164,7 @@ class MicropubControllerTest extends TestCase
'name' => 'The Barton Arms',
'geo' => 'geo:53.4974,-2.3768',
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertJson(['response' => 'created']);
$this->assertDatabaseHas('places', ['slug' => 'the-barton-arms']);
@ -181,7 +181,7 @@ class MicropubControllerTest extends TestCase
'latitude' => '53.4974',
'longitude' => '-2.3768',
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertJson(['response' => 'created']);
$this->assertDatabaseHas('places', ['slug' => 'the-barton-arms']);
@ -196,7 +196,7 @@ class MicropubControllerTest extends TestCase
'h' => 'entry',
'content' => 'A random note',
],
['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()]
['HTTP_Authorization' => 'Bearer '.$this->getInvalidToken()]
);
$response->assertStatus(400);
$response->assertJson(['error' => 'invalid_token']);
@ -211,7 +211,7 @@ class MicropubControllerTest extends TestCase
'h' => 'entry',
'content' => 'A random note',
],
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithNoScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithNoScope()]
);
$response->assertStatus(400);
$response->assertJson(['error_description' => 'The provided token has no scopes']);
@ -226,7 +226,7 @@ class MicropubControllerTest extends TestCase
'h' => 'entry',
'content' => 'A random note',
],
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithIncorrectScope()]
);
$response->assertStatus(401);
$response->assertJson(['error' => 'insufficient_scope']);
@ -265,10 +265,10 @@ class MicropubControllerTest extends TestCase
'https://mastodon.social/@jonnybarnes',
'https://bsky.app/profile/jonnybarnes.uk',
],
'photo' => [config('filesystems.disks.public.url') . '/media/test-photo.jpg'],
'photo' => [config('filesystems.disks.public.url').'/media/test-photo.jpg'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertStatus(201)
@ -292,7 +292,7 @@ class MicropubControllerTest extends TestCase
'location' => ['geo:1.23,4.56'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertStatus(201)
@ -326,7 +326,7 @@ class MicropubControllerTest extends TestCase
'location' => [$place->uri],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertStatus(201)
@ -358,7 +358,7 @@ class MicropubControllerTest extends TestCase
],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertStatus(201)
@ -391,7 +391,7 @@ class MicropubControllerTest extends TestCase
]],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertStatus(201)
@ -444,7 +444,7 @@ class MicropubControllerTest extends TestCase
'content' => [$note],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithIncorrectScope()]
);
$response
->assertJson([
@ -465,7 +465,7 @@ class MicropubControllerTest extends TestCase
'content' => ['Some content'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson([
@ -485,10 +485,10 @@ class MicropubControllerTest extends TestCase
'type' => ['h-card'],
'properties' => [
'name' => [$faker->name],
'geo' => ['geo:' . $faker->latitude . ',' . $faker->longitude],
'geo' => ['geo:'.$faker->latitude.','.$faker->longitude],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'created'])
@ -505,10 +505,10 @@ class MicropubControllerTest extends TestCase
'type' => ['h-card'],
'properties' => [
'name' => [$faker->name],
'geo' => ['geo:' . $faker->latitude . ',' . $faker->longitude . ';u=35'],
'geo' => ['geo:'.$faker->latitude.','.$faker->longitude.';u=35'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'created'])
@ -528,7 +528,7 @@ class MicropubControllerTest extends TestCase
'content' => ['replaced content'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -551,7 +551,7 @@ class MicropubControllerTest extends TestCase
'content' => [['value' => 'plain text', 'html' => 'html version']],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -577,7 +577,7 @@ class MicropubControllerTest extends TestCase
],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -602,7 +602,7 @@ class MicropubControllerTest extends TestCase
'photo' => ['https://example.org/photo.jpg'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -619,12 +619,12 @@ class MicropubControllerTest extends TestCase
'/api/post',
[
'action' => 'update',
'url' => config('app.url') . '/blog/A',
'url' => config('app.url').'/blog/A',
'add' => [
'syndication' => ['https://www.swarmapp.com/checkin/123'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['error' => 'invalid'])
@ -638,12 +638,12 @@ class MicropubControllerTest extends TestCase
'/api/post',
[
'action' => 'update',
'url' => config('app.url') . '/notes/ZZZZ',
'url' => config('app.url').'/notes/ZZZZ',
'add' => [
'syndication' => ['https://www.swarmapp.com/checkin/123'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['error' => 'invalid_request'])
@ -663,7 +663,7 @@ class MicropubControllerTest extends TestCase
'syndication' => ['https://www.swarmapp.com/checkin/123'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['error' => 'unsupported_operation'])
@ -677,12 +677,12 @@ class MicropubControllerTest extends TestCase
'/api/post',
[
'action' => 'update',
'url' => config('app.url') . '/notes/B',
'url' => config('app.url').'/notes/B',
'add' => [
'syndication' => ['https://www.swarmapp.com/checkin/123'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithIncorrectScope()]
);
$response
->assertStatus(401)
@ -705,7 +705,7 @@ class MicropubControllerTest extends TestCase
],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -750,7 +750,7 @@ class MicropubControllerTest extends TestCase
'syndication' => ['https://www.swarmapp.com/checkin/123'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -777,7 +777,7 @@ class MicropubControllerTest extends TestCase
'url' => $note->uri,
'delete' => ['syndication'],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -805,7 +805,7 @@ class MicropubControllerTest extends TestCase
'syndication' => ['https://www.swarmapp.com/checkin/123'],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -820,7 +820,7 @@ class MicropubControllerTest extends TestCase
public function micropub_client_api_request_can_delete_photo(): void
{
$note = Note::factory()->create();
$media = new \App\Models\Media;
$media = new Media;
$media->path = 'https://example.org/photo.jpg';
$media->type = 'image';
$media->save();
@ -833,7 +833,7 @@ class MicropubControllerTest extends TestCase
'url' => $note->uri,
'delete' => ['photo'],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response
->assertJson(['response' => 'updated'])
@ -860,7 +860,7 @@ class MicropubControllerTest extends TestCase
'content' => [$content],
],
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response

View file

@ -28,7 +28,7 @@ class MicropubMediaTest extends TestCase
$response = $this->get(
'/api/media?q=last',
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertStatus(200);
$response->assertJson(['url' => null]);
@ -50,7 +50,7 @@ class MicropubMediaTest extends TestCase
{
$response = $this->get(
'/api/media?q=last',
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithNoScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithNoScope()]
);
$response->assertStatus(400);
$response->assertJsonFragment(['error_description' => 'The provided token has no scopes']);
@ -61,7 +61,7 @@ class MicropubMediaTest extends TestCase
{
$response = $this->get(
'/api/media?q=last',
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithIncorrectScope()]
);
$response->assertStatus(401);
$response->assertJsonFragment(['error_description' => 'The tokens scope does not have the necessary requirements.']);
@ -72,7 +72,7 @@ class MicropubMediaTest extends TestCase
{
$response = $this->get(
'/api/media',
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertStatus(200);
$response->assertJson(['status' => 'OK']);
@ -82,7 +82,7 @@ class MicropubMediaTest extends TestCase
public function client_can_list_last_upload(): void
{
Queue::fake();
$file = __DIR__ . '/../aaron.png';
$file = __DIR__.'/../aaron.png';
$token = $this->getToken();
$response = $this->post(
@ -90,7 +90,7 @@ class MicropubMediaTest extends TestCase
[
'file' => new UploadedFile($file, 'aaron.png', 'image/png', null, true),
],
['HTTP_Authorization' => 'Bearer ' . $token]
['HTTP_Authorization' => 'Bearer '.$token]
);
$path = parse_url($response->headers->get('Location'), PHP_URL_PATH);
@ -98,12 +98,12 @@ class MicropubMediaTest extends TestCase
$lastUploadResponse = $this->get(
'/api/media?q=last',
['HTTP_Authorization' => 'Bearer ' . $token]
['HTTP_Authorization' => 'Bearer '.$token]
);
$lastUploadResponse->assertJson(['url' => $response->headers->get('Location')]);
// now remove file
unlink(storage_path('app/private/media/') . $filename);
unlink(storage_path('app/private/media/').$filename);
$this->removeDirIfEmpty(storage_path('app/private/media'));
}
@ -111,7 +111,7 @@ class MicropubMediaTest extends TestCase
public function client_can_source_uploads(): void
{
Queue::fake();
$file = __DIR__ . '/../aaron.png';
$file = __DIR__.'/../aaron.png';
$token = $this->getToken();
$response = $this->post(
@ -119,7 +119,7 @@ class MicropubMediaTest extends TestCase
[
'file' => new UploadedFile($file, 'aaron.png', 'image/png', null, true),
],
['HTTP_Authorization' => 'Bearer ' . $token]
['HTTP_Authorization' => 'Bearer '.$token]
);
$path = parse_url($response->headers->get('Location'), PHP_URL_PATH);
@ -127,7 +127,7 @@ class MicropubMediaTest extends TestCase
$sourceUploadResponse = $this->get(
'/api/media?q=source',
['HTTP_Authorization' => 'Bearer ' . $token]
['HTTP_Authorization' => 'Bearer '.$token]
);
$sourceUploadResponse->assertJson(['items' => [[
'url' => $response->headers->get('Location'),
@ -135,7 +135,7 @@ class MicropubMediaTest extends TestCase
]]]);
// now remove file
unlink(storage_path('app/private/media/') . $filename);
unlink(storage_path('app/private/media/').$filename);
$this->removeDirIfEmpty(storage_path('app/private/media'));
}
@ -143,7 +143,7 @@ class MicropubMediaTest extends TestCase
public function client_can_source_uploads_with_limit(): void
{
Queue::fake();
$file = __DIR__ . '/../aaron.png';
$file = __DIR__.'/../aaron.png';
$token = $this->getToken();
$response = $this->post(
@ -151,7 +151,7 @@ class MicropubMediaTest extends TestCase
[
'file' => new UploadedFile($file, 'aaron.png', 'image/png', null, true),
],
['HTTP_Authorization' => 'Bearer ' . $token]
['HTTP_Authorization' => 'Bearer '.$token]
);
$path = parse_url($response->headers->get('Location'), PHP_URL_PATH);
@ -159,7 +159,7 @@ class MicropubMediaTest extends TestCase
$sourceUploadResponse = $this->get(
'/api/media?q=source&limit=1',
['HTTP_Authorization' => 'Bearer ' . $token]
['HTTP_Authorization' => 'Bearer '.$token]
);
$sourceUploadResponse->assertJson(['items' => [[
'url' => $response->headers->get('Location'),
@ -169,7 +169,7 @@ class MicropubMediaTest extends TestCase
$this->assertCount(1, json_decode($sourceUploadResponse->getContent(), true)['items']);
// now remove file
unlink(storage_path('app/private/media/') . $filename);
unlink(storage_path('app/private/media/').$filename);
$this->removeDirIfEmpty(storage_path('app/private/media'));
}
@ -179,7 +179,7 @@ class MicropubMediaTest extends TestCase
$response = $this->post(
'/api/media',
[],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertStatus(400);
$response->assertJson([
@ -194,7 +194,7 @@ class MicropubMediaTest extends TestCase
{
$response = $this->get(
'/api/media?q=unknown',
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertStatus(400);
$response->assertJson(['error' => 'invalid_request']);
@ -227,7 +227,7 @@ class MicropubMediaTest extends TestCase
$response = $this->post(
'/api/media',
[],
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithNoScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithNoScope()]
);
$response->assertStatus(400);
$response->assertJsonFragment(['error_description' => 'The provided token has no scopes']);
@ -239,7 +239,7 @@ class MicropubMediaTest extends TestCase
$response = $this->post(
'/api/media',
[],
['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()]
['HTTP_Authorization' => 'Bearer '.$this->getTokenWithIncorrectScope()]
);
$response->assertStatus(401);
$response->assertJsonFragment([
@ -251,14 +251,14 @@ class MicropubMediaTest extends TestCase
public function media_endpoint_upload_file(): void
{
Queue::fake();
$file = __DIR__ . '/../aaron.png';
$file = __DIR__.'/../aaron.png';
$response = $this->post(
'/api/media',
[
'file' => new UploadedFile($file, 'aaron.png', 'image/png', null, true),
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$path = parse_url($response->headers->get('Location'), PHP_URL_PATH);
@ -266,7 +266,7 @@ class MicropubMediaTest extends TestCase
Queue::assertPushed(ProcessMedia::class);
Storage::disk('local')->assertExists($filename);
// now remove file
unlink(storage_path('app/private/') . $filename);
unlink(storage_path('app/private/').$filename);
$this->removeDirIfEmpty(storage_path('app/private/media'));
}
@ -274,14 +274,14 @@ class MicropubMediaTest extends TestCase
public function media_endpoint_upload_audio_file(): void
{
Queue::fake();
$file = __DIR__ . '/../audio.mp3';
$file = __DIR__.'/../audio.mp3';
$response = $this->post(
'/api/media',
[
'file' => new UploadedFile($file, 'audio.mp3', 'audio/mpeg', null, true),
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$path = parse_url($response->headers->get('Location'), PHP_URL_PATH);
@ -289,7 +289,7 @@ class MicropubMediaTest extends TestCase
Queue::assertPushed(ProcessMedia::class);
Storage::disk('local')->assertExists($filename);
// now remove file
unlink(storage_path('app/private/') . $filename);
unlink(storage_path('app/private/').$filename);
$this->removeDirIfEmpty(storage_path('app/private/media'));
}
@ -297,14 +297,14 @@ class MicropubMediaTest extends TestCase
public function media_endpoint_upload_video_file(): void
{
Queue::fake();
$file = __DIR__ . '/../video.ogv';
$file = __DIR__.'/../video.ogv';
$response = $this->post(
'/api/media',
[
'file' => new UploadedFile($file, 'video.ogv', 'video/ogg', null, true),
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$path = parse_url($response->headers->get('Location'), PHP_URL_PATH);
@ -312,7 +312,7 @@ class MicropubMediaTest extends TestCase
Queue::assertPushed(ProcessMedia::class);
Storage::disk('local')->assertExists($filename);
// now remove file
unlink(storage_path('app/private/') . $filename);
unlink(storage_path('app/private/').$filename);
$this->removeDirIfEmpty(storage_path('app/private/media'));
}
@ -326,7 +326,7 @@ class MicropubMediaTest extends TestCase
[
'file' => UploadedFile::fake()->create('document.pdf', 100),
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$path = parse_url($response->headers->get('Location'), PHP_URL_PATH);
@ -334,7 +334,7 @@ class MicropubMediaTest extends TestCase
Queue::assertPushed(ProcessMedia::class);
Storage::disk('local')->assertExists($filename);
// now remove file
unlink(storage_path('app/private/') . $filename);
unlink(storage_path('app/private/').$filename);
$this->removeDirIfEmpty(storage_path('app/private/media'));
}
@ -348,14 +348,14 @@ class MicropubMediaTest extends TestCase
'/api/media',
[
'file' => new UploadedFile(
__DIR__ . '/../aaron.png',
__DIR__.'/../aaron.png',
'aaron.png',
'image/png',
UPLOAD_ERR_INI_SIZE,
true
),
],
['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
);
$response->assertStatus(400);
$response->assertJson(['error_description' => 'The uploaded file failed validation']);

View file

@ -50,7 +50,7 @@ class NotesControllerTest extends TestCase
public function old_note_urls_redirect(): void
{
$note = Note::factory()->create();
$response = $this->get('/note/' . $note->id);
$response = $this->get('/note/'.$note->id);
$response->assertRedirect($note->uri);
}

View file

@ -20,10 +20,10 @@ class ParseCachedWebMentionsTest extends TestCase
{
parent::setUp();
mkdir(storage_path('HTML') . '/https/aaronpk.localhost/reply', 0777, true);
mkdir(storage_path('HTML') . '/http/tantek.com', 0777, true);
copy(__DIR__ . '/../aaron.html', storage_path('HTML') . '/https/aaronpk.localhost/reply/1');
copy(__DIR__ . '/../tantek.html', storage_path('HTML') . '/http/tantek.com/index.html');
mkdir(storage_path('HTML').'/https/aaronpk.localhost/reply', 0777, true);
mkdir(storage_path('HTML').'/http/tantek.com', 0777, true);
copy(__DIR__.'/../aaron.html', storage_path('HTML').'/https/aaronpk.localhost/reply/1');
copy(__DIR__.'/../tantek.html', storage_path('HTML').'/http/tantek.com/index.html');
}
#[Test]
@ -39,16 +39,16 @@ class ParseCachedWebMentionsTest extends TestCase
'created_at' => Carbon::now()->subDays(5),
'updated_at' => Carbon::now()->subDays(5),
]);
$this->assertFileExists(storage_path('HTML') . '/https/aaronpk.localhost/reply/1');
$this->assertFileExists(storage_path('HTML') . '/http/tantek.com/index.html');
$htmlAaron = file_get_contents(storage_path('HTML') . '/https/aaronpk.localhost/reply/1');
$htmlAaron = str_replace('href="/notes', 'href="' . config('app.url') . '/notes', $htmlAaron);
$htmlAaron = str_replace('datetime=""', 'dateime="' . carbon()->now()->toIso8601String() . '"', $htmlAaron);
file_put_contents(storage_path('HTML') . '/https/aaronpk.localhost/reply/1', $htmlAaron);
$htmlTantek = file_get_contents(storage_path('HTML') . '/http/tantek.com/index.html');
$htmlTantek = str_replace('href="/notes', 'href="' . config('app.url') . '/notes', $htmlTantek);
$htmlTantek = str_replace('datetime=""', 'dateime="' . carbon()->now()->toIso8601String() . '"', $htmlTantek);
file_put_contents(storage_path('HTML') . '/http/tantek.com/index.html', $htmlTantek);
$this->assertFileExists(storage_path('HTML').'/https/aaronpk.localhost/reply/1');
$this->assertFileExists(storage_path('HTML').'/http/tantek.com/index.html');
$htmlAaron = file_get_contents(storage_path('HTML').'/https/aaronpk.localhost/reply/1');
$htmlAaron = str_replace('href="/notes', 'href="'.config('app.url').'/notes', $htmlAaron);
$htmlAaron = str_replace('datetime=""', 'dateime="'.carbon()->now()->toIso8601String().'"', $htmlAaron);
file_put_contents(storage_path('HTML').'/https/aaronpk.localhost/reply/1', $htmlAaron);
$htmlTantek = file_get_contents(storage_path('HTML').'/http/tantek.com/index.html');
$htmlTantek = str_replace('href="/notes', 'href="'.config('app.url').'/notes', $htmlTantek);
$htmlTantek = str_replace('datetime=""', 'dateime="'.carbon()->now()->toIso8601String().'"', $htmlTantek);
file_put_contents(storage_path('HTML').'/http/tantek.com/index.html', $htmlTantek);
Artisan::call('webmentions:parsecached');
@ -62,11 +62,11 @@ class ParseCachedWebMentionsTest extends TestCase
protected function tearDown(): void
{
$fs = new FileSystem;
if ($fs->exists(storage_path() . '/HTML/https')) {
$fs->deleteDirectory(storage_path() . '/HTML/https');
if ($fs->exists(storage_path().'/HTML/https')) {
$fs->deleteDirectory(storage_path().'/HTML/https');
}
if ($fs->exists(storage_path() . '/HTML/http')) {
$fs->deleteDirectory(storage_path() . '/HTML/http');
if ($fs->exists(storage_path().'/HTML/http')) {
$fs->deleteDirectory(storage_path().'/HTML/http');
}
parent::tearDown();

Some files were not shown because too many files have changed in this diff Show more