primo commit
Some checks are pending
Deploy to VPS / build (push) Waiting to run
Deploy to VPS / deploy (push) Blocked by required conditions

This commit is contained in:
AV77web 2026-06-20 23:26:06 +02:00
commit c66b23f927
7 changed files with 362 additions and 0 deletions

46
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,46 @@
name: Deploy to VPS
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: docker
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Setup Node.js
uses: https://github.com/actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm ci --prefer-offline
- name: Build
run: npm run build
deploy:
needs: build
runs-on: docker
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Deploy via SSH
uses: https://github.com/appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.AZURE_HOST }}
username: ${{ secrets.AZURE_USER }}
key: ${{ secrets.AZURE_SSH_KEY }}
script: |
echo "START DEPLOY"
cd /opt/apps/corrieribackend
git pull --ff-only
docker compose up -d --build

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules/
dist/
.env

56
docker-compose.yml Normal file
View file

@ -0,0 +1,56 @@
services:
app:
build:
context: .
dockerfile: dockerfile/Dockerfile
container_name: corrieribackend
restart: unless-stopped
env_file:
- .env
environment:
NODE_ENV: production
DB_HOST: mysql
DB_PORT: 3306
PORT: 3005
FRONTEND_URL: https://corrierifrontend.andreavillari.it
AUTH_URL: https://corrieribackend.andreavillari.it
AUTH_TRUST_HOST: "true"
ports:
- "127.0.0.1:3005:3005"
depends_on:
mysql:
condition: service_healthy
mysql:
image: mysql:8.0
container_name: mysql_corrieri
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- dbdata:/var/lib/mysql
- ./initdb:/docker-entrypoint-initdb.d # opzionale: script SQL iniziali
ports:
- "127.0.0.1:3308:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10
phpmyadmin:
image: phpmyadmin:latest
container_name: phpmyadmin_corrieri
restart: always
ports:
- "127.0.0.1:8084:80"
environment:
PMA_HOST: mysql
depends_on:
- mysql
volumes:
dbdata:

28
dockerfile/Dockerfile Normal file
View file

@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json tsconfig.build.json ./
COPY src ./src
RUN npm run build
FROM node:${NODE_VERSION}-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3001
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
#COPY --from=build /app/src/openapi ./dist/openapi
EXPOSE 3001
CMD ["node", "dist/index.js"]

29
initdb/00_database.sql Normal file
View file

@ -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;

148
initdb/01_schema.sql Normal file
View file

@ -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 ;

52
initdb/02_seed.sql Normal file
View file

@ -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);