refactor: one PHP file per route with subdirectories
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 19s

Properly organize routes following design-to-pack pattern:
- routes/markers/get.php
- routes/markers/create.php
- routes/markers/update.php
- routes/markers/delete.php
- routes/position/update.php
- routes/image/capture.php
- routes/image/check-flag.php
- routes/image/clear-flag.php

Each route in its own file for better maintainability.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-02-06 11:58:54 +01:00
parent 590c842072
commit 208d46ff28
11 changed files with 552 additions and 536 deletions

View file

@ -51,9 +51,14 @@ Kirby::plugin('geoproject/map-editor', [
],
'api' => [
'routes' => [
require __DIR__ . '/routes/markers.php',
require __DIR__ . '/routes/position.php',
require __DIR__ . '/routes/image.php',
require __DIR__ . '/routes/markers/get.php',
require __DIR__ . '/routes/markers/create.php',
require __DIR__ . '/routes/markers/update.php',
require __DIR__ . '/routes/markers/delete.php',
require __DIR__ . '/routes/position/update.php',
require __DIR__ . '/routes/image/capture.php',
require __DIR__ . '/routes/image/check-flag.php',
require __DIR__ . '/routes/image/clear-flag.php',
]
],
'hooks' => [

View file

@ -1,185 +0,0 @@
<?php
/**
* Image Capture Routes
* POST for capturing map image, GET/DELETE for regeneration flag
*/
return [
// POST capture and save map image
[
'pattern' => 'map-editor/pages/(:all)/capture-image',
'method' => 'POST',
'auth' => false,
'action' => function (string $pageId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$mapPage = kirby()->page($pageId);
if (!$mapPage) {
return [
'status' => 'error',
'message' => 'Map page not found',
'code' => 404
];
}
if (!$mapPage->permissions()->can('update')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$data = kirby()->request()->data();
if (!isset($data['image'])) {
return [
'status' => 'error',
'message' => 'Image data is required',
'code' => 400
];
}
$imageData = $data['image'];
if (preg_match('/^data:image\/(png|jpeg|jpg);base64,(.+)$/', $imageData, $matches)) {
$imageData = $matches[2];
$extension = $matches[1] === 'jpeg' ? 'jpg' : $matches[1];
} else {
return [
'status' => 'error',
'message' => 'Invalid image format',
'code' => 400
];
}
$decodedImage = base64_decode($imageData);
if ($decodedImage === false) {
return [
'status' => 'error',
'message' => 'Failed to decode image',
'code' => 400
];
}
$filename = 'map-static.' . $extension;
$tempPath = sys_get_temp_dir() . '/' . uniqid() . '.' . $extension;
file_put_contents($tempPath, $decodedImage);
$existingFile = $mapPage->files()->filterBy('name', 'map-static')->first();
if ($existingFile) {
$existingFile->delete();
}
try {
$file = $mapPage->createFile([
'source' => $tempPath,
'filename' => $filename
]);
@unlink($tempPath);
} catch (Exception $e) {
@unlink($tempPath);
throw $e;
}
return [
'status' => 'success',
'data' => [
'message' => 'Image saved successfully',
'filename' => $filename,
'path' => $file->root()
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
],
// GET check if regeneration flag exists
[
'pattern' => 'map-editor/pages/(:all)/check-regenerate-flag',
'method' => 'GET',
'auth' => false,
'action' => function (string $pageId) {
try {
$page = kirby()->page($pageId);
if (!$page) {
return [
'status' => 'error',
'message' => 'Page not found',
'code' => 404
];
}
$markerFile = $page->root() . '/.regenerate-map-image';
$needsRegeneration = file_exists($markerFile);
return [
'status' => 'success',
'data' => [
'needsRegeneration' => $needsRegeneration
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
],
// DELETE clear regeneration flag
[
'pattern' => 'map-editor/pages/(:all)/clear-regenerate-flag',
'method' => 'DELETE',
'auth' => false,
'action' => function (string $pageId) {
try {
$page = kirby()->page($pageId);
if (!$page) {
return [
'status' => 'error',
'message' => 'Page not found',
'code' => 404
];
}
$markerFile = $page->root() . '/.regenerate-map-image';
if (file_exists($markerFile)) {
unlink($markerFile);
}
return [
'status' => 'success',
'data' => [
'message' => 'Flag cleared'
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
]
];

View file

@ -0,0 +1,109 @@
<?php
/**
* POST capture and save map image
*/
return [
'pattern' => 'map-editor/pages/(:all)/capture-image',
'method' => 'POST',
'auth' => false,
'action' => function (string $pageId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$mapPage = kirby()->page($pageId);
if (!$mapPage) {
return [
'status' => 'error',
'message' => 'Map page not found',
'code' => 404
];
}
if (!$mapPage->permissions()->can('update')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$data = kirby()->request()->data();
if (!isset($data['image'])) {
return [
'status' => 'error',
'message' => 'Image data is required',
'code' => 400
];
}
$imageData = $data['image'];
if (preg_match('/^data:image\/(png|jpeg|jpg);base64,(.+)$/', $imageData, $matches)) {
$imageData = $matches[2];
$extension = $matches[1] === 'jpeg' ? 'jpg' : $matches[1];
} else {
return [
'status' => 'error',
'message' => 'Invalid image format',
'code' => 400
];
}
$decodedImage = base64_decode($imageData);
if ($decodedImage === false) {
return [
'status' => 'error',
'message' => 'Failed to decode image',
'code' => 400
];
}
$filename = 'map-static.' . $extension;
$tempPath = sys_get_temp_dir() . '/' . uniqid() . '.' . $extension;
file_put_contents($tempPath, $decodedImage);
$existingFile = $mapPage->files()->filterBy('name', 'map-static')->first();
if ($existingFile) {
$existingFile->delete();
}
try {
$file = $mapPage->createFile([
'source' => $tempPath,
'filename' => $filename
]);
@unlink($tempPath);
} catch (Exception $e) {
@unlink($tempPath);
throw $e;
}
return [
'status' => 'success',
'data' => [
'message' => 'Image saved successfully',
'filename' => $filename,
'path' => $file->root()
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -0,0 +1,39 @@
<?php
/**
* GET check if regeneration flag exists
*/
return [
'pattern' => 'map-editor/pages/(:all)/check-regenerate-flag',
'method' => 'GET',
'auth' => false,
'action' => function (string $pageId) {
try {
$page = kirby()->page($pageId);
if (!$page) {
return [
'status' => 'error',
'message' => 'Page not found',
'code' => 404
];
}
$markerFile = $page->root() . '/.regenerate-map-image';
$needsRegeneration = file_exists($markerFile);
return [
'status' => 'success',
'data' => [
'needsRegeneration' => $needsRegeneration
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -0,0 +1,41 @@
<?php
/**
* DELETE clear regeneration flag
*/
return [
'pattern' => 'map-editor/pages/(:all)/clear-regenerate-flag',
'method' => 'DELETE',
'auth' => false,
'action' => function (string $pageId) {
try {
$page = kirby()->page($pageId);
if (!$page) {
return [
'status' => 'error',
'message' => 'Page not found',
'code' => 404
];
}
$markerFile = $page->root() . '/.regenerate-map-image';
if (file_exists($markerFile)) {
unlink($markerFile);
}
return [
'status' => 'success',
'data' => [
'message' => 'Flag cleared'
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -1,346 +0,0 @@
<?php
/**
* Markers CRUD Routes
* GET, POST, PATCH, DELETE operations for markers
*/
return [
// GET all markers for a map page
[
'pattern' => 'map-editor/pages/(:all)/markers',
'method' => 'GET',
'auth' => false,
'action' => function (string $pageId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$mapPage = kirby()->page($pageId);
if (!$mapPage) {
return [
'status' => 'error',
'message' => 'Map page not found',
'code' => 404
];
}
if (!$mapPage->isReadable()) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$markerPages = $mapPage
->children()
->listed()
->filterBy('intendedTemplate', 'marker')
->sortBy('num', 'asc');
$markers = [];
foreach ($markerPages as $marker) {
$iconFile = $marker->markerIcon()->toFile();
$iconUrl = $iconFile ? $iconFile->url() : null;
$iconSize = $marker->markerIconSize()->isNotEmpty()
? (int) $marker->markerIconSize()->value()
: 40;
$markers[] = [
'id' => $marker->id(),
'slug' => $marker->slug(),
'title' => $marker->title()->value(),
'position' => [
'lat' => (float) $marker->latitude()->value(),
'lon' => (float) $marker->longitude()->value()
],
'num' => $marker->num(),
'panelUrl' => (string) $marker->panel()->url(),
'iconUrl' => $iconUrl,
'iconSize' => $iconSize
];
}
return [
'status' => 'success',
'data' => [
'markers' => $markers
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
],
// POST create new marker
[
'pattern' => 'map-editor/pages/(:all)/markers',
'method' => 'POST',
'auth' => false,
'action' => function (string $pageId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$mapPage = kirby()->page($pageId);
if (!$mapPage) {
return [
'status' => 'error',
'message' => 'Map page not found',
'code' => 404
];
}
if (!$mapPage->permissions()->can('create')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$data = kirby()->request()->data();
if (!isset($data['position']['lat']) || !isset($data['position']['lon'])) {
return [
'status' => 'error',
'message' => 'Position (lat, lon) is required',
'code' => 400
];
}
$lat = (float) $data['position']['lat'];
$lon = (float) $data['position']['lon'];
if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) {
return [
'status' => 'error',
'message' => 'Invalid coordinates',
'code' => 400
];
}
$existingMarkers = $mapPage
->children()
->filterBy('intendedTemplate', 'marker');
$nextNum = $existingMarkers->count() + 1;
$title = 'Marqueur ' . $nextNum;
$slug = Str::slug($title);
$newMarker = $mapPage->createChild([
'slug' => $slug,
'template' => 'marker',
'content' => [
'title' => $title,
'latitude' => $lat,
'longitude' => $lon
]
]);
$newMarker->changeStatus('listed', $nextNum);
$iconFile = $newMarker->markerIcon()->toFile();
$iconUrl = $iconFile ? $iconFile->url() : null;
$iconSize = $newMarker->markerIconSize()->isNotEmpty()
? (int) $newMarker->markerIconSize()->value()
: 40;
return [
'status' => 'success',
'data' => [
'marker' => [
'id' => $newMarker->id(),
'slug' => $newMarker->slug(),
'title' => $title,
'position' => [
'lat' => $lat,
'lon' => $lon
],
'num' => $nextNum,
'panelUrl' => (string) $newMarker->panel()->url(),
'iconUrl' => $iconUrl,
'iconSize' => $iconSize
]
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
],
// PATCH update marker position
[
'pattern' => 'map-editor/pages/(:all)/markers/(:all)',
'method' => 'PATCH',
'auth' => false,
'action' => function (string $pageId, string $markerId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$marker = kirby()->page($markerId);
if (!$marker) {
return [
'status' => 'error',
'message' => 'Marker not found',
'code' => 404
];
}
if (!$marker->permissions()->can('update')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$data = kirby()->request()->data();
if (!isset($data['position']['lat']) || !isset($data['position']['lon'])) {
return [
'status' => 'error',
'message' => 'Position (lat, lon) is required',
'code' => 400
];
}
$lat = (float) $data['position']['lat'];
$lon = (float) $data['position']['lon'];
if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) {
return [
'status' => 'error',
'message' => 'Invalid coordinates',
'code' => 400
];
}
$marker->update([
'latitude' => $lat,
'longitude' => $lon
]);
$iconFile = $marker->markerIcon()->toFile();
$iconUrl = $iconFile ? $iconFile->url() : null;
$iconSize = $marker->markerIconSize()->isNotEmpty()
? (int) $marker->markerIconSize()->value()
: 40;
return [
'status' => 'success',
'data' => [
'marker' => [
'id' => $marker->id(),
'slug' => $marker->slug(),
'title' => $marker->title()->value(),
'position' => [
'lat' => $lat,
'lon' => $lon
],
'num' => $marker->num(),
'panelUrl' => (string) $marker->panel()->url(),
'iconUrl' => $iconUrl,
'iconSize' => $iconSize
]
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
],
// DELETE marker
[
'pattern' => 'map-editor/pages/(:all)/markers/(:all)',
'method' => 'DELETE',
'auth' => false,
'action' => function (string $pageId, string $markerId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$marker = kirby()->page($markerId);
if (!$marker) {
return [
'status' => 'error',
'message' => 'Marker not found',
'code' => 404
];
}
if (!$marker->permissions()->can('delete')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$marker->delete(true);
return [
'status' => 'success',
'data' => [
'message' => 'Marker deleted successfully'
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
]
];

View file

@ -0,0 +1,114 @@
<?php
/**
* POST create new marker
*/
return [
'pattern' => 'map-editor/pages/(:all)/markers',
'method' => 'POST',
'auth' => false,
'action' => function (string $pageId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$mapPage = kirby()->page($pageId);
if (!$mapPage) {
return [
'status' => 'error',
'message' => 'Map page not found',
'code' => 404
];
}
if (!$mapPage->permissions()->can('create')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$data = kirby()->request()->data();
if (!isset($data['position']['lat']) || !isset($data['position']['lon'])) {
return [
'status' => 'error',
'message' => 'Position (lat, lon) is required',
'code' => 400
];
}
$lat = (float) $data['position']['lat'];
$lon = (float) $data['position']['lon'];
if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) {
return [
'status' => 'error',
'message' => 'Invalid coordinates',
'code' => 400
];
}
$existingMarkers = $mapPage
->children()
->filterBy('intendedTemplate', 'marker');
$nextNum = $existingMarkers->count() + 1;
$title = 'Marqueur ' . $nextNum;
$slug = Str::slug($title);
$newMarker = $mapPage->createChild([
'slug' => $slug,
'template' => 'marker',
'content' => [
'title' => $title,
'latitude' => $lat,
'longitude' => $lon
]
]);
$newMarker->changeStatus('listed', $nextNum);
$iconFile = $newMarker->markerIcon()->toFile();
$iconUrl = $iconFile ? $iconFile->url() : null;
$iconSize = $newMarker->markerIconSize()->isNotEmpty()
? (int) $newMarker->markerIconSize()->value()
: 40;
return [
'status' => 'success',
'data' => [
'marker' => [
'id' => $newMarker->id(),
'slug' => $newMarker->slug(),
'title' => $title,
'position' => [
'lat' => $lat,
'lon' => $lon
],
'num' => $nextNum,
'panelUrl' => (string) $newMarker->panel()->url(),
'iconUrl' => $iconUrl,
'iconSize' => $iconSize
]
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -0,0 +1,57 @@
<?php
/**
* DELETE marker
*/
return [
'pattern' => 'map-editor/pages/(:all)/markers/(:all)',
'method' => 'DELETE',
'auth' => false,
'action' => function (string $pageId, string $markerId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$marker = kirby()->page($markerId);
if (!$marker) {
return [
'status' => 'error',
'message' => 'Marker not found',
'code' => 404
];
}
if (!$marker->permissions()->can('delete')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$marker->delete(true);
return [
'status' => 'success',
'data' => [
'message' => 'Marker deleted successfully'
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -0,0 +1,84 @@
<?php
/**
* GET all markers for a map page
*/
return [
'pattern' => 'map-editor/pages/(:all)/markers',
'method' => 'GET',
'auth' => false,
'action' => function (string $pageId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$mapPage = kirby()->page($pageId);
if (!$mapPage) {
return [
'status' => 'error',
'message' => 'Map page not found',
'code' => 404
];
}
if (!$mapPage->isReadable()) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$markerPages = $mapPage
->children()
->listed()
->filterBy('intendedTemplate', 'marker')
->sortBy('num', 'asc');
$markers = [];
foreach ($markerPages as $marker) {
$iconFile = $marker->markerIcon()->toFile();
$iconUrl = $iconFile ? $iconFile->url() : null;
$iconSize = $marker->markerIconSize()->isNotEmpty()
? (int) $marker->markerIconSize()->value()
: 40;
$markers[] = [
'id' => $marker->id(),
'slug' => $marker->slug(),
'title' => $marker->title()->value(),
'position' => [
'lat' => (float) $marker->latitude()->value(),
'lon' => (float) $marker->longitude()->value()
],
'num' => $marker->num(),
'panelUrl' => (string) $marker->panel()->url(),
'iconUrl' => $iconUrl,
'iconSize' => $iconSize
];
}
return [
'status' => 'success',
'data' => [
'markers' => $markers
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -0,0 +1,99 @@
<?php
/**
* PATCH update marker position
*/
return [
'pattern' => 'map-editor/pages/(:all)/markers/(:all)',
'method' => 'PATCH',
'auth' => false,
'action' => function (string $pageId, string $markerId) {
try {
$user = kirby()->user();
if (!$user && !kirby()->option('debug', false)) {
return [
'status' => 'error',
'message' => 'Unauthorized',
'code' => 401
];
}
$marker = kirby()->page($markerId);
if (!$marker) {
return [
'status' => 'error',
'message' => 'Marker not found',
'code' => 404
];
}
if (!$marker->permissions()->can('update')) {
return [
'status' => 'error',
'message' => 'Forbidden',
'code' => 403
];
}
$data = kirby()->request()->data();
if (!isset($data['position']['lat']) || !isset($data['position']['lon'])) {
return [
'status' => 'error',
'message' => 'Position (lat, lon) is required',
'code' => 400
];
}
$lat = (float) $data['position']['lat'];
$lon = (float) $data['position']['lon'];
if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) {
return [
'status' => 'error',
'message' => 'Invalid coordinates',
'code' => 400
];
}
$marker->update([
'latitude' => $lat,
'longitude' => $lon
]);
$iconFile = $marker->markerIcon()->toFile();
$iconUrl = $iconFile ? $iconFile->url() : null;
$iconSize = $marker->markerIconSize()->isNotEmpty()
? (int) $marker->markerIconSize()->value()
: 40;
return [
'status' => 'success',
'data' => [
'marker' => [
'id' => $marker->id(),
'slug' => $marker->slug(),
'title' => $marker->title()->value(),
'position' => [
'lat' => $lat,
'lon' => $lon
],
'num' => $marker->num(),
'panelUrl' => (string) $marker->panel()->url(),
'iconUrl' => $iconUrl,
'iconSize' => $iconSize
]
]
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'code' => 500
];
}
}
];

View file

@ -1,8 +1,7 @@
<?php
/**
* Position Update Route
* PATCH route for updating page position (single mode)
* PATCH update page position (single mode)
*/
return [