Initial Commit
tests / ci (push) Canceled after 0s

This commit is contained in:
Mark Gregory
2026-10-15 16:51:54 +01:00
parent d119165ae5
commit 1e06af2105
45 changed files with 127 additions and 9 deletions
+3 -2
View File
@@ -1,6 +1,7 @@
<?php
namespace App\Http\Controllers;
use App\Models\LodgeBooking;
use App\Models\LodgePhoto;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -8,7 +9,7 @@ use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LodgeController extends Controller {
private function nightlyRate(): int { return max(1,(int)config('lodge.nightly_rate_pence',17500)); }
public function home() { return view('lodge.home',['rate'=>$this->nightlyRate()/100]); }
public function home() { return view('lodge.home',['rate'=>$this->nightlyRate()/100,'uploadedPhotos'=>LodgePhoto::latest()->get()]); }
public function availability(Request $request) {
$v=$request->validate(['check_in'=>'required|date|after_or_equal:today','check_out'=>'required|date|after:check_in','guests'=>'required|integer|min:1|max:'.config('lodge.max_guests',6)]);
$start=CarbonImmutable::parse($v['check_in']);$end=CarbonImmutable::parse($v['check_out']);$nights=$start->diffInDays($end);
@@ -29,6 +30,6 @@ class LodgeController extends Controller {
return redirect()->route('lodge.confirmation',['reference'=>$booking->reference]);
}
public function confirmation(string $reference) { $booking=LodgeBooking::where('reference',$reference)->firstOrFail();return view('lodge.confirmation',compact('booking')); }
public function admin(Request $request) { abort_unless($request->user()?->email===config('lodge.admin_email') && filled(config('lodge.admin_email')),403);return view('lodge.admin',['bookings'=>LodgeBooking::latest()->paginate(30)]); }
public function admin(Request $request) { abort_unless($request->user()?->email===config('lodge.admin_email') && filled(config('lodge.admin_email')),403);return view('lodge.admin',['bookings'=>LodgeBooking::latest()->paginate(30),'photos'=>LodgePhoto::latest()->get()]); }
public function update(Request $request,LodgeBooking $booking) {abort_unless($request->user()?->email===config('lodge.admin_email') && filled(config('lodge.admin_email')),403);$v=$request->validate(['status'=>'required|in:pending,confirmed,cancelled']);if($v['status']!=='cancelled' && LodgeBooking::whereKeyNot($booking->id)->whereIn('status',['pending','confirmed'])->where('check_in','<',$booking->check_out)->where('check_out','>',$booking->check_in)->exists()) throw ValidationException::withMessages(['status'=>'Date conflict.']);$booking->update($v);return back()->with('success','Booking updated.'); }
}
@@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers;
use App\Models\LodgePhoto;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LodgePhotoController extends Controller
{
private function authorizeAdmin(Request $request): void
{
abort_unless(filled(config('lodge.admin_email')) && $request->user()?->email === config('lodge.admin_email'), 403);
}
public function store(Request $request)
{
$this->authorizeAdmin($request);
$request->validate([
'photos' => ['required','array','min:1','max:10'],
'photos.*' => ['required','image','mimes:jpeg,png,webp','max:15360'],
]);
if (!extension_loaded('gd') || !function_exists('imagewebp')) {
throw ValidationException::withMessages(['photos' => 'PHP GD with WebP support is required. Install php8.5-gd.']);
}
foreach ($request->file('photos') as $file) {
$raw = file_get_contents($file->getRealPath());
$source = @imagecreatefromstring($raw);
unset($raw);
if (!$source) {
throw ValidationException::withMessages(['photos' => 'One image could not be decoded.']);
}
$width = imagesx($source); $height = imagesy($source);
if ($width < 1 || $height < 1 || $width * $height > 45000000) {
imagedestroy($source);
throw ValidationException::withMessages(['photos' => 'Image dimensions are not supported (maximum 45 megapixels).']);
}
$id = (string) Str::uuid();
$gallery = "lodge/gallery/{$id}.webp";
$thumbnail = "lodge/thumb/{$id}.webp";
try {
$this->saveWebp($source, $width, $height, 1600, 82, $gallery);
$this->saveWebp($source, $width, $height, 480, 77, $thumbnail);
LodgePhoto::create(['gallery_path'=>$gallery,'thumbnail_path'=>$thumbnail,'alt_text'=>'Cresta 31 lodge photograph']);
} catch (\Throwable $e) {
Storage::disk('public')->delete([$gallery,$thumbnail]);
throw $e;
} finally {
imagedestroy($source);
}
}
return back()->with('success', 'Photographs uploaded and optimised.');
}
private function saveWebp($source, int $width, int $height, int $maxWidth, int $quality, string $path): void
{
$scale = min(1, $maxWidth / $width);
$w = max(1, (int) round($width * $scale));
$h = max(1, (int) round($height * $scale));
$target = imagecreatetruecolor($w, $h);
imagefill($target, 0, 0, imagecolorallocate($target, 255, 255, 255));
imagecopyresampled($target, $source, 0, 0, 0, 0, $w, $h, $width, $height);
ob_start();
try {
if (!imagewebp($target, null, $quality)) {
throw new \RuntimeException('WebP conversion failed.');
}
$bytes = ob_get_clean();
} catch (\Throwable $e) {
ob_end_clean();
imagedestroy($target);
throw $e;
}
imagedestroy($target);
if (!Storage::disk('public')->put($path, $bytes)) {
throw new \RuntimeException('Unable to save the optimised image.');
}
}
public function destroy(Request $request, LodgePhoto $photo)
{
$this->authorizeAdmin($request);
$paths = [$photo->gallery_path, $photo->thumbnail_path];
$photo->delete();
Storage::disk('public')->delete($paths);
return back()->with('success', 'Photograph deleted.');
}
}