Skip to content

Commit d34f029

Browse files
Add verified author functionality
- Add verified_author_at column to users table with migration - Add methods to check and manage verified author status in User model - Add admin interface to verify/unverify authors in UsersController - Add publishing limits for verified authors in ArticlesController - Add VerifyAuthor and UnVerifyAuthor jobs for queue processing - Add user policies for verified author management - Add admin view updates for verified author management - Add tests for verified author functionality
1 parent e830f09 commit d34f029

File tree

14 files changed

+258
-6
lines changed

14 files changed

+258
-6
lines changed

app/Http/Controllers/Admin/UsersController.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use App\Jobs\DeleteUser;
1010
use App\Jobs\DeleteUserThreads;
1111
use App\Jobs\UnbanUser;
12+
use App\Jobs\UnVerifyAuthor;
13+
use App\Jobs\VerifyAuthor;
1214
use App\Models\User;
1315
use App\Policies\UserPolicy;
1416
use App\Queries\SearchUsers;
@@ -60,6 +62,29 @@ public function unban(User $user): RedirectResponse
6062
return redirect()->route('profile', $user->username());
6163
}
6264

65+
public function verifyAuthor(User $user)
66+
{
67+
$this->authorize(UserPolicy::VERIFY_AUTHOR, $user);
68+
69+
$this->dispatchSync(new VerifyAuthor($user));
70+
71+
$this->success($user->name() . ' was verified!');
72+
73+
return redirect()->route('admin.users');
74+
}
75+
76+
public function unverifyAuthor(User $user)
77+
78+
{
79+
$this->authorize(UserPolicy::VERIFY_AUTHOR, $user);
80+
81+
$this->dispatchSync(new UnverifyAuthor($user));
82+
83+
$this->success($user->name() . ' was unverified!');
84+
85+
return redirect()->route('admin.users');
86+
}
87+
6388
public function delete(User $user): RedirectResponse
6489
{
6590
$this->authorize(UserPolicy::DELETE, $user);

app/Http/Controllers/Articles/ArticlesController.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,7 @@ public function store(ArticleRequest $request)
115115

116116
$article = Article::findByUuidOrFail($uuid);
117117

118-
$this->success(
119-
$request->shouldBeSubmitted()
120-
? 'Thank you for submitting, unfortunately we can\'t accept every submission. You\'ll only hear back from us when we accept your article.'
121-
: 'Article successfully created!'
122-
);
118+
$this->maybeFlashSuccessMessage($request);
123119

124120
return $request->wantsJson()
125121
? ArticleResource::make($article)
@@ -176,4 +172,15 @@ public function delete(Request $request, Article $article)
176172
? response()->json([], Response::HTTP_NO_CONTENT)
177173
: redirect()->route('articles');
178174
}
175+
176+
private function maybeFlashSuccessMessage(ArticleRequest $request): void
177+
{
178+
if (! $request->author()->verifiedAuthorCanPublishMoreToday()) {
179+
$this->success(
180+
$request->shouldBeSubmitted()
181+
? 'Thank you for submitting, unfortunately we can\'t accept every submission. You\'ll only hear back from us when we accept your article.'
182+
: 'Article successfully created!'
183+
);
184+
}
185+
}
179186
}

app/Jobs/CreateArticle.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public function handle(): void
5050
'original_url' => $this->originalUrl,
5151
'slug' => $this->title,
5252
'submitted_at' => $this->shouldBeSubmitted ? now() : null,
53+
'approved_at' => $this->canBeAutoApproved() ? now() : null,
5354
]);
5455
$article->authoredBy($this->author);
5556
$article->syncTags($this->tags);
@@ -58,4 +59,9 @@ public function handle(): void
5859
event(new ArticleWasSubmittedForApproval($article));
5960
}
6061
}
62+
63+
private function canBeAutoApproved(): bool
64+
{
65+
return $this->shouldBeSubmitted && $this->author->verifiedAuthorCanPublishMoreToday();
66+
}
6167
}

app/Jobs/UnVerifyAuthor.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Models\User;
6+
use Illuminate\Contracts\Queue\ShouldQueue;
7+
use Illuminate\Foundation\Queue\Queueable;
8+
9+
class UnVerifyAuthor implements ShouldQueue
10+
{
11+
use Queueable;
12+
13+
/**
14+
* Create a new job instance.
15+
*/
16+
public function __construct(private User $user)
17+
{
18+
//
19+
}
20+
21+
/**
22+
* Execute the job.
23+
*/
24+
public function handle(): void
25+
{
26+
$this->user->unverifyAuthor();
27+
}
28+
}

