diff --git a/initdb/00_database.sql b/initdb/00_database.sql new file mode 100644 index 0000000..59bd14c --- /dev/null +++ b/initdb/00_database.sql @@ -0,0 +1,29 @@ +-- ============================================================ +-- Database: Gestione Prenotazioni Campi Sportivi (Prova S5) +-- Creazione database e utente applicativo +-- MySQL 8.0+ +-- ============================================================ +-- Con Docker Compose il database e l'utente vengono creati +-- automaticamente dalle variabili MYSQL_* nel file .env. +-- Questo script è utile per installazione manuale su MySQL locale. +-- ============================================================ + +CREATE DATABASE IF NOT EXISTS prenotazioni + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +CREATE USER IF NOT EXISTS 'prenotazioni_user'@'%' + IDENTIFIED BY 'altra_password_sicura'; + +CREATE USER IF NOT EXISTS 'prenotazioni_user'@'localhost' + IDENTIFIED BY 'altra_password_sicura'; + +GRANT SELECT, INSERT, UPDATE, DELETE + ON prenotazioni.* + TO 'prenotazioni_user'@'%'; + +GRANT SELECT, INSERT, UPDATE, DELETE + ON prenotazioni.* + TO 'prenotazioni_user'@'localhost'; + +FLUSH PRIVILEGES; diff --git a/initdb/01_schema.sql b/initdb/01_schema.sql new file mode 100644 index 0000000..32f1f86 --- /dev/null +++ b/initdb/01_schema.sql @@ -0,0 +1,148 @@ +-- ============================================================ +-- Schema tabelle – Gestione Prenotazioni campi sportivi +-- Allineato alle API: /operatori, /clienti, /campi, /prenotazioni +-- ============================================================ + +USE prenotazioni; + +-- ------------------------------------------------------------ +-- Tabella operatore (API: GET/POST/PUT/DELETE /operatori) +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS operatore ( + OperatoreID INT UNSIGNED NOT NULL AUTO_INCREMENT, + Nome VARCHAR(100) NOT NULL, + Cognome VARCHAR(100) NOT NULL, + Email VARCHAR(255) NOT NULL, + PasswordHash VARCHAR(255) NOT NULL COMMENT 'Password salvata come hash (es. bcrypt)', + Admin BOOLEAN NOT NULL DEFAULT FALSE, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + PRIMARY KEY (OperatoreID), + UNIQUE KEY uk_operatore_email (Email) +) ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_unicode_ci; + + +-- ------------------------------------------------------------ +-- Tabella cliente (API: GET/POST/PUT/DELETE /clienti) +-- ------------------------------------------------------------ +CREATE TABLE IF NOT EXISTS cliente ( + ClienteID INT UNSIGNED NOT NULL AUTO_INCREMENT, + Nome VARCHAR(100) NOT NULL, + Cognome VARCHAR(100) NOT NULL, + Telefono VARCHAR(30) NOT NULL, + Email VARCHAR(255) NOT NULL, + Note TEXT, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + PRIMARY KEY (ClienteID), + UNIQUE KEY uk_cliente_email (Email) +) ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_unicode_ci; + + +-- ------------------------------------------------------------- +-- Tabella camposportivo (API: GET/POST/PUT/DELETE /campi) +-- ------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS camposportivo ( + CampoID INT UNSIGNED NOT NULL AUTO_INCREMENT, + Nome VARCHAR(100) NOT NULL, + Tipo ENUM('tennis', 'padel', 'calcetto') NOT NULL, + Coperto BOOLEAN NOT NULL DEFAULT FALSE, + PrezzoOrario DECIMAL(10, 2) NOT NULL, + Attivo BOOLEAN NOT NULL DEFAULT TRUE, + + PRIMARY KEY (CampoID), + UNIQUE KEY uk_camposportivo_nome (Nome) +) ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_unicode_ci; + + +-- ---------------------------------------------------------------- +-- Tabella prenotazione (API: GET/POST/PUT/DELETE /prenotazioni) +-- ---------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS prenotazione ( + PrenotazioneID INT UNSIGNED NOT NULL AUTO_INCREMENT, + Data DATE NOT NULL, + OraInizio TIME NOT NULL, + OraFine TIME NOT NULL, + Stato ENUM('prenotata', 'confermata', 'annullata', 'completata') NOT NULL DEFAULT 'prenotata', + CodicePrenotazione VARCHAR(8) NOT NULL, + ClienteID INT UNSIGNED NOT NULL, + CampoID INT UNSIGNED NOT NULL, + + PRIMARY KEY (PrenotazioneID), + UNIQUE KEY uk_prenotazione_codice (CodicePrenotazione), + KEY idx_prenotazione_campo_data (CampoID, Data), + + CONSTRAINT ck_prenotazione_orario_valido + CHECK (OraFine > OraInizio), + + CONSTRAINT fk_prenotazione_cliente + FOREIGN KEY (ClienteID) + REFERENCES cliente (ClienteID) + ON UPDATE CASCADE + ON DELETE RESTRICT, + + CONSTRAINT fk_prenotazione_campo + FOREIGN KEY (CampoID) + REFERENCES camposportivo (CampoID) + ON UPDATE CASCADE + ON DELETE RESTRICT +) ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_unicode_ci; + + +-- ---------------------------------------------------------------- +-- Trigger anti-sovrapposizione (stesso campo, stessa data) +-- Le prenotazioni annullate non occupano lo slot. +-- ---------------------------------------------------------------- +DROP TRIGGER IF EXISTS trg_prenotazione_no_overlap_insert; +DROP TRIGGER IF EXISTS trg_prenotazione_no_overlap_update; + +DELIMITER $$ + +CREATE TRIGGER trg_prenotazione_no_overlap_insert +BEFORE INSERT ON prenotazione +FOR EACH ROW +BEGIN + IF NEW.Stato <> 'annullata' AND EXISTS ( + SELECT 1 + FROM prenotazione AS p + WHERE p.CampoID = NEW.CampoID + AND p.Data = NEW.Data + AND p.Stato <> 'annullata' + AND NEW.OraInizio < p.OraFine + AND p.OraInizio < NEW.OraFine + ) THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Sovrapposizione: esiste già una prenotazione attiva in questo orario per il campo selezionato'; + END IF; +END$$ + +CREATE TRIGGER trg_prenotazione_no_overlap_update +BEFORE UPDATE ON prenotazione +FOR EACH ROW +BEGIN + IF NEW.Stato <> 'annullata' AND EXISTS ( + SELECT 1 + FROM prenotazione AS p + WHERE p.PrenotazioneID <> NEW.PrenotazioneID + AND p.CampoID = NEW.CampoID + AND p.Data = NEW.Data + AND p.Stato <> 'annullata' + AND NEW.OraInizio < p.OraFine + AND p.OraInizio < NEW.OraFine + ) THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Sovrapposizione: esiste già una prenotazione attiva in questo orario per il campo selezionato'; + END IF; +END$$ + +DELIMITER ; diff --git a/initdb/02_seed.sql b/initdb/02_seed.sql new file mode 100644 index 0000000..ff3a387 --- /dev/null +++ b/initdb/02_seed.sql @@ -0,0 +1,52 @@ +-- ============================================================ +-- Dati iniziali di test – Gestione Prenotazioni campi sportivi +-- Password per tutti gli operatori: Password123! +-- +-- Operatori: +-- 1 Andrea Villari – admin@centro.it (Admin) +-- 2 Maria Bianchi – maria.bianchi@centro.it (operatore) +-- +-- Codici prenotazione per test /verifica-prenotazione?codice=... +-- TENN9001, PADL2406, CALC7788, TENN3344 +-- ============================================================ + +USE prenotazioni; + +-- ------------------------------------------------------------ +-- Operatori +-- ------------------------------------------------------------ +INSERT INTO operatore (Nome, Cognome, Email, PasswordHash, Admin) VALUES + ('Andrea', 'Villari', 'admin@centro.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', TRUE), + ('Maria', 'Bianchi', 'maria.bianchi@centro.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', FALSE); + +-- ------------------------------------------------------------ +-- Clienti +-- ------------------------------------------------------------ +INSERT INTO cliente (Nome, Cognome, Telefono, Email, Note) VALUES + ('Mario', 'Rossi', '3331112233', 'mario.rossi@email.it', 'Cliente abituale tennis'), + ('Laura', 'Verdi', '3334445566', 'laura.verdi@email.it', NULL), + ('Marco', 'Neri', '3337778899', 'marco.neri@email.it', 'Preferisce padel la sera'), + ('Giulia', 'Ferrari', '3332223344', 'giulia.ferrari@email.it', 'Squadra calcetto del venerdì'); + +-- ------------------------------------------------------------ +-- Campi sportivi +-- ------------------------------------------------------------ +INSERT INTO camposportivo (Nome, Tipo, Coperto, PrezzoOrario, Attivo) VALUES + ('Tennis Centro 1', 'tennis', FALSE, 25.00, TRUE), + ('Tennis Centro 2', 'tennis', TRUE, 30.00, TRUE), + ('Padel Arena', 'padel', TRUE, 40.00, TRUE), + ('Calcetto Est', 'calcetto', FALSE, 50.00, TRUE); + +-- ------------------------------------------------------------ +-- Prenotazioni (orari non sovrapposti sullo stesso campo) +-- CampoID: 1=Tennis Centro 1, 2=Tennis Centro 2, 3=Padel Arena, 4=Calcetto Est +-- ClienteID: 1=Mario, 2=Laura, 3=Marco, 4=Giulia +-- ------------------------------------------------------------ +INSERT INTO prenotazione (Data, OraInizio, OraFine, Stato, CodicePrenotazione, ClienteID, CampoID) VALUES + ('2026-06-20', '09:00:00', '10:00:00', 'confermata', 'TENN9001', 1, 1), + ('2026-06-20', '10:00:00', '11:00:00', 'prenotata', 'TENN3344', 2, 1), + ('2026-06-20', '15:00:00', '16:30:00', 'confermata', 'PADL2406', 3, 3), + ('2026-06-21', '18:00:00', '19:00:00', 'prenotata', 'CALC7788', 4, 4), + ('2026-06-21', '17:00:00', '18:00:00', 'completata', 'TENN5522', 1, 2), + ('2026-06-22', '09:00:00', '10:00:00', 'annullata', 'ANNU1234', 2, 1), + ('2026-06-22', '09:00:00', '10:00:00', 'confermata', 'TENN9876', 3, 1); \ No newline at end of file