mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-app.git
synced 2026-06-11 02:50:29 +00:00
✨ **Enhance API functionality and localizations**
- 🌐 Added API documentation annotations for multiple controllers (Meetups, Cities, Countries, Courses, Highscores, Venues), improving public and developer-facing endpoint clarity. - ➕ Integrated and configured the `dedoc/scramble` package for automated OpenAPI documentation generation. - 🔒 Excluded internal routes and actions from API documentation using `ExcludeRouteFromDocs` attributes. - 🌍 Added new localization keys for API Token features across multiple languages (`lv`, `es`, etc.). - 🛠️ Introduced `Group`, `Response`, and `QueryParameter` attributes for better request descriptions and structured documentation. - 🚀 Enhanced functionality for listing operations in controllers with filters and query parameters like `search` and `selected`.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\LibraryItem;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[Group(name: 'Community', weight: 7)]
|
||||
class BindleController extends Controller
|
||||
{
|
||||
/**
|
||||
* Bindles (Bibliotheks-Einträge) auflisten
|
||||
*
|
||||
* Liefert die Bibliothekseinträge vom Typ 'bindle' mit id, name, link und image.
|
||||
*
|
||||
* @return Collection<int, array{id: int, name: string, link: string, image: string}>
|
||||
*/
|
||||
public function __invoke(): Collection
|
||||
{
|
||||
return LibraryItem::query()
|
||||
->where('type', 'bindle')
|
||||
->with([
|
||||
'media',
|
||||
])
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->map(fn ($item) => [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'link' => strtok($item->value, '?'),
|
||||
'image' => $item->getFirstMediaUrl('main'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Meetup;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
#[Group(name: 'Community', weight: 7)]
|
||||
class BtcMapCommunityController extends Controller
|
||||
{
|
||||
/**
|
||||
* Einundzwanzig-Communities für BTC Map
|
||||
*
|
||||
* Liefert die Einundzwanzig-Communities im BTC-Map-Format (GeoJSON-Tags).
|
||||
*/
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
return response()->json(
|
||||
Meetup::query()
|
||||
->with([
|
||||
'media',
|
||||
'city.country',
|
||||
])
|
||||
->where('community', '=', 'einundzwanzig')
|
||||
->when(
|
||||
app()->environment('production'),
|
||||
fn ($query) => $query->whereHas(
|
||||
'city',
|
||||
fn ($query) => $query
|
||||
->whereNotNull('cities.simplified_geojson')
|
||||
->whereNotNull('cities.population')
|
||||
->whereNotNull('cities.population_date'),
|
||||
),
|
||||
)
|
||||
->get()
|
||||
->map(fn ($meetup) => [
|
||||
'id' => $meetup->slug,
|
||||
'tags' => [
|
||||
'type' => 'community',
|
||||
'name' => $meetup->name,
|
||||
'continent' => 'europe',
|
||||
'icon:square' => $meetup->logoSquare,
|
||||
// 'contact:email' => null,
|
||||
'contact:twitter' => $meetup->twitter_username ? 'https://twitter.com/'.$meetup->twitter_username : null,
|
||||
'contact:website' => $meetup->webpage,
|
||||
'contact:telegram' => $meetup->telegram_link,
|
||||
'contact:nostr' => $meetup->nostr,
|
||||
// 'tips:lightning_address' => null,
|
||||
'organization' => 'einundzwanzig',
|
||||
'language' => $meetup->city->country->language_codes[0] ?? 'de',
|
||||
'geo_json' => $meetup->city->simplified_geojson,
|
||||
'population' => $meetup->city->population,
|
||||
'population:date' => $meetup->city->population_date,
|
||||
],
|
||||
])
|
||||
->toArray(),
|
||||
200,
|
||||
['Content-Type' => 'application/json;charset=UTF-8', 'Charset' => 'utf-8'],
|
||||
JSON_UNESCAPED_SLASHES,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,66 +5,61 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\City;
|
||||
use App\Models\Lecturer;
|
||||
use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
#[Group(name: 'Stammdaten', weight: 5)]
|
||||
class CityController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* @return \Illuminate\Http\Response
|
||||
* Städte auflisten und durchsuchen
|
||||
*
|
||||
* Öffentlicher Endpunkt; liefert id, name und das zugehörige Land, alphabetisch sortiert. Ohne 'selected' wird die Liste auf 10 Einträge begrenzt.
|
||||
*/
|
||||
#[QueryParameter(name: 'search', description: 'Teilstring-Suche im Namen der Stadt.', required: false, type: 'string')]
|
||||
#[QueryParameter(name: 'selected', description: 'Lädt gezielt die angegebenen IDs.', required: false, type: 'array')]
|
||||
public function index(Request $request)
|
||||
{
|
||||
return City::query()
|
||||
->with(['country:id,name'])
|
||||
->select('id', 'name','country_id')
|
||||
->orderBy('name')
|
||||
->when(
|
||||
->with(['country:id,name'])
|
||||
->select('id', 'name', 'country_id')
|
||||
->orderBy('name')
|
||||
->when(
|
||||
$request->search,
|
||||
fn(Builder $query) => $query
|
||||
fn (Builder $query) => $query
|
||||
->where('name', 'ilike', "%{$request->search}%")
|
||||
)
|
||||
->when(
|
||||
$request->exists('selected'),
|
||||
fn(Builder $query) => $query->whereIn('id',
|
||||
$request->input('selected', [])),
|
||||
fn(Builder $query) => $query->limit(10)
|
||||
)
|
||||
->get();
|
||||
->when(
|
||||
$request->exists('selected'),
|
||||
fn (Builder $query) => $query->whereIn('id',
|
||||
$request->input('selected', [])),
|
||||
fn (Builder $query) => $query->limit(10)
|
||||
)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function show(Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function update(Request $request, Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function destroy(Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
|
||||
@@ -4,11 +4,22 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Country;
|
||||
use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
#[Group(name: 'Stammdaten', weight: 5)]
|
||||
class CountryController extends Controller
|
||||
{
|
||||
/**
|
||||
* Länder auflisten und durchsuchen
|
||||
*
|
||||
* Öffentlicher Endpunkt; liefert id, name und code (Ländercode), alphabetisch sortiert. Ohne 'selected' wird das Ergebnis auf 10 Einträge begrenzt. Jedes Land enthält zusätzlich eine 'flag' (SVG-URL).
|
||||
*/
|
||||
#[QueryParameter(name: 'search', description: 'Suche in Name oder Code (Ländercode).', required: false, type: 'string')]
|
||||
#[QueryParameter(name: 'selected', description: 'Lädt gezielt die angegebenen Codes oder IDs.', required: false, type: 'array')]
|
||||
public function index(Request $request)
|
||||
{
|
||||
return Country::query()
|
||||
@@ -16,19 +27,17 @@ class CountryController extends Controller
|
||||
->orderBy('name')
|
||||
->when(
|
||||
$request->search,
|
||||
fn(Builder $query)
|
||||
=> $query
|
||||
fn (Builder $query) => $query
|
||||
->where('name', 'ilike', "%{$request->search}%")
|
||||
->orWhere('code', 'ilike', "%{$request->search}%"),
|
||||
)
|
||||
->when(
|
||||
$request->exists('selected'),
|
||||
fn(Builder $query)
|
||||
=> $query
|
||||
fn (Builder $query) => $query
|
||||
->whereIn('code', $request->input('selected', []))
|
||||
->orWhereIn('id',
|
||||
$request->input('selected', [])),
|
||||
fn(Builder $query) => $query->limit(10),
|
||||
fn (Builder $query) => $query->limit(10),
|
||||
)
|
||||
->get()
|
||||
->map(function (Country $country) {
|
||||
@@ -38,37 +47,25 @@ class CountryController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function show(Country $country)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function update(Request $request, Country $country)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function destroy(Country $country)
|
||||
{
|
||||
//
|
||||
|
||||
@@ -4,17 +4,28 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Course;
|
||||
use App\Models\Lecturer;
|
||||
use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Dedoc\Scramble\Attributes\Response as ResponseAttribute;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[Group(name: 'Kurse', weight: 1)]
|
||||
class CourseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* Kurse auflisten und durchsuchen
|
||||
*
|
||||
* Öffentlicher Endpunkt; liefert id und name, alphabetisch sortiert. Ohne den Parameter
|
||||
* 'selected' wird das Ergebnis auf 10 Einträge begrenzt. Jeder Kurs enthält zusätzlich
|
||||
* ein 'image' (Logo-Thumbnail-URL).
|
||||
*/
|
||||
#[QueryParameter(name: 'search', description: 'Teilstring-Suche im Namen des Kurses.', required: false, type: 'string')]
|
||||
#[QueryParameter(name: 'user_id', description: 'Filtert die Kurse nach ihrem Ersteller.', required: false, type: 'integer')]
|
||||
#[QueryParameter(name: 'selected', description: 'Lädt gezielt die angegebenen Kurs-IDs.', required: false, type: 'array')]
|
||||
public function index(Request $request)
|
||||
{
|
||||
return Course::query()
|
||||
@@ -43,12 +54,11 @@ class CourseController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* Kurs anlegen
|
||||
*
|
||||
* Allows an authenticated lecturer to create a course programmatically
|
||||
* (e.g. to sync courses from an external system). Validation mirrors the
|
||||
* Livewire course create form; `created_by` is set by the model's creating hook.
|
||||
* Erlaubt einem authentifizierten Referenten, einen Kurs programmatisch anzulegen.
|
||||
*/
|
||||
#[ResponseAttribute(status: 403, description: 'Nur Referenten (is_lecturer) dürfen Kurse anlegen.')]
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
abort_unless((bool) $request->user()->is_lecturer, Response::HTTP_FORBIDDEN);
|
||||
@@ -64,19 +74,18 @@ class CourseController extends Controller
|
||||
return response()->json($course->fresh(), Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function show(Course $course)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* Kurs aktualisieren
|
||||
*
|
||||
* Authorized for the course owner (or a super-admin).
|
||||
* Aktualisiert einen Kurs; nur für den Ersteller oder einen Super-Admin.
|
||||
*/
|
||||
#[ResponseAttribute(status: 403, description: 'Nur der Ersteller des Kurses oder ein Super-Admin darf ihn ändern.')]
|
||||
public function update(Request $request, Course $course): JsonResponse
|
||||
{
|
||||
abort_unless(
|
||||
@@ -95,9 +104,7 @@ class CourseController extends Controller
|
||||
return response()->json($course->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function destroy(Course $course)
|
||||
{
|
||||
//
|
||||
|
||||
@@ -4,22 +4,28 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CourseEvent;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Dedoc\Scramble\Attributes\Response as ResponseAttribute;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[Group(name: 'Kurs-Events', weight: 2)]
|
||||
class CourseEventController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the course events created by the authenticated user.
|
||||
* Eigene Kurs-Events auflisten
|
||||
*
|
||||
* Useful for an external sync client to detect which events already exist
|
||||
* (idempotent syncing). Optionally filtered by course_id.
|
||||
* Liefert alle vom authentifizierten Nutzer erstellten Kurs-Events (inkl. zugehörigem
|
||||
* Kurs und Veranstaltungsort), absteigend nach Startdatum. Ideal für idempotente
|
||||
* Synchronisierung durch externe Clients.
|
||||
*
|
||||
* @return Collection<int, CourseEvent>
|
||||
*/
|
||||
#[QueryParameter(name: 'course_id', description: 'Filtert die Kurs-Events auf einen bestimmten Kurs.', required: false, type: 'integer')]
|
||||
public function index(Request $request): Collection
|
||||
{
|
||||
return CourseEvent::query()
|
||||
@@ -34,12 +40,11 @@ class CourseEventController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created course event in storage.
|
||||
* Kurs-Event anlegen
|
||||
*
|
||||
* Allows an authenticated lecturer to create a dated course event
|
||||
* programmatically. Validation mirrors the Livewire course event form;
|
||||
* `created_by` is set by the model's creating hook.
|
||||
* Erlaubt einem authentifizierten Referenten, ein datiertes Kurs-Event programmatisch anzulegen.
|
||||
*/
|
||||
#[ResponseAttribute(status: 403, description: 'Nur Referenten (is_lecturer) dürfen Kurs-Events anlegen.')]
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
abort_unless((bool) $request->user()->is_lecturer, Response::HTTP_FORBIDDEN);
|
||||
@@ -58,10 +63,11 @@ class CourseEventController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified course event in storage.
|
||||
* Kurs-Event aktualisieren
|
||||
*
|
||||
* Authorized for the course event owner (or a super-admin).
|
||||
* Aktualisiert ein Kurs-Event; nur für den Ersteller oder einen Super-Admin.
|
||||
*/
|
||||
#[ResponseAttribute(status: 403, description: 'Nur der Ersteller des Kurs-Events oder ein Super-Admin darf es ändern.')]
|
||||
public function update(Request $request, CourseEvent $courseEvent): JsonResponse
|
||||
{
|
||||
abort_unless(
|
||||
|
||||
@@ -6,6 +6,8 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreHighscoreRequest;
|
||||
use App\Models\Highscore;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use swentel\nostr\Filter\Filter;
|
||||
@@ -15,8 +17,15 @@ use swentel\nostr\Relay\RelaySet;
|
||||
use swentel\nostr\Request\Request;
|
||||
use swentel\nostr\Subscription\Subscription;
|
||||
|
||||
#[Group(name: 'Highscores', weight: 6)]
|
||||
class HighscoreController extends Controller
|
||||
{
|
||||
/**
|
||||
* Highscore-Bestenliste abrufen
|
||||
*
|
||||
* Öffentliche Bestenliste des Spiels, absteigend nach Satoshis (dann nach Zeitpunkt).
|
||||
* Die Antwort hat die Form { data: [ { npub, name, satoshis, blocks, datetime } ] }.
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
// npub1pt0kw36ue3w2g4haxq3wgm6a2fhtptmzsjlc2j2vphtcgle72qesgpjyc6
|
||||
@@ -37,6 +46,15 @@ class HighscoreController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highscore einreichen
|
||||
*
|
||||
* Reicht einen Highscore ein (idempotent pro npub und Zeitpunkt).
|
||||
* Zusätzlich auf 10 Anfragen pro Minute begrenzt.
|
||||
* Fehlt ein Name, versucht der Server, ihn über das Nostr-Profil zu ergänzen.
|
||||
* Antwortet mit HTTP 202.
|
||||
*/
|
||||
#[Response(status: 429, description: 'Zu viele Anfragen (Limit: 10 pro Minute überschritten).')]
|
||||
public function store(StoreHighscoreRequest $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
@@ -4,48 +4,53 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Lecturer;
|
||||
use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
#[Group(name: 'Referenten', weight: 4)]
|
||||
class LecturerController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* Referenten auflisten und durchsuchen
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* Öffentlicher Endpunkt; liefert id und name, alphabetisch sortiert. Ohne den Parameter 'selected' wird die Liste auf 10 Einträge begrenzt. Jeder Referent enthält zusätzlich ein 'image' (Avatar-Thumbnail-URL).
|
||||
*/
|
||||
#[QueryParameter(name: 'search', description: 'Teilstring-Suche im Namen.', required: false, type: 'string')]
|
||||
#[QueryParameter(name: 'selected', description: 'Lädt gezielt die angegebenen IDs.', required: false, type: 'array')]
|
||||
public function index(Request $request)
|
||||
{
|
||||
return Lecturer::query()
|
||||
->select('id', 'name', )
|
||||
->orderBy('name')
|
||||
->select('id', 'name')
|
||||
->orderBy('name')
|
||||
// ->when($request->has('user_id'),
|
||||
// fn(Builder $query) => $query->where('created_by', $request->user_id))
|
||||
->when(
|
||||
$request->search,
|
||||
fn (Builder $query) => $query
|
||||
->where('name', 'ilike', "%{$request->search}%")
|
||||
)
|
||||
->when(
|
||||
$request->exists('selected'),
|
||||
fn (Builder $query) => $query->whereIn('id',
|
||||
$request->input('selected', [])),
|
||||
fn (Builder $query) => $query->limit(10)
|
||||
)
|
||||
->get()
|
||||
->map(function (Lecturer $lecturer) {
|
||||
$lecturer->image = $lecturer->getFirstMediaUrl('avatar',
|
||||
'thumb');
|
||||
->when(
|
||||
$request->search,
|
||||
fn (Builder $query) => $query
|
||||
->where('name', 'ilike', "%{$request->search}%")
|
||||
)
|
||||
->when(
|
||||
$request->exists('selected'),
|
||||
fn (Builder $query) => $query->whereIn('id',
|
||||
$request->input('selected', [])),
|
||||
fn (Builder $query) => $query->limit(10)
|
||||
)
|
||||
->get()
|
||||
->map(function (Lecturer $lecturer) {
|
||||
$lecturer->image = $lecturer->getFirstMediaUrl('avatar',
|
||||
'thumb');
|
||||
|
||||
return $lecturer;
|
||||
});
|
||||
return $lecturer;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
@@ -53,9 +58,8 @@ class LecturerController extends Controller
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function show(Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
@@ -63,9 +67,8 @@ class LecturerController extends Controller
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function update(Request $request, Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
@@ -73,9 +76,8 @@ class LecturerController extends Controller
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function destroy(Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
|
||||
@@ -4,16 +4,31 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Meetup;
|
||||
use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Dedoc\Scramble\Attributes\Response;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
#[Group(name: 'Meetups', weight: 3)]
|
||||
class MeetupController extends Controller
|
||||
{
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function ical()
|
||||
{
|
||||
abort(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Eigene Meetups auflisten
|
||||
*
|
||||
* Liefert die Meetups des angemeldeten Nutzers (id, name, inklusive Stadt/Land und Profilbild),
|
||||
* alphabetisch sortiert. Erfordert eine authentifizierte Sitzung (sonst 401).
|
||||
*/
|
||||
#[QueryParameter(name: 'search', description: 'Teilstring-Suche im Meetup- oder Stadtnamen.', required: false, type: 'string')]
|
||||
#[QueryParameter(name: 'selected', description: 'Lädt gezielt die angegebenen Meetup-IDs.', required: false, type: 'array')]
|
||||
#[Response(status: 401, description: 'Nicht authentifiziert.')]
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
@@ -30,16 +45,15 @@ class MeetupController extends Controller
|
||||
->orderBy('name')
|
||||
->when(
|
||||
$request->search,
|
||||
fn(Builder $query)
|
||||
=> $query
|
||||
fn (Builder $query) => $query
|
||||
->where('name', 'like', "%{$request->search}%")
|
||||
->orWhereHas('city',
|
||||
fn(Builder $query) => $query->where('cities.name', 'ilike', "%{$request->search}%")),
|
||||
fn (Builder $query) => $query->where('cities.name', 'ilike', "%{$request->search}%")),
|
||||
)
|
||||
->when(
|
||||
$request->exists('selected'),
|
||||
fn(Builder $query) => $query->whereIn('id', $request->input('selected', [])),
|
||||
fn(Builder $query) => $query->limit(10),
|
||||
fn (Builder $query) => $query->whereIn('id', $request->input('selected', [])),
|
||||
fn (Builder $query) => $query->limit(10),
|
||||
)
|
||||
->get()
|
||||
->map(function (Meetup $meetup) {
|
||||
@@ -49,42 +63,26 @@ class MeetupController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(meetup $meetup)
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function show(Meetup $meetup)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, meetup $meetup)
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function update(Request $request, Meetup $meetup)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(meetup $meetup)
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function destroy(Meetup $meetup)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\MeetupEvent;
|
||||
use Carbon\Carbon;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\PathParameter;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[Group(name: 'Meetups', weight: 3)]
|
||||
class MeetupEventController extends Controller
|
||||
{
|
||||
/**
|
||||
* Meetup-Termine auflisten
|
||||
*
|
||||
* Liefert kommende/vergangene Meetup-Termine. Mit optionalem Datum wird auf den
|
||||
* jeweiligen Monat dieses Datums gefiltert.
|
||||
*
|
||||
* @return Collection<int, array<string, mixed>>
|
||||
*/
|
||||
#[PathParameter(name: 'date', description: 'Optionales Datum (Y-m-d); filtert auf den Monat dieses Datums.', required: false, type: 'string')]
|
||||
public function __invoke(?string $date = null): Collection
|
||||
{
|
||||
if ($date) {
|
||||
$date = Carbon::parse($date);
|
||||
}
|
||||
$events = MeetupEvent::query()
|
||||
->with([
|
||||
'meetup.city.country',
|
||||
'meetup.media',
|
||||
])
|
||||
->when(
|
||||
$date,
|
||||
fn ($query) => $query
|
||||
->where('start', '>=', $date)
|
||||
->where('start', '<=', $date->copy()->endOfMonth()),
|
||||
)
|
||||
->get();
|
||||
|
||||
return $events->map(fn ($event) => [
|
||||
'start' => $event->start->format('Y-m-d H:i'),
|
||||
'location' => $event->location,
|
||||
'description' => $event->description,
|
||||
'link' => $event->link,
|
||||
'meetup.name' => $event->meetup->name,
|
||||
'meetup.portalLink' => url()->route(
|
||||
'meetups.landingpage',
|
||||
[
|
||||
'country' => $event->meetup->city->country,
|
||||
'meetup' => $event->meetup,
|
||||
],
|
||||
),
|
||||
'meetup.url' => $event->meetup->telegram_link ?? $event->meetup->webpage,
|
||||
'meetup.country' => str($event->meetup->city->country->code)->upper(),
|
||||
'meetup.city' => $event->meetup->city->name,
|
||||
'meetup.longitude' => (float) $event->meetup->city->longitude,
|
||||
'meetup.latitude' => (float) $event->meetup->city->latitude,
|
||||
'meetup.twitter_username' => $event->meetup->twitter_username,
|
||||
'meetup.website' => $event->meetup->webpage,
|
||||
'meetup.simplex' => $event->meetup->simplex,
|
||||
'meetup.signal' => $event->meetup->signal,
|
||||
'meetup.nostr' => $event->meetup->nostr,
|
||||
'meetup.logo' => $event->meetup->getFirstMediaUrl('logo'),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Meetup;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[Group(name: 'Meetups', weight: 3)]
|
||||
class MeetupMapController extends Controller
|
||||
{
|
||||
/**
|
||||
* Öffentliche Meetups für die Community-Karte
|
||||
*
|
||||
* Liefert alle auf der Karte sichtbaren Meetups mit Geo- und Kontaktdaten.
|
||||
*
|
||||
* @return Collection<int, array<string, mixed>>
|
||||
*/
|
||||
#[QueryParameter(name: 'withIntro', description: 'Presence-Flag: Bei Vorhandensein wird der Intro-Text mitgeliefert.', required: false, type: 'string')]
|
||||
#[QueryParameter(name: 'withLogos', description: 'Presence-Flag: Bei Vorhandensein wird die Logo-URL mitgeliefert.', required: false, type: 'string')]
|
||||
public function __invoke(Request $request): Collection
|
||||
{
|
||||
return Meetup::query()
|
||||
->where('visible_on_map', true)
|
||||
->with([
|
||||
'meetupEvents',
|
||||
'city.country',
|
||||
'media',
|
||||
])
|
||||
->get()
|
||||
->map(fn ($meetup) => [
|
||||
'name' => $meetup->name,
|
||||
'portalLink' => url()->route(
|
||||
'meetups.landingpage',
|
||||
['country' => $meetup->city->country, 'meetup' => $meetup],
|
||||
),
|
||||
'url' => $meetup->telegram_link ?? $meetup->webpage,
|
||||
'top' => $meetup->github_data['top'] ?? null,
|
||||
'left' => $meetup->github_data['left'] ?? null,
|
||||
'country' => str($meetup->city->country->code)->upper(),
|
||||
'state' => $meetup->github_data['state'] ?? null,
|
||||
'city' => $meetup->city->name,
|
||||
'longitude' => (float) $meetup->city->longitude,
|
||||
'latitude' => (float) $meetup->city->latitude,
|
||||
'twitter_username' => $meetup->twitter_username,
|
||||
'website' => $meetup->webpage,
|
||||
'simplex' => $meetup->simplex,
|
||||
'signal' => $meetup->signal,
|
||||
'nostr' => $meetup->nostr,
|
||||
'next_event' => $meetup->nextEvent,
|
||||
'intro' => $request->has('withIntro') ? $meetup->intro : null,
|
||||
'logo' => $request->has('withLogos') ? $meetup->getFirstMediaUrl('logo') : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[Group(name: 'Community', weight: 7)]
|
||||
class NostrPlebController extends Controller
|
||||
{
|
||||
/**
|
||||
* Nostr-Pubkeys (npubs) der Community
|
||||
*
|
||||
* Liefert die eindeutigen npub-Public-Keys aller Nutzer mit hinterlegtem Nostr-Profil.
|
||||
*
|
||||
* @return Collection<int, string>
|
||||
*/
|
||||
public function __invoke(): Collection
|
||||
{
|
||||
return User::query()
|
||||
->select([
|
||||
'email',
|
||||
'public_key',
|
||||
'lightning_address',
|
||||
'lnurl',
|
||||
'node_id',
|
||||
'paynym',
|
||||
'lnbits',
|
||||
'nostr',
|
||||
'id',
|
||||
])
|
||||
->whereNotNull('nostr')
|
||||
->where('nostr', 'like', 'npub1%')
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->unique('nostr')
|
||||
->pluck('nostr');
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,24 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Lecturer;
|
||||
use App\Models\Venue;
|
||||
use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
use Dedoc\Scramble\Attributes\QueryParameter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
#[Group(name: 'Stammdaten', weight: 5)]
|
||||
class VenueController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* @return \Illuminate\Http\Response
|
||||
* Veranstaltungsorte auflisten und durchsuchen
|
||||
*
|
||||
* Öffentlicher Endpunkt; liefert id, name und die zugehörige Stadt/Land, alphabetisch sortiert.
|
||||
* Ohne 'selected' wird die Liste auf 10 Einträge begrenzt. Jeder Ort enthält zusätzlich
|
||||
* 'flag' (SVG-URL der Landesflagge) und 'description' (Stadt + Straße).
|
||||
*/
|
||||
#[QueryParameter(name: 'search', description: 'Teilstring-Suche im Namen des Veranstaltungsortes.', required: false, type: 'string')]
|
||||
#[QueryParameter(name: 'selected', description: 'Lädt gezielt die angegebenen Veranstaltungsort-IDs (umgeht die Begrenzung auf 10 Einträge).', required: false, type: 'array')]
|
||||
public function index(Request $request)
|
||||
{
|
||||
return Venue::query()
|
||||
@@ -22,19 +31,19 @@ class VenueController extends Controller
|
||||
->orderBy('name')
|
||||
->when(
|
||||
$request->search,
|
||||
fn(Builder $query) => $query
|
||||
fn (Builder $query) => $query
|
||||
->where('name', 'ilike', "%{$request->search}%")
|
||||
)
|
||||
->when(
|
||||
$request->exists('selected'),
|
||||
fn(Builder $query) => $query->whereIn('id',
|
||||
fn (Builder $query) => $query->whereIn('id',
|
||||
$request->input('selected', [])),
|
||||
fn(Builder $query) => $query->limit(10)
|
||||
fn (Builder $query) => $query->limit(10)
|
||||
)
|
||||
->get()
|
||||
->map(function (Venue $venue) {
|
||||
$venue->flag = asset('vendor/blade-country-flags/4x3-' . $venue->city->country->code . '.svg');
|
||||
$venue->description = $venue->city->name . ', ' . $venue->street;
|
||||
$venue->flag = asset('vendor/blade-country-flags/4x3-'.$venue->city->country->code.'.svg');
|
||||
$venue->description = $venue->city->name.', '.$venue->street;
|
||||
|
||||
return $venue;
|
||||
});
|
||||
@@ -42,8 +51,8 @@ class VenueController extends Controller
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
@@ -51,8 +60,8 @@ class VenueController extends Controller
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function show(Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
@@ -60,8 +69,8 @@ class VenueController extends Controller
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function update(Request $request, Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
@@ -69,8 +78,8 @@ class VenueController extends Controller
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
#[ExcludeRouteFromDocs]
|
||||
public function destroy(Lecturer $lecturer)
|
||||
{
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user