Compare commits
2 Commits
b9d360542d
...
16c4d58def
Author | SHA1 | Date | |
---|---|---|---|
16c4d58def | |||
fb182a1aec |
@@ -194,21 +194,32 @@ body {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-pill i {
|
||||||
.btn-pill i { font-size: 1rem; line-height: 1; margin: 0 .5rem; }
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
.btn-pill span {
|
.btn-pill span {
|
||||||
width: 0; opacity: 0; overflow: hidden; white-space: nowrap; margin-left: 0;
|
width: 0;
|
||||||
transition: width .18s ease, opacity .18s ease, margin-left .18s ease;
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0;
|
||||||
|
transition: width 0.18s ease, opacity 0.18s ease, margin-left 0.18s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hover: Button wächst, Text klappt auf */
|
/* Hover: Button wächst, Text klappt auf */
|
||||||
.btn-pill:hover {
|
.btn-pill:hover {
|
||||||
width: auto;
|
width: auto;
|
||||||
flex: 0 1 16rem;
|
flex: 0 1 16rem;
|
||||||
padding: .5rem .8rem;
|
padding: 0.5rem 0.8rem;
|
||||||
/* Keine Ausrichtung hier setzen – die Utility-Klasse bleibt wirksam */
|
/* Keine Ausrichtung hier setzen – die Utility-Klasse bleibt wirksam */
|
||||||
}
|
}
|
||||||
.btn-pill:hover span { width: auto; opacity: 1; margin-left: .35rem; }
|
.btn-pill:hover span {
|
||||||
|
width: auto;
|
||||||
|
opacity: 1;
|
||||||
|
margin-left: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Fokus sichtbar (zugänglich) */
|
/* Fokus sichtbar (zugänglich) */
|
||||||
.btn-pill:focus-visible {
|
.btn-pill:focus-visible {
|
||||||
@@ -216,6 +227,14 @@ body {
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================= Footer ================== */
|
||||||
|
|
||||||
|
.icon-sm {
|
||||||
|
width: 24px; /* oder 24px, wenn du es etwas größer magst */
|
||||||
|
height: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============ Responsive Tweaks ============ */
|
/* ============ Responsive Tweaks ============ */
|
||||||
@media (max-width: 575.98px) {
|
@media (max-width: 575.98px) {
|
||||||
.card-body {
|
.card-body {
|
||||||
|
BIN
img/heart-icon.png
Normal file
BIN
img/heart-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 MiB |
BIN
img/paw-icon.png
Normal file
BIN
img/paw-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
img/shakaru-icon.png
Normal file
BIN
img/shakaru-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
@@ -270,12 +270,22 @@ function wishlistMainBuilder(int $ListID, string $sortby = 'priority'): void
|
|||||||
|
|
||||||
// 3) Wünsche laden
|
// 3) Wünsche laden
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT w.ID, w.image, w.title, w.link, w.price, w.description,
|
SELECT
|
||||||
w.date, w.qty,
|
w.ID,
|
||||||
(SELECT COUNT(*) FROM wishes_reservations r WHERE r.wish_id = w.ID) AS reserved_count
|
w.image,
|
||||||
|
w.title,
|
||||||
|
w.link,
|
||||||
|
w.price,
|
||||||
|
w.description,
|
||||||
|
w.date,
|
||||||
|
w.qty,
|
||||||
|
COALESCE(rc.reserved_count, 0) AS reserved_count
|
||||||
FROM wishes w
|
FROM wishes w
|
||||||
|
LEFT JOIN v_wish_reserved_counts rc
|
||||||
|
ON rc.wish_id = w.ID
|
||||||
WHERE w.wishlist = ?
|
WHERE w.wishlist = ?
|
||||||
ORDER BY {$orderSql}";
|
ORDER BY {$orderSql}";
|
||||||
|
|
||||||
$stmt = $conn->prepare($sql);
|
$stmt = $conn->prepare($sql);
|
||||||
$stmt->bind_param('i', $ListID);
|
$stmt->bind_param('i', $ListID);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
@@ -311,7 +321,7 @@ HTML;
|
|||||||
$row['link'] !== null ? (string) $row['link'] : null,
|
$row['link'] !== null ? (string) $row['link'] : null,
|
||||||
(int) $row['price'],
|
(int) $row['price'],
|
||||||
$row['description'] !== null ? (string) $row['description'] : null,
|
$row['description'] !== null ? (string) $row['description'] : null,
|
||||||
(int) $row['reserved_count'], // echte Anzahl Reservierungen
|
(int) $row['reserved_count'],
|
||||||
$row['date'] !== null ? (string) $row['date'] : null,
|
$row['date'] !== null ? (string) $row['date'] : null,
|
||||||
isset($row['qty']) ? (int) $row['qty'] : 1
|
isset($row['qty']) ? (int) $row['qty'] : 1
|
||||||
);
|
);
|
||||||
|
125
index.php
125
index.php
@@ -77,13 +77,24 @@ function require_csrf(): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ensure_csrf_token();
|
ensure_csrf_token();
|
||||||
|
|
||||||
// ===== URL-Param: Liste per UUID (oder Alt-ID -> Redirect) =====
|
// ===== URL-Param: Liste per UUID (oder Alt-ID -> Redirect) =====
|
||||||
$ListID = -1;
|
$ListID = -1;
|
||||||
$ListUUID = '';
|
$ListUUID = '';
|
||||||
if (isset($_GET['list'])) {
|
$showCreateEmptyState = false;
|
||||||
|
|
||||||
|
if (!isset($_GET['list'])) {
|
||||||
|
// Kein Parameter => nur Empty-State ohne Message
|
||||||
|
$showCreateEmptyState = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
$raw = trim((string) $_GET['list']);
|
$raw = trim((string) $_GET['list']);
|
||||||
if (preg_match('/^[0-9a-fA-F-]{32,36}$/', $raw)) {
|
|
||||||
|
if ($raw === '') {
|
||||||
|
// Leerer Parameter => nur Empty-State ohne Message
|
||||||
|
$showCreateEmptyState = true;
|
||||||
|
|
||||||
|
} elseif (preg_match('/^[0-9a-fA-F-]{32,36}$/', $raw)) {
|
||||||
|
// UUID übergeben
|
||||||
$c = db();
|
$c = db();
|
||||||
$s = $c->prepare('SELECT ID, uuid FROM lists WHERE uuid=?');
|
$s = $c->prepare('SELECT ID, uuid FROM lists WHERE uuid=?');
|
||||||
$s->bind_param('s', $raw);
|
$s->bind_param('s', $raw);
|
||||||
@@ -92,27 +103,40 @@ if (isset($_GET['list'])) {
|
|||||||
if ($r && ($row = $r->fetch_assoc())) {
|
if ($r && ($row = $r->fetch_assoc())) {
|
||||||
$ListID = (int) $row['ID'];
|
$ListID = (int) $row['ID'];
|
||||||
$ListUUID = (string) $row['uuid'];
|
$ListUUID = (string) $row['uuid'];
|
||||||
|
} else {
|
||||||
|
// UUID-Format ok, aber nicht vorhanden => Message + Empty-State
|
||||||
|
$message = ['msg' => 'Diese Liste gibt es nicht. Lege eine neue an oder prüfe den Link.', 'type' => 'warning'];
|
||||||
|
$showCreateEmptyState = true;
|
||||||
}
|
}
|
||||||
$s->close();
|
$s->close();
|
||||||
$c->close();
|
$c->close();
|
||||||
} else {
|
|
||||||
$ListID = (int) $raw;
|
} elseif (preg_match('/^\d+$/', $raw)) {
|
||||||
if ($ListID >= 0) {
|
// Numerische ID übergeben -> auf UUID umleiten, wenn vorhanden
|
||||||
|
$id = (int) $raw;
|
||||||
$c = db();
|
$c = db();
|
||||||
$s = $c->prepare('SELECT uuid FROM lists WHERE ID=?');
|
$s = $c->prepare('SELECT uuid FROM lists WHERE ID=?');
|
||||||
$s->bind_param('i', $ListID);
|
$s->bind_param('i', $id);
|
||||||
$s->execute();
|
$s->execute();
|
||||||
$r = $s->get_result();
|
$r = $s->get_result();
|
||||||
if ($r && ($row = $r->fetch_assoc())) {
|
if ($r && ($row = $r->fetch_assoc())) {
|
||||||
$ListUUID = (string) $row['uuid'];
|
$uuid = (string) $row['uuid'];
|
||||||
$scheme = $secure ? 'https' : 'http';
|
$scheme = $secure ? 'https' : 'http';
|
||||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||||
header('Location: ' . $scheme . '://' . $host . '/?list=' . urlencode($ListUUID), true, 301);
|
header('Location: ' . $scheme . '://' . $host . '/?list=' . urlencode($uuid), true, 301);
|
||||||
exit;
|
exit;
|
||||||
|
} else {
|
||||||
|
// ID-Format ok, aber nicht vorhanden => Message + Empty-State
|
||||||
|
$message = ['msg' => 'Diese Liste gibt es nicht. Lege eine neue an oder prüfe den Link.', 'type' => 'warning'];
|
||||||
|
$showCreateEmptyState = true;
|
||||||
}
|
}
|
||||||
$s->close();
|
$s->close();
|
||||||
$c->close();
|
$c->close();
|
||||||
}
|
|
||||||
|
} else {
|
||||||
|
// Weder gültige UUID noch Zahl => Message + Empty-State
|
||||||
|
$message = ['msg' => 'Diese Liste gibt es nicht. Lege eine neue an oder prüfe den Link.', 'type' => 'warning'];
|
||||||
|
$showCreateEmptyState = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +276,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="css/tweaks.css">
|
<link rel="stylesheet" href="css/tweaks.css">
|
||||||
<link rel="stylesheet" href="css/fontawesome.min.css"/>
|
<link rel="stylesheet" href="css/fontawesome.min.css" />
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
|
||||||
@@ -277,6 +301,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</a>
|
</a>
|
||||||
<div class="nav navbar-nav navbar-right">
|
<div class="nav navbar-nav navbar-right">
|
||||||
<div class="d-grid gap-2 d-flex">
|
<div class="d-grid gap-2 d-flex">
|
||||||
|
<button type="button" class="btn btn-outline-success my-2 my-sm-0" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#createListModal">Neue Liste</button>
|
||||||
|
<?php if ($showCreateEmptyState == false): ?>
|
||||||
<?php if ($loggedin): ?>
|
<?php if ($loggedin): ?>
|
||||||
<button type="button" class="btn btn-outline-secondary my-2 my-sm-0" data-bs-toggle="modal"
|
<button type="button" class="btn btn-outline-secondary my-2 my-sm-0" data-bs-toggle="modal"
|
||||||
data-bs-target="#itemModal" data-mode="add" data-listuuid="<?= e($ListUUID) ?>"
|
data-bs-target="#itemModal" data-mode="add" data-listuuid="<?= e($ListUUID) ?>"
|
||||||
@@ -300,6 +327,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<option <?= $sortby === 'random' ? 'selected' : '' ?> value="random">Zufall</option>
|
<option <?= $sortby === 'random' ? 'selected' : '' ?> value="random">Zufall</option>
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -314,13 +342,46 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($showCreateEmptyState)): ?>
|
||||||
|
<!-- Empty State -->
|
||||||
|
<section class="py-5 text-center container">
|
||||||
|
<div class="row py-lg-4">
|
||||||
|
<div class="col-lg-8 col-md-10 mx-auto">
|
||||||
|
<h1 class="fw-light">Willkommen bei Simple Wishlist</h1>
|
||||||
|
<p class="lead text-muted">
|
||||||
|
Es wurde keine gültige Liste ausgewählt. Du kannst jetzt eine neue Liste anlegen.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button class="btn btn-success btn-lg" data-bs-toggle="modal" data-bs-target="#createListModal">
|
||||||
|
Neue Liste anlegen
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<?php else: ?>
|
||||||
<?php wishlistMainBuilder($ListID, $sortby); ?>
|
<?php wishlistMainBuilder($ListID, $sortby); ?>
|
||||||
|
<?php endif; ?>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="text-muted py-5">
|
<footer class="mt-5 text-center text-muted small">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p class="float-end mb-1"><a href="#">Back to top</a></p>
|
<hr class="mb-3 opacity-25">
|
||||||
<p class="mb-1">Simple Wishlist © by Marcel Peterkau</p>
|
<p class="mb-1">
|
||||||
|
Made with
|
||||||
|
<img src="img/heart-icon.png" alt="Shakaru" class="icon-sm mx-1">
|
||||||
|
and
|
||||||
|
<img src="img/paw-icon.png" alt="Paw" class="icon-sm mx-1">
|
||||||
|
by
|
||||||
|
<strong>
|
||||||
|
<img src="img/shakaru-icon.png" alt="Shakaru" class="icon-sm mx-1">
|
||||||
|
Shakaru
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
© <?= date('Y') ?> Simple Wishlist · Marcel Peterkau
|
||||||
|
· <a href="#" class="text-decoration-none">Back to top ↑</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
@@ -478,6 +539,42 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Create List Modal -->
|
||||||
|
<div class="modal fade" id="createListModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Neue Liste anlegen</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="listName" class="form-label">Titel der Liste</label>
|
||||||
|
<input type="text" class="form-control" id="listName" name="listName" required maxlength="255">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="listDescription" class="form-label">Beschreibung (optional)</label>
|
||||||
|
<textarea class="form-control" id="listDescription" name="listDescription" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="listPassword" class="form-label">Admin-Passwort (zum Bearbeiten)</label>
|
||||||
|
<input type="password" class="form-control" id="listPassword" name="listPassword" required>
|
||||||
|
<div class="form-text">
|
||||||
|
Dieses Passwort brauchst du später für Login/Bearbeitung der Liste.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<input type="hidden" name="csrf" value="<?= e($_SESSION['csrf']) ?>">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||||
|
<button type="submit" class="btn btn-primary" name="listadd">Anlegen</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="js/bootstrap.bundle.min.js"></script>
|
<script src="js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="js/jquery.min.js"></script>
|
<script src="js/jquery.min.js"></script>
|
||||||
<script src="js/wishlist.js"></script>
|
<script src="js/wishlist.js"></script>
|
||||||
|
165
wishlist.sql
165
wishlist.sql
@@ -1,91 +1,98 @@
|
|||||||
-- phpMyAdmin SQL Dump
|
-- ============================================================
|
||||||
-- version 5.2.0
|
-- Simple Wishlist – Minimal-Schema
|
||||||
-- https://www.phpmyadmin.net/
|
-- Tested on MariaDB 10.x / MySQL 8.x
|
||||||
--
|
-- ============================================================
|
||||||
-- Host: localhost
|
|
||||||
-- Erstellungszeit: 04. Okt 2022 um 22:36
|
|
||||||
-- Server-Version: 10.5.15-MariaDB-0+deb11u1
|
|
||||||
-- PHP-Version: 8.0.22
|
|
||||||
|
|
||||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
/* Optional: eigene DB anlegen/verwenden
|
||||||
START TRANSACTION;
|
CREATE DATABASE IF NOT EXISTS `wishlist`
|
||||||
SET time_zone = "+00:00";
|
DEFAULT CHARACTER SET utf8mb4
|
||||||
|
COLLATE utf8mb4_unicode_ci;
|
||||||
|
USE `wishlist`;
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Tabellen löschen (idempotent für wiederholte Importe)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
DROP TABLE IF EXISTS `wishes_reservations`;
|
||||||
|
DROP TABLE IF EXISTS `wishes`;
|
||||||
|
DROP TABLE IF EXISTS `lists`;
|
||||||
|
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
-- ------------------------------------------------------------
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
-- LISTEN (Metadaten & Admin-Passwort)
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
-- ------------------------------------------------------------
|
||||||
/*!40101 SET NAMES utf8mb4 */;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Datenbank: `wishlist`
|
|
||||||
--
|
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Tabellenstruktur für Tabelle `lists`
|
|
||||||
--
|
|
||||||
|
|
||||||
CREATE TABLE `lists` (
|
CREATE TABLE `lists` (
|
||||||
`ID` int(11) NOT NULL,
|
`ID` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
`title` varchar(255) NOT NULL,
|
`uuid` CHAR(36) NOT NULL, -- öffentliche, stabile ID (Links)
|
||||||
`description` text NOT NULL,
|
`title` VARCHAR(255) NOT NULL,
|
||||||
`edit_pw` varchar(64) NOT NULL
|
`description` TEXT NOT NULL,
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
`edit_pw` VARCHAR(255) NOT NULL, -- Passwort-Hash (password_hash)
|
||||||
|
PRIMARY KEY (`ID`),
|
||||||
-- --------------------------------------------------------
|
UNIQUE KEY `uq_lists_uuid` (`uuid`)
|
||||||
|
) ENGINE=InnoDB
|
||||||
--
|
DEFAULT CHARSET = utf8mb4
|
||||||
-- Tabellenstruktur für Tabelle `wishes`
|
COLLATE = utf8mb4_unicode_ci;
|
||||||
--
|
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- WÜNSCHE (einer Liste zugeordnet)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
CREATE TABLE `wishes` (
|
CREATE TABLE `wishes` (
|
||||||
`ID` int(11) NOT NULL,
|
`ID` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
`wishlist` int(11) NOT NULL DEFAULT 0,
|
`wishlist` INT(11) NOT NULL, -- FK -> lists.ID
|
||||||
`title` varchar(128) NOT NULL,
|
`title` VARCHAR(128) NOT NULL,
|
||||||
`description` text NOT NULL,
|
`description` TEXT NOT NULL,
|
||||||
`link` text NOT NULL,
|
`link` TEXT NOT NULL, -- optionaler Anbieter-Link (leer erlaubt)
|
||||||
`image` text NOT NULL,
|
`image` TEXT DEFAULT NULL, -- Dateiname (lokal) oder NULL
|
||||||
`price` int(11) NOT NULL,
|
`price` INT(11) NOT NULL DEFAULT 0, -- Preis in Cent
|
||||||
`reserved` tinyint(1) NOT NULL DEFAULT 0,
|
`date` DATE NOT NULL DEFAULT (CURRENT_DATE),
|
||||||
`pass_hash` varchar(64) NOT NULL DEFAULT '',
|
`priority` INT(11) NOT NULL DEFAULT 0, -- Sortierung (höher = weiter oben)
|
||||||
`date` date NOT NULL DEFAULT current_timestamp()
|
`qty` INT(11) NOT NULL DEFAULT 1, -- Anzahl benötigter Exemplare
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
--
|
PRIMARY KEY (`ID`),
|
||||||
-- Indizes der exportierten Tabellen
|
KEY `idx_wishes_wishlist` (`wishlist`),
|
||||||
--
|
CONSTRAINT `fk_wishes_list`
|
||||||
|
FOREIGN KEY (`wishlist`) REFERENCES `lists` (`ID`)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4
|
||||||
|
COLLATE = utf8mb4_unicode_ci;
|
||||||
|
|
||||||
--
|
-- ------------------------------------------------------------
|
||||||
-- Indizes für die Tabelle `lists`
|
-- RESERVIERUNGEN (pro Wunsch mehrere Einträge, je Reservierung einer)
|
||||||
--
|
-- ------------------------------------------------------------
|
||||||
ALTER TABLE `lists`
|
CREATE TABLE `wishes_reservations` (
|
||||||
ADD PRIMARY KEY (`ID`);
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`wish_id` INT(11) NOT NULL, -- FK -> wishes.ID
|
||||||
|
`pass_hash` VARCHAR(255) NOT NULL, -- Passwort-Hash (password_hash)
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_wr_wish` (`wish_id`),
|
||||||
|
CONSTRAINT `fk_wr_wish`
|
||||||
|
FOREIGN KEY (`wish_id`) REFERENCES `wishes` (`ID`)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4
|
||||||
|
COLLATE = utf8mb4_unicode_ci;
|
||||||
|
|
||||||
--
|
-- ------------------------------------------------------------
|
||||||
-- Indizes für die Tabelle `wishes`
|
-- OPTIONALE HILFS-VIEW: reserved_count je Wunsch
|
||||||
--
|
-- Erleichtert das Rendern (Badge/Buttons) ohne Subquery im Code.
|
||||||
ALTER TABLE `wishes`
|
-- Nutzung: SELECT w.*, rc.reserved_count FROM wishes w
|
||||||
ADD PRIMARY KEY (`ID`);
|
-- LEFT JOIN v_wish_reserved_counts rc ON rc.wish_id = w.ID
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
CREATE OR REPLACE VIEW `v_wish_reserved_counts` AS
|
||||||
|
SELECT
|
||||||
|
wr.wish_id,
|
||||||
|
COUNT(*) AS reserved_count
|
||||||
|
FROM wishes_reservations wr
|
||||||
|
GROUP BY wr.wish_id;
|
||||||
|
|
||||||
--
|
-- ------------------------------------------------------------
|
||||||
-- AUTO_INCREMENT für exportierte Tabellen
|
-- EMPFOHLENE INDIZES (optional – je nach Abfragen)
|
||||||
--
|
-- ------------------------------------------------------------
|
||||||
|
-- Wenn häufig nach (wishlist, priority DESC) sortiert wird:
|
||||||
|
CREATE INDEX `idx_wishes_wishlist_priority` ON `wishes` (`wishlist`, `priority`);
|
||||||
|
|
||||||
--
|
-- Wenn oft nach Datum sortiert/gefiltert wird:
|
||||||
-- AUTO_INCREMENT für Tabelle `lists`
|
CREATE INDEX `idx_wishes_wishlist_date` ON `wishes` (`wishlist`, `date`);
|
||||||
--
|
|
||||||
ALTER TABLE `lists`
|
|
||||||
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
|
|
||||||
|
|
||||||
--
|
-- Fertig.
|
||||||
-- AUTO_INCREMENT für Tabelle `wishes`
|
|
||||||
--
|
|
||||||
ALTER TABLE `wishes`
|
|
||||||
MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT;
|
|
||||||
COMMIT;
|
|
||||||
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
|
Reference in New Issue
Block a user