From 2dd3903a4ea1e23ce8b2c9d99b79d32ad1b8cf46 Mon Sep 17 00:00:00 2001 From: boban Date: Tue, 21 Apr 2026 02:13:15 +0200 Subject: [PATCH] feat(api): API-Token-Verwaltung & GET /events/{id} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pro-User können in den Einstellungen (Tab „API") benannte Tokens erstellen und widerrufen. Feature api_access im FeatureSeeder als Pro-Feature ergänzt. Neuer GET /events/{id} Endpunkt im EventController. Co-Authored-By: Claude Sonnet 4.6 --- .../Http/Controllers/Api/EventController.php | 10 ++ src/app/Livewire/Settings/Index.php | 29 +++++ src/database/seeders/FeatureSeeder.php | 3 +- .../views/livewire/settings/index.blade.php | 106 ++++++++++++++++++ src/routes/api.php | 1 + 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/app/Http/Controllers/Api/EventController.php b/src/app/Http/Controllers/Api/EventController.php index bac1d95..2b879d1 100644 --- a/src/app/Http/Controllers/Api/EventController.php +++ b/src/app/Http/Controllers/Api/EventController.php @@ -67,6 +67,16 @@ class EventController extends Controller ], 201); } + public function show(Request $request, string $id): JsonResponse + { + $event = $request->user()->events()->with('contacts')->findOrFail($id); + + return response()->json([ + 'success' => true, + 'data' => $event, + ]); + } + public function update(Request $request, string $id): JsonResponse { $event = $request->user()->events()->findOrFail($id); diff --git a/src/app/Livewire/Settings/Index.php b/src/app/Livewire/Settings/Index.php index dab9481..78c7c99 100644 --- a/src/app/Livewire/Settings/Index.php +++ b/src/app/Livewire/Settings/Index.php @@ -44,6 +44,10 @@ class Index extends Component public string $new_password = ''; public string $new_password_confirmation = ''; + // ── API-Token ───────────────────────────────────────────────────────── + public string $newTokenName = ''; + public ?string $createdToken = null; + protected $listeners = [ 'smtp:test' => 'testSmtp', ]; @@ -326,6 +330,31 @@ class Index extends Component && ($this->smtp_password || isset(auth()->user()->settings['smtp_password']))); } + public function getApiTokensProperty() + { + return auth()->user()->tokens()->latest()->get(); + } + + public function createApiToken(): void + { + $this->validate(['newTokenName' => 'required|string|max:80']); + + $plain = \Illuminate\Support\Str::random(64); + auth()->user()->tokens()->create([ + 'token' => hash('sha256', $plain), + 'name' => $this->newTokenName, + ]); + + $this->createdToken = $plain; + $this->newTokenName = ''; + } + + public function revokeApiToken(string $id): void + { + auth()->user()->tokens()->where('id', $id)->delete(); + $this->createdToken = null; + } + public function render() { $affiliate = auth()->user()->affiliate; diff --git a/src/database/seeders/FeatureSeeder.php b/src/database/seeders/FeatureSeeder.php index 506b131..fea2c10 100644 --- a/src/database/seeders/FeatureSeeder.php +++ b/src/database/seeders/FeatureSeeder.php @@ -45,6 +45,7 @@ class FeatureSeeder extends Seeder ['key' => 'ai_agent', 'label' => 'KI-Assistent', 'icon' => 'heroicon-o-sparkles', 'group' => $productivity, 'sort' => 3], ['key' => 'calendar_sync', 'label' => 'Kalender-Synchronisierung', 'icon' => 'heroicon-o-arrow-path', 'group' => $integration, 'sort' => 1], ['key' => 'automations', 'label' => 'Automationen', 'icon' => 'heroicon-o-bolt', 'group' => $integration, 'sort' => 2], + ['key' => 'api_access', 'label' => 'API-Zugang', 'icon' => 'heroicon-o-code-bracket', 'group' => $integration, 'sort' => 3], ['key' => 'speed', 'label' => 'GPT-4o Modell (schneller & präziser)', 'icon' => 'heroicon-o-bolt', 'group' => $performance, 'sort' => 1], ]; @@ -69,7 +70,7 @@ class FeatureSeeder extends Seeder $freeFeatures = ['calendar', 'reminders', 'tasks', 'notes', 'contacts', 'ai_agent']; // Features die nur Pro bekommt - $proFeatures = ['calendar_sync', 'automations', 'speed']; + $proFeatures = ['calendar_sync', 'automations', 'api_access', 'speed']; // Bestehende Verknüpfungen leeren DB::table('feature_plan')->delete(); diff --git a/src/resources/views/livewire/settings/index.blade.php b/src/resources/views/livewire/settings/index.blade.php index 05a7ffc..093af2b 100644 --- a/src/resources/views/livewire/settings/index.blade.php +++ b/src/resources/views/livewire/settings/index.blade.php @@ -20,6 +20,7 @@ 'smtp' => ['label' => t('settings.tab.smtp'), 'icon' => 'envelope'], 'credits' => ['label' => t('settings.tab.credits'), 'icon' => 'bolt'], ...(!auth()->user()->isInternalUser() ? ['affiliate' => ['label' => 'Affiliate', 'icon' => 'gift']] : []), + 'api' => ['label' => 'API', 'icon' => 'code-bracket'], 'account' => ['label' => t('settings.tab.account'), 'icon' => 'shield-exclamation'], ] as $key => $tab) + + @error('newTokenName')

{{ $message }}

@enderror + + + @if($createdToken) +
+

Token wurde erstellt — kopiere ihn jetzt, er wird nur einmal angezeigt.

+
+ {{ $createdToken }} + +
+
+ @endif + @else +
+ +
+

Pro-Feature

+

API-Tokens sind für Pro-Nutzer verfügbar.

+
+
+ @endif + + {{-- Bestehende Tokens --}} + @if($this->apiTokens->isNotEmpty()) +
+ +
+ + + + + + + + + + + @foreach($this->apiTokens as $token) + + + + + + + @endforeach + +
NameErstelltZuletzt verwendet
{{ $token->name ?? 'Unbenannt' }}{{ $token->created_at->format('d.m.Y') }}{{ $token->last_used_at?->diffForHumans() ?? '—' }} + +
+
+
+ + {{-- API-Referenz --}} +
+

Verwendung

+ Authorization: Bearer <dein-token> + GET https://api.aziros.com/v1/events?from=2026-01-01&to=2026-12-31 +
+ @endif + + + + + {{-- ════════════════════════════════════════════════════════════ --}} {{-- TAB: GEFAHRENZONE --}} {{-- ════════════════════════════════════════════════════════════ --}} diff --git a/src/routes/api.php b/src/routes/api.php index 5f7bad2..755edbf 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -57,6 +57,7 @@ Route::prefix('v1')->group(function () { // Kalender Route::get('/events', [EventController::class, 'index']); Route::post('/events', [EventController::class, 'store']); + Route::get('/events/{id}', [EventController::class, 'show']); Route::put('/events/{id}', [EventController::class, 'update']); Route::delete('/events/{id}', [EventController::class, 'destroy']);