Skip to content

Commit 7901557

Browse files
committed
wip
1 parent 4a3e3ce commit 7901557

File tree

18 files changed

+152
-90
lines changed

18 files changed

+152
-90
lines changed

app/Console/Commands/SyncArticleImages.php

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
namespace App\Console\Commands;
44

5+
use App\Jobs\SyncArticleImage;
56
use App\Models\Article;
67
use Illuminate\Console\Command;
7-
use Illuminate\Support\Facades\Http;
88

99
final class SyncArticleImages extends Command
1010
{
@@ -22,46 +22,8 @@ public function handle(): void
2222

2323
Article::unsyncedImages()->chunk(100, function ($articles) {
2424
$articles->each(function ($article) {
25-
$imageData = $this->fetchUnsplashImageDataFromId($article);
26-
27-
if (! is_null($imageData)) {
28-
$article->hero_image_url = $imageData['image_url'];
29-
$article->hero_image_author_name = $imageData['author_name'];
30-
$article->hero_image_author_url = $imageData['author_url'];
31-
$article->save();
32-
} else {
33-
$this->warn("Failed to fetch image data for image {$article->hero_image_id}");
34-
}
25+
SyncArticleImage::dispatch($article);
3526
});
3627
});
3728
}
38-
39-
protected function fetchUnsplashImageDataFromId(Article $article): ?array
40-
{
41-
$response = Http::retry(3, 100, throw: false)
42-
->withToken(config('services.unsplash.access_key'), 'Client-ID')
43-
->get("https://api.unsplash.com/photos/{$article->hero_image_id}");
44-
45-
if ($response->failed()) {
46-
$article->hero_image_id = null;
47-
$article->save();
48-
49-
$this->warn("Failed to fetch image data for image {$article->hero_image_id}");
50-
51-
return null;
52-
}
53-
54-
$response = $response->json();
55-
56-
// Trigger as Unsplash download...
57-
Http::retry(3, 100, throw: false)
58-
->withToken(config('services.unsplash.access_key'), 'Client-ID')
59-
->get($response['links']['download_location']);
60-
61-
return [
62-
'image_url' => $response['urls']['raw'],
63-
'author_name' => $response['user']['name'],
64-
'author_url' => $response['user']['links']['html'],
65-
];
66-
}
6729
}

app/Http/Controllers/Admin/UsersController.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function unban(User $user): RedirectResponse
6464

6565
public function verifyAuthor(User $user)
6666
{
67-
$this->authorize(UserPolicy::VERIFY_AUTHOR, $user);
67+
$this->authorize(UserPolicy::ADMIN, $user);
6868

6969
$this->dispatchSync(new VerifyAuthor($user));
7070

@@ -74,9 +74,8 @@ public function verifyAuthor(User $user)
7474
}
7575

