🗓️ Add MeetupEventFactory, implement rate limiting for calendar downloads, and enhance test coverage

- **Added:** `MeetupEventFactory` for generating test data.
- **Implemented:** Rate limiting (`throttle:calendar`) for `stream-calendar` routes to prevent abuse.
- **Enhanced:** `DownloadMeetupCalendar` controller with validation and cleaner query structure.
- **Added:** Feature tests for calendar downloading, invalid input handling, and rate limiting.
This commit is contained in:
HolgerHatGarKeineNode
2026-01-17 21:18:55 +01:00
parent d3acc365fd
commit da1324adda
5 changed files with 140 additions and 24 deletions

View File

@@ -17,46 +17,50 @@ class DownloadMeetupCalendar extends Controller
public function __invoke(Request $request): Response
{
if ($request->has('meetup')) {
$validated = $request->validate([
'meetup' => ['required', 'integer'],
]);
$meetup = Meetup::query()
->with([
'meetupEvents.meetup',
])
->findOrFail($request->input('meetup'));
->with([
'meetupEvents.meetup',
])
->findOrFail($validated['meetup']);
$events = $meetup->meetupEvents()->where('start', '>=', now())->get();
$image = $meetup->getFirstMediaUrl('logo');
} elseif ($request->has('my')) {
$ids = $request->input('my');
$events = MeetupEvent::query()
->with([
'meetup',
])
->where('start', '>=', now())
->whereHas('meetup', fn($query) => $query->whereIn('meetups.id', $ids))
->get();
->with([
'meetup',
])
->where('start', '>=', now())
->whereHas('meetup', fn ($query) => $query->whereIn('meetups.id', $ids))
->get();
$image = asset('img/einundzwanzig-horizontal.png');
} else {
$events = MeetupEvent::query()
->with([
'meetup',
])
->where('start', '>=', now())
->get();
->with([
'meetup',
])
->where('start', '>=', now())
->get();
$image = asset('img/einundzwanzig-horizontal.png');
}
$entries = [];
foreach ($events as $event) {
$entries[] = Event::create($event->meetup->name)
->uniqueIdentifier(str($event->meetup->name)->slug().$event->id)
->address($event->location ?? __('no location set'))
->description(str_replace(["\r", "\n"], '', $event->description).' Link: '.$event->link)
->image($event->meetup->getFirstMedia('logo') ? $event->meetup->getFirstMediaUrl('logo') : $image)
->startsAt($event->start);
->uniqueIdentifier(str($event->meetup->name)->slug().$event->id)
->address($event->location ?? __('no location set'))
->description(str_replace(["\r", "\n"], '', $event->description).' Link: '.$event->link)
->image($event->meetup->getFirstMedia('logo') ? $event->meetup->getFirstMediaUrl('logo') : $image)
->startsAt($event->start);
}
$calendar = Calendar::create()
->refreshInterval(5)
->event($entries);
->refreshInterval(5)
->event($entries);
return response($calendar->get())
->header('Content-Type', 'text/calendar; charset=utf-8');

View File

@@ -3,11 +3,14 @@
namespace App\Providers;
use App\Support\Carbon;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Events\DiagnosingHealth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Laravel\Nightwatch\Facades\Nightwatch;
@@ -31,6 +34,8 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
$this->configureRateLimiting();
Livewire::setUpdateRoute(function ($handle) {
return Route::post('/livewire/update', $handle)
->middleware(['web', Sample::rate(0)]);
@@ -46,4 +51,14 @@ class AppServiceProvider extends ServiceProvider
Model::preventLazyLoading(app()->environment('local'));
}
/**
* Configure the rate limiters for the application.
*/
protected function configureRateLimiting(): void
{
RateLimiter::for('calendar', function (Request $request) {
return Limit::perMinute(60)->by($request->ip());
});
}
}