commit
335acb130e
114 changed files with 4696 additions and 2415 deletions
|
@ -8,6 +8,9 @@ indent_style = space
|
|||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js,css}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ SESSION_SECURE_COOKIE=true
|
|||
|
||||
LOG_SLACK_WEBHOOK_URL=
|
||||
FLARE_KEY=
|
||||
IGNITION_OPEN_AI_KEY=
|
||||
|
||||
FONT_LINK=
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
parserOptions:
|
||||
sourceType: 'module'
|
||||
ecmaVersion: 'latest'
|
||||
extends: 'eslint:recommended'
|
||||
env:
|
||||
browser: true
|
||||
|
@ -9,7 +10,7 @@ ignorePatterns:
|
|||
rules:
|
||||
indent:
|
||||
- error
|
||||
- 4
|
||||
- 2
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
|
@ -24,3 +25,14 @@ rules:
|
|||
- allow:
|
||||
- warn
|
||||
- error
|
||||
no-await-in-loop:
|
||||
- error
|
||||
no-promise-executor-return:
|
||||
- error
|
||||
require-atomic-updates:
|
||||
- error
|
||||
max-nested-callbacks:
|
||||
- error
|
||||
- 3
|
||||
prefer-promise-reject-errors:
|
||||
- error
|
||||
|
|
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
|
@ -2,6 +2,8 @@ name: Deploy
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
|
2
.github/workflows/phpunit.yml
vendored
2
.github/workflows/phpunit.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
|||
- name: Setup PHP with pecl extensions
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.1'
|
||||
php-version: '8.2'
|
||||
extensions: phpredis,imagick
|
||||
|
||||
- name: Copy .env
|
||||
|
|
2
.github/workflows/pint.yml
vendored
2
.github/workflows/pint.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
- name: Setup PHP with pecl extensions
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.1'
|
||||
php-version: '8.2'
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
{
|
||||
"extends": ["stylelint-config-standard"],
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"import-notation": "string"
|
||||
}
|
||||
"extends": ["stylelint-config-standard"]
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ use Illuminate\Support\Facades\DB;
|
|||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class MigratePlaceDataFromPostgis extends Command
|
||||
{
|
||||
|
|
|
@ -9,6 +9,9 @@ use Illuminate\Console\Command;
|
|||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\FileSystem\FileSystem;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ParseCachedWebMentions extends Command
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,9 @@ use App\Jobs\DownloadWebMention;
|
|||
use App\Models\WebMention;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ReDownloadWebMentions extends Command
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -7,23 +7,12 @@ use Throwable;
|
|||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* The list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
$this->reportable(function (Throwable $_e) {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class TwitterContentException extends \Exception
|
||||
{
|
||||
}
|
|
@ -9,6 +9,9 @@ use App\Models\Article;
|
|||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ArticlesController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
|
|
|
@ -10,6 +10,9 @@ use Illuminate\Http\RedirectResponse;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class BioController extends Controller
|
||||
{
|
||||
public function show(): View
|
||||
|
|
|
@ -7,9 +7,11 @@ namespace App\Http\Controllers\Admin;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\MicropubClient;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ClientsController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -9,10 +9,12 @@ use App\Models\Contact;
|
|||
use GuzzleHttp\Client;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ContactsController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,9 @@ namespace App\Http\Controllers\Admin;
|
|||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,9 @@ use App\Models\Like;
|
|||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class LikesController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,9 @@ use Illuminate\Http\RedirectResponse;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class NotesController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
257
app/Http/Controllers/Admin/PasskeysController.php
Normal file
257
app/Http/Controllers/Admin/PasskeysController.php
Normal file
|
@ -0,0 +1,257 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Passkey;
|
||||
use App\Models\User;
|
||||
use Cose\Algorithm\Manager;
|
||||
use Cose\Algorithm\Signature\ECDSA\ES256;
|
||||
use Cose\Algorithm\Signature\EdDSA\Ed25519;
|
||||
use Cose\Algorithm\Signature\RSA\RS256;
|
||||
use Cose\Algorithms;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
use ParagonIE\ConstantTime\Base64UrlSafe;
|
||||
use Throwable;
|
||||
use Webauthn\AttestationStatement\AttestationObjectLoader;
|
||||
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
|
||||
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
|
||||
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
|
||||
use Webauthn\AuthenticatorAssertionResponse;
|
||||
use Webauthn\AuthenticatorAssertionResponseValidator;
|
||||
use Webauthn\AuthenticatorAttestationResponse;
|
||||
use Webauthn\AuthenticatorAttestationResponseValidator;
|
||||
use Webauthn\AuthenticatorSelectionCriteria;
|
||||
use Webauthn\Exception\WebauthnException;
|
||||
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||
use Webauthn\PublicKeyCredentialLoader;
|
||||
use Webauthn\PublicKeyCredentialParameters;
|
||||
use Webauthn\PublicKeyCredentialRequestOptions;
|
||||
use Webauthn\PublicKeyCredentialRpEntity;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class PasskeysController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$passkeys = $user->passkey;
|
||||
|
||||
return view('admin.passkeys.index', compact('passkeys'));
|
||||
}
|
||||
|
||||
public function getCreateOptions(): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
// RP Entity i.e. the application
|
||||
$rpEntity = PublicKeyCredentialRpEntity::create(
|
||||
config('app.name'),
|
||||
config('url.longurl'),
|
||||
);
|
||||
|
||||
// User Entity
|
||||
$userEntity = PublicKeyCredentialUserEntity::create(
|
||||
$user->name,
|
||||
(string) $user->id,
|
||||
$user->name,
|
||||
);
|
||||
|
||||
// Challenge
|
||||
$challenge = random_bytes(16);
|
||||
|
||||
// List of supported public key parameters
|
||||
$pubKeyCredParams = collect([
|
||||
Algorithms::COSE_ALGORITHM_EDDSA,
|
||||
Algorithms::COSE_ALGORITHM_ES256,
|
||||
Algorithms::COSE_ALGORITHM_RS256,
|
||||
])->map(
|
||||
fn ($algorithm) => PublicKeyCredentialParameters::create('public-key', $algorithm)
|
||||
)->toArray();
|
||||
|
||||
$authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create(
|
||||
userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED,
|
||||
requireResidentKey: true,
|
||||
);
|
||||
|
||||
$options = PublicKeyCredentialCreationOptions::create(
|
||||
$rpEntity,
|
||||
$userEntity,
|
||||
$challenge,
|
||||
$pubKeyCredParams,
|
||||
authenticatorSelection: $authenticatorSelectionCriteria,
|
||||
attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE
|
||||
);
|
||||
|
||||
$options = json_encode($options, JSON_THROW_ON_ERROR);
|
||||
|
||||
session(['create_options' => $options]);
|
||||
|
||||
return JsonResponse::fromJsonString($options);
|
||||
}
|
||||
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$publicKeyCredentialCreationOptionsData = session('create_options');
|
||||
if (empty($publicKeyCredentialCreationOptionsData)) {
|
||||
throw new WebAuthnException('No public key credential request options found');
|
||||
}
|
||||
$publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::createFromString($publicKeyCredentialCreationOptionsData);
|
||||
|
||||
// Unset session data to mitigate replay attacks
|
||||
session()->forget('create_options');
|
||||
|
||||
$attestationSupportManager = AttestationStatementSupportManager::create();
|
||||
$attestationSupportManager->add(NoneAttestationStatementSupport::create());
|
||||
$attestationObjectLoader = AttestationObjectLoader::create($attestationSupportManager);
|
||||
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader);
|
||||
|
||||
$publicKeyCredential = $publicKeyCredentialLoader->load(json_encode($request->all(), JSON_THROW_ON_ERROR));
|
||||
|
||||
if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
|
||||
throw new WebAuthnException('Invalid response type');
|
||||
}
|
||||
|
||||
$attestationStatementSupportManager = AttestationStatementSupportManager::create();
|
||||
$attestationStatementSupportManager->add(NoneAttestationStatementSupport::create());
|
||||
|
||||
$authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create(
|
||||
attestationStatementSupportManager: $attestationStatementSupportManager,
|
||||
publicKeyCredentialSourceRepository: null,
|
||||
tokenBindingHandler: null,
|
||||
extensionOutputCheckerHandler: ExtensionOutputCheckerHandler::create(),
|
||||
);
|
||||
|
||||
$securedRelyingPartyId = [];
|
||||
if (App::environment('local', 'development')) {
|
||||
$securedRelyingPartyId = [config('url.longurl')];
|
||||
}
|
||||
|
||||
$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
|
||||
authenticatorAttestationResponse: $publicKeyCredential->response,
|
||||
publicKeyCredentialCreationOptions: $publicKeyCredentialCreationOptions,
|
||||
request: config('url.longurl'),
|
||||
securedRelyingPartyId: $securedRelyingPartyId,
|
||||
);
|
||||
|
||||
$user->passkey()->create([
|
||||
'passkey_id' => Base64UrlSafe::encodeUnpadded($publicKeyCredentialSource->publicKeyCredentialId),
|
||||
'passkey' => json_encode($publicKeyCredentialSource, JSON_THROW_ON_ERROR),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Passkey created successfully',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestOptions(): JsonResponse
|
||||
{
|
||||
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
|
||||
challenge: random_bytes(16),
|
||||
userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
|
||||
);
|
||||
|
||||
$publicKeyCredentialRequestOptions = json_encode($publicKeyCredentialRequestOptions, JSON_THROW_ON_ERROR);
|
||||
|
||||
session(['request_options' => $publicKeyCredentialRequestOptions]);
|
||||
|
||||
return JsonResponse::fromJsonString($publicKeyCredentialRequestOptions);
|
||||
}
|
||||
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
$requestOptions = session('request_options');
|
||||
session()->forget('request_options');
|
||||
|
||||
if (empty($requestOptions)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No request options found',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::createFromString($requestOptions);
|
||||
|
||||
$attestationSupportManager = AttestationStatementSupportManager::create();
|
||||
$attestationSupportManager->add(NoneAttestationStatementSupport::create());
|
||||
$attestationObjectLoader = AttestationObjectLoader::create($attestationSupportManager);
|
||||
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader);
|
||||
|
||||
$publicKeyCredential = $publicKeyCredentialLoader->load(json_encode($request->all(), JSON_THROW_ON_ERROR));
|
||||
|
||||
if (! $publicKeyCredential->response instanceof AuthenticatorAssertionResponse) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Invalid response type',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$passkey = Passkey::firstWhere('passkey_id', $publicKeyCredential->id);
|
||||
if (! $passkey) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Passkey not found',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$credential = PublicKeyCredentialSource::createFromArray(json_decode($passkey->passkey, true, 512, JSON_THROW_ON_ERROR));
|
||||
|
||||
$algorithmManager = Manager::create();
|
||||
$algorithmManager->add(new Ed25519());
|
||||
$algorithmManager->add(new ES256());
|
||||
$algorithmManager->add(new RS256());
|
||||
|
||||
$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
|
||||
publicKeyCredentialSourceRepository: null,
|
||||
tokenBindingHandler: null,
|
||||
extensionOutputCheckerHandler: ExtensionOutputCheckerHandler::create(),
|
||||
algorithmManager: $algorithmManager,
|
||||
);
|
||||
|
||||
$securedRelyingPartyId = [];
|
||||
if (App::environment('local', 'development')) {
|
||||
$securedRelyingPartyId = [config('url.longurl')];
|
||||
}
|
||||
|
||||
try {
|
||||
$authenticatorAssertionResponseValidator->check(
|
||||
credentialId: $credential,
|
||||
authenticatorAssertionResponse: $publicKeyCredential->response,
|
||||
publicKeyCredentialRequestOptions: $publicKeyCredentialRequestOptions,
|
||||
request: config('url.longurl'),
|
||||
userHandle: null,
|
||||
securedRelyingPartyId: $securedRelyingPartyId,
|
||||
);
|
||||
} catch (Throwable) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Passkey could not be verified',
|
||||
], 500);
|
||||
}
|
||||
|
||||
$user = User::find($passkey->user_id);
|
||||
Auth::login($user);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Passkey verified successfully',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,9 @@ use App\Services\PlaceService;
|
|||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class PlacesController extends Controller
|
||||
{
|
||||
protected PlaceService $placeService;
|
||||
|
|
|
@ -10,6 +10,9 @@ use Illuminate\Http\RedirectResponse;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class SyndicationTargetsController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,9 @@ use Illuminate\Http\RedirectResponse;
|
|||
use Illuminate\View\View;
|
||||
use Jonnybarnes\IndieWeb\Numbers;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ArticlesController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -9,6 +9,9 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
|
@ -31,7 +34,7 @@ class AuthController extends Controller
|
|||
$credentials = $request->only('name', 'password');
|
||||
|
||||
if (Auth::attempt($credentials, true)) {
|
||||
return redirect()->intended('/');
|
||||
return redirect()->intended('/admin');
|
||||
}
|
||||
|
||||
return redirect()->route('login');
|
||||
|
|
|
@ -7,6 +7,9 @@ namespace App\Http\Controllers;
|
|||
use App\Models\Bookmark;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class BookmarksController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,9 @@ use App\Models\Contact;
|
|||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ContactsController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -9,6 +9,9 @@ use App\Models\Note;
|
|||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class FeedsController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -7,16 +7,18 @@ use App\Models\Bio;
|
|||
use App\Models\Bookmark;
|
||||
use App\Models\Like;
|
||||
use App\Models\Note;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class FrontPageController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show all the recent activity.
|
||||
*/
|
||||
public function index(Request $request): Response|View
|
||||
public function index(): Response|View
|
||||
{
|
||||
$notes = Note::latest()->with(['media', 'client', 'place'])->get();
|
||||
$articles = Article::latest()->get();
|
||||
|
|
|
@ -7,6 +7,9 @@ namespace App\Http\Controllers;
|
|||
use App\Models\Like;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class LikesController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,9 @@ use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
|||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class MicropubController extends Controller
|
||||
{
|
||||
protected TokenService $tokenService;
|
||||
|
|
|
@ -24,6 +24,9 @@ use Lcobucci\JWT\Token\InvalidTokenStructure;
|
|||
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class MicropubMediaController extends Controller
|
||||
{
|
||||
protected TokenService $tokenService;
|
||||
|
|
|
@ -8,19 +8,21 @@ use App\Models\Note;
|
|||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\View\View;
|
||||
use Jonnybarnes\IndieWeb\Numbers;
|
||||
|
||||
// Need to sort out Twitter and webmentions!
|
||||
|
||||
/**
|
||||
* @todo Need to sort out Twitter and webmentions!
|
||||
*
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class NotesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show all the notes. This is also the homepage.
|
||||
*/
|
||||
public function index(Request $request): View|Response
|
||||
public function index(): View|Response
|
||||
{
|
||||
$notes = Note::latest()
|
||||
->with('place', 'media', 'client')
|
||||
|
|
|
@ -7,6 +7,9 @@ namespace App\Http\Controllers;
|
|||
use App\Models\Place;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class PlacesController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,9 @@ use App\Models\Note;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class SearchController extends Controller
|
||||
{
|
||||
public function search(Request $request): View
|
||||
|
|
|
@ -6,6 +6,9 @@ namespace App\Http\Controllers;
|
|||
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class ShortURLsController extends Controller
|
||||
{
|
||||
/*
|
||||
|
|
|
@ -12,6 +12,9 @@ use Illuminate\Http\Request;
|
|||
use IndieAuth\Client;
|
||||
use JsonException;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class TokenEndpointController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,9 @@ use Illuminate\Http\Response;
|
|||
use Illuminate\View\View;
|
||||
use Jonnybarnes\IndieWeb\Numbers;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class WebMentionsController extends Controller
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,8 @@ class CSPHeader
|
|||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
|
|
|
@ -10,6 +10,8 @@ class CorsHeaders
|
|||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
|
|
|
@ -10,6 +10,8 @@ class LinkHeadersMiddleware
|
|||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
|
|
|
@ -14,6 +14,8 @@ class LocalhostSessionMiddleware
|
|||
* Whilst we are developing locally, automatically log in as
|
||||
* `['me' => config('app.url')]` as I can’t manually log in as
|
||||
* a .localhost domain.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
|
|
|
@ -13,6 +13,8 @@ class MyAuthMiddleware
|
|||
{
|
||||
/**
|
||||
* Check the user is logged in.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
|
|
|
@ -10,6 +10,8 @@ class ValidateSignature extends Middleware
|
|||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedProperty
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
|
|
|
@ -12,6 +12,8 @@ class VerifyMicropubToken
|
|||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
|
|
|
@ -71,7 +71,6 @@ class SendWebMentions implements ShouldQueue
|
|||
|
||||
$endpoint = null;
|
||||
|
||||
/** @var Client $guzzle */
|
||||
$guzzle = resolve(Client::class);
|
||||
$response = $guzzle->get($url);
|
||||
//check HTTP Headers for webmention endpoint
|
||||
|
|
25
app/Models/Passkey.php
Normal file
25
app/Models/Passkey.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Passkey extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/** @inerhitDoc */
|
||||
protected $fillable = [
|
||||
'passkey_id',
|
||||
'passkey',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
|
@ -24,4 +25,9 @@ class User extends Authenticatable
|
|||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
public function passkey(): HasMany
|
||||
{
|
||||
return $this->hasMany(Passkey::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,15 @@ use App\Models\Tag;
|
|||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* @todo Do we need psalm-suppress for these observer methods?
|
||||
*/
|
||||
class NoteObserver
|
||||
{
|
||||
/**
|
||||
* Listen to the Note created event.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function created(Note $note): void
|
||||
{
|
||||
|
@ -35,6 +40,8 @@ class NoteObserver
|
|||
|
||||
/**
|
||||
* Listen to the Note updated event.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function updated(Note $note): void
|
||||
{
|
||||
|
@ -59,6 +66,8 @@ class NoteObserver
|
|||
|
||||
/**
|
||||
* Listen to the Note deleting event.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function deleting(Note $note): void
|
||||
{
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace App\Providers;
|
|||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
|
@ -6,6 +6,9 @@ use Illuminate\Support\Facades\Gate;
|
|||
use Laravel\Horizon\Horizon;
|
||||
use Laravel\Horizon\HorizonApplicationServiceProvider;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class HorizonServiceProvider extends HorizonApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,7 @@ use App\Models\Article;
|
|||
|
||||
class ArticleService extends Service
|
||||
{
|
||||
public function create(array $request, ?string $client = null): Article
|
||||
public function create(array $request, string $client = null): Article
|
||||
{
|
||||
return Article::create([
|
||||
'title' => $this->getDataByKey($request, 'name'),
|
||||
|
|
|
@ -18,7 +18,7 @@ class BookmarkService extends Service
|
|||
/**
|
||||
* Create a new Bookmark.
|
||||
*/
|
||||
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')) {
|
||||
//micropub request
|
||||
|
|
|
@ -13,7 +13,7 @@ class LikeService extends Service
|
|||
/**
|
||||
* Create a new Like.
|
||||
*/
|
||||
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')) {
|
||||
//micropub request
|
||||
|
|
|
@ -15,7 +15,7 @@ class HEntryService
|
|||
/**
|
||||
* Create the relevant model from some h-entry data.
|
||||
*/
|
||||
public function process(array $request, ?string $client = null): ?string
|
||||
public function process(array $request, string $client = null): ?string
|
||||
{
|
||||
if (Arr::get($request, 'properties.like-of') || Arr::get($request, 'like-of')) {
|
||||
return resolve(LikeService::class)->create($request)->longurl;
|
||||
|
|
|
@ -18,7 +18,7 @@ class NoteService extends Service
|
|||
/**
|
||||
* Create a new note.
|
||||
*/
|
||||
public function create(array $request, ?string $client = null): Note
|
||||
public function create(array $request, string $client = null): Note
|
||||
{
|
||||
$note = Note::create(
|
||||
[
|
||||
|
|
|
@ -9,7 +9,7 @@ use Illuminate\Support\Arr;
|
|||
|
||||
abstract class Service
|
||||
{
|
||||
abstract public function create(array $request, ?string $client = null): Model;
|
||||
abstract public function create(array $request, string $client = null): Model;
|
||||
|
||||
protected function getDataByKey(array $request, string $key): ?string
|
||||
{
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
"keywords": ["laravel", "framework", "indieweb"],
|
||||
"license": "CC0-1.0",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"php": "^8.2",
|
||||
"ext-dom": "*",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-pgsql": "*",
|
||||
"cviebrock/eloquent-sluggable": "^10.0",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"indieauth/client": "^1.1",
|
||||
|
@ -27,7 +28,8 @@
|
|||
"mf2/mf2": "~0.3",
|
||||
"spatie/commonmark-highlighter": "^3.0",
|
||||
"spatie/laravel-ignition": "^2.1",
|
||||
"symfony/html-sanitizer": "^6.1"
|
||||
"symfony/html-sanitizer": "^6.1",
|
||||
"web-auth/webauthn-lib": "^4.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.0",
|
||||
|
@ -39,8 +41,10 @@
|
|||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"openai-php/client": "^0.7.1",
|
||||
"phpunit/php-code-coverage": "^10.0",
|
||||
"phpunit/phpunit": "^10.1",
|
||||
"psalm/plugin-laravel": "^2.8",
|
||||
"spatie/laravel-ray": "^1.12",
|
||||
"vimeo/psalm": "^5.0"
|
||||
},
|
||||
|
|
2889
composer.lock
generated
2889
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Article>
|
||||
*/
|
||||
class ArticleFactory extends Factory
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Database\Factories;
|
|||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Bio>
|
||||
*/
|
||||
class BioFactory extends Factory
|
||||
|
|
|
@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Bookmark>
|
||||
*/
|
||||
class BookmarkFactory extends Factory
|
||||
|
|
|
@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Contact>
|
||||
*/
|
||||
class ContactFactory extends Factory
|
||||
|
|
|
@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Like>
|
||||
*/
|
||||
class LikeFactory extends Factory
|
||||
|
|
|
@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Media>
|
||||
*/
|
||||
class MediaFactory extends Factory
|
||||
|
|
|
@ -6,6 +6,8 @@ use App\Models\MicropubClient;
|
|||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\MicropubClient>
|
||||
*/
|
||||
class MicropubClientFactory extends Factory
|
||||
|
|
|
@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Note>
|
||||
*/
|
||||
class NoteFactory extends Factory
|
||||
|
|
34
database/factories/PasskeyFactory.php
Normal file
34
database/factories/PasskeyFactory.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Passkey;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Passkey>
|
||||
*/
|
||||
class PasskeyFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Passkey::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => $this->faker->numberBetween(1, 1000),
|
||||
'passkey_id' => $this->faker->uuid,
|
||||
'passkey' => $this->faker->sha256,
|
||||
'transports' => ['internal'],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ use App\Models\Place;
|
|||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Place>
|
||||
*/
|
||||
class PlaceFactory extends Factory
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Database\Factories;
|
|||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\SyndicationTarget>
|
||||
*/
|
||||
class SyndicationTargetFactory extends Factory
|
||||
|
|
|
@ -6,6 +6,8 @@ use App\Models\Tag;
|
|||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tag>
|
||||
*/
|
||||
class TagFactory extends Factory
|
||||
|
|
|
@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
|
|
|
@ -6,6 +6,8 @@ use App\Models\WebMention;
|
|||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\WebMention>
|
||||
*/
|
||||
class WebMentionFactory extends Factory
|
||||
|
|
32
database/migrations/2023_08_27_113904_create_passkeys.php
Normal file
32
database/migrations/2023_08_27_113904_create_passkeys.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('passkeys', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->string('passkey_id')->unique();
|
||||
$table->text('passkey');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('passkeys');
|
||||
}
|
||||
};
|
|
@ -11,6 +11,8 @@ class ArticlesTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the articles table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -5,6 +5,9 @@ namespace Database\Seeders;
|
|||
use App\Models\Bio;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class BioSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,8 @@ class BookmarksTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the bookmarks table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -11,6 +11,8 @@ class ClientsTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the clients table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -10,6 +10,8 @@ class ContactsTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the contacts table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -4,6 +4,9 @@ namespace Database\Seeders;
|
|||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* @psalm-suppress UnusedClass
|
||||
*/
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,8 @@ class LikesTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the likes table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -14,6 +14,8 @@ class NotesTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the notes table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -9,6 +9,8 @@ class PlacesTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the places table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -9,6 +9,8 @@ class UsersTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the users table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
|
@ -9,6 +9,8 @@ class WebMentionsTableSeeder extends Seeder
|
|||
{
|
||||
/**
|
||||
* Seed the webmentions table.
|
||||
*
|
||||
* @psalm-suppress PossiblyUnusedMethod
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
|
|
2872
package-lock.json
generated
2872
package-lock.json
generated
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
@ -5,29 +5,29 @@
|
|||
"repository": "https://github.com/jonnybarnes/jonnybarnes.uk",
|
||||
"license": "CC0-1.0",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.5",
|
||||
"@csstools/postcss-oklab-function": "^2.2.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-loader": "^9.1.2",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@csstools/postcss-oklab-function": "^3.0.7",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"babel-loader": "^9.1.3",
|
||||
"browserlist": "^1.0.1",
|
||||
"compression-webpack-plugin": "^10.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"cssnano": "^6.0.1",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint-webpack-plugin": "^4.0.1",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-combine-duplicated-selectors": "^10.0.2",
|
||||
"postcss-combine-media-query": "^1.0.1",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-loader": "^7.3.3",
|
||||
"postcss-nesting": "^11.3.0",
|
||||
"postcss-nesting": "^12.0.1",
|
||||
"style-loader": "^3.3.3",
|
||||
"stylelint": "^15.7.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint": "^15.11.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-webpack-plugin": "^4.1.1",
|
||||
"webpack": "^5.87.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
19
psalm.xml
19
psalm.xml
|
@ -1,23 +1,18 @@
|
|||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
errorLevel="7"
|
||||
resolveFromConfigFile="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
findUnusedBaselineEntry="true"
|
||||
findUnusedCode="true"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="app" />
|
||||
<directory name="app"/>
|
||||
<directory name="database/factories"/>
|
||||
<directory name="database/seeders"/>
|
||||
<ignoreFiles>
|
||||
<directory name="vendor" />
|
||||
<directory name="vendor"/>
|
||||
</ignoreFiles>
|
||||
</projectFiles>
|
||||
|
||||
<issueHandlers>
|
||||
<InvalidStaticInvocation>
|
||||
<errorLevel type="suppress">
|
||||
<file name="app/Providers/RouteServiceProvider.php" />
|
||||
</errorLevel>
|
||||
</InvalidStaticInvocation>
|
||||
</issueHandlers>
|
||||
</psalm>
|
||||
<plugins><pluginClass class="Psalm\LaravelPlugin\Plugin"/></plugins></psalm>
|
||||
|
|
|
@ -1 +1 @@
|
|||
:root{--font-family-headings:"Archer SSm A","Archer SSm B",serif;--font-family-body:"Verlag A","Verlag B",sans-serif;--font-family-monospace:"Operator Mono SSm A","Operator Mono SSm B",monospace;--font-size-sm:0.75rem;--font-size-base:1rem;--font-size-md:1.25rem;--font-size-lg:1.5rem;--font-size-xl:1.75rem;--font-size-xxl:2rem;--font-size-xxxl:2.25rem;--color-primary:#334700;--color-secondary:#e3ffb7;--color-link:#00649e;--color-link-visited:#bc7aff;--color-primary-shadow:rgba(16,25,0,.4)}@supports (color:color(display-p3 0 0 0)){:root{--color-primary:color(display-p3 0.21567 0.27838 0.03615);--color-secondary:color(display-p3 0.91016 0.99842 0.74082);--color-link:color(display-p3 0.01045 0.38351 0.63618);--color-link-visited:color(display-p3 0.70467 0.47549 0.99958);--color-primary-shadow:color(display-p3 0.06762 0.09646 0.00441/0.4)}}@supports (color:oklch(0% 0 0)){:root{--color-primary:oklch(36.8% 0.1 125.505);--color-secondary:oklch(96.3% 0.1 125.505);--color-link:oklch(48.09% 0.146 241.41);--color-link-visited:oklch(70.44% 0.21 304.41);--color-primary-shadow:oklch(19.56% 0.054 125.505/40%)}}body{background-color:var(--color-secondary);color:var(--color-primary);font-family:var(--font-family-body);font-size:var(--font-size-md)}code{font-family:var(--font-family-monospace)}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-headings)}.grid{display:grid;grid-template-columns:5vw 1fr 5vw;grid-template-rows:-webkit-min-content 1fr -webkit-min-content;grid-template-rows:min-content 1fr min-content;row-gap:1rem}#site-header{grid-column:2/3;grid-row:1/2}main{grid-row:2/3}footer,main{grid-column:2/3}footer{grid-row:3/4}footer .iwc-logo{max-width:85vw}a{color:var(--color-link)}a:visited{color:var(--color-link-visited)}#site-header a:visited{color:var(--color-link)}.hljs{border-radius:.5rem}.p-bridgy-twitter-content{display:none}.h-card .hovercard{-webkit-box-orient:vertical;-webkit-box-direction:normal;background-color:var(--color-secondary);border-radius:1rem;-webkit-box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);display:none;-ms-flex-direction:column;flex-direction:column;gap:.5rem;opacity:0;padding:1rem;position:absolute;-webkit-transition:opacity .5s ease-in-out;transition:opacity .5s ease-in-out;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:100}.h-card .hovercard .u-photo{max-width:6rem}.h-card .hovercard .social-icon{height:1rem;width:1rem}.h-card:hover .hovercard{display:-webkit-box;display:-ms-flexbox;display:flex;opacity:1}.h-entry{-webkit-border-start:1px solid var(--color-primary);-webkit-padding-start:.5rem;border-inline-start:1px solid var(--color-primary);padding-inline-start:.5rem}.h-entry .reply-to{font-style:italic}.h-entry .post-info a{text-decoration:none}.h-entry .note-metadata{-webkit-box-orient:horizontal;-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;gap:1rem}.h-entry .note-metadata .syndication-links{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.h-entry .note-metadata .syndication-links a{text-decoration:none}.h-entry .note-metadata .syndication-links a svg{height:1rem;width:1rem}
|
||||
:root{--font-family-headings:"Archer SSm A","Archer SSm B",serif;--font-family-body:"Verlag A","Verlag B",sans-serif;--font-family-monospace:"Operator Mono SSm A","Operator Mono SSm B",monospace;--font-size-sm:0.75rem;--font-size-base:1rem;--font-size-md:1.25rem;--font-size-lg:1.5rem;--font-size-xl:1.75rem;--font-size-xxl:2rem;--font-size-xxxl:2.25rem;--color-primary:#334700;--color-secondary:#e3ffb7;--color-link:#00649e;--color-link-visited:#bc7aff;--color-primary-shadow:rgba(16,25,0,.4)}@supports (color:color(display-p3 0 0 0)){:root{--color-primary:color(display-p3 0.21567 0.27838 0.03615);--color-secondary:color(display-p3 0.91016 0.99842 0.74082);--color-link:color(display-p3 0.01045 0.38351 0.63618);--color-link-visited:color(display-p3 0.70467 0.47549 0.99958);--color-primary-shadow:color(display-p3 0.06762 0.09646 0.00441/0.4)}}@supports (color:oklch(0% 0 0)){:root{--color-primary:oklch(36.8% 0.1 125.505deg);--color-secondary:oklch(96.3% 0.1 125.505deg);--color-link:oklch(48.09% 0.146 241.41deg);--color-link-visited:oklch(70.44% 0.21 304.41deg);--color-primary-shadow:oklch(19.56% 0.054 125.505deg/40%)}}body{background-color:var(--color-secondary);color:var(--color-primary);font-family:var(--font-family-body);font-size:var(--font-size-md)}code{font-family:var(--font-family-monospace)}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-headings)}.grid{display:grid;grid-template-columns:5vw 1fr 5vw;grid-template-rows:-webkit-min-content 1fr -webkit-min-content;grid-template-rows:min-content 1fr min-content;row-gap:1rem}#site-header{grid-column:2/3;grid-row:1/2}main{grid-row:2/3}footer,main{grid-column:2/3}footer{grid-row:3/4}footer .iwc-logo{max-width:85vw}footer .footer-actions{-webkit-box-orient:horizontal;-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;gap:1rem}a{color:var(--color-link)}a:visited{color:var(--color-link-visited)}#site-header a:visited,a.auth:visited{color:var(--color-link)}.hljs{border-radius:.5rem}.h-card .hovercard{-webkit-box-orient:vertical;-webkit-box-direction:normal;background-color:var(--color-secondary);border-radius:1rem;-webkit-box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);display:none;-ms-flex-direction:column;flex-direction:column;gap:.5rem;opacity:0;padding:1rem;position:absolute;-webkit-transition:opacity .5s ease-in-out;transition:opacity .5s ease-in-out;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:100}.h-card .hovercard .u-photo{max-width:6rem}.h-card .hovercard .social-icon{height:1rem;width:1rem}.h-card:hover .hovercard{display:-webkit-box;display:-ms-flexbox;display:flex;opacity:1}.h-entry{-webkit-border-start:1px solid var(--color-primary);-webkit-padding-start:.5rem;border-inline-start:1px solid var(--color-primary);padding-inline-start:.5rem}.h-entry .reply-to{font-style:italic}.h-entry .post-info a{text-decoration:none}.h-entry .note-metadata{-webkit-box-orient:horizontal;-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;gap:1rem}.h-entry .note-metadata .syndication-links{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.h-entry .note-metadata .syndication-links a{text-decoration:none}.h-entry .note-metadata .syndication-links a svg{height:1rem;width:1rem}
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
!function(){"use strict";let e=new class{constructor(){}async register(){const e=await this.getCreateOptions(),t={challenge:this.base64URLStringToBuffer(e.challenge),rp:{id:e.rp.id,name:e.rp.name},user:{id:(new TextEncoder).encode(window.atob(e.user.id)),name:e.user.name,displayName:e.user.displayName},pubKeyCredParams:e.pubKeyCredParams,excludeCredentials:[],authenticatorSelection:e.authenticatorSelection,timeout:6e4},a=await navigator.credentials.create({publicKey:t});if(!a)throw new Error("Error generating a passkey");const n={id:a.id?a.id:null,type:a.type?a.type:null,rawId:a.rawId?this.bufferToBase64URLString(a.rawId):null,response:{attestationObject:a.response.attestationObject?this.bufferToBase64URLString(a.response.attestationObject):null,clientDataJSON:a.response.clientDataJSON?this.bufferToBase64URLString(a.response.clientDataJSON):null}};if(!(await window.fetch("/admin/passkeys/register",{method:"POST",body:JSON.stringify(n),cache:"no-cache",headers:{"Content-Type":"application/json","X-CSRF-TOKEN":document.querySelector('meta[name="csrf-token"]').getAttribute("content")}})).ok)throw new Error("Error saving the passkey");window.location.reload()}async getCreateOptions(){const e=await fetch("/admin/passkeys/register",{method:"GET"});return await e.json()}async login(){const e=await this.getLoginData(),t=await navigator.credentials.get({publicKey:{challenge:this.base64URLStringToBuffer(e.challenge),userVerification:e.userVerification,timeout:6e4}});if(!t)throw new Error("Authentication failed");const a={id:t.id?t.id:"",type:t.type?t.type:"",rawId:t.rawId?this.bufferToBase64URLString(t.rawId):"",response:{authenticatorData:t.response.authenticatorData?this.bufferToBase64URLString(t.response.authenticatorData):"",clientDataJSON:t.response.clientDataJSON?this.bufferToBase64URLString(t.response.clientDataJSON):"",signature:t.response.signature?this.bufferToBase64URLString(t.response.signature):"",userHandle:t.response.userHandle?this.bufferToBase64URLString(t.response.userHandle):""}};if(!(await window.fetch("/login/passkey",{method:"POST",body:JSON.stringify(a),headers:{"Content-Type":"application/json","X-CSRF-TOKEN":document.querySelector('meta[name="csrf-token"]').getAttribute("content")}})).ok)throw new Error("Login failed");window.location.assign("/admin")}async getLoginData(){const e=await fetch("/login/passkey",{method:"GET"});return await e.json()}base64URLStringToBuffer(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),a=(4-t.length%4)%4,n=t.padEnd(t.length+a,"="),r=window.atob(n),i=new ArrayBuffer(r.length),s=new Uint8Array(i);for(let e=0;e<r.length;e++)s[e]=r.charCodeAt(e);return i}bufferToBase64URLString(e){const t=new Uint8Array(e);let a="";for(const e of t)a+=String.fromCharCode(e);return btoa(a).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}};document.querySelectorAll(".add-passkey").forEach((t=>{t.addEventListener("click",(()=>{e.register()}))})),document.querySelectorAll(".login-passkey").forEach((t=>{t.addEventListener("click",(()=>{e.login()}))}))}();
|
Binary file not shown.
12
public/vendor/horizon/app-dark.css
vendored
12
public/vendor/horizon/app-dark.css
vendored
File diff suppressed because one or more lines are too long
12
public/vendor/horizon/app.css
vendored
12
public/vendor/horizon/app.css
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/horizon/app.js
vendored
2
public/vendor/horizon/app.js
vendored
File diff suppressed because one or more lines are too long
9
public/vendor/horizon/mix-manifest.json
vendored
9
public/vendor/horizon/mix-manifest.json
vendored
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"/app.js": "/app.js?id=4f93645700a6c5485654",
|
||||
"/app.css": "/app.css?id=99048465add47d086ac7",
|
||||
"/app-dark.css": "/app-dark.css?id=f42b555818ed19478827"
|
||||
"/app.js": "/app.js?id=ff1533ec4a7afad65c5bd7bcc2cc7d7b",
|
||||
"/app-dark.css": "/app-dark.css?id=15c72df05e2b1147fa3e4b0670cfb435",
|
||||
"/app.css": "/app.css?id=4d6a1a7fe095eedc2cb2a4ce822ea8a5",
|
||||
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f",
|
||||
"/img/horizon.svg": "/img/horizon.svg?id=904d5b5185fefb09035384e15bfca765",
|
||||
"/img/sprite.svg": "/img/sprite.svg?id=afc4952b74895bdef3ab4ebe9adb746f"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@import "variables.css";
|
||||
@import "fonts.css";
|
||||
@import "layout.css";
|
||||
@import "colours.css";
|
||||
@import "code.css";
|
||||
@import "content.css";
|
||||
@import url('variables.css');
|
||||
@import url('fonts.css');
|
||||
@import url('layout.css');
|
||||
@import url('colours.css');
|
||||
@import url('code.css');
|
||||
@import url('content.css');
|
||||
|
|
|
@ -9,6 +9,10 @@ a {
|
|||
&:visited {
|
||||
color: var(--color-link-visited);
|
||||
}
|
||||
|
||||
&.auth:visited {
|
||||
color: var(--color-link);
|
||||
}
|
||||
}
|
||||
|
||||
#site-header {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@import "posse.css";
|
||||
@import "h-card.css";
|
||||
@import url('h-card.css');
|
||||
|
||||
.h-entry {
|
||||
border-inline-start: 1px solid var(--color-primary);
|
||||
|
|
|
@ -22,4 +22,10 @@ footer {
|
|||
& .iwc-logo {
|
||||
max-width: 85vw;
|
||||
}
|
||||
|
||||
& .footer-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.p-bridgy-twitter-content {
|
||||
display: none;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue