diff --git a/app/Events/ArticleApproved.php b/app/Events/ArticleApproved.php new file mode 100644 index 0000000..1e9ec9b --- /dev/null +++ b/app/Events/ArticleApproved.php @@ -0,0 +1,17 @@ +orderBy('created_at', 'desc') ->paginate(15); - return view('pages.articles.index', compact('articles')); + $publishingApprovalsEnabled = Setting::isPublishingApprovalsEnabled(); + + return view('pages.articles.index', compact('articles', 'publishingApprovalsEnabled')); + } + + public function approve(Article $article): RedirectResponse + { + $article->approve('manual'); + + return redirect()->back()->with('success', 'Article approved and queued for publishing.'); + } + + public function reject(Article $article): RedirectResponse + { + $article->reject('manual'); + + return redirect()->back()->with('success', 'Article rejected.'); } } diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 2952565..c8aae7d 100644 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -12,17 +12,20 @@ class SettingsController extends Controller public function index(): View { $articleProcessingEnabled = Setting::isArticleProcessingEnabled(); + $publishingApprovalsEnabled = Setting::isPublishingApprovalsEnabled(); - return view('pages.settings.index', compact('articleProcessingEnabled')); + return view('pages.settings.index', compact('articleProcessingEnabled', 'publishingApprovalsEnabled')); } public function update(Request $request): RedirectResponse { $request->validate([ 'article_processing_enabled' => 'boolean', + 'enable_publishing_approvals' => 'boolean', ]); Setting::setArticleProcessingEnabled($request->boolean('article_processing_enabled')); + Setting::setPublishingApprovalsEnabled($request->boolean('enable_publishing_approvals')); // If redirected from onboarding, go to dashboard if ($request->get('from') === 'onboarding') { diff --git a/app/Listeners/PublishApprovedArticle.php b/app/Listeners/PublishApprovedArticle.php new file mode 100644 index 0000000..8ff3e31 --- /dev/null +++ b/app/Listeners/PublishApprovedArticle.php @@ -0,0 +1,27 @@ +article; + + // Skip if already has publication (prevents duplicate processing) + if ($article->articlePublication()->exists()) { + return; + } + + // Only publish if the article is valid and approved + if ($article->isValid() && $article->isApproved()) { + event(new ArticleReadyToPublish($article)); + } + } +} diff --git a/app/Listeners/ValidateArticleListener.php b/app/Listeners/ValidateArticleListener.php index fa76c3f..842d303 100644 --- a/app/Listeners/ValidateArticleListener.php +++ b/app/Listeners/ValidateArticleListener.php @@ -4,6 +4,7 @@ use App\Events\NewArticleFetched; use App\Events\ArticleReadyToPublish; +use App\Models\Setting; use App\Services\Article\ValidationService; use Illuminate\Contracts\Queue\ShouldQueue; @@ -32,7 +33,17 @@ public function handle(NewArticleFetched $event): void return; } - event(new ArticleReadyToPublish($article)); + // Check if approval system is enabled + if (Setting::isPublishingApprovalsEnabled()) { + // If approvals are enabled, only proceed if article is approved + if ($article->isApproved()) { + event(new ArticleReadyToPublish($article)); + } + // If not approved, article will wait for manual approval + } else { + // If approvals are disabled, proceed with publishing + event(new ArticleReadyToPublish($article)); + } } } } diff --git a/app/Models/Article.php b/app/Models/Article.php index 521f392..b5f87dc 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Events\ArticleApproved; use App\Events\NewArticleFetched; use Database\Factories\ArticleFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -36,6 +37,9 @@ class Article extends Model 'description', 'is_valid', 'is_duplicate', + 'approval_status', + 'approved_at', + 'approved_by', 'fetched_at', 'validated_at', ]; @@ -48,6 +52,8 @@ public function casts(): array return [ 'is_valid' => 'boolean', 'is_duplicate' => 'boolean', + 'approval_status' => 'string', + 'approved_at' => 'datetime', 'fetched_at' => 'datetime', 'validated_at' => 'datetime', 'created_at' => 'datetime', @@ -68,6 +74,57 @@ public function isValid(): bool return $this->is_valid; } + public function isApproved(): bool + { + return $this->approval_status === 'approved'; + } + + public function isPending(): bool + { + return $this->approval_status === 'pending'; + } + + public function isRejected(): bool + { + return $this->approval_status === 'rejected'; + } + + public function approve(string $approvedBy = null): void + { + $this->update([ + 'approval_status' => 'approved', + 'approved_at' => now(), + 'approved_by' => $approvedBy, + ]); + + // Fire event to trigger publishing + event(new ArticleApproved($this)); + } + + public function reject(string $rejectedBy = null): void + { + $this->update([ + 'approval_status' => 'rejected', + 'approved_at' => now(), + 'approved_by' => $rejectedBy, + ]); + } + + public function canBePublished(): bool + { + if (!$this->isValid()) { + return false; + } + + // If approval system is disabled, auto-approve valid articles + if (!\App\Models\Setting::isPublishingApprovalsEnabled()) { + return true; + } + + // If approval system is enabled, only approved articles can be published + return $this->isApproved(); + } + /** * @return HasOne */ diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 9027ff0..e194892 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -41,4 +41,14 @@ public static function setArticleProcessingEnabled(bool $enabled): void { static::setBool('article_processing_enabled', $enabled); } + + public static function isPublishingApprovalsEnabled(): bool + { + return static::getBool('enable_publishing_approvals', false); + } + + public static function setPublishingApprovalsEnabled(bool $enabled): void + { + static::setBool('enable_publishing_approvals', $enabled); + } } diff --git a/database/migrations/2025_07_10_102123_add_approval_status_to_articles_table.php b/database/migrations/2025_07_10_102123_add_approval_status_to_articles_table.php new file mode 100644 index 0000000..da6adaf --- /dev/null +++ b/database/migrations/2025_07_10_102123_add_approval_status_to_articles_table.php @@ -0,0 +1,32 @@ +enum('approval_status', ['pending', 'approved', 'rejected']) + ->default('pending') + ->after('is_duplicate'); + $table->timestamp('approved_at')->nullable()->after('approval_status'); + $table->string('approved_by')->nullable()->after('approved_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('articles', function (Blueprint $table) { + $table->dropColumn(['approval_status', 'approved_at', 'approved_by']); + }); + } +}; diff --git a/resources/views/pages/articles/index.blade.php b/resources/views/pages/articles/index.blade.php index c014eca..535ce9f 100644 --- a/resources/views/pages/articles/index.blade.php +++ b/resources/views/pages/articles/index.blade.php @@ -15,7 +15,9 @@ ID URL Status + Approval Created At + Actions @@ -33,7 +35,35 @@ {{ $article->articlePublication ? 'Published' : 'Pending' }} + + + {{ ucfirst($article->approval_status) }} + + {{ $article->created_at->format('Y-m-d H:i') }} + + @if($publishingApprovalsEnabled && $article->isValid() && $article->isPending() && !$article->articlePublication) +
+
+ @csrf + +
+
+ @csrf + +
+
+ @elseif($article->isValid() && !$article->articlePublication) + Auto-publishing enabled + @endif + @endforeach diff --git a/resources/views/pages/settings/index.blade.php b/resources/views/pages/settings/index.blade.php index 6591dd4..7c1df5d 100644 --- a/resources/views/pages/settings/index.blade.php +++ b/resources/views/pages/settings/index.blade.php @@ -43,6 +43,30 @@ class="form-checkbox h-5 w-5 text-blue-600"> +
+

Publishing Control

+ +
+
+ +

When enabled, articles will require manual approval before being published to platforms.

+
+
+ +
+
+
+