This commit is contained in:
214
src/App/ListingCatalog.php
Normal file
214
src/App/ListingCatalog.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
final class ListingCatalog
|
||||
{
|
||||
private array $tableCache = [];
|
||||
|
||||
public function __construct(private \PDO $pdo)
|
||||
{
|
||||
}
|
||||
|
||||
public function ensureSchema(): void
|
||||
{
|
||||
$statements = [
|
||||
'CREATE TABLE IF NOT EXISTS listing_categories (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
slug VARCHAR(120) NOT NULL UNIQUE,
|
||||
title VARCHAR(160) NOT NULL,
|
||||
category_group ENUM("general","event","place","food","family","partner") NOT NULL DEFAULT "general",
|
||||
sort_order SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
'CREATE TABLE IF NOT EXISTS listing_places (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
created_by BIGINT UNSIGNED NULL,
|
||||
source_type ENUM("user","partner","admin","system") NOT NULL DEFAULT "user",
|
||||
title VARCHAR(180) NOT NULL,
|
||||
description TEXT NULL,
|
||||
street VARCHAR(180) NULL,
|
||||
zip CHAR(5) NULL,
|
||||
city VARCHAR(120) NULL,
|
||||
region VARCHAR(120) NULL,
|
||||
lat DECIMAL(10,7) NULL,
|
||||
lng DECIMAL(10,7) NULL,
|
||||
website_url VARCHAR(255) NULL,
|
||||
phone VARCHAR(60) NULL,
|
||||
place_kind VARCHAR(80) NULL,
|
||||
provider_hint ENUM("manual","osm","google") NOT NULL DEFAULT "manual",
|
||||
external_place_id VARCHAR(190) NULL,
|
||||
google_place_id VARCHAR(190) NULL,
|
||||
rating_value DECIMAL(3,2) NULL,
|
||||
rating_count INT UNSIGNED NULL,
|
||||
status ENUM("draft","published","archived") NOT NULL DEFAULT "published",
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_listing_places_user FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_listing_places_city (city),
|
||||
INDEX idx_listing_places_region (region),
|
||||
INDEX idx_listing_places_latlng (lat, lng),
|
||||
INDEX idx_listing_places_kind (place_kind)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
'CREATE TABLE IF NOT EXISTS listings (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
created_by BIGINT UNSIGNED NULL,
|
||||
owner_type ENUM("user","partner","admin","system") NOT NULL DEFAULT "user",
|
||||
listing_type ENUM("event","partner_offer","place","editorial_event") NOT NULL DEFAULT "event",
|
||||
primary_place_id BIGINT UNSIGNED NULL,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
teaser_public VARCHAR(280) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
visibility ENUM("public","members") NOT NULL DEFAULT "public",
|
||||
status ENUM("draft","published","cancelled","archived") NOT NULL DEFAULT "draft",
|
||||
supports_registration TINYINT(1) NOT NULL DEFAULT 0,
|
||||
supports_capacity TINYINT(1) NOT NULL DEFAULT 0,
|
||||
supports_pricing TINYINT(1) NOT NULL DEFAULT 0,
|
||||
is_recurring TINYINT(1) NOT NULL DEFAULT 0,
|
||||
legacy_event_id BIGINT UNSIGNED NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_listings_user FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_listings_place FOREIGN KEY (primary_place_id) REFERENCES listing_places(id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_listings_legacy_event FOREIGN KEY (legacy_event_id) REFERENCES events(id) ON DELETE SET NULL,
|
||||
INDEX idx_listings_type (listing_type),
|
||||
INDEX idx_listings_status (status),
|
||||
INDEX idx_listings_owner (owner_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
'CREATE TABLE IF NOT EXISTS listing_category_map (
|
||||
listing_id BIGINT UNSIGNED NOT NULL,
|
||||
category_id BIGINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (listing_id, category_id),
|
||||
CONSTRAINT fk_lcm_listing FOREIGN KEY (listing_id) REFERENCES listings(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_lcm_category FOREIGN KEY (category_id) REFERENCES listing_categories(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
'CREATE TABLE IF NOT EXISTS listing_occurrences (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
listing_id BIGINT UNSIGNED NOT NULL,
|
||||
occurrence_type ENUM("single","series","range","open_ended") NOT NULL DEFAULT "single",
|
||||
starts_at DATETIME NULL,
|
||||
ends_at DATETIME NULL,
|
||||
recurrence_rule VARCHAR(255) NULL,
|
||||
recurrence_until DATETIME NULL,
|
||||
capacity_total SMALLINT UNSIGNED NULL,
|
||||
booking_url VARCHAR(255) NULL,
|
||||
status ENUM("scheduled","cancelled","sold_out") NOT NULL DEFAULT "scheduled",
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_listing_occurrences_listing FOREIGN KEY (listing_id) REFERENCES listings(id) ON DELETE CASCADE,
|
||||
INDEX idx_listing_occurrences_start (starts_at),
|
||||
INDEX idx_listing_occurrences_type (occurrence_type),
|
||||
INDEX idx_listing_occurrences_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
'CREATE TABLE IF NOT EXISTS listing_prices (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
listing_id BIGINT UNSIGNED NOT NULL,
|
||||
occurrence_id BIGINT UNSIGNED NULL,
|
||||
label VARCHAR(120) NOT NULL,
|
||||
audience ENUM("general","adult","child","family","group") NOT NULL DEFAULT "general",
|
||||
price_type ENUM("free","fixed","from","up_to","range","request") NOT NULL DEFAULT "fixed",
|
||||
amount DECIMAL(10,2) NULL,
|
||||
amount_secondary DECIMAL(10,2) NULL,
|
||||
currency CHAR(3) NOT NULL DEFAULT "EUR",
|
||||
note VARCHAR(255) NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_listing_prices_listing FOREIGN KEY (listing_id) REFERENCES listings(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_listing_prices_occurrence FOREIGN KEY (occurrence_id) REFERENCES listing_occurrences(id) ON DELETE CASCADE,
|
||||
INDEX idx_listing_prices_listing (listing_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
'CREATE TABLE IF NOT EXISTS listing_benefits (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
listing_id BIGINT UNSIGNED NOT NULL,
|
||||
occurrence_id BIGINT UNSIGNED NULL,
|
||||
benefit_type ENUM("voucher_code","voucher_hint","discount_text") NOT NULL DEFAULT "voucher_hint",
|
||||
title VARCHAR(160) NOT NULL,
|
||||
code VARCHAR(120) NULL,
|
||||
description TEXT NULL,
|
||||
valid_from DATETIME NULL,
|
||||
valid_until DATETIME NULL,
|
||||
is_public TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_listing_benefits_listing FOREIGN KEY (listing_id) REFERENCES listings(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_listing_benefits_occurrence FOREIGN KEY (occurrence_id) REFERENCES listing_occurrences(id) ON DELETE CASCADE,
|
||||
INDEX idx_listing_benefits_listing (listing_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci',
|
||||
];
|
||||
|
||||
foreach ($statements as $sql) {
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
$seed = $this->pdo->prepare(
|
||||
'INSERT INTO listing_categories (slug, title, category_group, sort_order)
|
||||
VALUES (:slug, :title, :groupName, :sortOrder)
|
||||
ON DUPLICATE KEY UPDATE title = VALUES(title), category_group = VALUES(category_group), sort_order = VALUES(sort_order), updated_at = CURRENT_TIMESTAMP'
|
||||
);
|
||||
foreach ($this->defaultCategories() as $index => $category) {
|
||||
$seed->execute([
|
||||
'slug' => $category['slug'],
|
||||
'title' => $category['title'],
|
||||
'groupName' => $category['group'],
|
||||
'sortOrder' => $index + 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function status(): array
|
||||
{
|
||||
$tables = [
|
||||
'listing_categories',
|
||||
'listing_places',
|
||||
'listings',
|
||||
'listing_category_map',
|
||||
'listing_occurrences',
|
||||
'listing_prices',
|
||||
'listing_benefits',
|
||||
];
|
||||
$missing = [];
|
||||
foreach ($tables as $table) {
|
||||
if (!$this->hasTable($table)) {
|
||||
$missing[] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'complete' => $missing === [],
|
||||
'missing' => $missing,
|
||||
'tables' => $tables,
|
||||
];
|
||||
}
|
||||
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
if (array_key_exists($table, $this->tableCache)) {
|
||||
return $this->tableCache[$table];
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare('SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :table LIMIT 1');
|
||||
$stmt->execute(['table' => $table]);
|
||||
return $this->tableCache[$table] = (bool)$stmt->fetchColumn();
|
||||
} catch (\Throwable) {
|
||||
return $this->tableCache[$table] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function defaultCategories(): array
|
||||
{
|
||||
return [
|
||||
['slug' => 'spielplatz', 'title' => 'Spielplatz', 'group' => 'place'],
|
||||
['slug' => 'cafe', 'title' => 'Café', 'group' => 'food'],
|
||||
['slug' => 'restaurant', 'title' => 'Restaurant', 'group' => 'food'],
|
||||
['slug' => 'indoor-spielort', 'title' => 'Indoor-Spielort', 'group' => 'place'],
|
||||
['slug' => 'zirkus', 'title' => 'Zirkus', 'group' => 'event'],
|
||||
['slug' => 'huepfburgen', 'title' => 'Hüpfburgen', 'group' => 'event'],
|
||||
['slug' => 'workshop', 'title' => 'Workshop', 'group' => 'event'],
|
||||
['slug' => 'kurs', 'title' => 'Kurs', 'group' => 'event'],
|
||||
['slug' => 'familienfest', 'title' => 'Familienfest', 'group' => 'family'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user