146 lines
5.7 KiB
PHP
146 lines
5.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* cry01_spool.php — POST handlers.
|
|
* Validates input, builds spool envelope, POSTs to orchestrator.
|
|
* Does not render HTML — returns rendered output from cry01_renderer.php.
|
|
*/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SIGNAL POST HANDLER
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function cry01_handle_signal_post($association_slug, $access) {
|
|
// Validates and submits a capacity signal to the orchestrator spool receiver.
|
|
$g1_pubkey = trim($_POST['g1_pubkey'] ?? '');
|
|
$direction = trim($_POST['direction'] ?? '');
|
|
$category_id = trim($_POST['category_id'] ?? '');
|
|
$capacity_description = trim($_POST['capacity_description'] ?? '');
|
|
$g1_denomination = trim($_POST['g1_denomination'] ?? '');
|
|
$duration_days = intval($_POST['duration_days'] ?? 0);
|
|
|
|
// Validate required fields.
|
|
$errors = [];
|
|
if (!$g1_pubkey) $errors[] = 'Ğ1 public key is required.';
|
|
if (!in_array($direction, ['offer', 'seek'])) $errors[] = 'Direction must be offer or seek.';
|
|
if (!$category_id) $errors[] = 'Category is required.';
|
|
if (!$capacity_description) $errors[] = 'Description is required.';
|
|
if (!$g1_denomination || !is_numeric($g1_denomination) || floatval($g1_denomination) <= 0) {
|
|
$errors[] = 'Ğ1 amount must be a positive number.';
|
|
}
|
|
if ($duration_days < 1 || $duration_days > 365) {
|
|
$errors[] = 'Duration must be between 1 and 365 days.';
|
|
}
|
|
|
|
if ($errors) {
|
|
return '<div class="cry01-content"><div class="alert alert-danger"><ul class="mb-0">'
|
|
. implode('', array_map(fn($e) => '<li>' . cry01_h($e) . '</li>', $errors))
|
|
. '</ul></div></div>'
|
|
. cry01_render_signal_form($association_slug, $access);
|
|
}
|
|
|
|
$config = cry01_load_config();
|
|
$token = $config['node_token'] ?? '';
|
|
|
|
// Build the spool envelope per contracts/signal-v1.json.
|
|
$envelope = [
|
|
'_header' => [
|
|
'addon' => 'cry01',
|
|
'association_slug' => $association_slug,
|
|
'participant_xchan' => get_observer_hash(),
|
|
'submitted_at' => date('c'),
|
|
'node_token_hash' => hash('sha256', $token),
|
|
],
|
|
'_payload' => [
|
|
'g1_pubkey' => $g1_pubkey,
|
|
'capacity_description' => $capacity_description,
|
|
'g1_denomination' => $g1_denomination,
|
|
'duration_days' => $duration_days,
|
|
'category_id' => $category_id,
|
|
'direction' => $direction,
|
|
],
|
|
];
|
|
|
|
$result = cry01_post_to_orchestrator('/cry01/signal', $envelope);
|
|
|
|
if ($result === true) {
|
|
return '<div class="cry01-content">'
|
|
. '<div class="alert alert-success">Signal registered successfully. '
|
|
. 'A Verifiable Credential will be issued and timestamped to Bitcoin.</div>'
|
|
. '<p><a href="' . z_root() . '/cry01/' . cry01_h($association_slug) . '">← Back to Value Layer</a></p>'
|
|
. '</div>';
|
|
}
|
|
|
|
return '<div class="cry01-content">'
|
|
. '<div class="alert alert-danger">Signal could not be registered. '
|
|
. 'The orchestrator may be temporarily unavailable. Please try again.</div>'
|
|
. '</div>'
|
|
. cry01_render_signal_form($association_slug, $access);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MANAGE POST HANDLER
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function cry01_handle_manage_post($association_slug) {
|
|
// Handles operator manage actions: currently only cache refresh.
|
|
if (!cry01_verify_csrf()) {
|
|
return cry01_render_error('Invalid form token. Please reload and try again.');
|
|
}
|
|
|
|
$action = $_POST['action'] ?? '';
|
|
|
|
if ($action === 'refresh_cache') {
|
|
$ok = cry01_refresh_balance_cache();
|
|
$msg = $ok
|
|
? '<div class="alert alert-success">Balance cache refreshed successfully.</div>'
|
|
: '<div class="alert alert-warning">Cache refresh failed. Check that the Duniter node is reachable and the operator Ğ1 key is configured.</div>';
|
|
return '<div class="cry01-content">' . $msg . '</div>' . cry01_render_manage($association_slug);
|
|
}
|
|
|
|
return cry01_render_error('Unknown action.');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ORCHESTRATOR TRANSPORT
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function cry01_post_to_orchestrator($path, $payload) {
|
|
// POSTs a JSON envelope to the orchestrator spool receiver.
|
|
// Returns true on success (HTTP 201), false on any failure.
|
|
$config = cry01_load_config();
|
|
$base = rtrim($config['receiver_url'] ?? '', '/');
|
|
if (!$base) {
|
|
logger('cry01_spool: receiver_url not configured');
|
|
return false;
|
|
}
|
|
|
|
$url = $base . $path;
|
|
$body = json_encode($payload);
|
|
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'method' => 'POST',
|
|
'header' => "Content-Type: application/json\r\nContent-Length: " . strlen($body) . "\r\n",
|
|
'content' => $body,
|
|
'timeout' => 10,
|
|
'ignore_errors' => true,
|
|
],
|
|
]);
|
|
|
|
$raw = @file_get_contents($url, false, $context);
|
|
$headers = $http_response_header ?? [];
|
|
$status = 0;
|
|
|
|
foreach ($headers as $h) {
|
|
if (preg_match('/HTTP\/\d\.\d\s+(\d+)/', $h, $m)) {
|
|
$status = intval($m[1]);
|
|
}
|
|
}
|
|
|
|
if ($status === 201) return true;
|
|
|
|
logger('cry01_spool: orchestrator returned status ' . $status . ' for ' . $path);
|
|
return false;
|
|
}
|