diff --git a/app/Concerns/HasEditLog.php b/app/Concerns/HasEditLog.php
new file mode 100644
index 000000000..8848b4ec1
--- /dev/null
+++ b/app/Concerns/HasEditLog.php
@@ -0,0 +1,42 @@
+ $model->author_id,
+ 'editable_id' => $model->id,
+ 'editable_type' => constant($model::class.'::TABLE') ?? $model::class,
+ 'edited_at' => now(),
+ ]);
+
+ $cacheKey = sprintf('%s-%s', Str::slug($model::class), $model->id);
+ if (Cache::has($cacheKey)) {
+ Cache::forget($cacheKey);
+ }
+ });
+ }
+
+ public function edits(): MorphMany
+ {
+ return $this->morphMany(Edit::class, 'editable');
+ }
+
+ public function getLatestEditAttribute(): ?Edit
+ {
+ $cacheKey = sprintf('%s-%s', Str::slug($this::class), $this->id);
+
+ return Cache::rememberForever($cacheKey, function () {
+ return $this->edits()->latest('edited_at')->first();
+ });
+ }
+}
diff --git a/app/Models/Article.php b/app/Models/Article.php
index ef0f41851..3a21e7644 100644
--- a/app/Models/Article.php
+++ b/app/Models/Article.php
@@ -3,6 +3,7 @@
namespace App\Models;
use App\Concerns\HasAuthor;
+use App\Concerns\HasEditLog;
use App\Concerns\HasLikes;
use App\Concerns\HasSlug;
use App\Concerns\HasTags;
@@ -19,6 +20,7 @@ final class Article extends Model
{
use HasFactory;
use HasAuthor;
+ use HasEditLog;
use HasSlug;
use HasLikes;
use HasTimestamps;
diff --git a/app/Models/Edit.php b/app/Models/Edit.php
new file mode 100644
index 000000000..4227edbfa
--- /dev/null
+++ b/app/Models/Edit.php
@@ -0,0 +1,35 @@
+morphTo();
+ }
+}
diff --git a/app/Models/Reply.php b/app/Models/Reply.php
index 4dacf3f9c..ee7c3022d 100644
--- a/app/Models/Reply.php
+++ b/app/Models/Reply.php
@@ -3,6 +3,7 @@
namespace App\Models;
use App\Concerns\HasAuthor;
+use App\Concerns\HasEditLog;
use App\Concerns\HasLikes;
use App\Concerns\HasTimestamps;
use Illuminate\Database\Eloquent\Builder;
@@ -16,6 +17,7 @@ final class Reply extends Model
{
use HasFactory;
use HasAuthor;
+ use HasEditLog;
use HasLikes;
use HasTimestamps;
diff --git a/app/Models/Thread.php b/app/Models/Thread.php
index 44fa510bb..2babc5f77 100644
--- a/app/Models/Thread.php
+++ b/app/Models/Thread.php
@@ -3,6 +3,7 @@
namespace App\Models;
use App\Concerns\HasAuthor;
+use App\Concerns\HasEditLog;
use App\Concerns\HasLikes;
use App\Concerns\HasSlug;
use App\Concerns\HasTags;
@@ -30,6 +31,7 @@ final class Thread extends Model implements ReplyAble, SubscriptionAble, Feedabl
{
use HasFactory;
use HasAuthor;
+ use HasEditLog;
use HasLikes;
use HasSlug;
use HasTags;
diff --git a/database/factories/EditFactory.php b/database/factories/EditFactory.php
new file mode 100644
index 000000000..b925ab0ba
--- /dev/null
+++ b/database/factories/EditFactory.php
@@ -0,0 +1,44 @@
+faker->randomElement([Article::class, Reply::class, Thread::class]);
+ $editableFactory = call_user_func([$editableClass, 'factory']);
+
+ return [
+ 'author_id' => function () {
+ return User::factory()->create()->id;
+ },
+ 'editable_id' => function () use ($editableFactory) {
+ return $editableFactory->create()->id;
+ },
+ 'editable_type' => function () use ($editableClass) {
+ return constant($editableClass.'::TABLE') ?? $editableClass;
+ },
+ 'edited_at' => $this->faker->dateTimeThisMonth(),
+ ];
+ }
+}
diff --git a/database/migrations/2021_11_05_194336_create_edits_table.php b/database/migrations/2021_11_05_194336_create_edits_table.php
new file mode 100644
index 000000000..8ae7ceb79
--- /dev/null
+++ b/database/migrations/2021_11_05_194336_create_edits_table.php
@@ -0,0 +1,28 @@
+id();
+
+ $table->foreignIdFor(User::class, 'author_id');
+
+ $table->unsignedBigInteger('editable_id');
+ $table->string('editable_type');
+
+ $table->timestamp('edited_at');
+ });
+ }
+}
diff --git a/resources/views/articles/show.blade.php b/resources/views/articles/show.blade.php
index b1ae87cbd..aeae071e9 100644
--- a/resources/views/articles/show.blade.php
+++ b/resources/views/articles/show.blade.php
@@ -84,6 +84,14 @@ class="prose prose-lg text-gray-800 prose-lio"