Skip to content

Commit

Permalink
♻️ migrate departures endpoint to use TRWL-ID
Browse files Browse the repository at this point in the history
see #2411
  • Loading branch information
MrKrisKrisu committed Mar 10, 2024
1 parent 334ecdd commit 93c42e6
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 46 deletions.
11 changes: 10 additions & 1 deletion API_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ In this we try to keep track of changes to the API.
Primarily this should document changes that are not backwards compatible or belongs to already documented endpoints.
This is to help you keep track of the changes and to help you update your code accordingly.

## 2024-03-10

Replaced `GET /api/v1/trains/station/{name}/departures` with `GET /station/{id}/departures`.
The old endpoint is marked as deprecated and will be removed after 2024-06.

Please note, that the ID is the Träwelling internal ID and not the IBNR!

## 2024-03-01

> **warning**
> Possibly breaking change: The implementation of next/prev links on user/{username}/statuses endpoint has been changed to adhere to the documentation.
> Possibly breaking change: The implementation of next/prev links on user/{username}/statuses endpoint has been changed
> to adhere to the documentation.
## 2024-01-21

Expand Down
105 changes: 70 additions & 35 deletions app/Http/Controllers/API/v1/TransportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use App\Models\Event;
use App\Models\Station;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
Expand All @@ -30,17 +31,49 @@