7676
public function unverifyAuthor(User $user)
77-
7877
{
79-
$this->authorize(UserPolicy::VERIFY_AUTHOR, $user);
78+
$this->authorize(UserPolicy::ADMIN, $user);
8079

8180
$this->dispatchSync(new UnverifyAuthor($user));
8281

app/Http/Requests/ArticleRequest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Models\User;
66
use App\Rules\HttpImageRule;
77
use Illuminate\Http\Concerns\InteractsWithInput;
8+
use Illuminate\Validation\Rules\RequiredIf;
89

910
class ArticleRequest extends Request
1011
{
@@ -14,6 +15,7 @@ public function rules(): array
1415
{
1516
return [
1617
'title' => ['required', 'max:100'],
18+
'hero_image_id' => ['nullable', new RequiredIf(auth()->user()->isVerifiedAuthor())],
1719
'body' => ['required', new HttpImageRule],
1820
'tags' => 'array|nullable',
1921
'tags.*' => 'exists:tags,id',
@@ -58,4 +60,9 @@ public function shouldBeSubmitted(): bool
5860
{
5961
return $this->boolean('submitted');
6062
}
63+
64+
public function heroImageId(): ?string
65+
{
66+
return $this->get('hero_image_id');
67+
}
6168
}

app/Jobs/CreateArticle.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public function __construct(
2020
private string $body,
2121
private User $author,
2222
private bool $shouldBeSubmitted,
23+
private ?string $heroImageId = null,
2324
array $options = []
2425
) {
2526
$this->originalUrl = $options['original_url'] ?? null;
@@ -34,6 +35,7 @@ public static function fromRequest(ArticleRequest $request, UuidInterface $uuid)
3435
$request->body(),
3536
$request->author(),
3637
$request->shouldBeSubmitted(),
38+
$request->heroImageId(),
3739
[
3840
'original_url' => $request->originalUrl(),
3941
'tags' => $request->tags(),
@@ -46,6 +48,7 @@ public function handle(): void
4648
$article = new Article([
4749
'uuid' => $this->uuid->toString(),
4850
'title' => $this->title,
51+
'hero_image_id' => $this->heroImageId,
4952
'body' => $this->body,
5053
'original_url' => $this->originalUrl,
5154
'slug' => $this->title,
@@ -55,6 +58,10 @@ public function handle(): void
5558
$article->authoredBy($this->author);
5659
$article->syncTags($this->tags);
5760

61+
if ($article->hero_image_id) {
62+
SyncArticleImage::dispatch($article);
63+
}
64+
5865
if ($article->isAwaitingApproval()) {
5966
event(new ArticleWasSubmittedForApproval($article));
6067
}

app/Jobs/SyncArticleImage.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Models\Article;
6+
use Illuminate\Contracts\Queue\ShouldQueue;
7+
use Illuminate\Foundation\Bus\Dispatchable;
8+
use Illuminate\Foundation\Queue\Queueable;
9+
use Illuminate\Queue\InteractsWithQueue;
10+
use Illuminate\Queue\SerializesModels;
11+
use Illuminate\Support\Facades\Http;
12+
13+
final class SyncArticleImage implements ShouldQueue
14+
{
15+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
16+
17+
public function __construct(public Article $article)
18+
{
19+
//
20+
}
21+
22+
public function handle(): void
23+
{
24+
$imageData = $this->fetchUnsplashImageDataFromId($this->article);
25+
26+
if (! is_null($imageData)) {
27+
$this->article->hero_image_url = $imageData['image_url'];
28+
$this->article->hero_image_author_name = $imageData['author_name'];
29+
$this->article->hero_image_author_url = $imageData['author_url'];
30+
$this->article->save();
31+
}
32+
}
33+
34+
protected function fetchUnsplashImageDataFromId(Article $article): ?array
35+
{
36+
$response = Http::retry(3, 100, throw: false)
37+
->withToken(config('services.unsplash.access_key'), 'Client-ID')
38+
->get("https://api.unsplash.com/photos/{$article->hero_image_id}");
39+
40+
if ($response->failed()) {
41+
$article->hero_image_id = null;
42+
$article->save();
43+
44+
return null;
45+
}
46+
47+
$response = $response->json();
48+
49+
// Trigger as Unsplash download...
50+
Http::retry(3, 100, throw: false)
51+
->withToken(config('services.unsplash.access_key'), 'Client-ID')
52+
->get($response['links']['download_location']);
53+
54+
return [
55+
'image_url' => $response['urls']['raw'],
56+
'author_name' => $response['user']['name'],
57+
'author_url' => $response['user']['links']['html'],
58+
];
59+
}
60+
}

app/Jobs/UpdateArticle.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public function __construct(
1717
private string $title,
1818
private string $body,
1919
private bool $shouldBeSubmitted,
20+
private ?string $heroImageId = null,
2021
array $options = []
2122
) {
2223
$this->originalUrl = $options['original_url'] ?? null;
@@ -30,6 +31,7 @@ public static function fromRequest(Article $article, ArticleRequest $request): s
3031
$request->title(),
3132
$request->body(),
3233
$request->shouldBeSubmitted(),
34+
$request->heroImageId(),
3335
[
3436
'original_url' => $request->originalUrl(),
3537
'tags' => $request->tags(),
@@ -39,9 +41,12 @@ public static function fromRequest(Article $article, ArticleRequest $request): s
3941

4042
public function handle(): void
4143
{
44+
$originalImage = $this->article->hero_image_id;
45+
4246
$this->article->update([
4347
'title' => $this->title,
4448
'body' => $this->body,
49+
'hero_image_id' => $this->heroImageId,
4550
'original_url' => $this->originalUrl,
4651
'slug' => $this->title,
4752
]);
@@ -54,6 +59,10 @@ public function handle(): void
5459
}
5560

5661
$this->article->syncTags($this->tags);
62+
63+
if ($this->article->hero_image_id !== $originalImage) {
64+
SyncArticleImage::dispatch($this->article);
65+
}
5766
}
5867

5968
private function shouldUpdateSubmittedAt(): bool

app/Models/User.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ public function type(): int
151151
return (int) $this->type;
152152
}
153153

154+
public function isRegularUser(): bool
155+
{
156+
return $this->type() === self::DEFAULT;
157+
}
158+
154159
public function isModerator(): bool
155160
{
156161
return $this->type() === self::MODERATOR;
@@ -300,11 +305,9 @@ public function delete()
300305
parent::delete();
301306
}
302307

303-
// === Verified Author ===
304-
305308
public function isVerifiedAuthor(): bool
306309
{
307-
return !is_null($this->verified_author_at);
310+
return ! is_null($this->author_verified_at);
308311
}
309312

310313
public function isNotVerifiedAuthor(): bool
@@ -314,14 +317,14 @@ public function isNotVerifiedAuthor(): bool
314317

315318
public function verifyAuthor(): void
316319
{
317-
$this->verified_author_at = now();
320+
$this->author_verified_at = now();
318321
$this->save();
319322
}
320323

321324

322325
public function unverifyAuthor(): void
323326
{
324-
$this->verified_author_at = null;
327+
$this->author_verified_at = null;
325328
$this->save();
326329
}
327330

@@ -333,7 +336,6 @@ public function unverifyAuthor(): void
333336
*
334337
* @return bool True if under the daily limit, false otherwise
335338
*/
336-
337339
public function verifiedAuthorCanPublishMoreToday(): bool
338340
{
339341
$limit = 2; // Default limit for verified authors
@@ -342,12 +344,10 @@ public function verifiedAuthorCanPublishMoreToday(): bool
342344
}
343345
$publishedTodayCount = $this->articles()
344346
->whereDate('submitted_at', today())
345-
->where('submitted_at', '>', $this->verified_author_at)->count(); // to ensure we only count articles published after verify the author
347+
->where('submitted_at', '>', $this->author_verified_at)->count(); // to ensure we only count articles published after verify the author
346348
return $publishedTodayCount < $limit;
347349
}
348350

349-
// === End Verified Author ===
350-
351351
public function countSolutions(): int
352352
{
353353
return $this->replyAble()->isSolution()->count();

app/Policies/UserPolicy.php

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,23 @@ final class UserPolicy
1414

1515
const DELETE = 'delete';
1616

17-
const VERIFY_AUTHOR = 'verifyAuthor';
18-
19-
2017
public function admin(User $user): bool
2118
{
2219
return $user->isAdmin() || $user->isModerator();
2320
}
2421

2522
public function ban(User $user, User $subject): bool
2623
{
27-
return ($user->isAdmin() && ! $subject->isAdmin()) ||
28-
($user->isModerator() && ! $subject->isAdmin() && ! $subject->isModerator());
29-
}
24+
if ($subject->isAdmin()) {
25+
return false;
26+
}
3027

31-
public function verifyAuthor(User $user, User $subject): bool
32-
{
33-
return ($user->isAdmin() && ! $subject->isAdmin()) ||
34-
($user->isModerator() && ! $subject->isAdmin() && ! $subject->isModerator());
28+
return $user->isAdmin() || ($user->isModerator() && ! $subject->isModerator());
3529
}
3630

3731
public function block(User $user, User $subject): bool
3832
{
39-
return ! $user->is($subject) && ! $subject->isModerator() && ! $subject->isAdmin();
33+
return ! $user->is($subject) && $subject->isRegularUser();
4034
}
4135

4236
public function delete(User $user, User $subject): bool

database/migrations/2025_06_14_222049_add_verified_author_at_to_users_table.php

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,12 @@
66

77
return new class extends Migration
88
{
9-
/**
10-
* Run the migrations.
11-
*/
129
public function up(): void
1310
{
1411
Schema::table('users', function (Blueprint $table) {
15-
$table->timestamp('verified_author_at')
12+
$table->timestamp('author_verified_at')
1613
->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');
14+
->after('email_verified_at');
2915
});
3016
}
3117
};

database/seeders/UserSeeder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public function run(): void
2020
'github_username' => 'driesvints',
2121
'password' => bcrypt('password'),
2222
'type' => User::ADMIN,
23+
'author_verified_at' => now(),
2324
]);
2425

2526
User::factory()->createQuietly([

0 commit comments

Comments
 (0)