Implementiere Marktmanagement-MCP-API
This commit is contained in:
1 parent
2607f448ad
commit
df5a533983
4 files changed
+622
-5
No files matched your search
@@ -47,12 +47,16 @@ OTC-Verkäufen abgeleitet.
|
||||
## 4. Authentifizierung und Autorisierung
|
||||
|
||||
- MCP-Endpunkt über geschütztes HTTPS
|
||||
- OAuth-/OIDC-basierte Benutzerautorisierung
|
||||
- Token mit Benutzeridentität, Zielgruppe und Modul-Scope
|
||||
- getrennte Scopes für Lesen, Schreiben und Archivieren
|
||||
- Prüfung an der Transportgrenze
|
||||
- Bearer-Token-Prüfung an der Transportgrenze
|
||||
- der konfigurierte Token identifiziert den technischen Hermes-Benutzerkontext
|
||||
- zusätzliche Prüfung im jeweiligen Tool-Handler
|
||||
- keine langlebigen gemeinsamen Zugangsdaten
|
||||
- kein direkter Datenbankzugriff für Hermes
|
||||
|
||||
Für die erste Implementierungsstufe wird bewusst keine neue OAuth-/OIDC- oder
|
||||
Entitlement-Infrastruktur eingeführt. Der Token wird ausschließlich über die
|
||||
geschützte App-Konfiguration bereitgestellt und mit konstanter Zeit verglichen.
|
||||
Die Implementierung verwendet dafür den Konfigurationswert
|
||||
`MARKET_MCP_TOKEN`.
|
||||
|
||||
Die fachliche Ownership der Lizenz-/Entitlement-Daten bleibt offen. Die
|
||||
Schnittstelle prüft nur die über den bestehenden Zugriffskontext gelieferte
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/service.php';
|
||||
|
||||
function market_management_mcp_token(array $env): string
|
||||
{
|
||||
$token = env_value('MARKET_MCP_TOKEN', $env);
|
||||
if ($token === '') {
|
||||
throw new RuntimeException('MARKET_MCP_TOKEN ist nicht konfiguriert.');
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
function market_management_mcp_authorize(array $env): string
|
||||
{
|
||||
$header = (string) ($_SERVER['HTTP_AUTHORIZATION'] ?? '');
|
||||
if (!preg_match('/^Bearer\s+(.+)$/i', trim($header), $matches)) {
|
||||
throw new RuntimeException('Bearer-Token fehlt.');
|
||||
}
|
||||
|
||||
$token = trim($matches[1]);
|
||||
$expected = market_management_mcp_token($env);
|
||||
if ($token === '' || !hash_equals($expected, $token)) {
|
||||
throw new RuntimeException('Bearer-Token ist ungültig.');
|
||||
}
|
||||
|
||||
return 'bearer:' . hash('sha256', $token);
|
||||
}
|
||||
|
||||
function market_management_mcp_tools(): array
|
||||
{
|
||||
return [
|
||||
['name' => 'market_management.search_markets', 'description' => 'Märkte suchen', 'inputSchema' => ['type' => 'object']],
|
||||
['name' => 'market_management.get_market', 'description' => 'Markt mit Durchführungen lesen', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.create_market', 'description' => 'Markt mit bestehender Kategorie anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name', 'category_id']]],
|
||||
['name' => 'market_management.update_market', 'description' => 'Markt ändern', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.archive_market', 'description' => 'Markt archivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.reactivate_market', 'description' => 'Markt reaktivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id']]],
|
||||
['name' => 'market_management.list_categories', 'description' => 'Bestehende Kategorien lesen', 'inputSchema' => ['type' => 'object']],
|
||||
['name' => 'market_management.create_occurrence', 'description' => 'Durchführung anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['market_definition_id']]],
|
||||
['name' => 'market_management.get_occurrence', 'description' => 'Durchführung mit Tagen lesen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
['name' => 'market_management.update_occurrence', 'description' => 'Durchführung ändern', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
['name' => 'market_management.archive_occurrence', 'description' => 'Durchführung archivieren', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
['name' => 'market_management.reactivate_occurrence', 'description' => 'Durchführung reaktivieren', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id']]],
|
||||
['name' => 'market_management.create_day', 'description' => 'Durchführungstag anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['occurrence_id', 'date']]],
|
||||
['name' => 'market_management.update_day', 'description' => 'Durchführungstag ändern', 'inputSchema' => ['type' => 'object', 'required' => ['day_id']]],
|
||||
['name' => 'market_management.delete_day', 'description' => 'Durchführungstag entfernen', 'inputSchema' => ['type' => 'object', 'required' => ['day_id']]],
|
||||
];
|
||||
}
|
||||
|
||||
function market_management_mcp_call(PDO $pdo, string $tool, array $arguments): array
|
||||
{
|
||||
return match ($tool) {
|
||||
'market_management.search_markets' => market_management_search_markets($pdo, $arguments),
|
||||
'market_management.get_market' => market_management_get_market(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['market_id'] ?? null, 'market_id'),
|
||||
(bool) ($arguments['include_archived'] ?? false)
|
||||
) ?? throw new RuntimeException('Markt nicht gefunden.'),
|
||||
'market_management.create_market' => market_management_create_market($pdo, $arguments),
|
||||
'market_management.update_market' => market_management_update_market(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['market_id'] ?? null, 'market_id'),
|
||||
$arguments
|
||||
),
|
||||
'market_management.archive_market' => market_management_set_market_archive(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['market_id'] ?? null, 'market_id'),
|
||||
true
|
||||
),
|
||||
'market_management.reactivate_market' => market_management_set_market_archive(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['market_id'] ?? null, 'market_id'),
|
||||
false
|
||||
),
|
||||
'market_management.list_categories' => market_management_list_categories($pdo),
|
||||
'market_management.create_occurrence' => market_management_create_occurrence($pdo, $arguments),
|
||||
'market_management.get_occurrence' => market_management_get_occurrence(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['occurrence_id'] ?? null, 'occurrence_id'),
|
||||
(bool) ($arguments['include_archived'] ?? false)
|
||||
) ?? throw new RuntimeException('Durchführung nicht gefunden.'),
|
||||
'market_management.update_occurrence' => market_management_update_occurrence(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['occurrence_id'] ?? null, 'occurrence_id'),
|
||||
$arguments
|
||||
),
|
||||
'market_management.archive_occurrence' => market_management_set_occurrence_archive(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['occurrence_id'] ?? null, 'occurrence_id'),
|
||||
true
|
||||
),
|
||||
'market_management.reactivate_occurrence' => market_management_set_occurrence_archive(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['occurrence_id'] ?? null, 'occurrence_id'),
|
||||
false
|
||||
),
|
||||
'market_management.create_day' => market_management_create_day($pdo, $arguments),
|
||||
'market_management.update_day' => market_management_update_day(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['day_id'] ?? null, 'day_id'),
|
||||
$arguments
|
||||
),
|
||||
'market_management.delete_day' => market_management_delete_day(
|
||||
$pdo,
|
||||
market_management_require_positive_id($arguments['day_id'] ?? null, 'day_id')
|
||||
),
|
||||
default => throw new InvalidArgumentException('Unbekanntes MCP-Tool: ' . $tool),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../shared/db.php';
|
||||
|
||||
const MARKET_MANAGEMENT_STATUSES = [
|
||||
'open',
|
||||
'waitlist',
|
||||
'registered',
|
||||
'confirmation_pending',
|
||||
'confirmed',
|
||||
'cancelled',
|
||||
'completed',
|
||||
];
|
||||
|
||||
function market_management_require_positive_id(mixed $value, string $field): int
|
||||
{
|
||||
if (!is_int($value) && !ctype_digit((string) $value)) {
|
||||
throw new InvalidArgumentException($field . ' muss eine positive Ganzzahl sein.');
|
||||
}
|
||||
|
||||
$id = (int) $value;
|
||||
if ($id <= 0) {
|
||||
throw new InvalidArgumentException($field . ' muss eine positive Ganzzahl sein.');
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
function market_management_optional_string(array $input, string $field): ?string
|
||||
{
|
||||
if (!array_key_exists($field, $input) || $input[$field] === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim((string) $input[$field]);
|
||||
return $value === '' ? null : $value;
|
||||
}
|
||||
|
||||
function market_management_optional_bool(array $input, string $field): ?bool
|
||||
{
|
||||
if (!array_key_exists($field, $input) || $input[$field] === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_bool($input[$field])) {
|
||||
return $input[$field];
|
||||
}
|
||||
|
||||
if ($input[$field] === 'true' || $input[$field] === '1' || $input[$field] === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($input[$field] === 'false' || $input[$field] === '0' || $input[$field] === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException($field . ' muss boolean sein.');
|
||||
}
|
||||
|
||||
function market_management_optional_number(array $input, string $field): ?string
|
||||
{
|
||||
if (!array_key_exists($field, $input) || $input[$field] === null || $input[$field] === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = parse_number($input[$field]);
|
||||
if ($value === null || $value < 0) {
|
||||
throw new InvalidArgumentException($field . ' muss eine nichtnegative Zahl sein.');
|
||||
}
|
||||
|
||||
return number_format($value, 2, '.', '');
|
||||
}
|
||||
|
||||
function market_management_optional_integer(array $input, string $field): ?int
|
||||
{
|
||||
if (!array_key_exists($field, $input) || $input[$field] === null || $input[$field] === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = parse_number($input[$field]);
|
||||
if ($value === null || $value < 0 || floor($value) !== $value) {
|
||||
throw new InvalidArgumentException($field . ' muss eine nichtnegative Ganzzahl sein.');
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
function market_management_category_exists(PDO $pdo, int $categoryId): bool
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT 1 FROM market_category WHERE id = :id AND is_active = TRUE');
|
||||
$stmt->execute([':id' => $categoryId]);
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
function market_management_create_market(PDO $pdo, array $input): array
|
||||
{
|
||||
$name = trim((string) ($input['name'] ?? ''));
|
||||
$categoryId = market_management_require_positive_id($input['category_id'] ?? null, 'category_id');
|
||||
|
||||
if ($name === '') {
|
||||
throw new InvalidArgumentException('name ist erforderlich.');
|
||||
}
|
||||
if (!market_management_category_exists($pdo, $categoryId)) {
|
||||
throw new InvalidArgumentException('category_id verweist nicht auf eine aktive Kategorie.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO market_definition (name, category_id)
|
||||
VALUES (:name, :category_id)
|
||||
RETURNING id, name, category_id, archived_at, created_at, updated_at'
|
||||
);
|
||||
$stmt->execute([':name' => $name, ':category_id' => $categoryId]);
|
||||
return (array) $stmt->fetch();
|
||||
}
|
||||
|
||||
function market_management_search_markets(PDO $pdo, array $input): array
|
||||
{
|
||||
$search = market_management_optional_string($input, 'search');
|
||||
$includeArchived = (bool) ($input['include_archived'] ?? false);
|
||||
$limit = min(100, max(1, (int) ($input['limit'] ?? 50)));
|
||||
|
||||
$where = [];
|
||||
$params = [':limit' => $limit];
|
||||
if (!$includeArchived) {
|
||||
$where[] = 'm.archived_at IS NULL';
|
||||
}
|
||||
if ($search !== null) {
|
||||
$where[] = 'm.name ILIKE :search';
|
||||
$params[':search'] = '%' . $search . '%';
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT m.id, m.name, m.category_id, c.code AS category_code, c.name AS category_name,
|
||||
m.archived_at, m.created_at, m.updated_at
|
||||
FROM market_definition m
|
||||
INNER JOIN market_category c ON c.id = m.category_id
|
||||
WHERE ' . ($where === [] ? 'TRUE' : implode(' AND ', $where)) . '
|
||||
ORDER BY m.name ASC, m.id ASC
|
||||
LIMIT :limit'
|
||||
);
|
||||
foreach ($params as $key => $value) {
|
||||
$stmt->bindValue($key, $value, $key === ':limit' ? PDO::PARAM_INT : PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
function market_management_get_market(PDO $pdo, int $marketId, bool $includeArchived = false): ?array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT m.id, m.name, m.category_id, c.code AS category_code, c.name AS category_name,
|
||||
m.archived_at, m.created_at, m.updated_at
|
||||
FROM market_definition m
|
||||
INNER JOIN market_category c ON c.id = m.category_id
|
||||
WHERE m.id = :id AND (:include_archived OR m.archived_at IS NULL)'
|
||||
);
|
||||
$stmt->execute([':id' => $marketId, ':include_archived' => $includeArchived]);
|
||||
$market = $stmt->fetch();
|
||||
if (!is_array($market)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$occurrences = $pdo->prepare(
|
||||
'SELECT id, status, place, address, municipality_reference_id, notes,
|
||||
registration_deadline, request_or_registration_date, confirmation_date,
|
||||
payment_deadline, payment_status, payment_date, paid_amount,
|
||||
contract_folder_url, archived_at, created_at, updated_at
|
||||
FROM market_occurrence
|
||||
WHERE market_definition_id = :market_id
|
||||
AND (:include_archived OR archived_at IS NULL)
|
||||
ORDER BY id ASC'
|
||||
);
|
||||
$occurrences->execute([':market_id' => $marketId, ':include_archived' => $includeArchived]);
|
||||
$market['occurrences'] = $occurrences->fetchAll();
|
||||
return $market;
|
||||
}
|
||||
|
||||
function market_management_update_market(PDO $pdo, int $marketId, array $input): array
|
||||
{
|
||||
$sets = [];
|
||||
$params = [':id' => $marketId];
|
||||
if (array_key_exists('name', $input)) {
|
||||
$name = trim((string) $input['name']);
|
||||
if ($name === '') {
|
||||
throw new InvalidArgumentException('name darf nicht leer sein.');
|
||||
}
|
||||
$sets[] = 'name = :name';
|
||||
$params[':name'] = $name;
|
||||
}
|
||||
if (array_key_exists('category_id', $input)) {
|
||||
$categoryId = market_management_require_positive_id($input['category_id'], 'category_id');
|
||||
if (!market_management_category_exists($pdo, $categoryId)) {
|
||||
throw new InvalidArgumentException('category_id verweist nicht auf eine aktive Kategorie.');
|
||||
}
|
||||
$sets[] = 'category_id = :category_id';
|
||||
$params[':category_id'] = $categoryId;
|
||||
}
|
||||
if ($sets === []) {
|
||||
throw new InvalidArgumentException('Mindestens ein änderbares Feld ist erforderlich.');
|
||||
}
|
||||
|
||||
$sets[] = 'updated_at = NOW()';
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE market_definition SET ' . implode(', ', $sets) . ' WHERE id = :id
|
||||
RETURNING id, name, category_id, archived_at, created_at, updated_at'
|
||||
);
|
||||
$stmt->execute($params);
|
||||
$market = $stmt->fetch();
|
||||
if (!is_array($market)) {
|
||||
throw new RuntimeException('Markt nicht gefunden.');
|
||||
}
|
||||
return $market;
|
||||
}
|
||||
|
||||
function market_management_set_market_archive(PDO $pdo, int $marketId, bool $archive): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE market_definition
|
||||
SET archived_at = ' . ($archive ? 'COALESCE(archived_at, NOW())' : 'NULL') . ', updated_at = NOW()
|
||||
WHERE id = :id
|
||||
RETURNING id, name, category_id, archived_at, created_at, updated_at'
|
||||
);
|
||||
$stmt->execute([':id' => $marketId]);
|
||||
$market = $stmt->fetch();
|
||||
if (!is_array($market)) {
|
||||
throw new RuntimeException('Markt nicht gefunden.');
|
||||
}
|
||||
return $market;
|
||||
}
|
||||
|
||||
function market_management_list_categories(PDO $pdo): array
|
||||
{
|
||||
return $pdo->query(
|
||||
'SELECT id, code, name FROM market_category WHERE is_active = TRUE ORDER BY name ASC, id ASC'
|
||||
)->fetchAll();
|
||||
}
|
||||
|
||||
function market_management_validate_status(mixed $status): string
|
||||
{
|
||||
$value = trim((string) $status);
|
||||
if (!in_array($value, MARKET_MANAGEMENT_STATUSES, true)) {
|
||||
throw new InvalidArgumentException('Ungültiger Durchführungsstatus.');
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
function market_management_write_fields(array $input, array $definitions): array
|
||||
{
|
||||
$fields = [];
|
||||
$params = [];
|
||||
foreach ($definitions as $field => $type) {
|
||||
if (!array_key_exists($field, $input)) {
|
||||
continue;
|
||||
}
|
||||
$value = match ($type) {
|
||||
'string' => market_management_optional_string($input, $field),
|
||||
'bool' => market_management_optional_bool($input, $field),
|
||||
'number' => market_management_optional_number($input, $field),
|
||||
'integer' => market_management_optional_integer($input, $field),
|
||||
'id' => market_management_require_positive_id($input[$field], $field),
|
||||
default => throw new LogicException('Unbekannter Feldtyp.'),
|
||||
};
|
||||
$fields[] = $field . ' = :' . $field;
|
||||
$params[':' . $field] = $value;
|
||||
}
|
||||
return [$fields, $params];
|
||||
}
|
||||
|
||||
function market_management_create_occurrence(PDO $pdo, array $input): array
|
||||
{
|
||||
$marketId = market_management_require_positive_id($input['market_definition_id'] ?? null, 'market_definition_id');
|
||||
if (market_management_get_market($pdo, $marketId, true) === null) {
|
||||
throw new InvalidArgumentException('market_definition_id verweist nicht auf einen Markt.');
|
||||
}
|
||||
$status = array_key_exists('status', $input) ? market_management_validate_status($input['status']) : 'open';
|
||||
$definitions = [
|
||||
'place' => 'string', 'address' => 'string', 'municipality_reference_id' => 'id', 'notes' => 'string',
|
||||
'registration_deadline' => 'string', 'request_or_registration_date' => 'string', 'confirmation_date' => 'string',
|
||||
'payment_deadline' => 'string', 'stand_size' => 'string', 'table_available' => 'bool',
|
||||
'table_rentable' => 'bool', 'own_table_required' => 'bool', 'shelter_available' => 'bool',
|
||||
'power_available' => 'bool', 'water_available' => 'bool', 'lighting_available' => 'bool',
|
||||
'other_infrastructure' => 'string', 'own_stand_requirements' => 'string', 'travel_address' => 'string',
|
||||
'travel_distance_km' => 'number', 'travel_duration_minutes' => 'integer', 'parking_options' => 'string',
|
||||
'delivery_access' => 'string', 'direct_unload_possible' => 'bool', 'storage_options' => 'string',
|
||||
'travel_notes' => 'string', 'payment_status' => 'string', 'payment_date' => 'string',
|
||||
'paid_amount' => 'number', 'payment_method_code' => 'string', 'cancellation_terms' => 'string',
|
||||
'revenue_share_terms' => 'string', 'other_participation_conditions' => 'string', 'contract_folder_url' => 'string',
|
||||
];
|
||||
[$fields, $params] = market_management_write_fields($input, $definitions);
|
||||
if (isset($params[':payment_status']) && !in_array($params[':payment_status'], ['paid', 'unpaid'], true)) {
|
||||
throw new InvalidArgumentException('Ungültiger Zahlungsstatus.');
|
||||
}
|
||||
array_unshift($fields, 'market_definition_id = :market_definition_id', 'status = :status');
|
||||
$params[':market_definition_id'] = $marketId;
|
||||
$params[':status'] = $status;
|
||||
$columns = array_map(static fn (string $field): string => explode(' = ', $field, 2)[0], $fields);
|
||||
$values = array_map(static fn (string $field): string => explode(' = ', $field, 2)[1], $fields);
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO market_occurrence (' . implode(', ', $columns) . ')
|
||||
VALUES (' . implode(', ', $values) . ')
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute($params);
|
||||
return (array) $stmt->fetch();
|
||||
}
|
||||
|
||||
function market_management_get_occurrence(PDO $pdo, int $occurrenceId, bool $includeArchived = false): ?array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT * FROM market_occurrence
|
||||
WHERE id = :id AND (:include_archived OR archived_at IS NULL)'
|
||||
);
|
||||
$stmt->execute([':id' => $occurrenceId, ':include_archived' => $includeArchived]);
|
||||
$occurrence = $stmt->fetch();
|
||||
if (!is_array($occurrence)) {
|
||||
return null;
|
||||
}
|
||||
$days = $pdo->prepare(
|
||||
'SELECT * FROM market_occurrence_day
|
||||
WHERE occurrence_id = :occurrence_id ORDER BY date ASC, id ASC'
|
||||
);
|
||||
$days->execute([':occurrence_id' => $occurrenceId]);
|
||||
$occurrence['days'] = $days->fetchAll();
|
||||
return $occurrence;
|
||||
}
|
||||
|
||||
function market_management_update_occurrence(PDO $pdo, int $occurrenceId, array $input): array
|
||||
{
|
||||
$definitions = [
|
||||
'status' => 'string', 'place' => 'string', 'address' => 'string', 'municipality_reference_id' => 'id',
|
||||
'notes' => 'string', 'registration_deadline' => 'string', 'request_or_registration_date' => 'string',
|
||||
'confirmation_date' => 'string', 'payment_deadline' => 'string', 'stand_size' => 'string',
|
||||
'table_available' => 'bool', 'table_rentable' => 'bool', 'own_table_required' => 'bool',
|
||||
'shelter_available' => 'bool', 'power_available' => 'bool', 'water_available' => 'bool',
|
||||
'lighting_available' => 'bool', 'other_infrastructure' => 'string', 'own_stand_requirements' => 'string',
|
||||
'travel_address' => 'string', 'travel_distance_km' => 'number', 'travel_duration_minutes' => 'integer',
|
||||
'parking_options' => 'string', 'delivery_access' => 'string', 'direct_unload_possible' => 'bool',
|
||||
'storage_options' => 'string', 'travel_notes' => 'string', 'payment_status' => 'string',
|
||||
'payment_date' => 'string', 'paid_amount' => 'number', 'payment_method_code' => 'string',
|
||||
'cancellation_terms' => 'string', 'revenue_share_terms' => 'string',
|
||||
'other_participation_conditions' => 'string', 'contract_folder_url' => 'string',
|
||||
];
|
||||
[$fields, $params] = market_management_write_fields($input, $definitions);
|
||||
if (array_key_exists('status', $input)) {
|
||||
$params[':status'] = market_management_validate_status($input['status']);
|
||||
}
|
||||
if (isset($params[':payment_status']) && !in_array($params[':payment_status'], ['paid', 'unpaid'], true)) {
|
||||
throw new InvalidArgumentException('Ungültiger Zahlungsstatus.');
|
||||
}
|
||||
if ($fields === []) {
|
||||
throw new InvalidArgumentException('Mindestens ein änderbares Feld ist erforderlich.');
|
||||
}
|
||||
$fields[] = 'updated_at = NOW()';
|
||||
$params[':id'] = $occurrenceId;
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE market_occurrence SET ' . implode(', ', $fields) . ' WHERE id = :id RETURNING *'
|
||||
);
|
||||
$stmt->execute($params);
|
||||
$occurrence = $stmt->fetch();
|
||||
if (!is_array($occurrence)) {
|
||||
throw new RuntimeException('Durchführung nicht gefunden.');
|
||||
}
|
||||
return $occurrence;
|
||||
}
|
||||
|
||||
function market_management_create_day(PDO $pdo, array $input): array
|
||||
{
|
||||
$occurrenceId = market_management_require_positive_id($input['occurrence_id'] ?? null, 'occurrence_id');
|
||||
if (market_management_get_occurrence($pdo, $occurrenceId, true) === null) {
|
||||
throw new InvalidArgumentException('occurrence_id verweist nicht auf eine Durchführung.');
|
||||
}
|
||||
$date = trim((string) ($input['date'] ?? ''));
|
||||
if ($date === '') {
|
||||
throw new InvalidArgumentException('date ist erforderlich.');
|
||||
}
|
||||
$definitions = [
|
||||
'stand_start_time' => 'string', 'stand_end_time' => 'string', 'setup_start_time' => 'string',
|
||||
'setup_end_time' => 'string', 'delivery_start_time' => 'string', 'delivery_end_time' => 'string',
|
||||
'observations' => 'string',
|
||||
];
|
||||
[$fields, $params] = market_management_write_fields($input, $definitions);
|
||||
array_unshift($fields, 'occurrence_id = :occurrence_id', 'date = :date');
|
||||
$params[':occurrence_id'] = $occurrenceId;
|
||||
$params[':date'] = $date;
|
||||
$columns = array_map(static fn (string $field): string => explode(' = ', $field, 2)[0], $fields);
|
||||
$values = array_map(static fn (string $field): string => explode(' = ', $field, 2)[1], $fields);
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO market_occurrence_day (' . implode(', ', $columns) . ')
|
||||
VALUES (' . implode(', ', $values) . ') RETURNING *'
|
||||
);
|
||||
$stmt->execute($params);
|
||||
return (array) $stmt->fetch();
|
||||
}
|
||||
|
||||
function market_management_update_day(PDO $pdo, int $dayId, array $input): array
|
||||
{
|
||||
$definitions = [
|
||||
'date' => 'string', 'stand_start_time' => 'string', 'stand_end_time' => 'string',
|
||||
'setup_start_time' => 'string', 'setup_end_time' => 'string', 'delivery_start_time' => 'string',
|
||||
'delivery_end_time' => 'string', 'observations' => 'string',
|
||||
];
|
||||
[$fields, $params] = market_management_write_fields($input, $definitions);
|
||||
if ($fields === []) {
|
||||
throw new InvalidArgumentException('Mindestens ein änderbares Feld ist erforderlich.');
|
||||
}
|
||||
$fields[] = 'updated_at = NOW()';
|
||||
$params[':id'] = $dayId;
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE market_occurrence_day SET ' . implode(', ', $fields) . ' WHERE id = :id RETURNING *'
|
||||
);
|
||||
$stmt->execute($params);
|
||||
$day = $stmt->fetch();
|
||||
if (!is_array($day)) {
|
||||
throw new RuntimeException('Durchführungstag nicht gefunden.');
|
||||
}
|
||||
return $day;
|
||||
}
|
||||
|
||||
function market_management_set_occurrence_archive(PDO $pdo, int $occurrenceId, bool $archive): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE market_occurrence
|
||||
SET archived_at = ' . ($archive ? 'COALESCE(archived_at, NOW())' : 'NULL') . ', updated_at = NOW()
|
||||
WHERE id = :id RETURNING *'
|
||||
);
|
||||
$stmt->execute([':id' => $occurrenceId]);
|
||||
$occurrence = $stmt->fetch();
|
||||
if (!is_array($occurrence)) {
|
||||
throw new RuntimeException('Durchführung nicht gefunden.');
|
||||
}
|
||||
return $occurrence;
|
||||
}
|
||||
|
||||
function market_management_delete_day(PDO $pdo, int $dayId): array
|
||||
{
|
||||
$stmt = $pdo->prepare('DELETE FROM market_occurrence_day WHERE id = :id RETURNING id, occurrence_id, date');
|
||||
$stmt->execute([':id' => $dayId]);
|
||||
$day = $stmt->fetch();
|
||||
if (!is_array($day)) {
|
||||
throw new RuntimeException('Durchführungstag nicht gefunden.');
|
||||
}
|
||||
return $day;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../modules/shared/db.php';
|
||||
require_once __DIR__ . '/../../modules/market_management/mcp.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
|
||||
$env = expand_env_values(parse_env_file(__DIR__ . '/../../.env'));
|
||||
|
||||
try {
|
||||
$userSubject = market_management_mcp_authorize($env);
|
||||
$pdo = connect_database($env);
|
||||
$body = file_get_contents('php://input');
|
||||
$request = json_decode($body === false ? '' : $body, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($request) || ($request['jsonrpc'] ?? '') !== '2.0') {
|
||||
throw new InvalidArgumentException('Ungültige JSON-RPC-Anfrage.');
|
||||
}
|
||||
|
||||
$id = $request['id'] ?? null;
|
||||
$method = (string) ($request['method'] ?? '');
|
||||
$params = is_array($request['params'] ?? null) ? $request['params'] : [];
|
||||
|
||||
if ($method === 'initialize') {
|
||||
$result = [
|
||||
'protocolVersion' => (string) ($params['protocolVersion'] ?? '2025-03-26'),
|
||||
'capabilities' => ['tools' => ['listChanged' => false]],
|
||||
'serverInfo' => ['name' => 'naurua-market-management', 'version' => '1.0.0'],
|
||||
];
|
||||
} elseif ($method === 'notifications/initialized') {
|
||||
http_response_code(202);
|
||||
exit;
|
||||
} elseif ($method === 'tools/list') {
|
||||
$result = ['tools' => market_management_mcp_tools()];
|
||||
} elseif ($method === 'tools/call') {
|
||||
$tool = (string) ($params['name'] ?? '');
|
||||
$arguments = is_array($params['arguments'] ?? null) ? $params['arguments'] : [];
|
||||
$result = [
|
||||
'content' => [[
|
||||
'type' => 'text',
|
||||
'text' => json_encode(market_management_mcp_call($pdo, $tool, $arguments), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
|
||||
]],
|
||||
'isError' => false,
|
||||
];
|
||||
} else {
|
||||
throw new InvalidArgumentException('Unbekannte JSON-RPC-Methode: ' . $method);
|
||||
}
|
||||
|
||||
echo json_encode(['jsonrpc' => '2.0', 'id' => $id, 'result' => $result], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code($e instanceof RuntimeException && str_contains($e->getMessage(), 'Token') ? 401 : 400);
|
||||
echo json_encode([
|
||||
'jsonrpc' => '2.0',
|
||||
'id' => $request['id'] ?? null,
|
||||
'error' => ['code' => -32600, 'message' => $e->getMessage()],
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
Reference in new issue
Block a user