class TransportController extends Controller
{
/**
* @see All slashes (as well as encoded to %2F) in $name need to be replaced, preferrably by a space (%20)
*/
public function getLegacyDepartures(Request $request, string $name): JsonResponse { //TODO: remove endpoint after 2024-06
$validated = $request->validate([
'when' => ['nullable', 'date'],
'travelType' => ['nullable', new Enum(TravelType::class)],
]);

try {
$trainStationboardResponse = TransportBackend::getDepartures(
stationQuery: $name,
when: isset($validated['when']) ? Carbon::parse($validated['when']) : null,
travelType: TravelType::tryFrom($validated['travelType'] ?? null),
localtime: isset($validated['when']) && !preg_match('(\+|Z)', $validated['when'])
);
} catch (HafasException) {
return $this->sendError(__('messages.exception.generalHafas', [], 'en'), 502);
} catch (ModelNotFoundException) {
return $this->sendError(__('controller.transport.no-station-found', [], 'en'));
} catch (Exception $exception) {
report($exception);
return $this->sendError('An unknown error occurred.', 500);
}
return $this->sendResponse(
data: $trainStationboardResponse['departures'],
additional: ["meta" => ['station' => StationDto::fromModel($trainStationboardResponse['station']),
'times' => $trainStationboardResponse['times'],
]]
);
}

/**
* @OA\Get(
* path="/trains/station/{name}/departures",
* path="/station/{id}/departures",
* operationId="getDepartures",
* tags={"Checkin"},
* summary="Get departures from a station",
* description="Get departures from a station",
* @OA\Parameter(
* name="name",
* name="id",
* in="path",
* description="Name of the station (replace slashes with spaces)",
* description="Träwelling-ID of the station (station needs to be looked up first)",
* required=true,
* ),
* @OA\Parameter(
Expand Down Expand Up @@ -130,56 +163,58 @@ class TransportController extends Controller
* )
* )
* ),
* @OA\Response(
* response=404,
* description="Station not found",
* ),
* @OA\Response(
* response=502,
* description="Error with our data provider",
* ),
* @OA\Response(
* response=422,
* description="Invalid input",
* ),
* @OA\Response(response=401, description="Unauthorized"),
* security={
* {"passport": {"create-statuses"}}, {"token": {}}
*
* }
* @OA\Response(response=401, description="Unauthorized"),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=422, description="Invalid input"),
* @OA\Response(response=502, description="Error with our data provider"),
* security={{"passport": {"create-statuses"}}, {"token": {}}}
* )
*
* @param Request $request
* @param string $name
* @param int $stationId
*
* @return JsonResponse
* @see All slashes (as well as encoded to %2F) in $name need to be replaced, preferrably by a space (%20)
*/
public function departures(Request $request, string $name): JsonResponse {
public function getDepartures(Request $request, int $stationId): JsonResponse {
$validated = $request->validate([
'when' => ['nullable', 'date'],
'travelType' => ['nullable', new Enum(TravelType::class)],
]);

$timestamp = isset($validated['when']) ? Carbon::parse($validated['when']) : now();
$station = Station::findOrFail($stationId);

try {
$trainStationboardResponse = TransportBackend::getDepartures(
stationQuery: $name,
when: isset($validated['when']) ? Carbon::parse($validated['when']) : null,
travelType: TravelType::tryFrom($validated['travelType'] ?? null),
localtime: isset($validated['when']) && !preg_match('(\+|Z)', $validated['when'])
$departures = HafasController::getDepartures(
station: $station,
when: $timestamp,
type: TravelType::tryFrom($validated['travelType'] ?? null),
localtime: isset($validated['when']) && !preg_match('(\+|Z)', $validated['when'])
)->sortBy(function($departure) {
return $departure->when ?? $departure->plannedWhen;
});

return $this->sendResponse(
data: $departures->values(),
additional: [
'meta' => [
'station' => StationDto::fromModel($station),
'times' => [
'now' => $timestamp,
'prev' => $timestamp->clone()->subMinutes(15),
'next' => $timestamp->clone()->addMinutes(15)
],
]
]
);
} catch (HafasException) {
return $this->sendError(__('messages.exception.generalHafas', [], 'en'), 502);
} catch (ModelNotFoundException) {
return $this->sendError(__('controller.transport.no-station-found', [], 'en'));
} catch (Exception $exception) {
report($exception);
return $this->sendError('An unknown error occurred.', 500);
}

return $this->sendResponse(
data: $trainStationboardResponse['departures'],
additional: ["meta" => ['station' => StationDto::fromModel($trainStationboardResponse['station']),
'times' => $trainStationboardResponse['times'],
]]
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion resources/vue/components/Stationboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default {
let query = this.stationString.replace(/%2F/, " ").replace(/\//, " ");
let travelType = this.travelType ? this.travelType : "";
fetch(`/api/v1/trains/station/${query}/departures?when=${time}&travelType=${travelType}`)
fetch(`/api/v1/trains/station/${query}/departures?when=${time}&travelType=${travelType}`) //TODO: change to ID
.then((response) => {
this.loading = false;
this.now = DateTime.now();
Expand Down
7 changes: 6 additions & 1 deletion routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,18 @@
Route::post('trip', [TripController::class, 'createTrip']);
Route::post('checkin', [TransportController::class, 'create']);
Route::group(['prefix' => 'station'], static function() {
Route::get('{name}/departures', [TransportController::class, 'departures']);
Route::get('{name}/departures', [TransportController::class, 'getLegacyDepartures']); //ToDo: Remove this endpoint after 2024-06 (replaced by id)
Route::put('{name}/home', [TransportController::class, 'setHome']);
Route::get('nearby', [TransportController::class, 'getNextStationByCoordinates']);
Route::get('autocomplete/{query}', [TransportController::class, 'getTrainStationAutocomplete']);
Route::get('history', [TransportController::class, 'getTrainStationHistory']);
});
});

Route::prefix('station')->middleware(['scope:write-statuses'])->group(static function() {
Route::get('/{id}/departures', [TransportController::class, 'getDepartures'])->whereNumber('id');
});

Route::group(['prefix' => 'statistics', 'middleware' => 'scope:read-statistics'], static function() {
Route::get('/', [StatisticsController::class, 'getPersonalStatistics']);
Route::get('/global', [StatisticsController::class, 'getGlobalStatistics']);
Expand Down
16 changes: 8 additions & 8 deletions storage/api-docs/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2969,7 +2969,7 @@
]
}
},
"/trains/station/{name}/departures": {
"/trains/station/{id}/departures": {
"get": {
"tags": [
"Checkin"
Expand All @@ -2979,9 +2979,9 @@
"operationId": "getDepartures",
"parameters": [
{
"name": "name",
"name": "id",
"in": "path",
"description": "Name of the station (replace slashes with spaces)",
"description": "Id of the station (stations needs to be looked up first)",
"required": true
},
{
Expand Down Expand Up @@ -3141,17 +3141,17 @@
}
}
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Station not found"
},
"502": {
"description": "Error with our data provider"
},
"422": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
"502": {
"description": "Error with our data provider"
}
},
"security": [
Expand Down

0 comments on commit 93c42e6

Please sign in to comment.