Skip to content

Commit d4ef10b

Browse files
authored
#115 Добавить отложенные публикации (#262)
* Добавлено поле publish_at * Добавлен global scope PublishedScope для Post * Добавлен тест для PublishedScope * Замена использования created_at на publish_at для Post сущностей * Добавлена проверка $post->publish_at?->isFuture() перед отправкой нотификации
1 parent 70c557d commit d4ef10b

File tree

7 files changed

+112
-12
lines changed

7 files changed

+112
-12
lines changed

app/Models/Post.php

+16-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use App\Models\Concerns\Taggable;
88
use App\Models\Enums\PostTypeEnum;
99
use App\Models\Enums\StatusEnum;
10+
use App\Models\Scopes\PublishedScope;
11+
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
1012
use Illuminate\Database\Eloquent\Builder;
1113
use Illuminate\Database\Eloquent\Factories\HasFactory;
1214
use Illuminate\Database\Eloquent\Model;
@@ -28,6 +30,7 @@
2830
* inherit. The 'type' attribute is used to distinguish between different
2931
* post types in the single 'posts' table.
3032
*/
33+
#[ScopedBy([PublishedScope::class])]
3134
class Post extends Model
3235
{
3336
use AsSource, Chartable, Filterable, HasAuthor, HasFactory, Likeable, LogsActivityFillable, Searchable, Taggable;
@@ -42,17 +45,19 @@ class Post extends Model
4245
'user_id',
4346
'type',
4447
'status',
48+
'publish_at',
4549
];
4650

4751
/**
4852
* @var array
4953
*/
5054
protected $casts = [
51-
'title' => 'string',
52-
'content' => 'string',
53-
'slug' => 'string',
54-
'type' => PostTypeEnum::class,
55-
'status' => StatusEnum::class,
55+
'title' => 'string',
56+
'content' => 'string',
57+
'slug' => 'string',
58+
'type' => PostTypeEnum::class,
59+
'status' => StatusEnum::class,
60+
'publish_at' => 'datetime',
5661
];
5762

5863
protected $attributes = [
@@ -71,6 +76,7 @@ class Post extends Model
7176
'title',
7277
'created_at',
7378
'updated_at',
79+
'publish_at',
7480
];
7581

7682
public static function boot()
@@ -94,6 +100,11 @@ public static function boot()
94100
return;
95101
}
96102

103+
// Не отправляем нотификацию, если публикация запланирована в будущее
104+
if ($post->publish_at?->isFuture()) {
105+
return;
106+
}
107+
97108
TelegramMessage::create()
98109
->to(config('services.telegram-bot-api.channel_id'))
99110
->escapedLine($post->title)

