Allow heading tags (h1-h6) in ContentSanitizer so news editor headings render
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('enhance_jobs', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('artwork_id')->nullable()->constrained('artworks')->nullOnDelete();
|
||||
|
||||
$table->string('status', 32)->default('pending');
|
||||
$table->string('engine', 64)->default('stub');
|
||||
$table->string('mode', 32)->default('standard');
|
||||
$table->unsignedTinyInteger('scale')->default(2);
|
||||
|
||||
$table->string('source_disk', 64)->nullable();
|
||||
$table->string('source_path')->nullable();
|
||||
$table->string('source_hash', 128)->nullable();
|
||||
|
||||
$table->unsignedInteger('input_width')->nullable();
|
||||
$table->unsignedInteger('input_height')->nullable();
|
||||
$table->unsignedBigInteger('input_filesize')->nullable();
|
||||
$table->string('input_mime', 128)->nullable();
|
||||
|
||||
$table->string('output_disk', 64)->nullable();
|
||||
$table->string('output_path')->nullable();
|
||||
$table->string('output_hash', 128)->nullable();
|
||||
|
||||
$table->unsignedInteger('output_width')->nullable();
|
||||
$table->unsignedInteger('output_height')->nullable();
|
||||
$table->unsignedBigInteger('output_filesize')->nullable();
|
||||
$table->string('output_mime', 128)->nullable();
|
||||
|
||||
$table->string('preview_disk', 64)->nullable();
|
||||
$table->string('preview_path')->nullable();
|
||||
|
||||
$table->unsignedInteger('processing_seconds')->nullable();
|
||||
$table->text('error_message')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
|
||||
$table->timestamp('queued_at')->nullable();
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('finished_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['user_id', 'status']);
|
||||
$table->index(['status', 'created_at']);
|
||||
$table->index(['artwork_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('enhance_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Add a composite unique index on (article_id, ip) to news_views so that
|
||||
* duplicate view inserts at the DB level are impossible even if the session
|
||||
* guard is bypassed (e.g. server restart mid-request).
|
||||
*
|
||||
* A separate unique index on (article_id, user_id) is added for logged-in
|
||||
* users, skipping NULL user_ids so guest records don't conflict.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('news_views')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$driver = DB::getDriverName();
|
||||
|
||||
Schema::table('news_views', function (Blueprint $table) use ($driver): void {
|
||||
// De-duplicate existing rows before adding the index.
|
||||
// Keep only the earliest record per (article_id, ip) pair.
|
||||
if ($driver === 'sqlite') {
|
||||
DB::statement("
|
||||
DELETE FROM news_views
|
||||
WHERE ip IS NOT NULL
|
||||
AND id IN (
|
||||
SELECT nv1.id
|
||||
FROM news_views nv1
|
||||
INNER JOIN news_views nv2
|
||||
ON nv2.article_id = nv1.article_id
|
||||
AND nv2.ip = nv1.ip
|
||||
AND nv2.id < nv1.id
|
||||
WHERE nv1.ip IS NOT NULL
|
||||
)
|
||||
");
|
||||
} else {
|
||||
DB::statement("
|
||||
DELETE nv1 FROM news_views nv1
|
||||
INNER JOIN news_views nv2
|
||||
ON nv2.article_id = nv1.article_id
|
||||
AND nv2.ip = nv1.ip
|
||||
AND nv2.id < nv1.id
|
||||
WHERE nv1.ip IS NOT NULL
|
||||
");
|
||||
}
|
||||
|
||||
// De-duplicate by (article_id, user_id) for authenticated users.
|
||||
if ($driver === 'sqlite') {
|
||||
DB::statement("
|
||||
DELETE FROM news_views
|
||||
WHERE user_id IS NOT NULL
|
||||
AND id IN (
|
||||
SELECT nv1.id
|
||||
FROM news_views nv1
|
||||
INNER JOIN news_views nv2
|
||||
ON nv2.article_id = nv1.article_id
|
||||
AND nv2.user_id = nv1.user_id
|
||||
AND nv2.id < nv1.id
|
||||
WHERE nv1.user_id IS NOT NULL
|
||||
)
|
||||
");
|
||||
} else {
|
||||
DB::statement("
|
||||
DELETE nv1 FROM news_views nv1
|
||||
INNER JOIN news_views nv2
|
||||
ON nv2.article_id = nv1.article_id
|
||||
AND nv2.user_id = nv1.user_id
|
||||
AND nv2.id < nv1.id
|
||||
WHERE nv1.user_id IS NOT NULL
|
||||
");
|
||||
}
|
||||
|
||||
if (! $this->indexExists('news_views', 'news_views_article_ip_unique')) {
|
||||
$table->unique(['article_id', 'ip'], 'news_views_article_ip_unique');
|
||||
}
|
||||
|
||||
if (! $this->indexExists('news_views', 'news_views_article_user_unique')) {
|
||||
$table->unique(['article_id', 'user_id'], 'news_views_article_user_unique');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('news_views')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('news_views', function (Blueprint $table): void {
|
||||
if ($this->indexExists('news_views', 'news_views_article_ip_unique')) {
|
||||
$table->dropUnique('news_views_article_ip_unique');
|
||||
}
|
||||
|
||||
if ($this->indexExists('news_views', 'news_views_article_user_unique')) {
|
||||
$table->dropUnique('news_views_article_user_unique');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function indexExists(string $table, string $indexName): bool
|
||||
{
|
||||
if (DB::getDriverName() === 'sqlite') {
|
||||
return collect(DB::select("PRAGMA index_list('{$table}')"))
|
||||
->contains(static fn (object $row): bool => ($row->name ?? null) === $indexName);
|
||||
}
|
||||
|
||||
$indexes = DB::select("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
|
||||
|
||||
return count($indexes) > 0;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('news_article_relations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('news_article_relations', function (Blueprint $table): void {
|
||||
if (! Schema::hasColumn('news_article_relations', 'external_url')) {
|
||||
$table->string('external_url', 2048)->nullable()->after('entity_id');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::table('news_article_relations', function (Blueprint $table): void {
|
||||
$table->unsignedBigInteger('entity_id')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('news_article_relations')) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('news_article_relations')
|
||||
->whereNotNull('external_url')
|
||||
->delete();
|
||||
|
||||
Schema::table('news_article_relations', function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('news_article_relations', 'external_url')) {
|
||||
$table->dropColumn('external_url');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::table('news_article_relations', function (Blueprint $table): void {
|
||||
$table->unsignedBigInteger('entity_id')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user