Compare commits

..

16 commits

Author SHA1 Message Date
07db68882b Merge pull request 'MTM Laravel v12' (#31) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #31
2025-04-04 18:10:46 +02:00
1dfa17abca
Update Laravel to v12
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2025-04-01 21:10:30 +01:00
f2025b801b
Update dependencies
Some checks failed
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
2025-01-25 11:47:43 +00:00
4e7b911fe9
Improve commentt in script
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2025-01-25 11:20:37 +00:00
cf6e65cc03
Improve compress script
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
Generate zst and br files, then  remove any that are actually larger
than the source file
2024-12-28 11:07:48 +00:00
13786584a2 Merge pull request 'MTM WebMention username fix & snow fall' (#21) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #21
2024-11-30 17:01:53 +01:00
70e5b9bec7
Add snow fall to the site
Some checks failed
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Some tweaks to the node dependencies and compression script also done
2024-11-30 15:58:25 +00:00
84383ecd31
Apply the same fix to replies and reposts
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2024-11-30 15:31:17 +00:00
1d59d57c2e
Fix showing author like on a note 2024-11-30 15:30:07 +00:00
6d867fcc71 Merge pull request 'MTM Host images locally' (#12) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #12
2024-10-26 15:02:56 +02:00
43447ac47b
Fix tests after we corrected the media URLs
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
The media URLs have a path that starts `/storage/`.
2024-10-26 12:52:43 +01:00
d7da42b626
Host images locally
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
We don’t need the complexity of S3. Sepcifically the complexity of
managing my own AWS account, flysystem made the Laravel side easy.

A command is added to copy the the S3 files over to local storage.
2024-10-25 20:40:52 +01:00
d80e8164c8
Update dependencies
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
Further tidy up some config around phpactor and unit testing
2024-10-23 09:16:11 +01:00
7dd85a3988 Merge pull request 'MTM Improve customisability' (#8) from develop into main
Some checks failed
Deploy / Deploy (release) Has been cancelled
Reviewed-on: #8
2024-08-29 19:25:33 +02:00
37a8e4bba8
Load favicon from a favicon.png file if it exists
Some checks failed
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
This means different people will be able to use their own avatar/favicon
2024-08-23 18:55:08 +01:00
9abe4a6110
Only link to gpg key if it exists in the filesystem
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2024-08-23 18:27:20 +01:00
134 changed files with 3192 additions and 3946 deletions

View file

@ -12,7 +12,9 @@ APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database # APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12 BCRYPT_ROUNDS=12
@ -39,7 +41,7 @@ FILESYSTEM_DISK=local
QUEUE_CONNECTION=database QUEUE_CONNECTION=database
CACHE_STORE=database CACHE_STORE=database
CACHE_PREFIX= # CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1 MEMCACHED_HOST=127.0.0.1
@ -49,6 +51,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_MAILER=log MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1 MAIL_HOST=127.0.0.1
MAIL_PORT=2525 MAIL_PORT=2525
MAIL_USERNAME=null MAIL_USERNAME=null

2
.gitignore vendored
View file

@ -21,3 +21,5 @@ yarn-error.log
/.idea /.idea
/.vscode /.vscode
ray.php ray.php
/public/gpg.key
/public/assets/img/favicon.png

5
.phpactor.json Normal file
View file

@ -0,0 +1,5 @@
{
"$schema": "/Users/jonny/git/phpactor/phpactor.schema.json",
"language_server_phpstan.enabled": false,
"language_server_psalm.enabled": true
}

View file

@ -0,0 +1,69 @@
<?php
namespace App\Console\Commands;
use App\Models\Media;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class CopyMediaToLocal extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:copy-media-to-local';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Copy any historic media saved to S3 to the local filesystem';
/**
* Execute the console command.
*/
public function handle()
{
// Load all the Media records
$media = Media::all();
// Loop through each media record and copy the file from S3 to the local filesystem
foreach ($media as $mediaItem) {
$filename = $mediaItem->path;
$this->info('Processing: ' . $filename);
// If the file is already saved locally skip to next one
if (Storage::disk('local')->exists('public/' . $filename)) {
$this->info('File already exists locally, skipping');
continue;
}
// Copy the file from S3 to the local filesystem
if (! Storage::disk('s3')->exists($filename)) {
$this->error('File does not exist on S3');
continue;
}
$contents = Storage::disk('s3')->get($filename);
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;
if (Storage::disk('s3')->exists($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));
}
}
}
}

View file

@ -37,7 +37,7 @@ class ParseCachedWebMentions extends Command
{ {
$htmlFiles = $filesystem->allFiles(storage_path() . '/HTML'); $htmlFiles = $filesystem->allFiles(storage_path() . '/HTML');
foreach ($htmlFiles as $file) { foreach ($htmlFiles as $file) {
if ($file->getExtension() !== 'backup') { //we dont want to parse `.backup` files if ($file->getExtension() !== 'backup') { // we dont want to parse `.backup` files
$filepath = $file->getPathname(); $filepath = $file->getPathname();
$this->info('Loading HTML from: ' . $filepath); $this->info('Loading HTML from: ' . $filepath);
$html = $filesystem->get($filepath); $html = $filesystem->get($filepath);

View file

@ -6,5 +6,5 @@ use Exception;
class RemoteContentNotFoundException extends Exception class RemoteContentNotFoundException extends Exception
{ {
//used when guzzle cant find the remote content // used when guzzle cant find the remote content
} }

View file

@ -30,7 +30,7 @@ class ArticlesController extends Controller
public function store(): RedirectResponse public function store(): RedirectResponse
{ {
//if a `.md` is attached use that for the main content. // if a `.md` is attached use that for the main content.
if (request()->hasFile('article')) { if (request()->hasFile('article')) {
$file = request()->file('article')->openFile(); $file = request()->file('article')->openFile();
$content = $file->fread($file->getSize()); $content = $file->fread($file->getSize());

View file

@ -40,7 +40,7 @@ class ContactsController extends Controller
*/ */
public function store(): RedirectResponse public function store(): RedirectResponse
{ {
$contact = new Contact(); $contact = new Contact;
$contact->name = request()->input('name'); $contact->name = request()->input('name');
$contact->nick = request()->input('nick'); $contact->nick = request()->input('nick');
$contact->homepage = request()->input('homepage'); $contact->homepage = request()->input('homepage');
@ -79,7 +79,7 @@ class ContactsController extends Controller
if (request()->hasFile('avatar') && (request()->input('homepage') != '')) { if (request()->hasFile('avatar') && (request()->input('homepage') != '')) {
$dir = parse_url(request()->input('homepage'), PHP_URL_HOST); $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(); $filesystem = new Filesystem;
if ($filesystem->isDirectory($destination) === false) { if ($filesystem->isDirectory($destination) === false) {
$filesystem->makeDirectory($destination); $filesystem->makeDirectory($destination);
} }
@ -139,7 +139,7 @@ class ContactsController extends Controller
} }
if ($avatar !== null) { 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(); $filesystem = new Filesystem;
if ($filesystem->isDirectory($directory) === false) { if ($filesystem->isDirectory($directory) === false) {
$filesystem->makeDirectory($directory); $filesystem->makeDirectory($directory);
} }

View file

@ -67,7 +67,7 @@ class NotesController extends Controller
*/ */
public function update(int $noteId): RedirectResponse public function update(int $noteId): RedirectResponse
{ {
//update note data // update note data
$note = Note::findOrFail($noteId); $note = Note::findOrFail($noteId);
$note->note = request()->input('content'); $note->note = request()->input('content');
$note->in_reply_to = request()->input('in-reply-to'); $note->in_reply_to = request()->input('in-reply-to');

View file

@ -116,8 +116,8 @@ class PasskeysController extends Controller
throw new WebAuthnException('No public key credential request options found'); throw new WebAuthnException('No public key credential request options found');
} }
$attestationStatementSupportManager = new AttestationStatementSupportManager(); $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$webauthnSerializer = (new WebauthnSerializerFactory( $webauthnSerializer = (new WebauthnSerializerFactory(
$attestationStatementSupportManager $attestationStatementSupportManager
@ -133,12 +133,12 @@ class PasskeysController extends Controller
throw new WebAuthnException('Invalid response type'); throw new WebAuthnException('Invalid response type');
} }
$algorithmManager = new Manager(); $algorithmManager = new Manager;
$algorithmManager->add(new Ed25519()); $algorithmManager->add(new Ed25519);
$algorithmManager->add(new ES256()); $algorithmManager->add(new ES256);
$algorithmManager->add(new RS256()); $algorithmManager->add(new RS256);
$ceremonyStepManagerFactory = new CeremonyStepManagerFactory(); $ceremonyStepManagerFactory = new CeremonyStepManagerFactory;
$ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager); $ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager);
$ceremonyStepManagerFactory->setAttestationStatementSupportManager( $ceremonyStepManagerFactory->setAttestationStatementSupportManager(
$attestationStatementSupportManager $attestationStatementSupportManager
@ -206,8 +206,8 @@ class PasskeysController extends Controller
], 400); ], 400);
} }
$attestationStatementSupportManager = new AttestationStatementSupportManager(); $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$webauthnSerializer = (new WebauthnSerializerFactory( $webauthnSerializer = (new WebauthnSerializerFactory(
$attestationStatementSupportManager $attestationStatementSupportManager
@ -240,15 +240,15 @@ class PasskeysController extends Controller
'json' 'json'
); );
$algorithmManager = new Manager(); $algorithmManager = new Manager;
$algorithmManager->add(new Ed25519()); $algorithmManager->add(new Ed25519);
$algorithmManager->add(new ES256()); $algorithmManager->add(new ES256);
$algorithmManager->add(new RS256()); $algorithmManager->add(new RS256);
$attestationStatementSupportManager = new AttestationStatementSupportManager(); $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$ceremonyStepManagerFactory = new CeremonyStepManagerFactory(); $ceremonyStepManagerFactory = new CeremonyStepManagerFactory;
$ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager); $ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager);
$ceremonyStepManagerFactory->setAttestationStatementSupportManager( $ceremonyStepManagerFactory->setAttestationStatementSupportManager(
$attestationStatementSupportManager $attestationStatementSupportManager

View file

@ -18,7 +18,7 @@ class ContactsController extends Controller
*/ */
public function index(): View public function index(): View
{ {
$filesystem = new Filesystem(); $filesystem = new Filesystem;
$contacts = Contact::all(); $contacts = Contact::all();
foreach ($contacts as $contact) { foreach ($contacts as $contact) {
$contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST); $contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST);
@ -40,7 +40,7 @@ class ContactsController extends Controller
$contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST); $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(); $filesystem = new Filesystem;
$image = ($filesystem->exists($file)) ? $image = ($filesystem->exists($file)) ?
'/assets/profile-images/' . $contact->homepageHost . '/image' '/assets/profile-images/' . $contact->homepageHost . '/image'
: :

View file

@ -26,8 +26,8 @@ class IndieAuthController extends Controller
'authorization_endpoint' => route('indieauth.start'), 'authorization_endpoint' => route('indieauth.start'),
'token_endpoint' => route('indieauth.token'), 'token_endpoint' => route('indieauth.token'),
'code_challenge_methods_supported' => ['S256'], 'code_challenge_methods_supported' => ['S256'],
//'introspection_endpoint' => route('indieauth.introspection'), // 'introspection_endpoint' => route('indieauth.introspection'),
//'introspection_endpoint_auth_methods_supported' => ['none'], // 'introspection_endpoint_auth_methods_supported' => ['none'],
]); ]);
} }

View file

@ -53,13 +53,13 @@ class MicropubController extends Controller
try { try {
$tokenData = $this->tokenService->validateToken($request->input('access_token')); $tokenData = $this->tokenService->validateToken($request->input('access_token'));
} catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) { } catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->invalidTokenResponse(); return $micropubResponses->invalidTokenResponse();
} }
if ($tokenData->claims()->has('scope') === false) { if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->tokenHasNoScopeResponse(); return $micropubResponses->tokenHasNoScopeResponse();
} }
@ -73,7 +73,7 @@ class MicropubController extends Controller
} }
if (! in_array('create', $scopes)) { if (! in_array('create', $scopes)) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
} }
@ -91,7 +91,7 @@ class MicropubController extends Controller
$scopes = explode(' ', $scopes); $scopes = explode(' ', $scopes);
} }
if (! in_array('create', $scopes)) { if (! in_array('create', $scopes)) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
} }
@ -109,7 +109,7 @@ class MicropubController extends Controller
$scopes = explode(' ', $scopes); $scopes = explode(' ', $scopes);
} }
if (! in_array('update', $scopes)) { if (! in_array('update', $scopes)) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
} }
@ -136,7 +136,7 @@ class MicropubController extends Controller
try { try {
$tokenData = $this->tokenService->validateToken($request->input('access_token')); $tokenData = $this->tokenService->validateToken($request->input('access_token'));
} catch (RequiredConstraintsViolated|InvalidTokenStructure) { } catch (RequiredConstraintsViolated|InvalidTokenStructure) {
return (new MicropubResponses())->invalidTokenResponse(); return (new MicropubResponses)->invalidTokenResponse();
} }
if ($request->input('q') === 'syndicate-to') { if ($request->input('q') === 'syndicate-to') {

View file

@ -39,13 +39,13 @@ class MicropubMediaController extends Controller
try { try {
$tokenData = $this->tokenService->validateToken($request->input('access_token')); $tokenData = $this->tokenService->validateToken($request->input('access_token'));
} catch (RequiredConstraintsViolated|InvalidTokenStructure) { } catch (RequiredConstraintsViolated|InvalidTokenStructure) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->invalidTokenResponse(); return $micropubResponses->invalidTokenResponse();
} }
if ($tokenData->claims()->has('scope') === false) { if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->tokenHasNoScopeResponse(); return $micropubResponses->tokenHasNoScopeResponse();
} }
@ -55,7 +55,7 @@ class MicropubMediaController extends Controller
$scopes = explode(' ', $scopes); $scopes = explode(' ', $scopes);
} }
if (! in_array('create', $scopes)) { if (! in_array('create', $scopes)) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
} }
@ -111,13 +111,13 @@ class MicropubMediaController extends Controller
try { try {
$tokenData = $this->tokenService->validateToken($request->input('access_token')); $tokenData = $this->tokenService->validateToken($request->input('access_token'));
} catch (RequiredConstraintsViolated|InvalidTokenStructure) { } catch (RequiredConstraintsViolated|InvalidTokenStructure) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->invalidTokenResponse(); return $micropubResponses->invalidTokenResponse();
} }
if ($tokenData->claims()->has('scope') === false) { if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->tokenHasNoScopeResponse(); return $micropubResponses->tokenHasNoScopeResponse();
} }
@ -127,7 +127,7 @@ class MicropubMediaController extends Controller
$scopes = explode(' ', $scopes); $scopes = explode(' ', $scopes);
} }
if (! in_array('create', $scopes)) { if (! in_array('create', $scopes)) {
$micropubResponses = new MicropubResponses(); $micropubResponses = new MicropubResponses;
return $micropubResponses->insufficientScopeResponse(); return $micropubResponses->insufficientScopeResponse();
} }
@ -140,7 +140,10 @@ class MicropubMediaController extends Controller
], 400); ], 400);
} }
if ($request->file('file')->isValid() === false) { /** @var UploadedFile $file */
$file = $request->file('file');
if ($file->isValid() === false) {
return response()->json([ return response()->json([
'response' => 'error', 'response' => 'error',
'error' => 'invalid_request', 'error' => 'invalid_request',
@ -148,7 +151,7 @@ class MicropubMediaController extends Controller
], 400); ], 400);
} }
$filename = $this->saveFile($request->file('file')); $filename = Storage::disk('local')->putFile('media', $file);
/** @var ImageManager $manager */ /** @var ImageManager $manager */
$manager = resolve(ImageManager::class); $manager = resolve(ImageManager::class);
@ -162,18 +165,11 @@ class MicropubMediaController extends Controller
$media = Media::create([ $media = Media::create([
'token' => $request->bearerToken(), 'token' => $request->bearerToken(),
'path' => 'media/' . $filename, 'path' => $filename,
'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()), 'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()),
'image_widths' => $width, 'image_widths' => $width,
]); ]);
// put the file on S3 initially, the ProcessMedia job may edit this
Storage::disk('s3')->putFileAs(
'media',
new File(storage_path('app') . '/' . $filename),
$filename
);
ProcessMedia::dispatch($filename); ProcessMedia::dispatch($filename);
return response()->json([ return response()->json([
@ -195,7 +191,7 @@ class MicropubMediaController extends Controller
*/ */
private function getFileTypeFromMimeType(string $mimeType): string private function getFileTypeFromMimeType(string $mimeType): string
{ {
//try known images // try known images
$imageMimeTypes = [ $imageMimeTypes = [
'image/gif', 'image/gif',
'image/jpeg', 'image/jpeg',
@ -207,7 +203,7 @@ class MicropubMediaController extends Controller
if (in_array($mimeType, $imageMimeTypes)) { if (in_array($mimeType, $imageMimeTypes)) {
return 'image'; return 'image';
} }
//try known video // try known video
$videoMimeTypes = [ $videoMimeTypes = [
'video/mp4', 'video/mp4',
'video/mpeg', 'video/mpeg',
@ -218,7 +214,7 @@ class MicropubMediaController extends Controller
if (in_array($mimeType, $videoMimeTypes)) { if (in_array($mimeType, $videoMimeTypes)) {
return 'video'; return 'video';
} }
//try known audio types // try known audio types
$audioMimeTypes = [ $audioMimeTypes = [
'audio/midi', 'audio/midi',
'audio/mpeg', 'audio/mpeg',
@ -237,7 +233,7 @@ class MicropubMediaController extends Controller
* *
* @throws Exception * @throws Exception
*/ */
private function saveFile(UploadedFile $file): string private function saveFileToLocal(UploadedFile $file): string
{ {
$filename = Uuid::uuid4()->toString() . '.' . $file->extension(); $filename = Uuid::uuid4()->toString() . '.' . $file->extension();
Storage::disk('local')->putFileAs('', $file, $filename); Storage::disk('local')->putFileAs('', $file, $filename);

View file

@ -67,7 +67,7 @@ class NotesController extends Controller
*/ */
public function redirect(int $decId): RedirectResponse 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

@ -33,7 +33,7 @@ class WebMentionsController extends Controller
*/ */
public function receive(Request $request): Response public function receive(Request $request): Response
{ {
//first we trivially reject requests that lack all required inputs // first we trivially reject requests that lack all required inputs
if (($request->has('target') !== true) || ($request->has('source') !== true)) { if (($request->has('target') !== true) || ($request->has('source') !== true)) {
return response( return response(
'You need both the target and source parameters', 'You need both the target and source parameters',
@ -41,12 +41,12 @@ class WebMentionsController extends Controller
); );
} }
//next check the $target is valid // next check the $target is valid
$path = parse_url($request->input('target'), PHP_URL_PATH); $path = parse_url($request->input('target'), PHP_URL_PATH);
$pathParts = explode('/', $path); $pathParts = explode('/', $path);
if ($pathParts[1] === 'notes') { if ($pathParts[1] === 'notes') {
//we have a note // we have a note
$noteId = $pathParts[2]; $noteId = $pathParts[2];
try { try {
$note = Note::findOrFail(resolve(Numbers::class)->b60tonum($noteId)); $note = Note::findOrFail(resolve(Numbers::class)->b60tonum($noteId));

View file

@ -35,30 +35,30 @@ class DownloadWebMention implements ShouldQueue
public function handle(Client $guzzle): void public function handle(Client $guzzle): void
{ {
$response = $guzzle->request('GET', $this->source); $response = $guzzle->request('GET', $this->source);
//4XX and 5XX responses should get Guzzle to throw an exception, // 4XX and 5XX responses should get Guzzle to throw an exception,
//Laravel should catch and retry these automatically. // Laravel should catch and retry these automatically.
if ($response->getStatusCode() === 200) { if ($response->getStatusCode() === 200) {
$filesystem = new FileSystem(); $filesystem = new FileSystem;
$filename = storage_path('HTML') . '/' . $this->createFilenameFromURL($this->source); $filename = storage_path('HTML') . '/' . $this->createFilenameFromURL($this->source);
//backup file first // backup file first
$filenameBackup = $filename . '.' . date('Y-m-d') . '.backup'; $filenameBackup = $filename . '.' . date('Y-m-d') . '.backup';
if ($filesystem->exists($filename)) { if ($filesystem->exists($filename)) {
$filesystem->copy($filename, $filenameBackup); $filesystem->copy($filename, $filenameBackup);
} }
//check if base directory exists // check if base directory exists
if (! $filesystem->exists($filesystem->dirname($filename))) { if (! $filesystem->exists($filesystem->dirname($filename))) {
$filesystem->makeDirectory( $filesystem->makeDirectory(
$filesystem->dirname($filename), $filesystem->dirname($filename),
0755, //mode 0755, // mode
true //recursive true // recursive
); );
} }
//save new HTML // save new HTML
$filesystem->put( $filesystem->put(
$filename, $filename,
(string) $response->getBody() (string) $response->getBody()
); );
//remove backup if the same // remove backup if the same
if ($filesystem->exists($filenameBackup)) { if ($filesystem->exists($filenameBackup)) {
if ($filesystem->get($filename) === $filesystem->get($filenameBackup)) { if ($filesystem->get($filename) === $filesystem->get($filenameBackup)) {
$filesystem->delete($filenameBackup); $filesystem->delete($filenameBackup);

View file

@ -49,7 +49,7 @@ class ProcessLike implements ShouldQueue
$this->like->content = $tweet->html; $this->like->content = $tweet->html;
$this->like->save(); $this->like->save();
//POSSE like // POSSE like
try { try {
$client->request( $client->request(
'POST', 'POST',

View file

@ -32,35 +32,38 @@ class ProcessMedia implements ShouldQueue
*/ */
public function handle(ImageManager $manager): void public function handle(ImageManager $manager): void
{ {
//open file // Load file
$file = Storage::disk('local')->get('media/' . $this->filename);
// Open file
try { try {
$image = $manager->read(storage_path('app') . '/' . $this->filename); $image = $manager->read($file);
} catch (DecoderException) { } catch (DecoderException) {
// not an image; delete file and end job // not an image; delete file and end job
unlink(storage_path('app') . '/' . $this->filename); Storage::disk('local')->delete('media/' . $this->filename);
return; return;
} }
//create smaller versions if necessary
// Save the file publicly
Storage::disk('public')->put('media/' . $this->filename, $file);
// Create smaller versions if necessary
if ($image->width() > 1000) { if ($image->width() > 1000) {
$filenameParts = explode('.', $this->filename); $filenameParts = explode('.', $this->filename);
$extension = array_pop($filenameParts); $extension = array_pop($filenameParts);
// the following achieves this data flow // the following achieves this data flow
// foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar // foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar
$basename = ltrim(array_reduce($filenameParts, function ($carry, $item) { $basename = trim(implode('.', $filenameParts), '.');
return $carry . '.' . $item;
}, ''), '.'); $medium = $image->resize(width: 1000);
$medium = $image->resize(1000, null, function ($constraint) { Storage::disk('public')->put('media/' . $basename . '-medium.' . $extension, (string) $medium->encode());
$constraint->aspectRatio();
}); $small = $image->resize(width: 500);
Storage::disk('s3')->put('media/' . $basename . '-medium.' . $extension, (string) $medium->encode()); Storage::disk('public')->put('media/' . $basename . '-small.' . $extension, (string) $small->encode());
$small = $image->resize(500, null, function ($constraint) {
$constraint->aspectRatio();
});
Storage::disk('s3')->put('media/' . $basename . '-small.' . $extension, (string) $small->encode());
} }
// now we can delete the locally saved image // Now we can delete the locally saved image
unlink(storage_path('app') . '/' . $this->filename); Storage::disk('local')->delete('media/' . $this->filename);
} }
} }

View file

@ -44,7 +44,7 @@ class ProcessWebMention implements ShouldQueue
try { try {
$response = $guzzle->request('GET', $this->source); $response = $guzzle->request('GET', $this->source);
} catch (RequestException $e) { } catch (RequestException $e) {
throw new RemoteContentNotFoundException(); throw new RemoteContentNotFoundException;
} }
$this->saveRemoteContent((string) $response->getBody(), $this->source); $this->saveRemoteContent((string) $response->getBody(), $this->source);
$microformats = Mf2\parse((string) $response->getBody(), $this->source); $microformats = Mf2\parse((string) $response->getBody(), $this->source);
@ -85,7 +85,7 @@ class ProcessWebMention implements ShouldQueue
}// foreach }// foreach
// no webmention in the db so create new one // no webmention in the db so create new one
$webmention = new WebMention(); $webmention = new WebMention;
$type = $parser->getMentionType($microformats); // throw error here? $type = $parser->getMentionType($microformats); // throw error here?
dispatch(new SaveProfileImage($microformats)); dispatch(new SaveProfileImage($microformats));
$webmention->source = $this->source; $webmention->source = $this->source;

View file

@ -49,7 +49,7 @@ class SaveProfileImage implements ShouldQueue
$home = array_shift($home); $home = array_shift($home);
} }
//dont save pbs.twimg.com links // dont save pbs.twimg.com links
if ( if (
$photo $photo
&& parse_url($photo, PHP_URL_HOST) !== 'pbs.twimg.com' && parse_url($photo, PHP_URL_HOST) !== 'pbs.twimg.com'

View file

@ -72,7 +72,7 @@ class SendWebMentions implements ShouldQueue
$guzzle = resolve(Client::class); $guzzle = resolve(Client::class);
$response = $guzzle->get($url); $response = $guzzle->get($url);
//check HTTP Headers for webmention endpoint // check HTTP Headers for webmention endpoint
$links = Header::parse($response->getHeader('Link')); $links = Header::parse($response->getHeader('Link'));
foreach ($links as $link) { foreach ($links as $link) {
if (array_key_exists('rel', $link) && mb_stristr($link['rel'], 'webmention')) { if (array_key_exists('rel', $link) && mb_stristr($link['rel'], 'webmention')) {
@ -80,7 +80,7 @@ class SendWebMentions implements ShouldQueue
} }
} }
//failed to find a header so parse HTML // failed to find a header so parse HTML
$html = (string) $response->getBody(); $html = (string) $response->getBody();
$mf2 = new \Mf2\Parser($html, $url); $mf2 = new \Mf2\Parser($html, $url);
@ -108,7 +108,7 @@ class SendWebMentions implements ShouldQueue
} }
$urls = []; $urls = [];
$dom = new \DOMDocument(); $dom = new \DOMDocument;
$dom->loadHTML($html); $dom->loadHTML($html);
$anchors = $dom->getElementsByTagName('a'); $anchors = $dom->getElementsByTagName('a');
foreach ($anchors as $anchor) { foreach ($anchors as $anchor) {

View file

@ -58,10 +58,10 @@ class Article extends Model
{ {
return Attribute::get( return Attribute::get(
get: function () { get: function () {
$environment = new Environment(); $environment = new Environment;
$environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new CommonMarkCoreExtension);
$environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); $environment->addRenderer(FencedCode::class, new FencedCodeRenderer);
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer);
$markdownConverter = new MarkdownConverter($environment); $markdownConverter = new MarkdownConverter($environment);
return $markdownConverter->convert($this->main)->getContent(); return $markdownConverter->convert($this->main)->getContent();

View file

@ -33,7 +33,7 @@ class Media extends Model
return $attributes['path']; return $attributes['path'];
} }
return config('filesystems.disks.s3.url') . '/' . $attributes['path']; return config('app.url') . '/storage/' . $attributes['path'];
} }
); );
} }
@ -78,7 +78,7 @@ class Media extends Model
$basename = $this->getBasename($path); $basename = $this->getBasename($path);
$extension = $this->getExtension($path); $extension = $this->getExtension($path);
return config('filesystems.disks.s3.url') . '/' . $basename . '-' . $size . '.' . $extension; return config('app.url') . '/storage/' . $basename . '-' . $size . '.' . $extension;
} }
private function getBasename(string $path): string private function getBasename(string $path): string

View file

@ -111,7 +111,7 @@ class Note extends Model
{ {
if ($value !== null) { if ($value !== null) {
$normalized = normalizer_normalize($value, Normalizer::FORM_C); $normalized = normalizer_normalize($value, Normalizer::FORM_C);
if ($normalized === '') { //we dont want to save empty strings to the db if ($normalized === '') { // we dont want to save empty strings to the db
$normalized = null; $normalized = null;
} }
$this->attributes['note'] = $normalized; $this->attributes['note'] = $normalized;
@ -271,7 +271,7 @@ class Note extends Model
]); ]);
if ($oEmbed->httpstatus >= 400) { if ($oEmbed->httpstatus >= 400) {
throw new Exception(); throw new Exception;
} }
} catch (Exception $e) { } catch (Exception $e) {
return null; return null;
@ -388,18 +388,18 @@ class Note extends Model
'mentions_handle' => [ 'mentions_handle' => [
'prefix' => '@', 'prefix' => '@',
'pattern' => '([\w@.])+(\b)', 'pattern' => '([\w@.])+(\b)',
'generator' => new MentionGenerator(), 'generator' => new MentionGenerator,
], ],
], ],
]; ];
$environment = new Environment($config); $environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new CommonMarkCoreExtension);
$environment->addExtension(new AutolinkExtension()); $environment->addExtension(new AutolinkExtension);
$environment->addExtension(new MentionExtension()); $environment->addExtension(new MentionExtension);
$environment->addRenderer(Mention::class, new MentionRenderer()); $environment->addRenderer(Mention::class, new MentionRenderer);
$environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); $environment->addRenderer(FencedCode::class, new FencedCodeRenderer);
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer);
$markdownConverter = new MarkdownConverter($environment); $markdownConverter = new MarkdownConverter($environment);
return $markdownConverter->convert($note)->getContent(); return $markdownConverter->convert($note)->getContent();

View file

@ -59,7 +59,7 @@ class Place extends Model
* sin(radians(places.latitude))))"; * sin(radians(places.latitude))))";
return $query return $query
->select() //pick the columns you want here. ->select() // pick the columns you want here.
->selectRaw("{$haversine} AS distance") ->selectRaw("{$haversine} AS distance")
->whereRaw("{$haversine} < ?", [$distance]); ->whereRaw("{$haversine} < ?", [$distance]);
} }

View file

@ -42,7 +42,7 @@ class WebMention extends Model
return null; return null;
} }
$authorship = new Authorship(); $authorship = new Authorship;
$hCard = $authorship->findAuthor(json_decode($attributes['mf2'], true)); $hCard = $authorship->findAuthor(json_decode($attributes['mf2'], true));
if ($hCard === false) { if ($hCard === false) {
@ -123,7 +123,7 @@ class WebMention extends Model
$host = parse_url($url, PHP_URL_HOST); $host = parse_url($url, PHP_URL_HOST);
if ($host === 'pbs.twimg.com') { if ($host === 'pbs.twimg.com') {
//make sure we use HTTPS, we know twitter supports it // make sure we use HTTPS, we know twitter supports it
return str_replace('http://', 'https://', $url); return str_replace('http://', 'https://', $url);
} }
@ -135,12 +135,12 @@ class WebMention extends Model
$codebird = resolve(Codebird::class); $codebird = resolve(Codebird::class);
$info = $codebird->users_show(['screen_name' => $username]); $info = $codebird->users_show(['screen_name' => $username]);
$profile_image = $info->profile_image_url_https; $profile_image = $info->profile_image_url_https;
Cache::put($url, $profile_image, 10080); //1 week Cache::put($url, $profile_image, 10080); // 1 week
return $profile_image; return $profile_image;
} }
$filesystem = new Filesystem(); $filesystem = new Filesystem;
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) { if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
return '/assets/profile-images/' . $host . '/image'; return '/assets/profile-images/' . $host . '/image';
} }

View file

@ -88,9 +88,9 @@ class AppServiceProvider extends ServiceProvider
$this->app->bind('Lcobucci\JWT\Configuration', function () { $this->app->bind('Lcobucci\JWT\Configuration', function () {
$key = InMemory::plainText(config('app.key')); $key = InMemory::plainText(config('app.key'));
$config = Configuration::forSymmetricSigner(new Sha256(), $key); $config = Configuration::forSymmetricSigner(new Sha256, $key);
$config->setValidationConstraints(new SignedWith(new Sha256(), $key)); $config->setValidationConstraints(new SignedWith(new Sha256, $key));
return $config; return $config;
}); });
@ -98,7 +98,7 @@ class AppServiceProvider extends ServiceProvider
// Configure HtmlSanitizer // Configure HtmlSanitizer
$this->app->bind(HtmlSanitizer::class, function () { $this->app->bind(HtmlSanitizer::class, function () {
return new HtmlSanitizer( return new HtmlSanitizer(
(new HtmlSanitizerConfig()) (new HtmlSanitizerConfig)
->allowSafeElements() ->allowSafeElements()
->forceAttribute('a', 'rel', 'noopener nofollow') ->forceAttribute('a', 'rel', 'noopener nofollow')
); );

View file

@ -21,7 +21,7 @@ class BookmarkService extends Service
public function create(array $request, ?string $client = null): Bookmark public function create(array $request, ?string $client = null): Bookmark
{ {
if (Arr::get($request, 'properties.bookmark-of.0')) { if (Arr::get($request, 'properties.bookmark-of.0')) {
//micropub request // micropub request
$url = normalize_url(Arr::get($request, 'properties.bookmark-of.0')); $url = normalize_url(Arr::get($request, 'properties.bookmark-of.0'));
$name = Arr::get($request, 'properties.name.0'); $name = Arr::get($request, 'properties.name.0');
$content = Arr::get($request, 'properties.content.0'); $content = Arr::get($request, 'properties.content.0');
@ -61,8 +61,8 @@ class BookmarkService extends Service
try { try {
$response = $client->request('GET', 'https://web.archive.org/save/' . $url); $response = $client->request('GET', 'https://web.archive.org/save/' . $url);
} catch (ClientException $e) { } catch (ClientException $e) {
//throw an exception to be caught // throw an exception to be caught
throw new InternetArchiveException(); throw new InternetArchiveException;
} }
if ($response->hasHeader('Content-Location')) { if ($response->hasHeader('Content-Location')) {
if (Str::startsWith(Arr::get($response->getHeader('Content-Location'), 0), '/web')) { if (Str::startsWith(Arr::get($response->getHeader('Content-Location'), 0), '/web')) {
@ -70,7 +70,7 @@ class BookmarkService extends Service
} }
} }
//throw an exception to be caught // throw an exception to be caught
throw new InternetArchiveException(); throw new InternetArchiveException;
} }
} }

View file

@ -16,7 +16,7 @@ class LikeService extends Service
public function create(array $request, ?string $client = null): Like public function create(array $request, ?string $client = null): Like
{ {
if (Arr::get($request, 'properties.like-of.0')) { if (Arr::get($request, 'properties.like-of.0')) {
//micropub request // micropub request
$url = normalize_url(Arr::get($request, 'properties.like-of.0')); $url = normalize_url(Arr::get($request, 'properties.like-of.0'));
} }
if (Arr::get($request, 'like-of')) { if (Arr::get($request, 'like-of')) {

View file

@ -20,7 +20,7 @@ class UpdateService
{ {
$urlPath = parse_url(Arr::get($request, 'url'), PHP_URL_PATH); $urlPath = parse_url(Arr::get($request, 'url'), PHP_URL_PATH);
//is it a note we are updating? // is it a note we are updating?
if (mb_substr($urlPath, 1, 5) !== 'notes') { if (mb_substr($urlPath, 1, 5) !== 'notes') {
return response()->json([ return response()->json([
'error' => 'invalid', 'error' => 'invalid',
@ -37,7 +37,7 @@ class UpdateService
], 404); ], 404);
} }
//got the note, are we dealing with a “replace” request? // got the note, are we dealing with a “replace” request?
if (Arr::get($request, 'replace')) { if (Arr::get($request, 'replace')) {
foreach (Arr::get($request, 'replace') as $property => $value) { foreach (Arr::get($request, 'replace') as $property => $value) {
if ($property === 'content') { if ($property === 'content') {
@ -64,7 +64,7 @@ class UpdateService
]); ]);
} }
//how about “add” // how about “add”
if (Arr::get($request, 'add')) { if (Arr::get($request, 'add')) {
foreach (Arr::get($request, 'add') as $property => $value) { foreach (Arr::get($request, 'add') as $property => $value) {
if ($property === 'syndication') { if ($property === 'syndication') {
@ -83,7 +83,7 @@ class UpdateService
if ($property === 'photo') { if ($property === 'photo') {
foreach ($value as $photoURL) { foreach ($value as $photoURL) {
if (Str::startsWith($photoURL, 'https://')) { if (Str::startsWith($photoURL, 'https://')) {
$media = new Media(); $media = new Media;
$media->path = $photoURL; $media->path = $photoURL;
$media->type = 'image'; $media->type = 'image';
$media->save(); $media->save();

View file

@ -14,8 +14,8 @@ class PlaceService
*/ */
public function createPlace(array $data): Place public function createPlace(array $data): Place
{ {
//obviously a place needs a lat/lng, but this could be sent in a geo-url // obviously a place needs a lat/lng, but this could be sent in a geo-url
//if no geo array key, we assume the array already has lat/lng values // if no geo array key, we assume the array already has lat/lng values
if (array_key_exists('geo', $data) && $data['geo'] !== null) { if (array_key_exists('geo', $data) && $data['geo'] !== null) {
preg_match_all( preg_match_all(
'/([0-9\.\-]+)/', '/([0-9\.\-]+)/',
@ -25,7 +25,7 @@ class PlaceService
$data['latitude'] = $matches[0][0]; $data['latitude'] = $matches[0][0];
$data['longitude'] = $matches[0][1]; $data['longitude'] = $matches[0][1];
} }
$place = new Place(); $place = new Place;
$place->name = $data['name']; $place->name = $data['name'];
$place->description = $data['description']; $place->description = $data['description'];
$place->latitude = $data['latitude']; $place->latitude = $data['latitude'];
@ -40,7 +40,7 @@ class PlaceService
*/ */
public function createPlaceFromCheckin(array $checkin): Place public function createPlaceFromCheckin(array $checkin): Place
{ {
//check if the place exists if from swarm // check if the place exists if from swarm
if (Arr::has($checkin, 'properties.url')) { if (Arr::has($checkin, 'properties.url')) {
$place = Place::whereExternalURL(Arr::get($checkin, 'properties.url.0'))->get(); $place = Place::whereExternalURL(Arr::get($checkin, 'properties.url.0'))->get();
if (count($place) === 1) { if (count($place) === 1) {
@ -53,7 +53,7 @@ class PlaceService
if (Arr::has($checkin, 'properties.latitude') === false) { if (Arr::has($checkin, 'properties.latitude') === false) {
throw new \InvalidArgumentException('Missing required longitude/latitude'); throw new \InvalidArgumentException('Missing required longitude/latitude');
} }
$place = new Place(); $place = new Place;
$place->name = Arr::get($checkin, 'properties.name.0'); $place->name = Arr::get($checkin, 'properties.name.0');
$place->external_urls = Arr::get($checkin, 'properties.url.0'); $place->external_urls = Arr::get($checkin, 'properties.url.0');
$place->latitude = Arr::get($checkin, 'properties.latitude.0'); $place->latitude = Arr::get($checkin, 'properties.latitude.0');

View file

@ -19,7 +19,7 @@ class TokenService
$config = resolve(Configuration::class); $config = resolve(Configuration::class);
$token = $config->builder() $token = $config->builder()
->issuedAt(new DateTimeImmutable()) ->issuedAt(new DateTimeImmutable)
->withClaim('client_id', $data['client_id']) ->withClaim('client_id', $data['client_id'])
->withClaim('me', $data['me']) ->withClaim('me', $data['me'])
->withClaim('scope', $data['scope']) ->withClaim('scope', $data['scope'])

View file

@ -1,7 +1,8 @@
{ {
"$schema": "https://getcomposer.org/schema.json",
"name": "jonnybarnes/jonnybarnes.uk", "name": "jonnybarnes/jonnybarnes.uk",
"type": "project", "type": "project",
"description": "The code for jonnybarnes.uk, based on Laravel 10", "description": "The code for jonnybarnes.uk, based on Laravel 11",
"keywords": ["laravel", "framework", "indieweb"], "keywords": ["laravel", "framework", "indieweb"],
"license": "CC0-1.0", "license": "CC0-1.0",
"require": { "require": {
@ -11,14 +12,14 @@
"ext-json": "*", "ext-json": "*",
"ext-pgsql": "*", "ext-pgsql": "*",
"ext-sodium": "*", "ext-sodium": "*",
"cviebrock/eloquent-sluggable": "^11.0", "cviebrock/eloquent-sluggable": "^12.0",
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"indieauth/client": "^1.1", "indieauth/client": "^1.1",
"intervention/image": "^3", "intervention/image": "^3",
"jonnybarnes/indieweb": "~0.2", "jonnybarnes/indieweb": "~0.2",
"jonnybarnes/webmentions-parser": "~0.5", "jonnybarnes/webmentions-parser": "~0.5",
"jublonet/codebird-php": "4.0.0-beta.1", "jublonet/codebird-php": "4.0.0-beta.1",
"laravel/framework": "^11.0", "laravel/framework": "^12.0",
"laravel/horizon": "^5.0", "laravel/horizon": "^5.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/scout": "^10.1", "laravel/scout": "^10.1",
@ -40,16 +41,15 @@
"barryvdh/laravel-ide-helper": "^3.0", "barryvdh/laravel-ide-helper": "^3.0",
"fakerphp/faker": "^1.9.2", "fakerphp/faker": "^1.9.2",
"laravel/dusk": "^8.0", "laravel/dusk": "^8.0",
"laravel/pail": "^1.2",
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
"laravel/sail": "^1.18", "laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4", "mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^8.1", "nunomaduro/collision": "^8.1",
"openai-php/client": "^0.10.1", "openai-php/client": "^0.10.1",
"phpunit/php-code-coverage": "^10.0", "phpunit/php-code-coverage": "^11.0",
"phpunit/phpunit": "^10.1", "phpunit/phpunit": "^11.0",
"psalm/plugin-laravel": "^2.8", "spatie/laravel-ray": "^1.12"
"spatie/laravel-ray": "^1.12",
"vimeo/psalm": "^5.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -78,7 +78,13 @@
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
], ],
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php artisan key:generate --ansi" "@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
] ]
}, },
"extra": { "extra": {

4106
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -65,7 +65,7 @@ return [
| |
*/ */
'timezone' => env('APP_TIMEZONE', 'UTC'), 'timezone' => 'UTC',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -37,6 +37,9 @@ return [
'database' => env('DB_DATABASE', database_path('database.sqlite')), 'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '', 'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
], ],
'mysql' => [ 'mysql' => [
@ -145,6 +148,7 @@ return [
'options' => [ 'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'), 'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'persistent' => env('REDIS_PERSISTENT', false),
], ],
'default' => [ 'default' => [

View file

@ -119,7 +119,7 @@ return [
'full_log' => false, 'full_log' => false,
], ],
'views' => [ 'views' => [
'data' => false, //Note: Can slow down the application, because the data can be quite large.. 'data' => false, // Note: Can slow down the application, because the data can be quite large..
], ],
'route' => [ 'route' => [
'label' => true, // show complete route on bar 'label' => true, // show complete route on bar

View file

@ -32,8 +32,10 @@ return [
'local' => [ 'local' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app'), 'root' => storage_path('app/private'),
'serve' => true,
'throw' => false, 'throw' => false,
'report' => false,
], ],
'public' => [ 'public' => [
@ -42,6 +44,7 @@ return [
'url' => env('APP_URL').'/storage', 'url' => env('APP_URL').'/storage',
'visibility' => 'public', 'visibility' => 'public',
'throw' => false, 'throw' => false,
'report' => false,
], ],
's3' => [ 's3' => [
@ -54,6 +57,7 @@ return [
'endpoint' => env('AWS_ENDPOINT'), 'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false, 'throw' => false,
'report' => false,
], ],
], ],

View file

@ -38,14 +38,14 @@ return [
'smtp' => [ 'smtp' => [
'transport' => 'smtp', 'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'), 'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'), 'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525), 'port' => env('MAIL_PORT', 2525),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'), 'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'), 'password' => env('MAIL_PASSWORD'),
'timeout' => null, 'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN'), 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
], ],
'ses' => [ 'ses' => [

View file

@ -32,7 +32,7 @@ return [
| |
*/ */
'lifetime' => env('SESSION_LIFETIME', 120), 'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),

View file

@ -27,7 +27,7 @@ class ContactsTableSeeder extends Seeder
'homepage' => 'https://aaronparecki.com', 'homepage' => 'https://aaronparecki.com',
'facebook' => '123456', 'facebook' => '123456',
]); ]);
$fs = new FileSystem(); $fs = new FileSystem;
if (! $fs->exists(public_path('assets/profile-images/aaronparecki.com'))) { if (! $fs->exists(public_path('assets/profile-images/aaronparecki.com'))) {
$fs->makeDirectory(public_path('assets/profile-images/aaronparecki.com')); $fs->makeDirectory(public_path('assets/profile-images/aaronparecki.com'));
} }

View file

@ -20,7 +20,7 @@ class LikesTableSeeder extends Seeder
Like::factory(10)->create(); Like::factory(10)->create();
$now = Carbon::now()->subDays(rand(3, 6)); $now = Carbon::now()->subDays(rand(3, 6));
$faker = new Generator(); $faker = new Generator;
$faker->addProvider(new \Faker\Provider\en_US\Person($faker)); $faker->addProvider(new \Faker\Provider\en_US\Person($faker));
$faker->addProvider(new \Faker\Provider\Lorem($faker)); $faker->addProvider(new \Faker\Provider\Lorem($faker));
$faker->addProvider(new \Faker\Provider\Internet($faker)); $faker->addProvider(new \Faker\Provider\Internet($faker));

View file

@ -83,7 +83,7 @@ class NotesTableSeeder extends Seeder
->where('id', $noteWithoutContact->id) ->where('id', $noteWithoutContact->id)
->update(['updated_at' => $now->toDateTimeString()]); ->update(['updated_at' => $now->toDateTimeString()]);
//copy aarons profile pic in place // 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 ($spl->isDir() === false) {
mkdir(public_path() . '/assets/profile-images/aaronparecki.com', 0755); mkdir(public_path() . '/assets/profile-images/aaronparecki.com', 0755);
@ -154,7 +154,7 @@ class NotesTableSeeder extends Seeder
->update(['updated_at' => $now->toDateTimeString()]); ->update(['updated_at' => $now->toDateTimeString()]);
$now = Carbon::now()->subHours(5); $now = Carbon::now()->subHours(5);
$noteJustCheckin = new Note(); $noteJustCheckin = new Note;
$noteJustCheckin->setCreatedAt($now); $noteJustCheckin->setCreatedAt($now);
$place = Place::find(1); $place = Place::find(1);
$noteJustCheckin->place()->associate($place); $noteJustCheckin->place()->associate($place);
@ -164,12 +164,12 @@ class NotesTableSeeder extends Seeder
->update(['updated_at' => $now->toDateTimeString()]); ->update(['updated_at' => $now->toDateTimeString()]);
$now = Carbon::now()->subHours(4); $now = Carbon::now()->subHours(4);
$media = new Media(); $media = new Media;
$media->path = 'media/f1bc8faa-1a8f-45b8-a9b1-57282fa73f87.jpg'; $media->path = 'media/f1bc8faa-1a8f-45b8-a9b1-57282fa73f87.jpg';
$media->type = 'image'; $media->type = 'image';
$media->image_widths = '3648'; $media->image_widths = '3648';
$media->save(); $media->save();
$noteWithOnlyImage = new Note(); $noteWithOnlyImage = new Note;
$noteWithOnlyImage->setCreatedAt($now); $noteWithOnlyImage->setCreatedAt($now);
$noteWithOnlyImage->setUpdatedAt($now); $noteWithOnlyImage->setUpdatedAt($now);
$noteWithOnlyImage->save(); $noteWithOnlyImage->save();

View file

@ -14,7 +14,7 @@ class PlacesTableSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
$place = new Place(); $place = new Place;
$place->name = 'The Bridgewater Pub'; $place->name = 'The Bridgewater Pub';
$place->description = 'A lovely local pub with a decent selection of cask ales'; $place->description = 'A lovely local pub with a decent selection of cask ales';
$place->latitude = 53.4983; $place->latitude = 53.4983;

View file

@ -41,5 +41,14 @@ class WebMentionsTableSeeder extends Seeder
'type' => 'repost-of', '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',
'commentable_id' => '11',
'commentable_type' => 'App\Models\Note',
'type' => 'like-of',
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"uid": ["tag:bsky.app,2013:at://did:plc:n3jhgiq2ykctnpgzlm6p6b25/app.bsky.feed.post/3lalppbcyuc2w_liked_by_did:plc:ia23nh3t37r2lydmmqsixrps"], "url": ["https://bsky.app/profile/jonnybarnes.uk/post/3lalppbcyuc2w#liked_by_did:plc:ia23nh3t37r2lydmmqsixrps"], "name": [""], "author": [{"type": ["h-card"], "value": "bsky.app/profile/little... littledawg13.bsky.social", "properties": {"uid": ["tag:bsky.app,2013:did:plc:ia23nh3t37r2lydmmqsixrps"], "url": ["https://bsky.app/profile/littledawg13.bsky.social", "https://bsky.app/profile/did:plc:ia23nh3t37r2lydmmqsixrps"], "photo": [{"alt": "", "value": "https://cdn.bsky.app/img/avatar/plain/did:plc:ia23nh3t37r2lydmmqsixrps/bafkreifh7ydbyq7qe4maorornocksfnahijxqgx2i5zvvyq6y4i4mydfau@jpeg"}], "nickname": ["littledawg13.bsky.social"]}}], "like-of": ["https://bsky.app/profile/jonnybarnes.uk/post/3lalppbcyuc2w", "http://jonnybarnes.localhost/notes/B"]}}], "rel-urls": []}',
]);
} }
} }

View file

@ -138,7 +138,7 @@ if (! function_exists('normalize_url')) {
$url['query'] = ''; $url['query'] = '';
sort($queries); sort($queries);
foreach ($queries as $query) { foreach ($queries as $query) {
//lets drop query params we dont want // lets drop query params we dont want
$key = stristr($query, '=', true); $key = stristr($query, '=', true);
if (queryKeyIsBanned($key) === false) { if (queryKeyIsBanned($key) === false) {
$url['query'] .= "{$query}&"; $url['query'] .= "{$query}&";
@ -197,7 +197,7 @@ if (! function_exists('prettyPrintJson')) {
case '{': case '{':
case '[': case '[':
$level++; $level++;
//no break // no break
case ',': case ',':
$ends_line_level = $level; $ends_line_level = $level;
break; break;

1537
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,11 +7,11 @@
"license": "CC0-1.0", "license": "CC0-1.0",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.6.0", "@eslint/js": "^9.6.0",
"@stylistic/eslint-plugin": "^2.3.0", "@stylistic/eslint-plugin": "^3.0.0",
"eslint": "^9.7.0", "eslint": "^9.7.0",
"globals": "^15.8.0", "globals": "^15.8.0",
"stylelint": "^16.7.0", "stylelint": "^16.7.0",
"stylelint-config-standard": "^36.0.1" "stylelint-config-standard": "^37.0.0"
}, },
"scripts": { "scripts": {
"eslint": "eslint public/assets/js/*.js", "eslint": "eslint public/assets/js/*.js",

View file

@ -24,7 +24,7 @@
<env name="CACHE_STORE" value="array"/> <env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> --> <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> --> <!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="DB_DATABASE" value="jbukdev_testing"/> <env name="DB_DATABASE" value="jbuk_dev_testing"/>
<env name="MAIL_MAILER" value="array"/> <env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/> <env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/> <env name="QUEUE_CONNECTION" value="sync"/>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

@ -1,21 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mG8EV0W04xMFK4EEACIDAwSZOjA8NdI6UvbI/Sqw8LfpckfDXMuiowrVgcANjhDr
vQtvr0bYm7RnNlbiuwTQHQ064H3pwjJJYC12I5B6q1Is7h4PYzU4/ahtisb03U/Q
ThDDuWxDKQq2hcyfrNI02KO0I0pvbm55IEJhcm5lcyA8am9ubnlAam9ubnliYXJu
ZXMudWs+iLYEExMKAD4CGwMFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AWIQSEGbWh
2ITK9LCvj7MbLHavsWyFmwUCWSgPSgUJA8ON5wAKCRAbLHavsWyFm9hAAX9ymfnT
CUQDBqHmSR+YJ7RkNNFRdq4J1ABsvaRnpRynIE60dde1WqX62CvOkQDyY3sBgLJp
3KCNjB9VRoHHL3Gk1X78gxntU01wP+oYotA7tJescf34oM4CfzHoz4UdUTPK3Iif
BBMTCgAnBQJXRbTjAhsDBQkB4TOABQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJ
EBssdq+xbIWb0qwBfijJLkE/QUH7iAASLDtD3pUGs13TUynrzl2n8NdOnwmJTDdB
6eFT5+XxAgmuw+o28wF+MMEqzf+ELqjEyk/DZaZ3Kg9cJQAm3ybc0wFKy/kqQ2HY
TaFwEG3dAyfFDddkuaDEuHMEV0W04xIFK4EEACIDAwR0mRtrTq604CFiA8OdQR77
AVc8lRrNxFPwo7uJWsIEPBNVTHasC4OCXAvGgm9bPggQUNoOQ4fUCNmAgsVeGpYQ
/b67m0ydqjcrHpd0fIRbK5kyWYvYPRsZ6mTgiKosrZIDAQkJiJ4EGBMKACYCGwwW
IQSEGbWh2ITK9LCvj7MbLHavsWyFmwUCWSgPZQUJA8OOAgAKCRAbLHavsWyFm1fN
AYDMf1p4GegE1FHiUZo4m4Y5iQfbxT9Nmlgaopbmq+BxJRwPMxVzJOvKXo4DiUd0
nncBgOJUJ8esy6WGw+lUfkfvRNkhPw9CVt1GifjG4axGHGaDyDQdFdRcIeFyu0Fs
7HsLmg==
=sdL6
-----END PGP PUBLIC KEY BLOCK-----

BIN
public/assets/js/app.js.zst Normal file

Binary file not shown.

Binary file not shown.

View file

@ -1,55 +1,20 @@
<?php <?php
use Illuminate\Contracts\Http\Kernel; use Illuminate\Foundation\Application;
use Illuminate\Http\Request; use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
/* // Determine if the application is in maintenance mode...
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) { if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance; require $maintenance;
} }
/* // Register the Composer autoloader...
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/
require __DIR__.'/../vendor/autoload.php'; require __DIR__.'/../vendor/autoload.php';
/* // Bootstrap Laravel and handle the request...
|-------------------------------------------------------------------------- /** @var Application $app */
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php'; $app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class); $app->handleRequest(Request::capture());
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);

View file

@ -19,8 +19,12 @@
<link rel="token_endpoint" href="{{ route('indieauth.token') }}"> <link rel="token_endpoint" href="{{ route('indieauth.token') }}">
<link rel="micropub" href="{{ route('micropub-endpoint') }}"> <link rel="micropub" href="{{ route('micropub-endpoint') }}">
<link rel="webmention" href="{{ config('app.url') }}/webmention"> <link rel="webmention" href="{{ config('app.url') }}/webmention">
<link rel="shortcut icon" href="{{ config('app.url') }}/assets/img/memoji-orange-bg-small-fs8.png"> @if (File::exists(public_path('assets/img/favicon.png')))
<link rel="pgpkey" href="/assets/jonnybarnes-public-key-ecc.asc"> <link rel="icon" href="{{ config('app.url') }}/assets/img/favicon.png">
@endif
@if (File::exists(public_path('gpg.key')))
<link rel="pgpkey" href="{{ config('app.url')}}/gpg.key">
@endif
</head> </head>
<body class="grid"> <body class="grid">
<header id="site-header"> <header id="site-header">
@ -68,6 +72,16 @@
<!--scripts go here when needed--> <!--scripts go here when needed-->
@section('scripts') @section('scripts')
<script type="module" src="/assets/js/app.js"></script> <script type="module" src="/assets/js/app.js"></script>
<!-- Snow fall -->
<script type="module" src="/assets/frontend/is-land.js"></script>
<script type="module" src="/assets/frontend/snow-fall.js"></script>
<is-land on:media="(prefers-reduced-motion: no-preference)">
<snow-fall
count="240"
style="--snow-fall-color: rebeccapurple; --snow-fall-size: 8px"
></snow-fall>
</is-land>
@show @show
</body> </body>
</html> </html>

View file

@ -13,7 +13,7 @@
@if (array_key_exists('photo', $reply['author']['properties'])) @if (array_key_exists('photo', $reply['author']['properties']))
<img src="{{ $reply['author']['properties']['photo'][0] }}" alt="" class="photo u-photo logo"> <img src="{{ $reply['author']['properties']['photo'][0] }}" alt="" class="photo u-photo logo">
@endif @endif
<span class="fn">{{ $reply['author']['properties']['name'][0] }}</span> <span class="fn">{{ $reply['author']['properties']['name'][0] ?? $reply['author']['properties']['nickname'][0] ?? 'unknown' }}</span>
</a> </a>
@else @else
Unknown author Unknown author
@ -33,7 +33,7 @@
return ($webmention->type === 'like-of'); return ($webmention->type === 'like-of');
}) as $like) }) as $like)
<a href="{{ $like['author']['properties']['url'][0] }}"> <a href="{{ $like['author']['properties']['url'][0] }}">
<img src="{{ $like['author']['properties']['photo'][0] }}" alt="profile picture of {{ $like['author']['properties']['name'][0] }}" class="like-photo"> <img src="{{ $like['author']['properties']['photo'][0] }}" alt="profile picture of {{ $like['author']['properties']['name'][0] ?? $like['author']['properties']['nickname'][0] ?? 'unknown' }}" class="like-photo">
</a> </a>
@endforeach @endforeach
</div> </div>
@ -47,7 +47,7 @@
return ($webmention->type == 'repost-of'); return ($webmention->type == 'repost-of');
}) as $repost) }) as $repost)
<a href="{{ $repost['source'] }}"> <a href="{{ $repost['source'] }}">
<img src="{{ $repost['author']['properties']['photo'][0] }}" alt="{{ $repost['author']['properties']['name'][0] }} reposted this at {{ $repost['published'] }}"> <img src="{{ $repost['author']['properties']['photo'][0] }}" alt="{{ $repost['author']['properties']['name'][0] ?? $repost['author']['properties']['nickname'][0] ?? 'unknown' }} reposted this at {{ $repost['published'] }}">
</a> </a>
@endforeach @endforeach
</div> </div>

View file

@ -64,7 +64,7 @@ Route::domain(config('url.longurl'))->group(function () {
Route::middleware(MyAuthMiddleware::class)->prefix('admin')->group(function () { Route::middleware(MyAuthMiddleware::class)->prefix('admin')->group(function () {
Route::get('/', [HomeController::class, 'welcome']); Route::get('/', [HomeController::class, 'welcome']);
//Articles // Articles
Route::prefix('blog')->group(function () { Route::prefix('blog')->group(function () {
Route::get('/', [AdminArticlesController::class, 'index']); Route::get('/', [AdminArticlesController::class, 'index']);
Route::get('/create', [AdminArticlesController::class, 'create']); Route::get('/create', [AdminArticlesController::class, 'create']);

View file

@ -1,16 +1,41 @@
#!/usr/bin/env zsh #!/usr/bin/env zsh
if ! type brotli &> /dev/null; then if ! (( $+commands[fd] )) &> /dev/null; then
echo "fd not installed"
exit 1
fi
if ! (( $+commands[brotli] )) &> /dev/null; then
echo "brotli not installed" echo "brotli not installed"
exit 1 exit 1
fi fi
for file in ./public/assets/css/*.css if ! (( $+commands[zstd] )) &> /dev/null; then
do echo "zstd not installed"
brotli --force --quality=11 --output=$file.br -- $file exit 1
done fi
for file in ./public/assets/js/*.js # Make .br files
do fd --extension css --extension js --search-path ./public/assets --type f --exec brotli --force --best --output={}.br {}
brotli --force --quality=11 --output=$file.br -- $file # Make .zst files
fd --extension css --extension js --search-path ./public/assets --type f --exec zstd --quiet --force --ultra -22 --exclude-compressed {} -o {}.zst
# Remove files that actually got bigger!
fd --extension br --extension zst --search-path ./public/assets --type f --exec sh -c '
for file; do
src="${file%.br}"
src="${src%.zst}"
if [ -f "$src" ]; then
# Get file sizes using stat with cross-platform compatibility
if [ "$(uname)" = "Darwin" ]; then
file_size=$(stat -f%z "$file")
src_size=$(stat -f%z "$src")
else
file_size=$(stat -c%s "$file")
src_size=$(stat -c%s "$src")
fi
# Compare sizes and rm compressed file if larger than the source
[ "$file_size" -ge "$src_size" ] && rm "$file"
fi
done done
' _ {}

View file

@ -1,3 +1,4 @@
* *
!private/
!public/ !public/
!.gitignore !.gitignore

2
storage/app/private/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -12,7 +12,7 @@ class ExampleTest extends DuskTestCase
* *
* @return void * @return void
*/ */
public function testBasicExample() public function test_basic_example()
{ {
$this->browse(function (Browser $browser) { $this->browse(function (Browser $browser) {
$browser->visit('/') $browser->visit('/')

View file

@ -32,7 +32,7 @@ abstract class DuskTestCase extends BaseTestCase
{ {
$desiredCapabilities = DesiredCapabilities::chrome(); $desiredCapabilities = DesiredCapabilities::chrome();
$options = new ChromeOptions(); $options = new ChromeOptions;
$options->addArguments([ $options->addArguments([
'headless', 'headless',
'disable-gpu', 'disable-gpu',

View file

@ -6,13 +6,14 @@ namespace Tests\Feature\Admin;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class AdminHomeControllerTest extends TestCase class AdminHomeControllerTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function adminHomepageLoads(): void public function adminHomepageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();

View file

@ -5,25 +5,26 @@ declare(strict_types=1);
namespace Tests\Feature\Admin; namespace Tests\Feature\Admin;
use App\Models\User; use App\Models\User;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class AdminTest extends TestCase class AdminTest extends TestCase
{ {
/** @test */ #[Test]
public function adminPageRedirectsUnauthorisedUsersToLoginPage(): void public function adminPageRedirectsUnauthorisedUsersToLoginPage(): void
{ {
$response = $this->get('/admin'); $response = $this->get('/admin');
$response->assertRedirect('/login'); $response->assertRedirect('/login');
} }
/** @test */ #[Test]
public function loginPageLoads(): void public function loginPageLoads(): void
{ {
$response = $this->get('/login'); $response = $this->get('/login');
$response->assertViewIs('login'); $response->assertViewIs('login');
} }
/** @test */ #[Test]
public function loginAttemptWithBadCredentialsFails(): void public function loginAttemptWithBadCredentialsFails(): void
{ {
$response = $this->post('/login', [ $response = $this->post('/login', [
@ -33,7 +34,7 @@ class AdminTest extends TestCase
$response->assertRedirect('/login'); $response->assertRedirect('/login');
} }
/** @test */ #[Test]
public function loginSucceeds(): void public function loginSucceeds(): void
{ {
User::factory([ User::factory([
@ -49,7 +50,7 @@ class AdminTest extends TestCase
$response->assertRedirect('/admin'); $response->assertRedirect('/admin');
} }
/** @test */ #[Test]
public function whenLoggedInRedirectsToAdminPage(): void public function whenLoggedInRedirectsToAdminPage(): void
{ {
$user = User::factory()->create(); $user = User::factory()->create();
@ -57,14 +58,14 @@ class AdminTest extends TestCase
$response->assertRedirect('/'); $response->assertRedirect('/');
} }
/** @test */ #[Test]
public function loggedOutUsersSimplyRedirected(): void public function loggedOutUsersSimplyRedirected(): void
{ {
$response = $this->get('/logout'); $response = $this->get('/logout');
$response->assertRedirect('/'); $response->assertRedirect('/');
} }
/** @test */ #[Test]
public function loggedInUsersShownLogoutForm(): void public function loggedInUsersShownLogoutForm(): void
{ {
$user = User::factory()->create(); $user = User::factory()->create();
@ -72,7 +73,7 @@ class AdminTest extends TestCase
$response->assertViewIs('logout'); $response->assertViewIs('logout');
} }
/** @test */ #[Test]
public function loggedInUsersCanLogout(): void public function loggedInUsersCanLogout(): void
{ {
$user = User::factory()->create(); $user = User::factory()->create();

View file

@ -9,13 +9,14 @@ use App\Models\User;
use Faker\Factory; use Faker\Factory;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class ArticlesTest extends TestCase class ArticlesTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function adminArticlesPageLoads(): void public function adminArticlesPageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -25,7 +26,7 @@ class ArticlesTest extends TestCase
$response->assertSeeText('Select article to edit:'); $response->assertSeeText('Select article to edit:');
} }
/** @test */ #[Test]
public function adminCanLoadFormToCreateArticle(): void public function adminCanLoadFormToCreateArticle(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -35,7 +36,7 @@ class ArticlesTest extends TestCase
$response->assertSeeText('Title (URL)'); $response->assertSeeText('Title (URL)');
} }
/** @test */ #[Test]
public function admiNCanCreateNewArticle(): void public function admiNCanCreateNewArticle(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -48,7 +49,7 @@ class ArticlesTest extends TestCase
$this->assertDatabaseHas('articles', ['title' => 'Test Title']); $this->assertDatabaseHas('articles', ['title' => 'Test Title']);
} }
/** @test */ #[Test]
public function adminCanCreateNewArticleWithFile(): void public function adminCanCreateNewArticleWithFile(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -73,7 +74,7 @@ class ArticlesTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function articleCanLoadFormToEditArticle(): void public function articleCanLoadFormToEditArticle(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -86,7 +87,7 @@ class ArticlesTest extends TestCase
$response->assertSeeText('This is *my* new blog. It uses `Markdown`.'); $response->assertSeeText('This is *my* new blog. It uses `Markdown`.');
} }
/** @test */ #[Test]
public function adminCanEditArticle(): void public function adminCanEditArticle(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -104,7 +105,7 @@ class ArticlesTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function adminCanDeleteArticle(): void public function adminCanDeleteArticle(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();

View file

@ -7,13 +7,14 @@ namespace Tests\Feature\Admin;
use App\Models\Bio; use App\Models\Bio;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class BioTest extends TestCase class BioTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function adminBiosPageLoads(): void public function adminBiosPageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -23,7 +24,7 @@ class BioTest extends TestCase
$response->assertSeeText('Edit bio'); $response->assertSeeText('Edit bio');
} }
/** @test */ #[Test]
public function adminCanCreateBio(): void public function adminCanCreateBio(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -36,7 +37,7 @@ class BioTest extends TestCase
$this->assertDatabaseHas('bios', ['content' => 'Bio content']); $this->assertDatabaseHas('bios', ['content' => 'Bio content']);
} }
/** @test */ #[Test]
public function adminCanLoadExistingBio(): void public function adminCanLoadExistingBio(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -49,7 +50,7 @@ class BioTest extends TestCase
$response->assertSeeText('This is <em>my</em> bio. It uses <strong>HTML</strong>.'); $response->assertSeeText('This is <em>my</em> bio. It uses <strong>HTML</strong>.');
} }
/** @test */ #[Test]
public function adminCanEditBio(): void public function adminCanEditBio(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();

View file

@ -7,13 +7,14 @@ namespace Tests\Feature\Admin;
use App\Models\MicropubClient; use App\Models\MicropubClient;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class ClientsTest extends TestCase class ClientsTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function clientsPageLoads(): void public function clientsPageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -23,7 +24,7 @@ class ClientsTest extends TestCase
$response->assertSeeText('Clients'); $response->assertSeeText('Clients');
} }
/** @test */ #[Test]
public function adminCanLoadFormToCreateClient(): void public function adminCanLoadFormToCreateClient(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -33,7 +34,7 @@ class ClientsTest extends TestCase
$response->assertSeeText('New Client'); $response->assertSeeText('New Client');
} }
/** @test */ #[Test]
public function adminCanCreateNewClient(): void public function adminCanCreateNewClient(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -49,7 +50,7 @@ class ClientsTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function adminCanLoadEditFormForClient(): void public function adminCanLoadEditFormForClient(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -62,7 +63,7 @@ class ClientsTest extends TestCase
$response->assertSee('https://jbl5.dev/notes/new'); $response->assertSee('https://jbl5.dev/notes/new');
} }
/** @test */ #[Test]
public function adminCanEditClient(): void public function adminCanEditClient(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -80,7 +81,7 @@ class ClientsTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function adminCanDeleteClient(): void public function adminCanDeleteClient(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();

View file

@ -12,6 +12,7 @@ use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Response;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class ContactsTest extends TestCase class ContactsTest extends TestCase
@ -27,7 +28,7 @@ class ContactsTest extends TestCase
parent::tearDown(); parent::tearDown();
} }
/** @test */ #[Test]
public function contactIndexPageLoads(): void public function contactIndexPageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -36,7 +37,7 @@ class ContactsTest extends TestCase
$response->assertViewIs('admin.contacts.index'); $response->assertViewIs('admin.contacts.index');
} }
/** @test */ #[Test]
public function contactCreatePageLoads(): void public function contactCreatePageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -45,7 +46,7 @@ class ContactsTest extends TestCase
$response->assertViewIs('admin.contacts.create'); $response->assertViewIs('admin.contacts.create');
} }
/** @test */ #[Test]
public function adminCanCreateNewContact(): void public function adminCanCreateNewContact(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -62,7 +63,7 @@ class ContactsTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function adminCanSeeFormToEditContact(): void public function adminCanSeeFormToEditContact(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -72,7 +73,7 @@ class ContactsTest extends TestCase
$response->assertViewIs('admin.contacts.edit'); $response->assertViewIs('admin.contacts.edit');
} }
/** @test */ #[Test]
public function adminCanUpdateContact(): void public function adminCanUpdateContact(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -91,7 +92,7 @@ class ContactsTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function adminCanEditContactAndUploadAvatar(): void public function adminCanEditContactAndUploadAvatar(): void
{ {
copy(__DIR__ . '/../../aaron.png', sys_get_temp_dir() . '/tantek.png'); copy(__DIR__ . '/../../aaron.png', sys_get_temp_dir() . '/tantek.png');
@ -114,7 +115,7 @@ class ContactsTest extends TestCase
); );
} }
/** @test */ #[Test]
public function adminCanDeleteContact(): void public function adminCanDeleteContact(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -132,7 +133,7 @@ class ContactsTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function adminCanTriggerRetrievalOfRemoteAvatar(): void public function adminCanTriggerRetrievalOfRemoteAvatar(): void
{ {
$html = <<<'HTML' $html = <<<'HTML'
@ -161,7 +162,7 @@ class ContactsTest extends TestCase
); );
} }
/** @test */ #[Test]
public function gettingRemoteAvatarFailsGracefullyWithRemoteNotFound(): void public function gettingRemoteAvatarFailsGracefullyWithRemoteNotFound(): void
{ {
$mock = new MockHandler([ $mock = new MockHandler([
@ -178,7 +179,7 @@ class ContactsTest extends TestCase
$response->assertRedirect('/admin/contacts/' . $contact->id . '/edit'); $response->assertRedirect('/admin/contacts/' . $contact->id . '/edit');
} }
/** @test */ #[Test]
public function gettingRemoteAvatarFailsGracefullyWithRemoteError(): void public function gettingRemoteAvatarFailsGracefullyWithRemoteError(): void
{ {
$html = <<<'HTML' $html = <<<'HTML'
@ -201,7 +202,7 @@ class ContactsTest extends TestCase
$response->assertRedirect('/admin/contacts/' . $contact->id . '/edit'); $response->assertRedirect('/admin/contacts/' . $contact->id . '/edit');
} }
/** @test */ #[Test]
public function gettingRemoteAvatarFailsGracefullyForContactWithNoHompage(): void public function gettingRemoteAvatarFailsGracefullyForContactWithNoHompage(): void
{ {
$contact = Contact::create([ $contact = Contact::create([

View file

@ -9,13 +9,14 @@ use App\Models\Like;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class LikesTest extends TestCase class LikesTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function likesPageLoads(): void public function likesPageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -25,7 +26,7 @@ class LikesTest extends TestCase
$response->assertSeeText('Likes'); $response->assertSeeText('Likes');
} }
/** @test */ #[Test]
public function likeCreateFormLoads(): void public function likeCreateFormLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -35,7 +36,7 @@ class LikesTest extends TestCase
$response->assertSeeText('New Like'); $response->assertSeeText('New Like');
} }
/** @test */ #[Test]
public function adminCanCreateLike(): void public function adminCanCreateLike(): void
{ {
Queue::fake(); Queue::fake();
@ -51,7 +52,7 @@ class LikesTest extends TestCase
Queue::assertPushed(ProcessLike::class); Queue::assertPushed(ProcessLike::class);
} }
/** @test */ #[Test]
public function likeEditFormLoads(): void public function likeEditFormLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -62,7 +63,7 @@ class LikesTest extends TestCase
$response->assertSee('Edit Like'); $response->assertSee('Edit Like');
} }
/** @test */ #[Test]
public function adminCanEditLike(): void public function adminCanEditLike(): void
{ {
Queue::fake(); Queue::fake();
@ -80,7 +81,7 @@ class LikesTest extends TestCase
Queue::assertPushed(ProcessLike::class); Queue::assertPushed(ProcessLike::class);
} }
/** @test */ #[Test]
public function adminCanDeleteLike(): void public function adminCanDeleteLike(): void
{ {
$like = Like::factory()->create(); $like = Like::factory()->create();

View file

@ -9,13 +9,14 @@ use App\Models\Note;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class NotesTest extends TestCase class NotesTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function notesPageLoads(): void public function notesPageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -24,7 +25,7 @@ class NotesTest extends TestCase
$response->assertViewIs('admin.notes.index'); $response->assertViewIs('admin.notes.index');
} }
/** @test */ #[Test]
public function noteCreatePageLoads(): void public function noteCreatePageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -33,7 +34,7 @@ class NotesTest extends TestCase
$response->assertViewIs('admin.notes.create'); $response->assertViewIs('admin.notes.create');
} }
/** @test */ #[Test]
public function adminCanCreateNewNote(): void public function adminCanCreateNewNote(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -47,7 +48,7 @@ class NotesTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function noteEditFormLoads(): void public function noteEditFormLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -57,7 +58,7 @@ class NotesTest extends TestCase
$response->assertViewIs('admin.notes.edit'); $response->assertViewIs('admin.notes.edit');
} }
/** @test */ #[Test]
public function adminCanEditNote(): void public function adminCanEditNote(): void
{ {
Queue::fake(); Queue::fake();
@ -76,7 +77,7 @@ class NotesTest extends TestCase
Queue::assertPushed(SendWebMentions::class); Queue::assertPushed(SendWebMentions::class);
} }
/** @test */ #[Test]
public function adminCanDeleteNote(): void public function adminCanDeleteNote(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();

View file

@ -7,13 +7,14 @@ namespace Tests\Feature\Admin;
use App\Models\Place; use App\Models\Place;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class PlacesTest extends TestCase class PlacesTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function placesPageLoads(): void public function placesPageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -22,7 +23,7 @@ class PlacesTest extends TestCase
$response->assertViewIs('admin.places.index'); $response->assertViewIs('admin.places.index');
} }
/** @test */ #[Test]
public function createPlacePageLoads(): void public function createPlacePageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -31,7 +32,7 @@ class PlacesTest extends TestCase
$response->assertViewIs('admin.places.create'); $response->assertViewIs('admin.places.create');
} }
/** @test */ #[Test]
public function adminCanCreateNewPlace(): void public function adminCanCreateNewPlace(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -48,7 +49,7 @@ class PlacesTest extends TestCase
]); ]);
} }
/** @test */ #[Test]
public function editPlacePageLoads(): void public function editPlacePageLoads(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();
@ -58,7 +59,7 @@ class PlacesTest extends TestCase
$response->assertViewIs('admin.places.edit'); $response->assertViewIs('admin.places.edit');
} }
/** @test */ #[Test]
public function adminCanUpdatePlace(): void public function adminCanUpdatePlace(): void
{ {
$user = User::factory()->make(); $user = User::factory()->make();

View file

@ -7,20 +7,21 @@ namespace Tests\Feature;
use App\Models\Article; use App\Models\Article;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class ArticlesTest extends TestCase class ArticlesTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function articlesPageLoads(): void public function articlesPageLoads(): void
{ {
$response = $this->get('/blog'); $response = $this->get('/blog');
$response->assertViewIs('articles.index'); $response->assertViewIs('articles.index');
} }
/** @test */ #[Test]
public function singleArticlePageLoads() public function singleArticlePageLoads()
{ {
$article = Article::factory()->create(); $article = Article::factory()->create();
@ -28,7 +29,7 @@ class ArticlesTest extends TestCase
$response->assertViewIs('articles.show'); $response->assertViewIs('articles.show');
} }
/** @test */ #[Test]
public function wrongDateInUrlRedirectsToCorrectDate() public function wrongDateInUrlRedirectsToCorrectDate()
{ {
$article = Article::factory()->create(); $article = Article::factory()->create();
@ -36,7 +37,7 @@ class ArticlesTest extends TestCase
$response->assertRedirect('/blog/' . date('Y') . '/' . date('m') . '/' . $article->titleurl); $response->assertRedirect('/blog/' . date('Y') . '/' . date('m') . '/' . $article->titleurl);
} }
/** @test */ #[Test]
public function oldUrlsWithIdAreRedirected() public function oldUrlsWithIdAreRedirected()
{ {
$article = Article::factory()->create(); $article = Article::factory()->create();
@ -45,21 +46,21 @@ class ArticlesTest extends TestCase
$response->assertRedirect($article->link); $response->assertRedirect($article->link);
} }
/** @test */ #[Test]
public function unknownSlugGetsNotFoundResponse() public function unknownSlugGetsNotFoundResponse()
{ {
$response = $this->get('/blog/' . date('Y') . '/' . date('m') . '/unknown-slug'); $response = $this->get('/blog/' . date('Y') . '/' . date('m') . '/unknown-slug');
$response->assertNotFound(); $response->assertNotFound();
} }
/** @test */ #[Test]
public function unknownArticleIdGetsNotFoundResponse() public function unknownArticleIdGetsNotFoundResponse()
{ {
$response = $this->get('/blog/s/22'); $response = $this->get('/blog/s/22');
$response->assertNotFound(); $response->assertNotFound();
} }
/** @test */ #[Test]
public function someUrlsDoNotParseCorrectly(): void public function someUrlsDoNotParseCorrectly(): void
{ {
$response = $this->get('/blog/feed.js'); $response = $this->get('/blog/feed.js');

View file

@ -8,6 +8,7 @@ use App\Jobs\ProcessBookmark;
use App\Models\Bookmark; use App\Models\Bookmark;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
use Tests\TestToken; use Tests\TestToken;
@ -15,14 +16,14 @@ class BookmarksTest extends TestCase
{ {
use RefreshDatabase, TestToken; use RefreshDatabase, TestToken;
/** @test */ #[Test]
public function bookmarksPageLoadsWithoutError(): void public function bookmarksPageLoadsWithoutError(): void
{ {
$response = $this->get('/bookmarks'); $response = $this->get('/bookmarks');
$response->assertViewIs('bookmarks.index'); $response->assertViewIs('bookmarks.index');
} }
/** @test */ #[Test]
public function singleBookmarkPageLoadsWithoutError(): void public function singleBookmarkPageLoadsWithoutError(): void
{ {
$bookmark = Bookmark::factory()->create(); $bookmark = Bookmark::factory()->create();
@ -30,7 +31,7 @@ class BookmarksTest extends TestCase
$response->assertViewIs('bookmarks.show'); $response->assertViewIs('bookmarks.show');
} }
/** @test */ #[Test]
public function whenBookmarkIsAddedUsingHttpSyntaxCheckJobToTakeScreenshotIsInvoked(): void public function whenBookmarkIsAddedUsingHttpSyntaxCheckJobToTakeScreenshotIsInvoked(): void
{ {
Queue::fake(); Queue::fake();
@ -48,7 +49,7 @@ class BookmarksTest extends TestCase
$this->assertDatabaseHas('bookmarks', ['url' => 'https://example.org/blog-post']); $this->assertDatabaseHas('bookmarks', ['url' => 'https://example.org/blog-post']);
} }
/** @test */ #[Test]
public function whenBookmarkIsAddedUsingJsonSyntaxCheckJobToTakeScreenshotIsInvoked(): void public function whenBookmarkIsAddedUsingJsonSyntaxCheckJobToTakeScreenshotIsInvoked(): void
{ {
Queue::fake(); Queue::fake();
@ -68,7 +69,7 @@ class BookmarksTest extends TestCase
$this->assertDatabaseHas('bookmarks', ['url' => 'https://example.org/blog-post']); $this->assertDatabaseHas('bookmarks', ['url' => 'https://example.org/blog-post']);
} }
/** @test */ #[Test]
public function whenTheBookmarkIsCreatedCheckNecessaryTagsAreAlsoCreated(): void public function whenTheBookmarkIsCreatedCheckNecessaryTagsAreAlsoCreated(): void
{ {
Queue::fake(); Queue::fake();

View file

@ -6,6 +6,7 @@ namespace Tests\Feature;
use App\Models\Contact; use App\Models\Contact;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class ContactsTest extends TestCase class ContactsTest extends TestCase
@ -14,9 +15,8 @@ class ContactsTest extends TestCase
/** /**
* Check the `/contacts` page gives a good response. * Check the `/contacts` page gives a good response.
*
* @test
*/ */
#[Test]
public function contactsPageLoadsWithoutError(): void public function contactsPageLoadsWithoutError(): void
{ {
$response = $this->get('/contacts'); $response = $this->get('/contacts');
@ -25,9 +25,8 @@ class ContactsTest extends TestCase
/** /**
* Test an individual contact page with default profile image. * Test an individual contact page with default profile image.
*
* @test
*/ */
#[Test]
public function contactPageShouldFallbackToDefaultProfilePic(): void public function contactPageShouldFallbackToDefaultProfilePic(): void
{ {
Contact::factory()->create([ Contact::factory()->create([
@ -39,9 +38,8 @@ class ContactsTest extends TestCase
/** /**
* Test an individual contact page with a specific profile image. * Test an individual contact page with a specific profile image.
*
* @test
*/ */
#[Test]
public function contactPageShouldUseSpecificProfilePicIfPresent(): void public function contactPageShouldUseSpecificProfilePicIfPresent(): void
{ {
Contact::factory()->create([ Contact::factory()->create([
@ -52,7 +50,7 @@ class ContactsTest extends TestCase
$response->assertViewHas('image', '/assets/profile-images/aaronparecki.com/image'); $response->assertViewHas('image', '/assets/profile-images/aaronparecki.com/image');
} }
/** @test */ #[Test]
public function unknownContactReturnsNotFoundResponse(): void public function unknownContactReturnsNotFoundResponse(): void
{ {
$response = $this->get('/contacts/unknown'); $response = $this->get('/contacts/unknown');

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests\Feature; namespace Tests\Feature;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
use Tests\TestToken; use Tests\TestToken;
@ -11,7 +12,7 @@ class CorsHeadersTest extends TestCase
{ {
use TestToken; use TestToken;
/** @test */ #[Test]
public function checkCorsHeadersOnMediaEndpoint(): void public function checkCorsHeadersOnMediaEndpoint(): void
{ {
$response = $this->call( $response = $this->call(
@ -25,7 +26,7 @@ class CorsHeadersTest extends TestCase
$response->assertHeader('Access-Control-Allow-Origin', '*'); $response->assertHeader('Access-Control-Allow-Origin', '*');
} }
/** @test */ #[Test]
public function checkForNoCorsHeaderOnNonMediaEndpointLinks(): void public function checkForNoCorsHeaderOnNonMediaEndpointLinks(): void
{ {
$response = $this->get('/blog'); $response = $this->get('/blog');

View file

@ -8,6 +8,7 @@ use App\Models\Article;
use App\Models\Note; use App\Models\Note;
use App\Models\Place; use App\Models\Place;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class FeedsTest extends TestCase class FeedsTest extends TestCase
@ -16,9 +17,8 @@ class FeedsTest extends TestCase
/** /**
* Test the blog RSS feed. * Test the blog RSS feed.
*
* @test
*/ */
#[Test]
public function blogRssFeedIsPresent(): void public function blogRssFeedIsPresent(): void
{ {
Article::factory()->count(3)->create(); Article::factory()->count(3)->create();
@ -29,9 +29,8 @@ class FeedsTest extends TestCase
/** /**
* Test the notes RSS feed. * Test the notes RSS feed.
*
* @test
*/ */
#[Test]
public function notesRssFeedIsPresent(): void public function notesRssFeedIsPresent(): void
{ {
Note::factory()->count(3)->create(); Note::factory()->count(3)->create();
@ -42,9 +41,8 @@ class FeedsTest extends TestCase
/** /**
* Test the blog RSS feed. * Test the blog RSS feed.
*
* @test
*/ */
#[Test]
public function blogAtomFeedIsPresent(): void public function blogAtomFeedIsPresent(): void
{ {
Article::factory()->count(3)->create(); Article::factory()->count(3)->create();
@ -53,7 +51,7 @@ class FeedsTest extends TestCase
$response->assertOk(); $response->assertOk();
} }
/** @test */ #[Test]
public function blogJf2FeedIsPresent(): void public function blogJf2FeedIsPresent(): void
{ {
Article::factory()->count(3)->create(); Article::factory()->count(3)->create();
@ -77,9 +75,8 @@ class FeedsTest extends TestCase
/** /**
* Test the notes RSS feed. * Test the notes RSS feed.
*
* @test
*/ */
#[Test]
public function notesAtomFeedIsPresent(): void public function notesAtomFeedIsPresent(): void
{ {
Note::factory()->count(3)->create(); Note::factory()->count(3)->create();
@ -90,9 +87,8 @@ class FeedsTest extends TestCase
/** /**
* Test the blog JSON feed. * Test the blog JSON feed.
*
* @test
*/ */
#[Test]
public function blogJsonFeedIsPresent(): void public function blogJsonFeedIsPresent(): void
{ {
Article::factory()->count(3)->create(); Article::factory()->count(3)->create();
@ -103,9 +99,8 @@ class FeedsTest extends TestCase
/** /**
* Test the notes JSON feed. * Test the notes JSON feed.
*
* @test
*/ */
#[Test]
public function notesJsonFeedIsPresent(): void public function notesJsonFeedIsPresent(): void
{ {
Note::factory()->count(3)->create(); Note::factory()->count(3)->create();
@ -114,7 +109,7 @@ class FeedsTest extends TestCase
$response->assertOk(); $response->assertOk();
} }
/** @test */ #[Test]
public function notesJf2FeedIsPresent(): void public function notesJf2FeedIsPresent(): void
{ {
Note::factory()->count(3)->create(); Note::factory()->count(3)->create();
@ -139,9 +134,8 @@ class FeedsTest extends TestCase
/** /**
* Each JSON feed item must have one of `content_text` or `content_html`, * Each JSON feed item must have one of `content_text` or `content_html`,
* and whichever one they have cant be `null`. * and whichever one they have cant be `null`.
*
* @test
*/ */
#[Test]
public function jsonFeedsHaveRequiredAttributes(): void public function jsonFeedsHaveRequiredAttributes(): void
{ {
Note::factory()->count(3)->create(); Note::factory()->count(3)->create();
@ -161,7 +155,7 @@ class FeedsTest extends TestCase
} }
} }
/** @test */ #[Test]
public function jsonNoteFeedLoadsPlaceDataWithoutLazyLoading(): void public function jsonNoteFeedLoadsPlaceDataWithoutLazyLoading(): void
{ {
$place = Place::factory()->create(); $place = Place::factory()->create();

View file

@ -7,13 +7,14 @@ use App\Models\Bookmark;
use App\Models\Like; use App\Models\Like;
use App\Models\Note; use App\Models\Note;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class FrontPageTest extends TestCase class FrontPageTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
/** @test */ #[Test]
public function frontPageLoadsAllContent(): void public function frontPageLoadsAllContent(): void
{ {
Note::factory()->create(['note' => 'Note 1']); Note::factory()->create(['note' => 'Note 1']);

View file

@ -3,15 +3,15 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Models\User; use App\Models\User;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
class HorizonTest extends TestCase class HorizonTest extends TestCase
{ {
/** /**
* Horizon has its own test suite, here we just test it has been installed successfully. * Horizon has its own test suite, here we just test it has been installed successfully.
*
* @test
*/ */
#[Test]
public function horizonIsInstalled(): void public function horizonIsInstalled(): void
{ {
$user = User::factory()->create([ $user = User::factory()->create([

View file

@ -31,8 +31,8 @@ class IndieAuthTest extends TestCase
'authorization_endpoint' => route('indieauth.start'), 'authorization_endpoint' => route('indieauth.start'),
'token_endpoint' => route('indieauth.token'), 'token_endpoint' => route('indieauth.token'),
'code_challenge_methods_supported' => ['S256'], 'code_challenge_methods_supported' => ['S256'],
//'introspection_endpoint' => 'introspection_endpoint', // 'introspection_endpoint' => 'introspection_endpoint',
//'introspection_endpoint_auth_methods_supported' => ['none'], // 'introspection_endpoint_auth_methods_supported' => ['none'],
]); ]);
} }

View file

@ -14,6 +14,7 @@ use GuzzleHttp\Psr7\Response;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use Jonnybarnes\WebmentionsParser\Authorship; use Jonnybarnes\WebmentionsParser\Authorship;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase; use Tests\TestCase;
use Tests\TestToken; use Tests\TestToken;
@ -22,14 +23,14 @@ class LikesTest extends TestCase
use RefreshDatabase; use RefreshDatabase;
use TestToken; use TestToken;
/** @test */ #[Test]
public function likesPageHasCorrectView(): void public function likesPageHasCorrectView(): void
{ {
$response = $this->get('/likes'); $response = $this->get('/likes');
$response->assertViewIs('likes.index'); $response->assertViewIs('likes.index');
} }
/** @test */ #[Test]
public function singleLikePageHasCorrectView(): void public function singleLikePageHasCorrectView(): void
{ {
$like = Like::factory()->create(); $like = Like::factory()->create();
@ -37,7 +38,7 @@ class LikesTest extends TestCase
$response->assertViewIs('likes.show'); $response->assertViewIs('likes.show');
} }
/** @test */ #[Test]
public function checkLikeCreatedFromMicropubApiRequests(): void public function checkLikeCreatedFromMicropubApiRequests(): void
{ {
Queue::fake(); Queue::fake();
@ -57,7 +58,7 @@ class LikesTest extends TestCase
$this->assertDatabaseHas('likes', ['url' => 'https://example.org/blog-post']); $this->assertDatabaseHas('likes', ['url' => 'https://example.org/blog-post']);
} }
/** @test */ #[Test]
public function checkLikeCreatedFromMicropubWebRequests(): void public function checkLikeCreatedFromMicropubWebRequests(): void
{ {
Queue::fake(); Queue::fake();
@ -75,10 +76,10 @@ class LikesTest extends TestCase
$this->assertDatabaseHas('likes', ['url' => 'https://example.org/blog-post']); $this->assertDatabaseHas('likes', ['url' => 'https://example.org/blog-post']);
} }
/** @test */ #[Test]
public function likeWithSimpleAuthor(): void public function likeWithSimpleAuthor(): void
{ {
$like = new Like(); $like = new Like;
$like->url = 'http://example.org/note/id'; $like->url = 'http://example.org/note/id';
$like->save(); $like->save();
$id = $like->id; $id = $like->id;
@ -107,17 +108,17 @@ class LikesTest extends TestCase
$this->app->bind(Client::class, function () use ($client) { $this->app->bind(Client::class, function () use ($client) {
return $client; return $client;
}); });
$authorship = new Authorship(); $authorship = new Authorship;
$job->handle($client, $authorship); $job->handle($client, $authorship);
$this->assertEquals('Fred Bloggs', Like::find($id)->author_name); $this->assertEquals('Fred Bloggs', Like::find($id)->author_name);
} }
/** @test */ #[Test]
public function likeWithHCard(): void public function likeWithHCard(): void
{ {
$like = new Like(); $like = new Like;
$like->url = 'http://example.org/note/id'; $like->url = 'http://example.org/note/id';
$like->save(); $like->save();
$id = $like->id; $id = $like->id;
@ -150,17 +151,17 @@ class LikesTest extends TestCase
$this->app->bind(Client::class, function () use ($client) { $this->app->bind(Client::class, function () use ($client) {
return $client; return $client;
}); });
$authorship = new Authorship(); $authorship = new Authorship;
$job->handle($client, $authorship); $job->handle($client, $authorship);
$this->assertEquals('Fred Bloggs', Like::find($id)->author_name); $this->assertEquals('Fred Bloggs', Like::find($id)->author_name);
} }
/** @test */ #[Test]
public function likeWithoutMicroformats(): void public function likeWithoutMicroformats(): void
{ {
$like = new Like(); $like = new Like;
$like->url = 'http://example.org/note/id'; $like->url = 'http://example.org/note/id';
$like->save(); $like->save();
$id = $like->id; $id = $like->id;
@ -186,17 +187,17 @@ class LikesTest extends TestCase
$this->app->bind(Client::class, function () use ($client) { $this->app->bind(Client::class, function () use ($client) {
return $client; return $client;
}); });
$authorship = new Authorship(); $authorship = new Authorship;
$job->handle($client, $authorship); $job->handle($client, $authorship);
$this->assertNull(Like::find($id)->author_name); $this->assertNull(Like::find($id)->author_name);
} }
/** @test */ #[Test]
public function likeThatIsATweet(): void public function likeThatIsATweet(): void
{ {
$like = new Like(); $like = new Like;
$like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200'; $like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200';
$like->save(); $like->save();
$id = $like->id; $id = $like->id;
@ -219,24 +220,23 @@ class LikesTest extends TestCase
'author_url' => 'https://twitter.com/jonnybarnes', 'author_url' => 'https://twitter.com/jonnybarnes',
'html' => '<div>HTML of the tweet embed</div>', 'html' => '<div>HTML of the tweet embed</div>',
]; ];
$codebirdMock = $this->getMockBuilder(Codebird::class) $codebirdMock = $this->createPartialMock(Codebird::class, ['__call']);
->addMethods(['statuses_oembed']) $codebirdMock->method('__call')
->getMock(); ->with('statuses_oembed', $this->anything())
$codebirdMock->method('statuses_oembed')
->willReturn($info); ->willReturn($info);
$this->app->instance(Codebird::class, $codebirdMock); $this->app->instance(Codebird::class, $codebirdMock);
$authorship = new Authorship(); $authorship = new Authorship;
$job->handle($client, $authorship); $job->handle($client, $authorship);
$this->assertEquals('Jonny Barnes', Like::find($id)->author_name); $this->assertEquals('Jonny Barnes', Like::find($id)->author_name);
} }
/** @test */ #[Test]
public function noErrorForFailureToPosseWithBridgy(): void public function noErrorForFailureToPosseWithBridgy(): void
{ {
$like = new Like(); $like = new Like;
$like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200'; $like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200';
$like->save(); $like->save();
$id = $like->id; $id = $like->id;
@ -257,21 +257,20 @@ class LikesTest extends TestCase
'author_url' => 'https://twitter.com/jonnybarnes', 'author_url' => 'https://twitter.com/jonnybarnes',
'html' => '<div>HTML of the tweet embed</div>', 'html' => '<div>HTML of the tweet embed</div>',
]; ];
$codebirdMock = $this->getMockBuilder(Codebird::class) $codebirdMock = $this->createPartialMock(Codebird::class, ['__call']);
->addMethods(['statuses_oembed']) $codebirdMock->method('__call')
->getMock(); ->with('statuses_oembed', $this->anything())
$codebirdMock->method('statuses_oembed')
->willReturn($info); ->willReturn($info);
$this->app->instance(Codebird::class, $codebirdMock); $this->app->instance(Codebird::class, $codebirdMock);
$authorship = new Authorship(); $authorship = new Authorship;
$job->handle($client, $authorship); $job->handle($client, $authorship);
$this->assertEquals('Jonny Barnes', Like::find($id)->author_name); $this->assertEquals('Jonny Barnes', Like::find($id)->author_name);
} }
/** @test */ #[Test]
public function unknownLikeGivesNotFoundResponse(): void public function unknownLikeGivesNotFoundResponse(): void
{ {
$response = $this->get('/likes/202'); $response = $this->get('/likes/202');

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