Add Insomnia export and dynamic hostname for API doc
Insomnia export (/api/doc/insomnia.json):
- Generates Insomnia v4 export format with all API routes
- Workspace with environment variables (base_url, env, email, password, jwt_token)
- Folders per section (Auth, Events, Categories, Billets, Scanner)
- Each request with correct method, URL with Insomnia template vars, headers, body
- Auth routes use base_url directly, others use base_url/api/{env}/...
- Download button (indigo) next to Spec JSON button
Dynamic hostname:
- Insomnia export uses request.getSchemeAndHttpHost() (not hardcoded)
- Template passes host via data-host attribute
- JS env switcher reads host from data-host or falls back to location.origin
- Base URLs update dynamically when switching sandbox/live
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,24 +1,26 @@
|
|||||||
const ENVS = {
|
|
||||||
sandbox: {
|
|
||||||
prefix: '/api/sandbox',
|
|
||||||
baseUrl: 'https://ticket.e-cosplay.fr/api/sandbox',
|
|
||||||
color: 'text-orange-400',
|
|
||||||
btnBg: 'bg-orange-500',
|
|
||||||
desc: 'Environnement de test. Les donnees ne sont pas modifiees.',
|
|
||||||
},
|
|
||||||
live: {
|
|
||||||
prefix: '/api/live',
|
|
||||||
baseUrl: 'https://ticket.e-cosplay.fr/api/live',
|
|
||||||
color: 'text-green-400',
|
|
||||||
btnBg: 'bg-green-600',
|
|
||||||
desc: 'Environnement de production. Les donnees sont reelles.',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const BTN_BASE = 'env-btn px-5 py-2 font-black uppercase text-xs tracking-widest transition-all cursor-pointer '
|
const BTN_BASE = 'env-btn px-5 py-2 font-black uppercase text-xs tracking-widest transition-all cursor-pointer '
|
||||||
|
|
||||||
function switchEnv(env) {
|
function getEnvs(host) {
|
||||||
const config = ENVS[env]
|
return {
|
||||||
|
sandbox: {
|
||||||
|
prefix: '/api/sandbox',
|
||||||
|
baseUrl: host + '/api/sandbox',
|
||||||
|
color: 'text-orange-400',
|
||||||
|
btnBg: 'bg-orange-500',
|
||||||
|
desc: 'Environnement de test. Les donnees ne sont pas modifiees.',
|
||||||
|
},
|
||||||
|
live: {
|
||||||
|
prefix: '/api/live',
|
||||||
|
baseUrl: host + '/api/live',
|
||||||
|
color: 'text-green-400',
|
||||||
|
btnBg: 'bg-green-600',
|
||||||
|
desc: 'Environnement de production. Les donnees sont reelles.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchEnv(env, envs) {
|
||||||
|
const config = envs[env]
|
||||||
if (!config) return
|
if (!config) return
|
||||||
|
|
||||||
document.querySelectorAll('.env-btn').forEach(btn => {
|
document.querySelectorAll('.env-btn').forEach(btn => {
|
||||||
@@ -42,7 +44,11 @@ export function initApiEnvSwitcher() {
|
|||||||
const switcher = document.getElementById('env-switcher')
|
const switcher = document.getElementById('env-switcher')
|
||||||
if (!switcher) return
|
if (!switcher) return
|
||||||
|
|
||||||
|
const hostEl = document.querySelector('[data-host]')
|
||||||
|
const host = hostEl ? hostEl.dataset.host : globalThis.location.origin
|
||||||
|
const envs = getEnvs(host)
|
||||||
|
|
||||||
document.querySelectorAll('.env-btn').forEach(btn => {
|
document.querySelectorAll('.env-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', () => switchEnv(btn.dataset.env))
|
btn.addEventListener('click', () => switchEnv(btn.dataset.env, envs))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
@@ -23,6 +24,105 @@ class ApiDocController extends AbstractController
|
|||||||
return $this->json($this->getApiSpec());
|
return $this->json($this->getApiSpec());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/doc/insomnia.json', name: 'app_api_doc_insomnia', methods: ['GET'])]
|
||||||
|
public function insomnia(Request $request): Response
|
||||||
|
{
|
||||||
|
$baseUrl = $request->getSchemeAndHttpHost();
|
||||||
|
$resources = [];
|
||||||
|
$workspaceId = 'wrk_eticket';
|
||||||
|
|
||||||
|
$resources[] = [
|
||||||
|
'_type' => 'workspace',
|
||||||
|
'_id' => $workspaceId,
|
||||||
|
'name' => 'E-Ticket API',
|
||||||
|
'description' => 'API E-Ticket - Organisateur & Scanner',
|
||||||
|
];
|
||||||
|
|
||||||
|
$envId = 'env_eticket';
|
||||||
|
$resources[] = [
|
||||||
|
'_type' => 'environment',
|
||||||
|
'_id' => $envId,
|
||||||
|
'parentId' => $workspaceId,
|
||||||
|
'name' => 'E-Ticket',
|
||||||
|
'data' => [
|
||||||
|
'base_url' => $baseUrl,
|
||||||
|
'env' => 'sandbox',
|
||||||
|
'email' => '',
|
||||||
|
'password' => '',
|
||||||
|
'jwt_token' => '',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$folderIndex = 0;
|
||||||
|
foreach ($this->getApiSpec() as $section) {
|
||||||
|
++$folderIndex;
|
||||||
|
$folderId = 'fld_'.$folderIndex;
|
||||||
|
|
||||||
|
$resources[] = [
|
||||||
|
'_type' => 'request_group',
|
||||||
|
'_id' => $folderId,
|
||||||
|
'parentId' => $workspaceId,
|
||||||
|
'name' => $section['name'],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($section['endpoints'] as $reqIndex => $endpoint) {
|
||||||
|
$isAuth = str_starts_with($endpoint['path'], '/api/auth');
|
||||||
|
$url = $isAuth
|
||||||
|
? '{{ _.base_url }}'.$endpoint['path']
|
||||||
|
: '{{ _.base_url }}/api/{{ _.env }}'.str_replace('/api', '', $endpoint['path']);
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
$headers[] = ['name' => 'Content-Type', 'value' => 'application/json'];
|
||||||
|
if (!$isAuth) {
|
||||||
|
$headers[] = ['name' => 'ETicket-Email', 'value' => '{{ _.email }}'];
|
||||||
|
$headers[] = ['name' => 'ETicket-JWT', 'value' => '{{ _.jwt_token }}'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = null;
|
||||||
|
if ($endpoint['request']) {
|
||||||
|
$example = [];
|
||||||
|
foreach ($endpoint['request'] as $name => $field) {
|
||||||
|
$example[$name] = $field['example'] ?? '';
|
||||||
|
}
|
||||||
|
$body = [
|
||||||
|
'mimeType' => 'application/json',
|
||||||
|
'text' => json_encode($example, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$resources[] = [
|
||||||
|
'_type' => 'request',
|
||||||
|
'_id' => 'req_'.$folderIndex.'_'.$reqIndex,
|
||||||
|
'parentId' => $folderId,
|
||||||
|
'name' => $endpoint['summary'],
|
||||||
|
'method' => $endpoint['method'],
|
||||||
|
'url' => $url,
|
||||||
|
'headers' => $headers,
|
||||||
|
'body' => $body,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$export = [
|
||||||
|
'_type' => 'export',
|
||||||
|
'__export_format' => 4,
|
||||||
|
'__export_date' => date('Y-m-d\TH:i:s\Z'),
|
||||||
|
'__export_source' => 'eticket.api.doc',
|
||||||
|
'resources' => $resources,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = new Response(
|
||||||
|
json_encode($export, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES),
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Content-Disposition' => 'attachment; filename="eticket-api-insomnia.json"',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array{name: string, slug: string, baseUrl: string, description: string, badge: string, badgeColor: string}>
|
* @return list<array{name: string, slug: string, baseUrl: string, description: string, badge: string, badgeColor: string}>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -38,9 +38,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 border-2 border-gray-700 bg-gray-800 px-4 py-3 flex items-center gap-3">
|
<div class="mt-3 border-2 border-gray-700 bg-gray-800 px-4 py-3 flex items-center gap-3" data-host="{{ app.request.schemeAndHttpHost }}">
|
||||||
<p class="text-[10px] font-black uppercase tracking-widest text-gray-500">Base URL</p>
|
<p class="text-[10px] font-black uppercase tracking-widest text-gray-500">Base URL</p>
|
||||||
<p class="font-mono font-bold text-sm text-[#fabf04]" id="env-base-url">https://ticket.e-cosplay.fr/api/sandbox</p>
|
<p class="font-mono font-bold text-sm text-[#fabf04]" id="env-base-url">{{ app.request.schemeAndHttpHost }}/api/sandbox</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-xs font-bold text-gray-400" id="env-description">Environnement de test. Les donnees ne sont pas modifiees.</p>
|
<p class="mt-2 text-xs font-bold text-gray-400" id="env-description">Environnement de test. Les donnees ne sont pas modifiees.</p>
|
||||||
<p class="mt-2 text-xs font-bold text-gray-500">L'authentification (<span class="font-mono">/api/auth/login</span>) est commune aux deux environnements (vos vrais identifiants).</p>
|
<p class="mt-2 text-xs font-bold text-gray-500">L'authentification (<span class="font-mono">/api/auth/login</span>) est commune aux deux environnements (vos vrais identifiants).</p>
|
||||||
@@ -64,10 +64,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8 flex flex-wrap gap-3">
|
||||||
<a href="{{ path('app_api_doc_json') }}" target="_blank" class="inline-flex items-center gap-2 px-6 py-3 border-4 border-[#fabf04] bg-[#fabf04] text-gray-900 font-black uppercase text-xs tracking-widest shadow-[6px_6px_0px_rgba(0,0,0,1)] hover:shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-y-[-2px] transition-all">
|
<a href="{{ path('app_api_doc_json') }}" target="_blank" class="inline-flex items-center gap-2 px-6 py-3 border-4 border-[#fabf04] bg-[#fabf04] text-gray-900 font-black uppercase text-xs tracking-widest shadow-[6px_6px_0px_rgba(0,0,0,1)] hover:shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-y-[-2px] transition-all">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
|
||||||
Voir la spec JSON
|
Spec JSON
|
||||||
|
</a>
|
||||||
|
<a href="{{ path('app_api_doc_insomnia') }}" class="inline-flex items-center gap-2 px-6 py-3 border-4 border-indigo-600 bg-indigo-600 text-white font-black uppercase text-xs tracking-widest shadow-[6px_6px_0px_rgba(0,0,0,1)] hover:shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-y-[-2px] transition-all">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
|
||||||
|
Insomnia
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user