Skip to content

Commit f3fbc01

Browse files
authored
✨ Make alerts dynamic and retrievable via API (#3467)
1 parent bf554be commit f3fbc01

31 files changed

+5368
-3825
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\API\v1;
4+
5+
use App\Http\Resources\AlertResource;
6+
use App\Models\Alert;
7+
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
8+
use OpenApi\Annotations as OA;
9+
10+
class AlertController extends Controller
11+
{
12+
/**
13+
* @OA\Get(
14+
* path="/alerts",
15+
* summary="Get all active alerts",
16+
* operationId="getActiveAlerts",
17+
* tags={"Notifications"},
18+
* @OA\Response(response=200, description="List of active alerts",@OA\JsonContent(
19+
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/AlertResource"),)
20+
* ))
21+
* )
22+
*/
23+
public function index(): AnonymousResourceCollection {
24+
$now = now()->startOfDay();
25+
26+
$alerts = Alert::with('translations')
27+
->where('active_from', '<=', $now)
28+
->where(function($query) use ($now) {
29+
$query->where('active_until', '>=', $now)
30+
->orWhereNull('active_until');
31+
})
32+
->orderBy('active_from', 'desc')
33+
->orderBy('active_until', 'desc')
34+
->get();
35+
36+
return AlertResource::collection($alerts);
37+
}
38+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Backend\Admin;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Http\Requests\StoreAlertRequest;
7+
use App\Models\Alert;
8+
use Illuminate\Contracts\View\View;
9+
use Illuminate\Http\RedirectResponse;
10+
use Illuminate\Support\Facades\DB;
11+
12+
class AlertController extends Controller
13+
{
14+
public function index(): View
15+
{
16+
$alerts = Alert::with('translations')
17+
->orderByDesc('active_from')
18+
->orderByDesc('active_until')
19+
->paginate(50);
20+
21+
return view('admin.alerts.index', [
22+
'alerts' => $alerts,
23+
]);
24+
}
25+
26+
public function create(): View
27+
{
28+
return view('admin.alerts.show', [
29+
'alert' => null,
30+
]);
31+
}
32+
33+
public function store(StoreAlertRequest $request): RedirectResponse
34+
{
35+
$alert = new Alert();
36+
$this->updateOrCreate($request, $alert);
37+
38+
return redirect()
39+
->route('admin.alerts')
40+
->with('success', __('Alert created successfully.'));
41+
}
42+
43+
public function edit(string $alertId): View
44+
{
45+
$alert = Alert::with('translations')
46+
->where('id', $alertId)
47+
->firstOrFail();
48+
return view('admin.alerts.show', [
49+
'alert' => $alert,
50+
]);
51+
}
52+
53+
public function update(StoreAlertRequest $request, string $id): RedirectResponse
54+
{
55+
$alert = Alert::findOrFail($id);
56+
$this->updateOrCreate($request, $alert);
57+
58+
return redirect()
59+
->route('admin.alerts')
60+
->with('success', __('Alert updated successfully.'));
61+
}
62+
63+
public function destroy(Alert $alert): RedirectResponse
64+
{
65+
$alert->delete();
66+
67+
return redirect()
68+
->route('admin.alerts')
69+
->with('success', __('Alert deleted successfully.'));
70+
}
71+
72+
private function updateOrCreate(StoreAlertRequest $request, Alert $alert): void
73+
{
74+
DB::beginTransaction();
75+
$alert->type = $request->type;
76+
$alert->active_from = $request->active_from;
77+
$alert->active_until = $request->active_until;
78+
$alert->url = $request->url;
79+
$alert->save();
80+
81+
$alert->translations()->updateOrCreate(
82+
['locale' => 'de'],
83+
[
84+
'title' => $request->title_de,
85+
'content' => $request->content_de,
86+
'url' => $request->url_de,
87+
]
88+
);
89+
90+
$alert->translations()->updateOrCreate(
91+
['locale' => 'en'],
92+
[
93+
'title' => $request->title_en,
94+
'content' => $request->content_en,
95+
'url' => $request->url_en,
96+
]
97+
);
98+
99+
DB::commit();
100+
}
101+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
/**
8+
* @property string $type
9+
* @property string $active_from
10+
* @property string $active_until
11+
* @property string $title_de
12+
* @property string $content_de
13+
* @property string $title_en
14+
* @property string $content_en
15+
* @property string $url_de
16+
* @property string $url_en
17+
* @property string $url
18+
*/
19+
class StoreAlertRequest extends FormRequest
20+
{
21+
22+
private const string MAX_255 = 'max:255';
23+
24+
public function rules(): array
25+
{
26+
return [
27+
'type' => ['required', 'string', self::MAX_255],
28+
'active_from' => ['required', 'date'],
29+
'active_until' => ['nullable', 'date', 'after_or_equal:active_from'],
30+
'title_de' => ['required', 'string', self::MAX_255],
31+
'content_de' => ['required', 'string', self::MAX_255],
32+
'title_en' => ['required', 'string', self::MAX_255],
33+
'content_en' => ['required', 'string', self::MAX_255],
34+
'url_de' => ['nullable', 'url', self::MAX_255],
35+
'url_en' => ['nullable', 'url', self::MAX_255],
36+
'url' => ['nullable', 'url', self::MAX_255],
37+
];
38+
}
39+
}

app/Http/Resources/AlertResource.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use App\Models\Alert;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Http\Resources\Json\JsonResource;
8+
use OpenApi\Annotations as OA;
9+
10+
/**
11+
* @OA\Schema(
12+
* schema="AlertResource",
13+
* required={"id", "type", "active_from", "active_until", "url", "translations"},
14+
* @OA\Property(property="id", type="string", example="123e4567-e89b-12d3-a456-426614174000"),
15+
* @OA\Property(property="type", type="enum", enum={"info", "warning", "danger", "success"}, example="info"),
16+
* @OA\Property(property="active_from", type="string", format="date-time", example="2023-10-01T00:00:00Z"),
17+
* @OA\Property(property="active_until", type="string", format="date-time", example="2023-10-31T23:59:59Z", nullable=true),
18+
* @OA\Property(property="url", type="string", example="https://example.com", nullable=true),
19+
* @OA\Property(
20+
* property="translations",
21+
* @OA\Items(ref="#/components/schemas/AlertTranslationResource"),
22+
* type="array",
23+
* )
24+
* )
25+
*/
26+
class AlertResource extends JsonResource
27+
{
28+
public function toArray(Request $request): array {
29+
/** @var Alert $this */
30+
return [
31+
'id' => $this->id,
32+
'type' => $this->type,
33+
'active_from' => $this->active_from,
34+
'active_until' => $this->active_until,
35+
'url' => $this->url,
36+
'translations' => AlertTranslationResource::collection($this->translations),
37+
];
38+
}
39+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use App\Models\AlertTranslation;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Http\Resources\Json\JsonResource;
8+
use OpenApi\Annotations as OA;
9+
10+
/**
11+
* @OA\Schema(
12+
* schema="AlertTranslationResource",
13+
* required={"title", "content", "url", "locale"},
14+
* @OA\Property(property="title", type="string", example="Alert Title"),
15+
* @OA\Property(property="content", type="string", example="Alert Content"),
16+
* @OA\Property(property="url", type="string", example="https://example.com"),
17+
* @OA\Property(property="locale", type="string", example="en"),
18+
* )
19+
*/
20+
class AlertTranslationResource extends JsonResource
21+
{
22+
23+
public function toArray(Request $request): array {
24+
/** @var AlertTranslation $this */
25+
return [
26+
'title' => $this->title,
27+
'content' => $this->content,
28+
'url' => $this->url,
29+
'locale' => $this->locale,
30+
];
31+
}
32+
}

app/Models/Alert.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Carbon\Carbon;
6+
use Illuminate\Database\Eloquent\Concerns\HasUuids;
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
/**
10+
* @property string $id
11+
* @property string $type
12+
* @property string|null $url
13+
* @property Carbon $active_from
14+
* @property Carbon $active_until
15+
* @property Carbon $created_at
16+
* @property Carbon $updated_at
17+
*
18+
* @property-read AlertTranslation[] $translations
19+
*/
20+
class Alert extends Model
21+
{
22+
use HasUuids;
23+
24+
protected $fillable = [
25+
'id',
26+
'type',
27+
'active_from',
28+
'active_until'
29+
];
30+
31+
protected $casts = [
32+
'active_from' => 'datetime',
33+
'active_until' => 'datetime',
34+
];
35+
36+
public function translations() {
37+
return $this->hasMany(AlertTranslation::class, 'alert_id', 'id');
38+
}
39+
}

app/Models/AlertTranslation.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Carbon\Carbon;
6+
use Illuminate\Database\Eloquent\Concerns\HasUuids;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
9+
10+
/**
11+
* @property string $id
12+
* @property string $banner_id
13+
* @property string $locale
14+
* @property string $content
15+
* @property string $title
16+
* @property string|null $url
17+
* @property Carbon $created_at
18+
* @property Carbon $updated_at
19+
*
20+
* @property-read Alert $banner
21+
*/
22+
class AlertTranslation extends Model
23+
{
24+
use HasUuids;
25+
26+
protected $fillable = [
27+
'id',
28+
'banner_id',
29+
'content',
30+
'title',
31+
'locale',
32+
'url',
33+
];
34+
35+
public function banner(): BelongsTo {
36+
return $this->belongsTo(Alert::class);
37+
}
38+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
public function up(): void {
10+
Schema::create('alerts', function(Blueprint $table) {
11+
$table->uuid('id')->primary();
12+
$table->string('type')->default('info');
13+
$table->string('url')->nullable();
14+
$table->date('active_from');
15+
$table->date('active_until')->nullable();
16+
$table->timestamps();
17+
});
18+
}
19+
20+
public function down(): void {
21+
Schema::dropIfExists('alerts');
22+
}
23+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use App\Models\Alert;
4+
use Illuminate\Database\Migrations\Migration;
5+
use Illuminate\Database\Schema\Blueprint;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
return new class extends Migration
9+
{
10+
public function up(): void {
11+
Schema::create('alert_translations', function(Blueprint $table) {
12+
$table->uuid('id')->primary();
13+
$table->foreignIdFor(Alert::class)->constrained('alerts')->cascadeOnDelete();
14+
$table->string('locale', 2);
15+
$table->string('title');
16+
$table->string('content');
17+
$table->string('url')->nullable();
18+
$table->timestamps();
19+
});
20+
}
21+
22+
public function down(): void {
23+
Schema::dropIfExists('alert_translations');
24+
}
25+
};

0 commit comments

Comments
 (0)