Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
cc2ff2b2a2 |
|||
|
73b9668088 |
|||
|
e8f2535cd1 |
|||
|
5481956d78 |
|||
|
7ce256baa5 |
|||
|
c3cf597950 |
|||
|
d2bc91794b |
|||
|
a31c218784 |
|||
|
eb35a0aa2d |
|||
|
6727138f43 |
|||
|
4aa93d63bb |
|||
|
77998a963e |
|||
|
568ae78864 |
|||
|
9e9fbdc127 |
|||
|
9532aae37e |
|||
|
e9d8f4c74b |
|||
|
be2bc267ad |
|||
|
a76c84b40f |
|||
|
99f306ede1 |
|||
|
714cd95d79 |
|||
|
6a0bb623fc |
|||
|
30ec2ec0e8 |
|||
|
4ed98c30b2 |
|||
|
faf8e5c1ec |
|||
|
24da24a677 |
|||
|
9d6cf6c815 |
|||
|
9c9a6392c8 |
|||
|
d5706b5f8f |
|||
|
f9f2744fad |
|||
|
31c49ac3fc |
|||
|
287520ad7b |
|||
|
2eab5749ea |
|||
|
e08763c526 |
|||
|
c420e19f08 |
|||
|
f7e13976e0 |
|||
|
db09718a96 |
|||
|
11e2c469cb |
|||
|
4569abc351 |
|||
|
dd203f6f31 |
|||
|
9f012b01e4 |
|||
|
e8e2bff5e4 |
|||
|
4451fc247e |
|||
|
0a13d27d6f |
|||
|
1137d2a07e |
|||
|
ad15b36f4d |
|||
|
24cb6d61aa |
|||
|
a342b432d4 |
|||
|
a5f146125b |
|||
|
4dd4977dd9 |
|||
| d31a59cbbe | |||
|
51780523b4 |
|||
|
9e788e0fe8 |
|||
|
76db869af5 |
|||
|
d3d6913474 |
|||
| 69e11d0799 | |||
|
6b4607e4f9 |
|||
| aef76e023a | |||
| 4409e42029 | |||
| 6183a065e9 | |||
| 0ef822de0c | |||
| 07db68882b | |||
| 13786584a2 | |||
| 6d867fcc71 | |||
| 7dd85a3988 | |||
| 863db8e491 | |||
|
b7d7db60e7 |
|||
|
0336744322 |
|||
|
7b13f91cc4 |
|||
|
f689128afc |
|||
|
2e624d9565 |
|||
|
d60117cd1d |
|||
|
fabe9d662b |
|||
|
507bac08f2 |
|||
|
c0802f8792 |
|||
|
c907bcde0c |
|||
|
784f4cb44a |
|||
|
528e981d7c |
|||
|
5f8d6ca882 |
|||
|
d56306e57b |
|||
|
43c654d9e7 |
|||
|
a7c8e58abd |
|||
|
b40f10cada |
|||
|
deba175f8f |
|||
|
c0c8e5262b |
|||
|
469d11ca7a |
|||
|
6ff247d58b |
|||
|
d5bac002c4 |
|||
|
481d9e22bb |
|||
|
335acb130e |
|||
|
f375e4217f |
|||
|
3dfe99aac7 |
|||
|
577d821709 |
|||
|
66f4f856a6 |
|||
|
73bc0b5338 |
|||
|
2f40e83568 |
|||
|
b2ef970810 |
|||
|
2b2826212f |
|||
|
94bb8c204b |
|||
|
87d49bddde |
|||
|
9383ed8d0b |
|||
|
2962675f9d |
|||
|
8532ee36d3 |
|||
|
22447b6027 |
|||
|
fcf0b4a778 |
|||
|
cc782bc39a |
|||
|
054bba1da9 |
|||
|
22e3eaab23 |
|||
|
e0efd19ef9 |
|||
|
0cd6f81a09 |
|||
|
30c9b2e248 |
|||
|
52ac67ba7a |
|||
|
272a5ea2fd |
|||
|
0bea4a1e95 |
|||
|
8ec738a765 |
|||
|
2f548725db |
|||
|
f6546e2335 |
|||
|
d52b8a3e83 |
|||
|
46133be181 |
|||
|
0faf896e8f |
|||
|
374e231a6b |
|||
|
729c52df2f |
|||
|
ac19c02e76 |
|||
|
0289f9f0b0 |
|||
|
14a10472a6 |
|||
|
b74bdd01ef |
|||
|
9c059f822f |
|||
|
75e369992d |
|||
|
a18f7b2d03 |
|||
|
7d1738964a |
|||
|
77004ec7d4 |
|||
|
3f891b580c |
|||
|
38f3244e31 |
|||
|
650d54292e |
|||
|
c40548be73 |
225 changed files with 8140 additions and 5779 deletions
|
|
@ -70,11 +70,6 @@ ADMIN_USER=admin# pick something better, this is used for `/admin`
|
||||||
ADMIN_PASS=password
|
ADMIN_PASS=password
|
||||||
DISPLAY_NAME='Joe Bloggs'# This is used for example in the header and titles
|
DISPLAY_NAME='Joe Bloggs'# This is used for example in the header and titles
|
||||||
|
|
||||||
TWITTER_CONSUMER_KEY=
|
|
||||||
TWITTER_CONSUMER_SECRET=
|
|
||||||
TWITTER_ACCESS_TOKEN=
|
|
||||||
TWITTER_ACCESS_TOKEN_SECRET=
|
|
||||||
|
|
||||||
SCOUT_DRIVER=database
|
SCOUT_DRIVER=database
|
||||||
SCOUT_QUEUE=false
|
SCOUT_QUEUE=false
|
||||||
|
|
||||||
|
|
@ -83,8 +78,8 @@ SESSION_SAME_SITE=strict
|
||||||
|
|
||||||
LOG_SLACK_WEBHOOK_URL=
|
LOG_SLACK_WEBHOOK_URL=
|
||||||
|
|
||||||
|
BRRR_WEBHOOK_URL=
|
||||||
|
|
||||||
FLARE_KEY=
|
FLARE_KEY=
|
||||||
|
|
||||||
IGNITION_OPEN_AI_KEY=
|
IGNITION_OPEN_AI_KEY=
|
||||||
|
|
||||||
BRIDGY_MASTODON_TOKEN=
|
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@ class MentionRenderer implements NodeRendererInterface
|
||||||
|
|
||||||
// This is not [@]handle@instance, so return a Twitter link
|
// This is not [@]handle@instance, so return a Twitter link
|
||||||
if (count($parts) === 1) {
|
if (count($parts) === 1) {
|
||||||
return new HtmlElement('a', ['href' => 'https://twitter.com/' . $parts[0]], '@' . $mentionText);
|
return new HtmlElement('a', ['href' => 'https://twitter.com/'.$parts[0]], '@'.$mentionText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the Mastodon profile link
|
// Render the Mastodon profile link
|
||||||
return new HtmlElement('a', ['href' => 'https://' . $parts[1] . '/@' . $parts[0]], '@' . $mentionText);
|
return new HtmlElement('a', ['href' => 'https://'.$parts[1].'/@'.$parts[0]], '@'.$mentionText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ class CopyMediaToLocal extends Command
|
||||||
foreach ($media as $mediaItem) {
|
foreach ($media as $mediaItem) {
|
||||||
$filename = $mediaItem->path;
|
$filename = $mediaItem->path;
|
||||||
|
|
||||||
$this->info('Processing: ' . $filename);
|
$this->info('Processing: '.$filename);
|
||||||
|
|
||||||
// If the file is already saved locally skip to next one
|
// If the file is already saved locally skip to next one
|
||||||
if (Storage::disk('local')->exists('public/' . $filename)) {
|
if (Storage::disk('local')->exists('public/'.$filename)) {
|
||||||
$this->info('File already exists locally, skipping');
|
$this->info('File already exists locally, skipping');
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -50,19 +50,19 @@ class CopyMediaToLocal extends Command
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$contents = Storage::disk('s3')->get($filename);
|
$contents = Storage::disk('s3')->get($filename);
|
||||||
Storage::disk('local')->put('public/' . $filename, $contents);
|
Storage::disk('local')->put('public/'.$filename, $contents);
|
||||||
|
|
||||||
// Copy -medium and -small versions if they exist
|
// Copy -medium and -small versions if they exist
|
||||||
$filenameParts = explode('.', $filename);
|
$filenameParts = explode('.', $filename);
|
||||||
$extension = array_pop($filenameParts);
|
$extension = array_pop($filenameParts);
|
||||||
$basename = trim(implode('.', $filenameParts), '.');
|
$basename = trim(implode('.', $filenameParts), '.');
|
||||||
$mediumFilename = $basename . '-medium.' . $extension;
|
$mediumFilename = $basename.'-medium.'.$extension;
|
||||||
$smallFilename = $basename . '-small.' . $extension;
|
$smallFilename = $basename.'-small.'.$extension;
|
||||||
if (Storage::disk('s3')->exists($mediumFilename)) {
|
if (Storage::disk('s3')->exists($mediumFilename)) {
|
||||||
Storage::disk('local')->put('public/' . $mediumFilename, Storage::disk('s3')->get($mediumFilename));
|
Storage::disk('local')->put('public/'.$mediumFilename, Storage::disk('s3')->get($mediumFilename));
|
||||||
}
|
}
|
||||||
if (Storage::disk('s3')->exists($smallFilename)) {
|
if (Storage::disk('s3')->exists($smallFilename)) {
|
||||||
Storage::disk('local')->put('public/' . $smallFilename, Storage::disk('s3')->get($smallFilename));
|
Storage::disk('local')->put('public/'.$smallFilename, Storage::disk('s3')->get($smallFilename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
app/Console/Commands/MigrateMedia.php
Normal file
101
app/Console/Commands/MigrateMedia.php
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\Note;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class MigrateMedia extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:migrate-media';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Migrate media';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// First check new `media_note` table exists
|
||||||
|
if (! DB::getSchemaBuilder()->hasTable('media_note')) {
|
||||||
|
$this->error('The table "media_note" does not exist.');
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all media already saved in `media_endpoint` table
|
||||||
|
$this->line('Updating existing local media');
|
||||||
|
$mediaEndpointMedia = Media::all();
|
||||||
|
// Save relationship in new `media_note` table based on `media_endpoint.note_id`
|
||||||
|
$this->withProgressBar($mediaEndpointMedia, function (Media $mediaEndpointMediaItem) {
|
||||||
|
$note = Note::find($mediaEndpointMediaItem->note_id);
|
||||||
|
if ($note) {
|
||||||
|
$note->media()->syncWithoutDetaching($mediaEndpointMediaItem->id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load all media records from `media` table
|
||||||
|
$this->line('');
|
||||||
|
$this->line('Migrating old media from S3');
|
||||||
|
$oldMedia = DB::table('media')->get();
|
||||||
|
foreach ($oldMedia as $oldMediaItem) {
|
||||||
|
// We only want to process the S3 media
|
||||||
|
if ($oldMediaItem->disk !== 's3') {
|
||||||
|
$this->warn('Original media item never stored in S3');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check media exists in S3
|
||||||
|
if (! Storage::disk('s3')->exists($oldMediaItem->id.DIRECTORY_SEPARATOR.$oldMediaItem->file_name)) {
|
||||||
|
$this->warn('Original media item not found in S3');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// We want to just copy the file, check it does not already exist locally
|
||||||
|
if (Storage::disk('public')->exists('media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name)) {
|
||||||
|
$this->warn('File already exists locally with filename of original media item');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save relationship based on `media.model_id`
|
||||||
|
// I have already checked they are all notes
|
||||||
|
$noteId = $oldMediaItem->model_id;
|
||||||
|
$note = Note::find($noteId);
|
||||||
|
if (! $note) {
|
||||||
|
$this->warn('Note no longer exists');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create media entry in database and attach to note
|
||||||
|
$newMediaItem = Media::create([
|
||||||
|
'path' => 'media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name,
|
||||||
|
'type' => 'image',
|
||||||
|
]);
|
||||||
|
$note->media()->syncWithoutDetaching($newMediaItem->id);
|
||||||
|
|
||||||
|
// Copy the file
|
||||||
|
Storage::disk('public')->writeStream('media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name, Storage::disk('s3')->readStream($oldMediaItem->id.DIRECTORY_SEPARATOR.$oldMediaItem->file_name));
|
||||||
|
|
||||||
|
$this->info('Media item migrated from S3');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line('');
|
||||||
|
$this->line('Migration finished');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -63,7 +63,7 @@ class MigratePlaceDataFromPostgis extends Command
|
||||||
$places = Place::all();
|
$places = Place::all();
|
||||||
|
|
||||||
$places->each(function ($place) {
|
$places->each(function ($place) {
|
||||||
$this->info('Extracting Postgis data for place: ' . $place->name);
|
$this->info('Extracting Postgis data for place: '.$place->name);
|
||||||
|
|
||||||
$place->latitude = $place->location->getLat();
|
$place->latitude = $place->location->getLat();
|
||||||
$place->longitude = $place->location->getLng();
|
$place->longitude = $place->location->getLng();
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,11 @@ class ParseCachedWebMentions extends Command
|
||||||
*/
|
*/
|
||||||
public function handle(FileSystem $filesystem): void
|
public function handle(FileSystem $filesystem): void
|
||||||
{
|
{
|
||||||
$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 don’t want to parse `.backup` files
|
if ($file->getExtension() !== 'backup') { // we don’t 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);
|
||||||
$url = $this->urlFromFilename($filepath);
|
$url = $this->urlFromFilename($filepath);
|
||||||
$webmention = WebMention::where('source', $url)->firstOrFail();
|
$webmention = WebMention::where('source', $url)->firstOrFail();
|
||||||
|
|
@ -53,7 +53,7 @@ class ParseCachedWebMentions extends Command
|
||||||
*/
|
*/
|
||||||
private function urlFromFilename(string $filepath): string
|
private function urlFromFilename(string $filepath): string
|
||||||
{
|
{
|
||||||
$dir = mb_substr($filepath, mb_strlen(storage_path() . '/HTML/'));
|
$dir = mb_substr($filepath, mb_strlen(storage_path().'/HTML/'));
|
||||||
$url = str_replace(['http/', 'https/'], ['http://', 'https://'], $dir);
|
$url = str_replace(['http/', 'https/'], ['http://', 'https://'], $dir);
|
||||||
if (mb_substr($url, -10) === 'index.html') {
|
if (mb_substr($url, -10) === 'index.html') {
|
||||||
$url = mb_substr($url, 0, -10);
|
$url = mb_substr($url, 0, -10);
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class ReDownloadWebMentions extends Command
|
||||||
{
|
{
|
||||||
$webmentions = WebMention::all();
|
$webmentions = WebMention::all();
|
||||||
foreach ($webmentions as $webmention) {
|
foreach ($webmentions as $webmention) {
|
||||||
$this->info('Initiation re-download of ' . $webmention->source);
|
$this->info('Initiation re-download of '.$webmention->source);
|
||||||
dispatch(new DownloadWebMention($webmention->source));
|
dispatch(new DownloadWebMention($webmention->source));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
app/Console/Commands/ReprocessMediaImages.php
Normal file
68
app/Console/Commands/ReprocessMediaImages.php
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Image\ImageException;
|
||||||
|
use Illuminate\Support\Facades\Image;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ReprocessMediaImages extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'app:reprocess-media-images {--dry-run : List images that would be processed without making changes}';
|
||||||
|
|
||||||
|
protected $description = 'Regenerate medium and small image variants using correct aspect-ratio scaling';
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$media = Media::where('type', 'image')
|
||||||
|
->whereNotNull('image_widths')
|
||||||
|
->where('image_widths', '>', 1000)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$dryRun = $this->option('dry-run');
|
||||||
|
|
||||||
|
$this->info("Found {$media->count()} images to reprocess.".($dryRun ? ' (dry run)' : ''));
|
||||||
|
|
||||||
|
foreach ($media as $item) {
|
||||||
|
$path = $item->path;
|
||||||
|
|
||||||
|
if (! Storage::disk('public')->exists($path)) {
|
||||||
|
$this->warn("{$path}: original not found on public disk, skipping.");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line("{$path} ({$item->image_widths}px wide)");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("Processing: {$path}");
|
||||||
|
|
||||||
|
$image = Image::fromStorage($path, 'public');
|
||||||
|
try {
|
||||||
|
$image->width();
|
||||||
|
} catch (ImageException) {
|
||||||
|
$this->warn(' Could not decode image, skipping.');
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filenameParts = explode('.', $path);
|
||||||
|
$extension = array_pop($filenameParts);
|
||||||
|
$basename = trim(implode('.', $filenameParts), '.');
|
||||||
|
|
||||||
|
Storage::disk('public')->put($basename.'-medium.'.$extension, $image->scale(width: 1000)->toBytes());
|
||||||
|
Storage::disk('public')->put($basename.'-small.'.$extension, $image->scale(width: 500)->toBytes());
|
||||||
|
|
||||||
|
$this->info(' Done.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Reprocessing complete.'.($dryRun ? ' (dry run — no files were changed)' : ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
7
app/Exceptions/MicropubUnsupportedModelException.php
Normal file
7
app/Exceptions/MicropubUnsupportedModelException.php
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
class MicropubUnsupportedModelException extends \Exception {}
|
||||||
16
app/Http/Controllers/AboutPageController.php
Normal file
16
app/Http/Controllers/AboutPageController.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\About;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class AboutPageController extends Controller
|
||||||
|
{
|
||||||
|
public function show(): View
|
||||||
|
{
|
||||||
|
return view('about', [
|
||||||
|
'about' => About::first()?->content,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
app/Http/Controllers/Admin/AboutController.php
Normal file
32
app/Http/Controllers/Admin/AboutController.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\About;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class AboutController extends Controller
|
||||||
|
{
|
||||||
|
public function show(): View
|
||||||
|
{
|
||||||
|
$about = About::first();
|
||||||
|
|
||||||
|
return view('admin.about.show', [
|
||||||
|
'aboutEntry' => $about,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$about = About::firstOrNew();
|
||||||
|
$about->content = $request->input('content');
|
||||||
|
$about->save();
|
||||||
|
|
||||||
|
return redirect()->route('admin.about.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,11 @@ namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Contact;
|
use App\Models\Contact;
|
||||||
use GuzzleHttp\Client;
|
|
||||||
use Illuminate\Filesystem\Filesystem;
|
use Illuminate\Filesystem\Filesystem;
|
||||||
|
use Illuminate\Http\Client\RequestException;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class ContactsController extends Controller
|
class ContactsController extends Controller
|
||||||
|
|
@ -75,7 +76,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);
|
||||||
|
|
@ -103,7 +104,7 @@ class ContactsController extends Controller
|
||||||
* This method attempts to find the microformat marked-up profile image
|
* This method attempts to find the microformat marked-up profile image
|
||||||
* from a given homepage and save it accordingly
|
* from a given homepage and save it accordingly
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
|
* @return RedirectResponse|View
|
||||||
*/
|
*/
|
||||||
public function getAvatar(int $contactId)
|
public function getAvatar(int $contactId)
|
||||||
{
|
{
|
||||||
|
|
@ -112,14 +113,13 @@ class ContactsController extends Controller
|
||||||
$avatar = null;
|
$avatar = null;
|
||||||
$contact = Contact::findOrFail($contactId);
|
$contact = Contact::findOrFail($contactId);
|
||||||
if ($contact->homepage !== null && mb_strlen($contact->homepage) !== 0) {
|
if ($contact->homepage !== null && mb_strlen($contact->homepage) !== 0) {
|
||||||
$client = resolve(Client::class);
|
|
||||||
try {
|
try {
|
||||||
$response = $client->get($contact->homepage);
|
$response = Http::throw()->get($contact->homepage);
|
||||||
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
|
} catch (RequestException $e) {
|
||||||
return redirect('/admin/contacts/' . $contactId . '/edit')
|
return redirect('/admin/contacts/'.$contactId.'/edit')
|
||||||
->with('error', 'Bad resposne from contact’s homepage');
|
->with('error', 'Bad resposne from contact’s homepage');
|
||||||
}
|
}
|
||||||
$mf2 = \Mf2\parse((string) $response->getBody(), $contact->homepage);
|
$mf2 = \Mf2\parse($response->body(), $contact->homepage);
|
||||||
foreach ($mf2['items'] as $microformat) {
|
foreach ($mf2['items'] as $microformat) {
|
||||||
if (Arr::get($microformat, 'type.0') === 'h-card') {
|
if (Arr::get($microformat, 'type.0') === 'h-card') {
|
||||||
$avatarURL = Arr::get($microformat, 'properties.photo.0.value');
|
$avatarURL = Arr::get($microformat, 'properties.photo.0.value');
|
||||||
|
|
@ -128,19 +128,19 @@ class ContactsController extends Controller
|
||||||
}
|
}
|
||||||
if ($avatarURL !== null) {
|
if ($avatarURL !== null) {
|
||||||
try {
|
try {
|
||||||
$avatar = $client->get($avatarURL);
|
$avatar = Http::throw()->get($avatarURL);
|
||||||
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
|
} catch (RequestException $e) {
|
||||||
return redirect('/admin/contacts/' . $contactId . '/edit')
|
return redirect('/admin/contacts/'.$contactId.'/edit')
|
||||||
->with('error', 'Unable to download avatar');
|
->with('error', 'Unable to download avatar');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
$filesystem->put($directory . '/image', $avatar->getBody());
|
$filesystem->put($directory.'/image', $avatar->body());
|
||||||
|
|
||||||
return view('admin.contacts.getavatarsuccess', [
|
return view('admin.contacts.getavatarsuccess', [
|
||||||
'homepage' => parse_url($contact->homepage, PHP_URL_HOST),
|
'homepage' => parse_url($contact->homepage, PHP_URL_HOST),
|
||||||
|
|
@ -148,6 +148,6 @@ class ContactsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect('/admin/contacts/' . $contactId . '/edit');
|
return redirect('/admin/contacts/'.$contactId.'/edit');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ class PasskeysController extends Controller
|
||||||
// Unset session data to mitigate replay attacks
|
// Unset session data to mitigate replay attacks
|
||||||
$request->session()->forget('create_options');
|
$request->session()->forget('create_options');
|
||||||
if (empty($publicKeyCredentialCreationOptionsData)) {
|
if (empty($publicKeyCredentialCreationOptionsData)) {
|
||||||
throw new WebAuthnException('No public key credential request options found');
|
throw new WebauthnException('No public key credential request options found');
|
||||||
}
|
}
|
||||||
|
|
||||||
$attestationStatementSupportManager = new AttestationStatementSupportManager;
|
$attestationStatementSupportManager = new AttestationStatementSupportManager;
|
||||||
|
|
@ -145,7 +145,7 @@ class PasskeysController extends Controller
|
||||||
);
|
);
|
||||||
|
|
||||||
if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
|
if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
|
||||||
throw new WebAuthnException('Invalid response type');
|
throw new WebauthnException('Invalid response type');
|
||||||
}
|
}
|
||||||
|
|
||||||
$algorithmManager = new Manager;
|
$algorithmManager = new Manager;
|
||||||
|
|
@ -254,7 +254,7 @@ class PasskeysController extends Controller
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$passkey = Passkey::firstWhere('passkey_id', $publicKeyCredential->id);
|
$passkey = Passkey::firstWhere('passkey_id', Base64UrlSafe::encodeUnpadded($publicKeyCredential->rawId));
|
||||||
if (! $passkey) {
|
if (! $passkey) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ class PlacesController extends Controller
|
||||||
foreach ($place1->notes as $note) {
|
foreach ($place1->notes as $note) {
|
||||||
$note->place()->dissociate();
|
$note->place()->dissociate();
|
||||||
$note->place()->associate($place2->id);
|
$note->place()->associate($place2->id);
|
||||||
|
$note->save();
|
||||||
}
|
}
|
||||||
$place1->delete();
|
$place1->delete();
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +128,7 @@ class PlacesController extends Controller
|
||||||
foreach ($place2->notes as $note) {
|
foreach ($place2->notes as $note) {
|
||||||
$note->place()->dissociate();
|
$note->place()->dissociate();
|
||||||
$note->place()->associate($place1->id);
|
$note->place()->associate($place1->id);
|
||||||
|
$note->save();
|
||||||
}
|
}
|
||||||
$place2->delete();
|
$place2->delete();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
app/Http/Controllers/Admin/SettingsController.php
Normal file
32
app/Http/Controllers/Admin/SettingsController.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class SettingsController extends Controller
|
||||||
|
{
|
||||||
|
public function show(): View
|
||||||
|
{
|
||||||
|
$settings = Setting::first();
|
||||||
|
|
||||||
|
return view('admin.settings.show', [
|
||||||
|
'settings' => $settings,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$settings = Setting::firstOrNew();
|
||||||
|
$settings->winter_effect_enabled = $request->boolean('winter_effect_enabled');
|
||||||
|
$settings->save();
|
||||||
|
|
||||||
|
return redirect()->route('admin.settings.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Http/Controllers/Admin/TokensController.php
Normal file
65
app/Http/Controllers/Admin/TokensController.php
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\MicropubToken;
|
||||||
|
use App\Services\TokenService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class TokensController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Show a list of issued Micropub tokens.
|
||||||
|
*/
|
||||||
|
public function index(): View
|
||||||
|
{
|
||||||
|
$tokens = MicropubToken::latest()->get();
|
||||||
|
|
||||||
|
return view('admin.tokens.index', compact('tokens'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form to manually generate a new Micropub token.
|
||||||
|
*
|
||||||
|
* This is for clients (e.g. iA Writer) that don't support the IndieAuth
|
||||||
|
* PKCE flow and instead expect to be given a token directly.
|
||||||
|
*/
|
||||||
|
public function create(): View
|
||||||
|
{
|
||||||
|
return view('admin.tokens.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually generate a new Micropub token.
|
||||||
|
*/
|
||||||
|
public function store(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'client_id' => 'required|string',
|
||||||
|
'scope' => 'required|array|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = resolve(TokenService::class)->getNewToken([
|
||||||
|
'me' => config('app.url'),
|
||||||
|
'client_id' => $validated['client_id'],
|
||||||
|
'scope' => implode(' ', $validated['scope']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect('/admin/tokens')->with('new_token', $token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke a Micropub token.
|
||||||
|
*/
|
||||||
|
public function revoke(MicropubToken $token): RedirectResponse
|
||||||
|
{
|
||||||
|
$token->revoke();
|
||||||
|
|
||||||
|
return redirect('/admin/tokens');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ class ArticlesController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index(?int $year = null, ?int $month = null): View
|
public function index(?int $year = null, ?int $month = null): View
|
||||||
{
|
{
|
||||||
$articles = Article::where('published', '1')
|
$articles = Article::published()
|
||||||
->date($year, $month)
|
->date($year, $month)
|
||||||
->orderBy('updated_at', 'desc')
|
->orderBy('updated_at', 'desc')
|
||||||
->simplePaginate(5);
|
->simplePaginate(5);
|
||||||
|
|
@ -38,9 +38,9 @@ class ArticlesController extends Controller
|
||||||
|
|
||||||
if ($article->updated_at->year != $year || $article->updated_at->month != $month) {
|
if ($article->updated_at->year != $year || $article->updated_at->month != $month) {
|
||||||
return redirect('/blog/'
|
return redirect('/blog/'
|
||||||
. $article->updated_at->year
|
.$article->updated_at->year
|
||||||
. '/' . $article->updated_at->format('m')
|
.'/'.$article->updated_at->format('m')
|
||||||
. '/' . $slug);
|
.'/'.$slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('articles.show', compact('article'));
|
return view('articles.show', compact('article'));
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ class ContactsController extends Controller
|
||||||
$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);
|
||||||
$file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image';
|
$file = public_path().'/assets/profile-images/'.$contact->homepageHost.'/image';
|
||||||
$contact->image = ($filesystem->exists($file)) ?
|
$contact->image = ($filesystem->exists($file)) ?
|
||||||
'/assets/profile-images/' . $contact->homepageHost . '/image'
|
'/assets/profile-images/'.$contact->homepageHost.'/image'
|
||||||
:
|
:
|
||||||
'/assets/profile-images/default-image';
|
'/assets/profile-images/default-image';
|
||||||
}
|
}
|
||||||
|
|
@ -35,11 +35,11 @@ class ContactsController extends Controller
|
||||||
public function show(Contact $contact): View
|
public function show(Contact $contact): View
|
||||||
{
|
{
|
||||||
$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'
|
||||||
:
|
:
|
||||||
'/assets/profile-images/default-image';
|
'/assets/profile-images/default-image';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,73 +7,20 @@ namespace App\Http\Controllers;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
use App\Models\Note;
|
use App\Models\Note;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Response;
|
|
||||||
|
|
||||||
class FeedsController extends Controller
|
class FeedsController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Returns the blog RSS feed.
|
|
||||||
*/
|
|
||||||
public function blogRss(): Response
|
|
||||||
{
|
|
||||||
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
|
||||||
$buildDate = $articles->first()->updated_at->toRssString();
|
|
||||||
|
|
||||||
return response()
|
|
||||||
->view('articles.rss', compact('articles', 'buildDate'))
|
|
||||||
->header('Content-Type', 'application/rss+xml; charset=utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the blog Atom feed.
|
|
||||||
*/
|
|
||||||
public function blogAtom(): Response
|
|
||||||
{
|
|
||||||
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
|
||||||
|
|
||||||
return response()
|
|
||||||
->view('articles.atom', compact('articles'))
|
|
||||||
->header('Content-Type', 'application/atom+xml; charset=utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the notes RSS feed.
|
|
||||||
*/
|
|
||||||
public function notesRss(): Response
|
|
||||||
{
|
|
||||||
$notes = Note::latest()->take(20)->get();
|
|
||||||
$buildDate = $notes->first()->updated_at->toRssString();
|
|
||||||
|
|
||||||
return response()
|
|
||||||
->view('notes.rss', compact('notes', 'buildDate'))
|
|
||||||
->header('Content-Type', 'application/rss+xml; charset=utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the notes Atom feed.
|
|
||||||
*/
|
|
||||||
public function notesAtom(): Response
|
|
||||||
{
|
|
||||||
$notes = Note::latest()->take(20)->get();
|
|
||||||
|
|
||||||
return response()
|
|
||||||
->view('notes.atom', compact('notes'))
|
|
||||||
->header('Content-Type', 'application/atom+xml; charset=utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @todo sort out return type for json responses */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the blog JSON feed.
|
* Returns the blog JSON feed.
|
||||||
*/
|
*/
|
||||||
public function blogJson(): array
|
public function blogJson(): JsonResponse
|
||||||
{
|
{
|
||||||
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
$articles = Article::published()->latest('updated_at')->take(20)->get();
|
||||||
$data = [
|
$data = [
|
||||||
'version' => 'https://jsonfeed.org/version/1.1',
|
'version' => 'https://jsonfeed.org/version/1.1',
|
||||||
'title' => 'The JSON Feed for ' . config('user.display_name') . '’s blog',
|
'title' => 'The JSON Feed for '.config('user.display_name').'’s blog',
|
||||||
'home_page_url' => config('app.url') . '/blog',
|
'home_page_url' => config('app.url').'/blog',
|
||||||
'feed_url' => config('app.url') . '/blog/feed.json',
|
'feed_url' => config('app.url').'/blog/feed.json',
|
||||||
'authors' => [
|
'authors' => [
|
||||||
[
|
[
|
||||||
'name' => config('user.display_name'),
|
'name' => config('user.display_name'),
|
||||||
|
|
@ -85,29 +32,31 @@ class FeedsController extends Controller
|
||||||
|
|
||||||
foreach ($articles as $key => $article) {
|
foreach ($articles as $key => $article) {
|
||||||
$data['items'][$key] = [
|
$data['items'][$key] = [
|
||||||
'id' => config('app.url') . $article->link,
|
'id' => config('app.url').$article->link,
|
||||||
'title' => $article->title,
|
'title' => $article->title,
|
||||||
'url' => config('app.url') . $article->link,
|
'url' => config('app.url').$article->link,
|
||||||
'content_html' => $article->main,
|
'content_html' => $article->main,
|
||||||
'date_published' => $article->created_at->tz('UTC')->toRfc3339String(),
|
'date_published' => $article->created_at->tz('UTC')->toRfc3339String(),
|
||||||
'date_modified' => $article->updated_at->tz('UTC')->toRfc3339String(),
|
'date_modified' => $article->updated_at->tz('UTC')->toRfc3339String(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return response()->json($data, 200, [
|
||||||
|
'Content-Type' => 'application/feed+json',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the notes JSON feed.
|
* Returns the notes JSON feed.
|
||||||
*/
|
*/
|
||||||
public function notesJson(): array
|
public function notesJson(): JsonResponse
|
||||||
{
|
{
|
||||||
$notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get();
|
$notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get();
|
||||||
$data = [
|
$data = [
|
||||||
'version' => 'https://jsonfeed.org/version/1.1',
|
'version' => 'https://jsonfeed.org/version/1.1',
|
||||||
'title' => 'The JSON Feed for ' . config('user.display_name') . '’s notes',
|
'title' => 'The JSON Feed for '.config('user.display_name').'’s notes',
|
||||||
'home_page_url' => config('app.url') . '/notes',
|
'home_page_url' => config('app.url').'/notes',
|
||||||
'feed_url' => config('app.url') . '/notes/feed.json',
|
'feed_url' => config('app.url').'/notes/feed.json',
|
||||||
'authors' => [
|
'authors' => [
|
||||||
[
|
[
|
||||||
'name' => config('user.display_name'),
|
'name' => config('user.display_name'),
|
||||||
|
|
@ -130,7 +79,9 @@ class FeedsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return response()->json($data, 200, [
|
||||||
|
'Content-Type' => 'application/feed+json',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,14 +89,14 @@ class FeedsController extends Controller
|
||||||
*/
|
*/
|
||||||
public function blogJf2(): JsonResponse
|
public function blogJf2(): JsonResponse
|
||||||
{
|
{
|
||||||
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
$articles = Article::published()->latest('updated_at')->take(20)->get();
|
||||||
$items = [];
|
$items = [];
|
||||||
foreach ($articles as $article) {
|
foreach ($articles as $article) {
|
||||||
$items[] = [
|
$items[] = [
|
||||||
'type' => 'entry',
|
'type' => 'entry',
|
||||||
'published' => $article->created_at,
|
'published' => $article->created_at,
|
||||||
'uid' => config('app.url') . $article->link,
|
'uid' => config('app.url').$article->link,
|
||||||
'url' => config('app.url') . $article->link,
|
'url' => config('app.url').$article->link,
|
||||||
'content' => [
|
'content' => [
|
||||||
'text' => $article->main,
|
'text' => $article->main,
|
||||||
'html' => $article->html,
|
'html' => $article->html,
|
||||||
|
|
@ -156,7 +107,7 @@ class FeedsController extends Controller
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'type' => 'feed',
|
'type' => 'feed',
|
||||||
'name' => 'Blog feed for ' . config('app.name'),
|
'name' => 'Blog feed for '.config('app.name'),
|
||||||
'url' => url('/blog'),
|
'url' => url('/blog'),
|
||||||
'author' => [
|
'author' => [
|
||||||
'type' => 'card',
|
'type' => 'card',
|
||||||
|
|
@ -192,7 +143,7 @@ class FeedsController extends Controller
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'type' => 'feed',
|
'type' => 'feed',
|
||||||
'name' => 'Notes feed for ' . config('app.name'),
|
'name' => 'Notes feed for '.config('app.name'),
|
||||||
'url' => url('/notes'),
|
'url' => url('/notes'),
|
||||||
'author' => [
|
'author' => [
|
||||||
'type' => 'card',
|
'type' => 'card',
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class FrontPageController extends Controller
|
||||||
->withCount(['webmentions AS reposts' => function ($query) {
|
->withCount(['webmentions AS reposts' => function ($query) {
|
||||||
$query->where('type', 'repost-of');
|
$query->where('type', 'repost-of');
|
||||||
}])->get();
|
}])->get();
|
||||||
$articles = Article::latest()->get();
|
$articles = Article::published()->latest()->get();
|
||||||
$bookmarks = Bookmark::latest()->with('tags')->get();
|
$bookmarks = Bookmark::latest()->with('tags')->get();
|
||||||
$likes = Like::latest()->get();
|
$likes = Like::latest()->get();
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue