feat: announcement

This commit is contained in:
2026-06-24 17:52:59 +03:30
parent b24af706a0
commit 40b7efac2f
4 changed files with 167 additions and 0 deletions
@@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers;
use App\Models\Announcement;
use App\Traits\HandlesImageUpload;
use Illuminate\Http\Request;
class AnnouncementController extends Controller
{
use HandlesImageUpload;
// Admin list — every announcement, newest first.
public function index()
{
return response()->json([
'data' => Announcement::with('image')->latest()->get(),
]);
}
// App side: only the single latest currently-active announcement (or null).
public function latest()
{
$announcement = Announcement::with('image')
->active()
->orderByDesc('start_date')
->latest()
->first();
return response()->json(['data' => $announcement]);
}
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'link' => 'nullable|string|max:500',
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'image_id' => 'nullable|exists:images,id',
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
]);
// An uploaded image file takes precedence over a provided image_id.
$data['image_id'] = $this->uploadedImageId($request) ?? ($data['image_id'] ?? null);
$announcement = Announcement::create($data);
return response()->json([
'message' => 'Announcement created successfully',
'data' => $announcement->load('image'),
], 201);
}
public function show($id)
{
return response()->json(
Announcement::with('image')->findOrFail($id)
);
}
public function update(Request $request, $id)
{
$announcement = Announcement::findOrFail($id);
$data = $request->validate([
'title' => 'sometimes|string|max:255',
'description' => 'nullable|string',
'link' => 'nullable|string|max:500',
'start_date' => 'nullable|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'image_id' => 'nullable|exists:images,id',
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
]);
// An uploaded image file takes precedence over a provided image_id.
if (($uploadedImageId = $this->uploadedImageId($request)) !== null) {
$data['image_id'] = $uploadedImageId;
}
$announcement->update($data);
return response()->json([
'message' => 'Announcement updated successfully',
'data' => $announcement->load('image'),
]);
}
public function destroy($id)
{
$announcement = Announcement::findOrFail($id);
$announcement->delete();
return response()->json(['message' => 'Announcement deleted successfully']);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Announcement extends Model
{
protected $fillable = [
'image_id', 'title', 'description', 'link', 'start_date', 'end_date',
];
protected $casts = [
'start_date' => 'datetime',
'end_date' => 'datetime',
];
public function image(): BelongsTo
{
return $this->belongsTo(Image::class);
}
// Currently within its [start_date, end_date] window (null bounds = open-ended).
public function scopeActive($query)
{
$now = now();
return $query
->where(fn ($q) => $q->whereNull('start_date')->orWhere('start_date', '<=', $now))
->where(fn ($q) => $q->whereNull('end_date')->orWhere('end_date', '>=', $now));
}
}
@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('announcements', function (Blueprint $table) {
$table->id();
$table->foreignId('image_id')->nullable()->constrained('images')->nullOnDelete();
$table->string('title');
$table->text('description')->nullable();
$table->string('link')->nullable();
$table->dateTime('start_date')->nullable();
$table->dateTime('end_date')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('announcements');
}
};
+10
View File
@@ -18,6 +18,7 @@
use App\Http\Controllers\FaqController;
use App\Http\Controllers\FaqCategoryController;
use App\Http\Controllers\SliderController;
use App\Http\Controllers\AnnouncementController;
use App\Http\Controllers\SceneController;
use App\Http\Controllers\ThemeController;
use App\Http\Controllers\BellSoundController;
@@ -198,6 +199,15 @@
Route::post('/slider/{id}', [SliderController::class, 'update']); // update slider (multipart: image upload)
Route::delete('/slider/{id}', [SliderController::class, 'destroy']); // delete slider
/// announcements
Route::get('/announcements/latest', [AnnouncementController::class, 'latest']); // single latest active (app)
Route::get('/announcements', [AnnouncementController::class, 'index']); // list all (admin)
Route::post('/announcements', [AnnouncementController::class, 'store']); // create
Route::get('/announcements/{id}', [AnnouncementController::class, 'show']); // single
Route::put('/announcements/{id}', [AnnouncementController::class, 'update']); // update (json)
Route::post('/announcements/{id}', [AnnouncementController::class, 'update']); // update (multipart: image)
Route::delete('/announcements/{id}', [AnnouncementController::class, 'destroy']); // delete
/// themes (color themes a scene can use)
Route::get('/themes', [ThemeController::class, 'index']);