Files
claudetools/projects/msp-tools/quote-wizard/php-api/api/index.php
Mike Swanson fa15b03180 sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00
Synced files:
- Quote wizard frontend (all components, hooks, types, config)
- API updates (config, models, routers, schemas, services)
- Client work (bg-builders, gurushow)
- Scripts (BGB Lesley termination, CIPP, Datto, migration)
- Temp files (Bardach contacts, VWP investigation, misc)
- Credentials and session logs
- Email service, PHP API, session logs

Machine: ACG-M-L5090
Timestamp: 2026-03-10 19:11:00

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:59:08 -07:00

165 lines
5.5 KiB
PHP

<?php
/**
* Front controller / router for the MSP Quote Wizard PHP API.
*
* All requests are routed here via .htaccess. Parses the URI and method,
* emits CORS headers, then dispatches to the appropriate route handler.
*
* Route map:
* POST /quotes -> create quote
* GET /quotes/{token} -> get quote by token
* PUT /quotes/{token} -> update quote
* POST /quotes/{token}/items -> add item
* DELETE /quotes/{token}/items/{id} -> remove item
* POST /quotes/{token}/submit -> submit quote
* GET /admin/quotes -> list quotes (auth)
* GET /admin/quotes/stats -> get stats (auth)
* GET /admin/quotes/{id} -> get quote by ID (auth)
* PUT /admin/quotes/{id} -> update quote status (auth)
* POST /admin/quotes/{id}/sync-syncro -> sync to Syncro (auth)
*/
// Error reporting: log only, never display to client
ini_set('display_errors', '0');
error_reporting(E_ALL);
require_once __DIR__ . '/helpers.php';
// Emit CORS headers on every request (handles OPTIONS preflight too)
cors_headers();
// Parse request
$method = $_SERVER['REQUEST_METHOD'];
// Get the path relative to the API directory
// Strip the script directory from REQUEST_URI to get the route path
$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// Determine the base path (the directory where index.php lives)
$script_dir = dirname($_SERVER['SCRIPT_NAME']);
if ($script_dir !== '/' && $script_dir !== '\\') {
$path = substr($request_uri, strlen($script_dir));
} else {
$path = $request_uri;
}
// Normalize: ensure leading slash, remove trailing slash (except root)
$path = '/' . ltrim($path, '/');
if ($path !== '/' && substr($path, -1) === '/') {
$path = rtrim($path, '/');
}
// Split path into segments for matching
$segments = array_values(array_filter(explode('/', $path), function ($s) {
return $s !== '';
}));
$seg_count = count($segments);
// --------------------------------------------------------------------------
// Route dispatch
// --------------------------------------------------------------------------
// -- Public quote routes: /quotes/... --
if ($seg_count >= 1 && $segments[0] === 'quotes') {
require_once __DIR__ . '/routes/quotes.php';
// POST /quotes -> create
if ($seg_count === 1 && $method === 'POST') {
handle_create_quote();
}
// GET /quotes/{token} -> get
if ($seg_count === 2 && $method === 'GET') {
handle_get_quote($segments[1]);
}
// PUT /quotes/{token} -> update
if ($seg_count === 2 && $method === 'PUT') {
handle_update_quote($segments[1]);
}
// POST /quotes/{token}/items -> add item
if ($seg_count === 3 && $segments[2] === 'items' && $method === 'POST') {
handle_add_item($segments[1]);
}
// DELETE /quotes/{token}/items/{id} -> remove item
if ($seg_count === 4 && $segments[2] === 'items' && $method === 'DELETE') {
handle_remove_item($segments[1], $segments[3]);
}
// POST /quotes/{token}/submit -> submit
if ($seg_count === 3 && $segments[2] === 'submit' && $method === 'POST') {
handle_submit_quote($segments[1]);
}
// If we got here with a quotes path but no match, 404
error_response('Not found', 404);
}
// -- Admin routes: /admin/quotes/... --
if ($seg_count >= 2 && $segments[0] === 'admin' && $segments[1] === 'quotes') {
require_once __DIR__ . '/routes/admin.php';
// GET /admin/quotes -> list
if ($seg_count === 2 && $method === 'GET') {
handle_list_quotes();
}
// GET /admin/quotes/stats -> stats
if ($seg_count === 3 && $segments[2] === 'stats' && $method === 'GET') {
handle_get_stats();
}
// GET /admin/quotes/{id} -> get by ID
if ($seg_count === 3 && $segments[2] !== 'stats' && $method === 'GET') {
handle_admin_get_quote($segments[2]);
}
// PUT /admin/quotes/{id} -> admin update
if ($seg_count === 3 && $method === 'PUT') {
handle_admin_update_quote($segments[2]);
}
// POST /admin/quotes/{id}/sync-syncro -> syncro sync
if ($seg_count === 4 && $segments[3] === 'sync-syncro' && $method === 'POST') {
handle_sync_syncro($segments[2]);
}
// If we got here with an admin path but no match, 404
error_response('Not found', 404);
}
// --------------------------------------------------------------------------
// Health check: GET /health
// --------------------------------------------------------------------------
if ($seg_count === 1 && $segments[0] === 'health' && $method === 'GET') {
// Quick DB connectivity check
try {
require_once __DIR__ . '/db.php';
$db = get_db();
$db->query('SELECT 1');
json_response(['status' => 'ok', 'database' => 'connected']);
} catch (\Throwable $e) {
json_response(['status' => 'error', 'database' => 'disconnected'], 503);
}
}
// --------------------------------------------------------------------------
// Root: GET /
// --------------------------------------------------------------------------
if ($seg_count === 0 && $method === 'GET') {
json_response([
'service' => 'MSP Quote Wizard API',
'version' => '1.0.0',
'status' => 'running',
]);
}
// --------------------------------------------------------------------------
// 404 fallback
// --------------------------------------------------------------------------
error_response('Not found', 404);