primo commit
This commit is contained in:
commit
c66b23f927
7 changed files with 362 additions and 0 deletions
46
.github/workflows/deploy.yml
vendored
Normal file
46
.github/workflows/deploy.yml
vendored
Normal 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
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
56
docker-compose.yml
Normal file
56
docker-compose.yml
Normal 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
28
dockerfile/Dockerfile
Normal 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
29
initdb/00_database.sql
Normal 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
148
initdb/01_schema.sql
Normal 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
52
initdb/02_seed.sql
Normal 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);
|
||||
Loading…
Add table
Reference in a new issue