From f9ffa5a6b65dde45e776c2409a5c5ef5404b0081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Gla=CC=88ser?= Date: Sat, 19 Sep 2026 10:08:49 +0200 Subject: [PATCH] Ergaenze Website-URL am Marktstamm --- .../0025_market_definition_website_url.sql | 10 ++++++ .../market_management_data_architecture.md | 1 + .../market_management_hermes_mcp_api.md | 3 +- .../market_management_technisches_konzept.md | 3 +- .../market_management_requirements.md | 1 + modules/market_management/mcp.php | 4 +-- modules/market_management/service.php | 35 ++++++++++++++----- 7 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 db/migrations/0025_market_definition_website_url.sql diff --git a/db/migrations/0025_market_definition_website_url.sql b/db/migrations/0025_market_definition_website_url.sql new file mode 100644 index 0000000..bdca6f8 --- /dev/null +++ b/db/migrations/0025_market_definition_website_url.sql @@ -0,0 +1,10 @@ +BEGIN; + +ALTER TABLE market_definition + ADD COLUMN website_url TEXT; + +ALTER TABLE market_definition + ADD CONSTRAINT chk_market_definition_website_url + CHECK (website_url IS NULL OR website_url ~* '^https?://'); + +COMMIT; diff --git a/docs/modules/market_management/data/market_management_data_architecture.md b/docs/modules/market_management/data/market_management_data_architecture.md index cac1ee4..171ee73 100644 --- a/docs/modules/market_management/data/market_management_data_architecture.md +++ b/docs/modules/market_management/data/market_management_data_architecture.md @@ -47,6 +47,7 @@ Ebene. - Marktname - Marktart/Kategorie aus einer deterministischen Liste +- optionale Website-URL der Veranstaltung ### Durchführung diff --git a/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md b/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md index 6e429d4..9c86a33 100644 --- a/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md +++ b/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md @@ -80,9 +80,10 @@ Das Ergebnis steht bei `tools/call` als JSON im Textinhalt des ersten - optional: `include_archived` - `market_management.create_market` - erforderlich: `name`, `category_id` + - optional: `website_url` als HTTP(S)-URL der Veranstaltung - `market_management.update_market` - erforderlich: `market_id` - - änderbar: `name`, `category_id` + - änderbar: `name`, `category_id`, `website_url` - `market_management.archive_market` - erforderlich: `market_id` - `market_management.reactivate_market` diff --git a/docs/modules/market_management/konzepte/market_management_technisches_konzept.md b/docs/modules/market_management/konzepte/market_management_technisches_konzept.md index db08566..2864f73 100644 --- a/docs/modules/market_management/konzepte/market_management_technisches_konzept.md +++ b/docs/modules/market_management/konzepte/market_management_technisches_konzept.md @@ -84,6 +84,7 @@ Normativ: - jeder Markt besitzt genau einen eindeutigen Namen - jeder Markt besitzt genau eine Kategorie +- jeder Markt kann eine optionale Website-URL der Veranstaltung besitzen - der Markt enthält keine Durchführungs-, Kontakt-, Kosten-, Zahlungs- oder Erlösdaten - Markt und Durchführungen werden getrennt archiviert und reaktiviert @@ -142,7 +143,7 @@ Minimale Pflichtfelder: Über alle Statuswerte hinweg gelten nur diese Pflichtfelder: -- Marktdefinition: `name`, `category_id` +- Marktdefinition: `name`, `category_id`; `website_url` ist optional - Durchführung: `market_definition_id`, initialer Status `open`, mindestens ein Durchführungstag - Durchführungstag: `occurrence_id`, `date` diff --git a/docs/modules/market_management/requirements/market_management_requirements.md b/docs/modules/market_management/requirements/market_management_requirements.md index 38e6321..88c4436 100644 --- a/docs/modules/market_management/requirements/market_management_requirements.md +++ b/docs/modules/market_management/requirements/market_management_requirements.md @@ -40,6 +40,7 @@ Der zentrale Marktstamm enthält ausschließlich: - Marktname - Marktart/Kategorie aus einer deterministischen Liste +- optionale Website-URL der Veranstaltung Alle übrigen veränderlichen Angaben werden pro Durchführung erfasst. Tages- und Verkaufsdaten werden pro Durchführungstag erfasst. diff --git a/modules/market_management/mcp.php b/modules/market_management/mcp.php index 3ced45b..3454f52 100644 --- a/modules/market_management/mcp.php +++ b/modules/market_management/mcp.php @@ -34,8 +34,8 @@ function market_management_mcp_tools(): array ['name' => 'market_management.search_markets', 'description' => 'Märkte suchen', 'inputSchema' => ['type' => 'object']], ['name' => 'market_management.search_occurrences', 'description' => 'Durchführungen suchen und auflisten', '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.create_market', 'description' => 'Markt mit bestehender Kategorie und optionaler Website-URL anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name', 'category_id'], 'properties' => ['name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]], + ['name' => 'market_management.update_market', 'description' => 'Markt inklusive Website-URL ändern', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer'], 'name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]], ['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']], diff --git a/modules/market_management/service.php b/modules/market_management/service.php index 9eac824..e6f6e60 100644 --- a/modules/market_management/service.php +++ b/modules/market_management/service.php @@ -86,6 +86,20 @@ function market_management_optional_integer(array $input, string $field): ?int return (int) $value; } +function market_management_optional_url(array $input, string $field): ?string +{ + $value = market_management_optional_string($input, $field); + if ($value === null) { + return null; + } + + if (filter_var($value, FILTER_VALIDATE_URL) === false || !preg_match('/^https?:\/\//i', $value)) { + throw new InvalidArgumentException($field . ' muss eine gültige HTTP(S)-URL sein.'); + } + + return $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'); @@ -97,6 +111,7 @@ 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'); + $websiteUrl = market_management_optional_url($input, 'website_url'); if ($name === '') { throw new InvalidArgumentException('name ist erforderlich.'); @@ -106,11 +121,11 @@ function market_management_create_market(PDO $pdo, array $input): array } $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' + 'INSERT INTO market_definition (name, category_id, website_url) + VALUES (:name, :category_id, :website_url) + RETURNING id, name, category_id, website_url, archived_at, created_at, updated_at' ); - $stmt->execute([':name' => $name, ':category_id' => $categoryId]); + $stmt->execute([':name' => $name, ':category_id' => $categoryId, ':website_url' => $websiteUrl]); return (array) $stmt->fetch(); } @@ -178,7 +193,7 @@ function market_management_search_markets(PDO $pdo, array $input): array } $stmt = $pdo->prepare( - 'SELECT m.id, m.name, m.category_id, c.code AS category_code, c.name AS category_name, + 'SELECT m.id, m.name, m.category_id, m.website_url, c.code AS category_code, c.name AS category_name, m.archived_at, m.created_at, m.updated_at, MIN(d.date) AS earliest_date, MAX(d.date) AS latest_date, COUNT(DISTINCT o.id) AS occurrence_count @@ -202,7 +217,7 @@ function market_management_get_market(PDO $pdo, int $marketId, bool $includeArch { $marketArchivePredicate = $includeArchived ? 'TRUE' : 'm.archived_at IS NULL'; $stmt = $pdo->prepare( - 'SELECT m.id, m.name, m.category_id, c.code AS category_code, c.name AS category_name, + 'SELECT m.id, m.name, m.category_id, m.website_url, 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 @@ -331,6 +346,10 @@ function market_management_update_market(PDO $pdo, int $marketId, array $input): $sets[] = 'category_id = :category_id'; $params[':category_id'] = $categoryId; } + if (array_key_exists('website_url', $input)) { + $sets[] = 'website_url = :website_url'; + $params[':website_url'] = market_management_optional_url($input, 'website_url'); + } if ($sets === []) { throw new InvalidArgumentException('Mindestens ein änderbares Feld ist erforderlich.'); } @@ -338,7 +357,7 @@ function market_management_update_market(PDO $pdo, int $marketId, array $input): $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' + RETURNING id, name, category_id, website_url, archived_at, created_at, updated_at' ); $stmt->execute($params); $market = $stmt->fetch(); @@ -354,7 +373,7 @@ function market_management_set_market_archive(PDO $pdo, int $marketId, bool $arc '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' + RETURNING id, name, category_id, website_url, archived_at, created_at, updated_at' ); $stmt->execute([':id' => $marketId]); $market = $stmt->fetch();