app/Jobs/VerifyAuthor.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Models\User;
6+
use Illuminate\Contracts\Queue\ShouldQueue;
7+
use Illuminate\Foundation\Queue\Queueable;
8+
9+
class VerifyAuthor implements ShouldQueue
10+
{
11+
use Queueable;
12+
13+
/**
14+
* Create a new job instance.
15+
*/
16+
public function __construct(private User $user)
17+
{
18+
//
19+
}
20+
21+
/**
22+
* Execute the job.
23+
*/
24+
public function handle(): void
25+
{
26+
$this->user->verifyAuthor();
27+
}
28+
}

app/Models/Article.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public function isShared(): bool
196196

197197
public function isAwaitingApproval(): bool
198198
{
199-
return $this->isSubmitted() && $this->isNotApproved() && $this->isNotDeclined();
199+
return $this->isSubmitted() && $this->isNotApproved() && $this->isNotDeclined() && ! $this->author()->verifiedAuthorCanPublishMoreToday();
200200
}
201201

202202
public function isNotAwaitingApproval(): bool

app/Models/User.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,54 @@ public function delete()
300300
parent::delete();
301301
}
302302

303+
// === Verified Author ===
304+
305+
public function isVerifiedAuthor(): bool
306+
{
307+
return !is_null($this->verified_author_at);
308+
}
309+
310+
public function isNotVerifiedAuthor(): bool
311+
{
312+
return !$this->isVerifiedAuthor();
313+
}
314+
315+
public function verifyAuthor(): void
316+
{
317+
$this->verified_author_at = now();
318+
$this->save();
319+
}
320+
321+
322+
public function unverifyAuthor(): void
323+
{
324+
$this->verified_author_at = null;
325+
$this->save();
326+
}
327+
328+
/**
329+
* Check if the verified author can publish more articles today.
330+
*
331+
* Verified authors are allowed to publish up to 2 articles per day,
332+
* but will start count from the moment they are verified.
333+
*
334+
* @return bool True if under the daily limit, false otherwise
335+
*/
336+
337+
public function verifiedAuthorCanPublishMoreToday(): bool
338+
{
339+
$limit = 2; // Default limit for verified authors
340+
if ($this->isNotVerifiedAuthor()) {
341+
return false;
342+
}
343+
$publishedTodayCount = $this->articles()
344+
->whereDate('submitted_at', today())
345+
->where('submitted_at', '>', $this->verified_author_at)->count(); // to ensure we only count articles published after verify the author
346+
return $publishedTodayCount < $limit;
347+
}
348+
349+
// === End Verified Author ===
350+
303351
public function countSolutions(): int
304352
{
305353
return $this->replyAble()->isSolution()->count();

app/Policies/UserPolicy.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ final class UserPolicy
1414

1515
const DELETE = 'delete';
1616

17+
const VERIFY_AUTHOR = 'verifyAuthor';
18+
19+
1720
public function admin(User $user): bool
1821
{
1922
return $user->isAdmin() || $user->isModerator();
@@ -25,6 +28,12 @@ public function ban(User $user, User $subject): bool
2528
($user->isModerator() && ! $subject->isAdmin() && ! $subject->isModerator());
2629
}
2730

31+
public function verifyAuthor(User $user, User $subject): bool
32+
{
33+
return ($user->isAdmin() && ! $subject->isAdmin()) ||
34+
($user->isModerator() && ! $subject->isAdmin() && ! $subject->isModerator());
35+
}
36+
2837
public function block(User $user, User $subject): bool
2938
{
3039
return ! $user->is($subject) && ! $subject->isModerator() && ! $subject->isAdmin();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('users', function (Blueprint $table) {
15+
$table->timestamp('verified_author_at')
16+
->nullable()
17+
->after('email_verified_at')
18+
->comment('Indicates if the user is a verified author');
19+
});
20+
}
21+
22+
/**
23+
* Reverse the migrations.
24+
*/
25+
public function down(): void
26+
{
27+
Schema::table('users', function (Blueprint $table) {
28+
$table->dropColumn('verified_author_at');
29+
});
30+
}
31+
};

laravel

28 KB
Binary file not shown.

0 commit comments

Comments
 (0)