app/Models/Scopes/PublishedScope.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace App\Models\Scopes;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Scope;
8+
9+
class PublishedScope implements Scope
10+
{
11+
public function apply(Builder $builder, Model $model): void
12+
{
13+
$builder->where('publish_at', '<=', now());
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\DB;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
return new class extends Migration
9+
{
10+
/**
11+
* Run the migrations.
12+
*/
13+
public function up(): void
14+
{
15+
if (Schema::hasColumn('posts', 'publish_at')) {
16+
return;
17+
}
18+
19+
Schema::table('posts', function (Blueprint $table) {
20+
$table->timestamp('publish_at')->nullable()->useCurrent();
21+
});
22+
23+
// Проставляем publish_at текущим значением created_at
24+
// Это нужно для согласованности данных в колонках
25+
DB::statement('UPDATE posts SET publish_at = created_at');
26+
}
27+
28+
/**
29+
* Reverse the migrations.
30+
*/
31+
public function down(): void
32+
{
33+
if (! Schema::hasColumn('posts', 'publish_at')) {
34+
return;
35+
}
36+
37+
Schema::table('posts', function (Blueprint $table) {
38+
$table->dropColumn('publish_at');
39+
});
40+
}
41+
};

resources/views/pages/feed.blade.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
<link><![CDATA[{{ config('app.url') }}]]></link>
99
<description><![CDATA[{{ config('site.description') }}]]></description>
1010
<language>{{ config('app.locale') }}</language>
11-
<pubDate>{{ $posts->first()?->created_at->toRssString() }}</pubDate>
11+
<pubDate>{{ $posts->first()?->publish_at->toRssString() }}</pubDate>
1212

1313
@foreach($posts as $post)
1414
<item>
1515
<title><![CDATA[{{ $post->title }}]]></title>
1616
<link>{{ route('post.show', $post) }}</link>
1717
{{-- <description><![CDATA[{!! $post->title !!}]]></description> --}}
1818
<guid isPermaLink="false">{{ $post->id }}</guid>
19-
<pubDate>{{ $post->created_at->toRssString() }}</pubDate>
19+
<pubDate>{{ $post->publish_at->toRssString() }}</pubDate>
2020
<enclosure url="{{ route('cover', ['text' => $post->title]) }}" length="0" type="image/jpg" />
2121
</item>
2222
@endforeach

resources/views/post/list.blade.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ class="position-absolute start-0 end-0 top-0 bottom-0 z-2"></a>
8989

9090
<time
9191
data-controller="tooltip"
92-
title="Опубликовано {{ $post->created_at->format('d.m.Y H:i') }}"
92+
title="Опубликовано {{ $post->publish_at->format('d.m.Y H:i') }}"
9393
class="text-body-secondary ms-auto user-select-none small"
94-
datetime="{{ $post->created_at->toISOString() }}">{{ $post->created_at->diffForHumans() }}</time>
94+
datetime="{{ $post->publish_at->toISOString() }}">{{ $post->publish_at->diffForHumans() }}</time>
9595
</div>
9696
</div>
9797
@empty

resources/views/post/show.blade.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ class="position-absolute top-0 end-0 m-4 btn btn-link link-secondary text-decora
8787

8888
<time
8989
data-controller="tooltip"
90-
title="Опубликовано {{ $post->created_at->format('d.m.Y H:i') }}"
90+
title="Опубликовано {{ $post->publish_at->format('d.m.Y H:i') }}"
9191
class="text-body-secondary ms-auto user-select-none small"
92-
datetime="{{ $post->created_at->toISOString() }}"
93-
itemprop="datePublished">{{ $post->created_at->diffForHumans() }}</time>
92+
datetime="{{ $post->publish_at->toISOString() }}"
93+
itemprop="datePublished">{{ $post->publish_at->diffForHumans() }}</time>
9494
</div>
9595
</div>
9696
</article>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Tests\Unit\Models\Scopes;
4+
5+
use App\Models\Post;
6+
use App\Models\Scopes\PublishedScope;
7+
use App\Models\User;
8+
use Illuminate\Support\Carbon;
9+
use Tests\TestCase;
10+
11+
class PublishedScopeTest extends TestCase
12+
{
13+
protected function setUp(): void
14+
{
15+
parent::setUp();
16+
17+
$testUser = User::factory()->create();
18+
19+
Post::factory()->create(['user_id' => $testUser->id, 'publish_at' => Carbon::now()->subDay()]);
20+
Post::factory()->create(['user_id' => $testUser->id, 'publish_at' => Carbon::now()]);
21+
Post::factory()->create(['user_id' => $testUser->id, 'publish_at' => Carbon::now()->addDay()]);
22+
Post::factory()->create(['user_id' => $testUser->id, 'publish_at' => null]);
23+
}
24+
25+
public function testFiltersCorrectlyForPosts(): void
26+
{
27+
Post::addGlobalScope(new PublishedScope);
28+
29+
$publishedPosts = Post::all();
30+
$this->assertCount(2, $publishedPosts);
31+
$this->assertTrue($publishedPosts->first()->publish_at->lessThan(Carbon::now()));
32+
}
33+
}

0 commit comments

Comments
 (0)