Merge pull request 'MTM Micropub Media support' (#68) from develop into main
Reviewed-on: #68
This commit is contained in:
commit
bc8d535b24
30 changed files with 309 additions and 67 deletions
92
app/Console/Commands/MigrateMedia.php
Normal file
92
app/Console/Commands/MigrateMedia.php
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?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()->attach($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;
|
||||||
|
}
|
||||||
|
// Copy the file
|
||||||
|
Storage::disk('public')->writeStream('media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name, Storage::disk('s3')->readStream($oldMediaItem->id.DIRECTORY_SEPARATOR.$oldMediaItem->file_name));
|
||||||
|
|
||||||
|
// Save relationship based on `media.model_id`
|
||||||
|
// I have already checked they are all notes
|
||||||
|
$note = $oldMediaItem->model_id;
|
||||||
|
$newMediaItem = Media::create([
|
||||||
|
'path' => 'media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name,
|
||||||
|
'type' => 'image',
|
||||||
|
]);
|
||||||
|
$note->media()->attach($newMediaItem->id);
|
||||||
|
|
||||||
|
$this->info('Media item migrated from S3');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line('');
|
||||||
|
$this->line('Migration finished');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Note;
|
use App\Models\Note;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
|
@ -53,7 +52,7 @@ class NotesController extends Controller
|
||||||
->withCount(['webmentions AS reposts' => function ($query) {
|
->withCount(['webmentions AS reposts' => function ($query) {
|
||||||
$query->where('type', 'repost-of');
|
$query->where('type', 'repost-of');
|
||||||
}])->firstOrFail();
|
}])->firstOrFail();
|
||||||
} catch (ModelNotFoundException $exception) {
|
} catch (\Exception) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,9 @@ class MicropubRequest extends FormRequest
|
||||||
// Add checkin value
|
// Add checkin value
|
||||||
$this->micropubData['checkin'] = Arr::get($data, 'checkin');
|
$this->micropubData['checkin'] = Arr::get($data, 'checkin');
|
||||||
$this->micropubData['syndication'] = Arr::get($data, 'properties.syndication.0');
|
$this->micropubData['syndication'] = Arr::get($data, 'properties.syndication.0');
|
||||||
|
|
||||||
|
// Add photos
|
||||||
|
$this->micropubData['photos'] = Arr::get($data, 'properties.photo');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeMicropubForm(): void
|
private function normalizeMicropubForm(): void
|
||||||
|
|
|
||||||
|
|
@ -33,20 +33,20 @@ class ProcessMedia implements ShouldQueue
|
||||||
public function handle(ImageManager $manager): void
|
public function handle(ImageManager $manager): void
|
||||||
{
|
{
|
||||||
// Load file
|
// Load file
|
||||||
$file = Storage::disk('local')->get('media/' . $this->filename);
|
$file = Storage::disk('local')->get($this->filename);
|
||||||
|
|
||||||
// Open file
|
// Open file
|
||||||
try {
|
try {
|
||||||
$image = $manager->read($file);
|
$image = $manager->read($file);
|
||||||
} catch (DecoderException) {
|
} catch (DecoderException) {
|
||||||
// not an image; delete file and end job
|
// not an image; delete file and end job
|
||||||
Storage::disk('local')->delete('media/' . $this->filename);
|
Storage::disk('local')->delete($this->filename);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the file publicly
|
// Save the file publicly
|
||||||
Storage::disk('public')->put('media/' . $this->filename, $file);
|
Storage::disk('public')->put($this->filename, $file);
|
||||||
|
|
||||||
// Create smaller versions if necessary
|
// Create smaller versions if necessary
|
||||||
if ($image->width() > 1000) {
|
if ($image->width() > 1000) {
|
||||||
|
|
@ -57,13 +57,13 @@ class ProcessMedia implements ShouldQueue
|
||||||
$basename = trim(implode('.', $filenameParts), '.');
|
$basename = trim(implode('.', $filenameParts), '.');
|
||||||
|
|
||||||
$medium = $image->resize(width: 1000);
|
$medium = $image->resize(width: 1000);
|
||||||
Storage::disk('public')->put('media/' . $basename . '-medium.' . $extension, (string) $medium->encode());
|
Storage::disk('public')->put($basename . '-medium.' . $extension, (string) $medium->encode());
|
||||||
|
|
||||||
$small = $image->resize(width: 500);
|
$small = $image->resize(width: 500);
|
||||||
Storage::disk('public')->put('media/' . $basename . '-small.' . $extension, (string) $small->encode());
|
Storage::disk('public')->put($basename . '-small.' . $extension, (string) $small->encode());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can delete the locally saved image
|
// Now we can delete the locally saved image
|
||||||
Storage::disk('local')->delete('media/' . $this->filename);
|
Storage::disk('local')->delete($this->filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ namespace App\Models;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Media extends Model
|
class Media extends Model
|
||||||
|
|
@ -20,9 +20,11 @@ class Media extends Model
|
||||||
/** @var array<int, string> */
|
/** @var array<int, string> */
|
||||||
protected $fillable = ['token', 'path', 'type', 'image_widths'];
|
protected $fillable = ['token', 'path', 'type', 'image_widths'];
|
||||||
|
|
||||||
public function note(): BelongsTo
|
public function notes(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Note::class);
|
return $this->belongsToMany(Note::class)
|
||||||
|
->withPivot('alt_text', 'order')
|
||||||
|
->withTimestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function url(): Attribute
|
protected function url(): Attribute
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
@ -92,9 +91,12 @@ class Note extends Model
|
||||||
return $this->belongsTo(Place::class);
|
return $this->belongsTo(Place::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function media(): HasMany
|
public function media(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Media::class);
|
return $this->BelongsToMany(Media::class)
|
||||||
|
->withPivot('alt_text', 'order')
|
||||||
|
->withTimestamps()
|
||||||
|
->orderBy('order');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,15 @@ class NoteService
|
||||||
$note->place()->associate($this->getCheckin($data));
|
$note->place()->associate($this->getCheckin($data));
|
||||||
$note->swarm_url = $this->getSwarmUrl($data);
|
$note->swarm_url = $this->getSwarmUrl($data);
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// $note->instagram_url = $this->getInstagramUrl($request);
|
// $note->instagram_url = $this->getInstagramUrl($request);
|
||||||
//
|
|
||||||
// foreach ($this->getMedia($request) as $media) {
|
foreach ($this->getMedia($data) as $index => $media) {
|
||||||
// $note->media()->save($media);
|
$note->media()->attach($media['value']->id, [
|
||||||
// }
|
'alt_text' => $media['alt'],
|
||||||
|
'order' => $index,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$note->save();
|
$note->save();
|
||||||
|
|
||||||
|
|
@ -188,23 +191,44 @@ class NoteService
|
||||||
/**
|
/**
|
||||||
* Get the media URLs from the request to create a new note.
|
* Get the media URLs from the request to create a new note.
|
||||||
*/
|
*/
|
||||||
private function getMedia(array $request): array
|
private function getMedia(array $data): array
|
||||||
{
|
{
|
||||||
$media = [];
|
$media = [];
|
||||||
$photos = Arr::get($request, 'photo') ?? Arr::get($request, 'properties.photo');
|
$photos = Arr::get($data, 'photos');
|
||||||
|
|
||||||
if (isset($photos)) {
|
if (isset($photos)) {
|
||||||
foreach ((array) $photos as $photo) {
|
foreach ((array) $photos as $photo) {
|
||||||
|
// $photo can be a string of the URL opf the photo
|
||||||
|
// or it can be an object with a `value` and `alt`
|
||||||
|
$photoUrl = null;
|
||||||
|
$photoAlt = null;
|
||||||
|
if (is_string($photo)) {
|
||||||
|
$photoUrl = $photo;
|
||||||
|
} elseif (is_array($photo)) {
|
||||||
|
$photoUrl = $photo['value'];
|
||||||
|
$photoAlt = $photo['alt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($photoUrl)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// check the media was uploaded to my endpoint, and use path
|
// check the media was uploaded to my endpoint, and use path
|
||||||
if (Str::startsWith($photo, config('filesystems.disks.s3.url'))) {
|
if (Str::startsWith($photoUrl, config('filesystems.disks.public.url'))) {
|
||||||
$path = substr($photo, strlen(config('filesystems.disks.s3.url')));
|
$path = substr($photoUrl, strlen(config('filesystems.disks.public.url')));
|
||||||
$media[] = Media::where('path', ltrim($path, '/'))->firstOrFail();
|
$media[] = [
|
||||||
|
'value' => Media::where('path', ltrim($path, '/'))->firstOrFail(),
|
||||||
|
'alt' => $photoAlt,
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
$newMedia = Media::firstOrNew(['path' => $photo]);
|
$newMedia = Media::firstOrNew(['path' => $photoUrl]);
|
||||||
// currently assuming this is a photo from Swarm or OwnYourGram
|
// currently assuming this is a photo from Swarm or OwnYourGram
|
||||||
$newMedia->type = 'image';
|
$newMedia->type = 'image';
|
||||||
$newMedia->save();
|
$newMedia->save();
|
||||||
$media[] = $newMedia;
|
$media[] = [
|
||||||
|
'value' => $newMedia,
|
||||||
|
'alt' => $photoAlt,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ return [
|
||||||
|
|
||||||
'previous_keys' => [
|
'previous_keys' => [
|
||||||
...array_filter(
|
...array_filter(
|
||||||
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ return [
|
||||||
| Password Confirmation Timeout
|
| Password Confirmation Timeout
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| Here you may define the amount of seconds before a password confirmation
|
| Here you may define the number of seconds before a password confirmation
|
||||||
| window expires and users are asked to re-enter their password via the
|
| window expires and users are asked to re-enter their password via the
|
||||||
| confirmation screen. By default, the timeout lasts for three hours.
|
| confirmation screen. By default, the timeout lasts for three hours.
|
||||||
|
|
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@ return [
|
||||||
| well as their drivers. You may even define multiple stores for the
|
| well as their drivers. You may even define multiple stores for the
|
||||||
| same cache driver to group types of items stored in your caches.
|
| same cache driver to group types of items stored in your caches.
|
||||||
|
|
|
|
||||||
| Supported drivers: "apc", "array", "database", "file", "memcached",
|
| Supported drivers: "array", "database", "file", "memcached",
|
||||||
| "redis", "dynamodb", "octane", "null"
|
| "redis", "dynamodb", "octane",
|
||||||
|
| "failover", "null"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -89,6 +90,14 @@ return [
|
||||||
'driver' => 'octane',
|
'driver' => 'octane',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'failover' => [
|
||||||
|
'driver' => 'failover',
|
||||||
|
'stores' => [
|
||||||
|
'database',
|
||||||
|
'array',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -102,6 +111,6 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'_cache_'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ return [
|
||||||
'busy_timeout' => null,
|
'busy_timeout' => null,
|
||||||
'journal_mode' => null,
|
'journal_mode' => null,
|
||||||
'synchronous' => null,
|
'synchronous' => null,
|
||||||
|
'transaction_mode' => 'DEFERRED',
|
||||||
],
|
],
|
||||||
|
|
||||||
'mysql' => [
|
'mysql' => [
|
||||||
|
|
@ -58,7 +59,7 @@ return [
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
'engine' => null,
|
'engine' => null,
|
||||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||||
]) : [],
|
]) : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -78,7 +79,7 @@ return [
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
'engine' => null,
|
'engine' => null,
|
||||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||||
]) : [],
|
]) : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -147,7 +148,7 @@ return [
|
||||||
|
|
||||||
'options' => [
|
'options' => [
|
||||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'_database_'),
|
||||||
'persistent' => env('REDIS_PERSISTENT', false),
|
'persistent' => env('REDIS_PERSISTENT', false),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -158,6 +159,10 @@ return [
|
||||||
'password' => env('REDIS_PASSWORD'),
|
'password' => env('REDIS_PASSWORD'),
|
||||||
'port' => env('REDIS_PORT', '6379'),
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
'database' => env('REDIS_DB', '0'),
|
'database' => env('REDIS_DB', '0'),
|
||||||
|
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||||
|
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||||
|
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||||
|
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||||
],
|
],
|
||||||
|
|
||||||
'cache' => [
|
'cache' => [
|
||||||
|
|
@ -167,6 +172,10 @@ return [
|
||||||
'password' => env('REDIS_PASSWORD'),
|
'password' => env('REDIS_PASSWORD'),
|
||||||
'port' => env('REDIS_PORT', '6379'),
|
'port' => env('REDIS_PORT', '6379'),
|
||||||
'database' => env('REDIS_CACHE_DB', '1'),
|
'database' => env('REDIS_CACHE_DB', '1'),
|
||||||
|
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||||
|
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||||
|
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||||
|
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ return [
|
||||||
'public' => [
|
'public' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => storage_path('app/public'),
|
'root' => storage_path('app/public'),
|
||||||
'url' => env('APP_URL').'/storage',
|
'url' => rtrim(env('APP_URL'), '/').'/storage',
|
||||||
'visibility' => 'public',
|
'visibility' => 'public',
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
'report' => false,
|
'report' => false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Monolog\Handler\NullHandler;
|
use Monolog\Handler\NullHandler;
|
||||||
use Monolog\Handler\StreamHandler;
|
|
||||||
use Monolog\Handler\SyslogUdpHandler;
|
use Monolog\Handler\SyslogUdpHandler;
|
||||||
use Monolog\Processor\PsrLogMessageProcessor;
|
use Monolog\Processor\PsrLogMessageProcessor;
|
||||||
|
|
||||||
|
|
@ -54,7 +53,7 @@ return [
|
||||||
|
|
||||||
'stack' => [
|
'stack' => [
|
||||||
'driver' => 'stack',
|
'driver' => 'stack',
|
||||||
'channels' => explode(',', env('LOG_STACK', 'single')),
|
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
|
||||||
'ignore_exceptions' => false,
|
'ignore_exceptions' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -97,11 +96,10 @@ return [
|
||||||
'stderr' => [
|
'stderr' => [
|
||||||
'driver' => 'monolog',
|
'driver' => 'monolog',
|
||||||
'level' => env('LOG_LEVEL', 'debug'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
'handler' => StreamHandler::class,
|
'handler_with' => [
|
||||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
|
||||||
'with' => [
|
|
||||||
'stream' => 'php://stderr',
|
'stream' => 'php://stderr',
|
||||||
],
|
],
|
||||||
|
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||||
'processors' => [PsrLogMessageProcessor::class],
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ return [
|
||||||
| your mailers below. You may also add additional mailers if needed.
|
| your mailers below. You may also add additional mailers if needed.
|
||||||
|
|
|
|
||||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||||
| "postmark", "log", "array", "failover", "roundrobin"
|
| "postmark", "resend", "log", "array",
|
||||||
|
| "failover", "roundrobin"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -45,7 +46,7 @@ return [
|
||||||
'username' => env('MAIL_USERNAME'),
|
'username' => env('MAIL_USERNAME'),
|
||||||
'password' => env('MAIL_PASSWORD'),
|
'password' => env('MAIL_PASSWORD'),
|
||||||
'timeout' => null,
|
'timeout' => null,
|
||||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||||
],
|
],
|
||||||
|
|
||||||
'ses' => [
|
'ses' => [
|
||||||
|
|
@ -60,6 +61,10 @@ return [
|
||||||
// ],
|
// ],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'resend' => [
|
||||||
|
'transport' => 'resend',
|
||||||
|
],
|
||||||
|
|
||||||
'sendmail' => [
|
'sendmail' => [
|
||||||
'transport' => 'sendmail',
|
'transport' => 'sendmail',
|
||||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||||
|
|
@ -80,6 +85,16 @@ return [
|
||||||
'smtp',
|
'smtp',
|
||||||
'log',
|
'log',
|
||||||
],
|
],
|
||||||
|
'retry_after' => 60,
|
||||||
|
],
|
||||||
|
|
||||||
|
'roundrobin' => [
|
||||||
|
'transport' => 'roundrobin',
|
||||||
|
'mailers' => [
|
||||||
|
'ses',
|
||||||
|
'postmark',
|
||||||
|
],
|
||||||
|
'retry_after' => 60,
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ return [
|
||||||
| used by your application. An example configuration is provided for
|
| used by your application. An example configuration is provided for
|
||||||
| each backend supported by Laravel. You're also free to add more.
|
| each backend supported by Laravel. You're also free to add more.
|
||||||
|
|
|
|
||||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
|
||||||
|
| "deferred", "background", "failover", "null"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -72,6 +73,22 @@ return [
|
||||||
'after_commit' => false,
|
'after_commit' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'deferred' => [
|
||||||
|
'driver' => 'deferred',
|
||||||
|
],
|
||||||
|
|
||||||
|
'background' => [
|
||||||
|
'driver' => 'background',
|
||||||
|
],
|
||||||
|
|
||||||
|
'failover' => [
|
||||||
|
'driver' => 'failover',
|
||||||
|
'connections' => [
|
||||||
|
'database',
|
||||||
|
'deferred',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,11 @@ return [
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'postmark' => [
|
'postmark' => [
|
||||||
'token' => env('POSTMARK_TOKEN'),
|
'key' => env('POSTMARK_API_KEY'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'resend' => [
|
||||||
|
'key' => env('RESEND_API_KEY'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'ses' => [
|
'ses' => [
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ return [
|
||||||
| incoming requests. Laravel supports a variety of storage options to
|
| incoming requests. Laravel supports a variety of storage options to
|
||||||
| persist session data. Database storage is a great default choice.
|
| persist session data. Database storage is a great default choice.
|
||||||
|
|
|
|
||||||
| Supported: "file", "cookie", "database", "apc",
|
| Supported: "file", "cookie", "database", "memcached",
|
||||||
| "memcached", "redis", "dynamodb", "array"
|
| "redis", "dynamodb", "array"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ return [
|
||||||
| define the cache store which should be used to store the session data
|
| define the cache store which should be used to store the session data
|
||||||
| between requests. This must match one of your defined cache stores.
|
| between requests. This must match one of your defined cache stores.
|
||||||
|
|
|
|
||||||
| Affects: "apc", "dynamodb", "memcached", "redis"
|
| Affects: "dynamodb", "memcached", "redis"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -125,12 +125,11 @@ return [
|
||||||
| the framework. Typically, you should not need to change this value
|
| the framework. Typically, you should not need to change this value
|
||||||
| since doing so does not grant a meaningful security improvement.
|
| since doing so does not grant a meaningful security improvement.
|
||||||
|
|
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'cookie' => env(
|
'cookie' => env(
|
||||||
'SESSION_COOKIE',
|
'SESSION_COOKIE',
|
||||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
|
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
|
||||||
),
|
),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -153,7 +152,7 @@ return [
|
||||||
|
|
|
|
||||||
| This value determines the domain and subdomains the session cookie is
|
| This value determines the domain and subdomains the session cookie is
|
||||||
| available to. By default, the cookie will be available to the root
|
| available to. By default, the cookie will be available to the root
|
||||||
| domain and all subdomains. Typically, this shouldn't be changed.
|
| domain without subdomains. Typically, this shouldn't be changed.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?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('media_note', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('note_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->foreignId('media_id')->constrained('media_endpoint')->onDelete('cascade');
|
||||||
|
$table->text('alt_text')->nullable();
|
||||||
|
$table->integer('order')->default(0);
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['note_id', 'media_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('media_note');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -84,7 +84,9 @@ class NotesTableSeeder extends Seeder
|
||||||
// copy aaron’s profile pic in place
|
// copy aaron’s profile pic in place
|
||||||
$spl = new SplFileInfo(public_path() . '/assets/profile-images/aaronparecki.com');
|
$spl = new SplFileInfo(public_path() . '/assets/profile-images/aaronparecki.com');
|
||||||
if ($spl->isDir() === false) {
|
if ($spl->isDir() === false) {
|
||||||
mkdir(public_path() . '/assets/profile-images/aaronparecki.com', 0755);
|
if (! mkdir($concurrentDirectory = public_path() . '/assets/profile-images/aaronparecki.com', 0755) && ! is_dir($concurrentDirectory)) {
|
||||||
|
throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
|
||||||
|
}
|
||||||
copy(base_path() . '/tests/aaron.png', public_path() . '/assets/profile-images/aaronparecki.com/image');
|
copy(base_path() . '/tests/aaron.png', public_path() . '/assets/profile-images/aaronparecki.com/image');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +173,9 @@ class NotesTableSeeder extends Seeder
|
||||||
$noteWithOnlyImage->setCreatedAt($now);
|
$noteWithOnlyImage->setCreatedAt($now);
|
||||||
$noteWithOnlyImage->setUpdatedAt($now);
|
$noteWithOnlyImage->setUpdatedAt($now);
|
||||||
$noteWithOnlyImage->save();
|
$noteWithOnlyImage->save();
|
||||||
$noteWithOnlyImage->media()->save($media);
|
$noteWithOnlyImage->media()->attach($media->id, [
|
||||||
|
'alt_text' => 'Test alt text',
|
||||||
|
]);
|
||||||
DB::table('notes')
|
DB::table('notes')
|
||||||
->where('id', $noteWithOnlyImage->id)
|
->where('id', $noteWithOnlyImage->id)
|
||||||
->update(['updated_at' => $now->toDateTimeString()]);
|
->update(['updated_at' => $now->toDateTimeString()]);
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
services:
|
services:
|
||||||
laravel.test:
|
laravel.test:
|
||||||
build:
|
build:
|
||||||
context: ./vendor/laravel/sail/runtimes/8.4
|
context: ./vendor/laravel/sail/runtimes/8.5
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
WWWGROUP: '${WWWGROUP}'
|
WWWGROUP: '${WWWGROUP}'
|
||||||
image: sail-8.4/app
|
image: sail-8.5/app
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- 'host.docker.internal:host-gateway'
|
- 'host.docker.internal:host-gateway'
|
||||||
ports:
|
ports:
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue