Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
144 commits
Select commit Hold shift + click to select a range
f038169
Update app.js
marcochimenti82-gif Dec 16, 2025
ab69cc9
Update app.js
marcochimenti82-gif Dec 16, 2025
c1e2ded
Update app.js
marcochimenti82-gif Dec 16, 2025
3a5ab20
Merge branch 'main' into patch-1
marcochimenti82-gif Dec 17, 2025
9d60a07
Update app.js
marcochimenti82-gif Dec 17, 2025
b525219
Update app.js
marcochimenti82-gif Dec 17, 2025
dd4bdc3
Update app.js
marcochimenti82-gif Dec 17, 2025
9f9ca78
Update app.js
marcochimenti82-gif Dec 19, 2025
a412825
Update app.js
marcochimenti82-gif Dec 20, 2025
81ba1ed
Update app.js
marcochimenti82-gif Dec 20, 2025
0cfaff9
Update app.js
marcochimenti82-gif Dec 20, 2025
3a837f6
Update app.js
marcochimenti82-gif Dec 20, 2025
982c64f
Update app.js
marcochimenti82-gif Dec 20, 2025
5c9a3b2
Update app.js
marcochimenti82-gif Dec 21, 2025
911f866
Update app.js
marcochimenti82-gif Dec 21, 2025
b463d48
Update app.js
marcochimenti82-gif Dec 21, 2025
695738f
Update package.json
marcochimenti82-gif Dec 21, 2025
c9a8890
Update yarn.lock
marcochimenti82-gif Dec 21, 2025
7845919
Update yarn.lock
marcochimenti82-gif Dec 21, 2025
353fd3f
Add twilio and googleapis dependencies
marcochimenti82-gif Dec 21, 2025
23638a5
Update app.js
marcochimenti82-gif Dec 24, 2025
50c23f3
Update app.js
marcochimenti82-gif Dec 24, 2025
24fff39
Update app.js
marcochimenti82-gif Dec 27, 2025
0cac0c0
Implement debug endpoint for calendar-test
marcochimenti82-gif Dec 27, 2025
cf4002c
Update app.js
marcochimenti82-gif Dec 27, 2025
f208f8b
Update app.js
marcochimenti82-gif Dec 27, 2025
28bc90f
Remove booking event validation and payload construction
marcochimenti82-gif Dec 27, 2025
9b7343d
Update app.js
marcochimenti82-gif Dec 27, 2025
82e435e
Refactor Google Calendar event creation and logging
marcochimenti82-gif Dec 28, 2025
e2b2ff9
Update app.js
marcochimenti82-gif Dec 28, 2025
e27ba85
Update app.js
marcochimenti82-gif Dec 28, 2025
a610d50
Add Italian voice prompts for reservation system
marcochimenti82-gif Dec 28, 2025
c3f5722
Update app.js
marcochimenti82-gif Dec 28, 2025
84f7e2f
Update prompts.js
marcochimenti82-gif Dec 28, 2025
7f20df7
Update app.js
marcochimenti82-gif Dec 28, 2025
de81540
Improve date parsing and add back command handling
marcochimenti82-gif Dec 28, 2025
2b77e00
Update app.js
marcochimenti82-gif Dec 28, 2025
cf329c7
Update prompts.js
marcochimenti82-gif Dec 28, 2025
dd2f11f
Update app.js
marcochimenti82-gif Dec 28, 2025
a9e1a01
Refactor step comments for clarity in prompts.js
marcochimenti82-gif Dec 28, 2025
33c33f0
Refactor app.js for better structure and fixes
marcochimenti82-gif Dec 28, 2025
b5f906c
Update prompts.js
marcochimenti82-gif Dec 29, 2025
0143930
Update app.js
marcochimenti82-gif Dec 29, 2025
c97ca9e
Update prompts.js
marcochimenti82-gif Dec 29, 2025
3c076e6
Update app.js
marcochimenti82-gif Dec 29, 2025
e81281f
Implement dotenv configuration loading
marcochimenti82-gif Jan 3, 2026
2bad071
Update prompts.js
marcochimenti82-gif Jan 3, 2026
2152ef3
Update package.json
marcochimenti82-gif Jan 3, 2026
97388f7
Enhance user input handling for reservations
marcochimenti82-gif Jan 3, 2026
32e9749
Remove WhatsApp number handling functions
marcochimenti82-gif Jan 3, 2026
8f4b9e5
Implement cancel command handling
marcochimenti82-gif Jan 3, 2026
fca9700
Update app.js
marcochimenti82-gif Jan 3, 2026
dad379a
Update app.js
marcochimenti82-gif Jan 3, 2026
f5c02a6
Refactor session steps and update prompts
marcochimenti82-gif Jan 3, 2026
0166bec
Update app.js
marcochimenti82-gif Jan 3, 2026
55b5e10
Implement critical reservation handling and Twilio notifications
marcochimenti82-gif Jan 4, 2026
b70370d
Add outbound call and TwiML routes
marcochimenti82-gif Jan 4, 2026
c213e78
Update app.js
marcochimenti82-gif Jan 4, 2026
da34d86
Update app.js
marcochimenti82-gif Jan 4, 2026
b26afaa
Update Twilio voice endpoint and refactor code
marcochimenti82-gif Jan 4, 2026
dd9758c
Update voice prompts for TuttiBrilli Enoteca
marcochimenti82-gif Jan 4, 2026
ae2a897
Implement SMS notification for critical reservations
marcochimenti82-gif Jan 4, 2026
9807677
Update prompts.js
marcochimenti82-gif Jan 4, 2026
19cfa52
Create schema.prisma
marcochimenti82-gif Jan 4, 2026
769ae56
Update schema.prisma
marcochimenti82-gif Jan 4, 2026
d5cd60d
add prisma dependencies and build script
marcochimenti82-gif Jan 4, 2026
5c3f4c2
fix prisma install on render
marcochimenti82-gif Jan 4, 2026
7c63fee
Update package.json for Prisma setup
marcochimenti82-gif Jan 4, 2026
1f7dab9
Update package.json
marcochimenti82-gif Jan 4, 2026
f5437bc
Update app.js
marcochimenti82-gif Jan 4, 2026
48db466
Update package.json
marcochimenti82-gif Jan 4, 2026
38821c6
Create twilio.js
marcochimenti82-gif Jan 4, 2026
1a93d05
Create calendar.js
marcochimenti82-gif Jan 4, 2026
4b9a9fd
Update schema.prisma
marcochimenti82-gif Jan 4, 2026
a0c3fa9
Create .env.example
marcochimenti82-gif Jan 4, 2026
e3ed538
Create render.yaml
marcochimenti82-gif Jan 4, 2026
55ad038
Create db.js
marcochimenti82-gif Jan 4, 2026
1c037dd
Create migrate-if-configured.js
marcochimenti82-gif Jan 4, 2026
e8d2662
Delete .env.example
marcochimenti82-gif Jan 4, 2026
2a6715b
Create .env.example
marcochimenti82-gif Jan 4, 2026
078b524
Delete lib/db.js
marcochimenti82-gif Jan 4, 2026
d55b0ed
Delete scripts/migrate-if-configured.js
marcochimenti82-gif Jan 4, 2026
5233626
Delete lib/lib/calendar.js
marcochimenti82-gif Jan 4, 2026
f52a59b
Create calendar.js
marcochimenti82-gif Jan 4, 2026
ca2f7c3
Update app.js
marcochimenti82-gif Jan 4, 2026
7941ee4
Update package.json
marcochimenti82-gif Jan 4, 2026
aa9c564
Update schema.prisma
marcochimenti82-gif Jan 4, 2026
474ef82
Create seed.js
marcochimenti82-gif Jan 4, 2026
d2b2936
Create migrate-if-configured.js
marcochimenti82-gif Jan 4, 2026
52f62c1
Create db.js
marcochimenti82-gif Jan 4, 2026
2e88cb6
Update calendar.js
marcochimenti82-gif Jan 4, 2026
af47d04
Update twilio.js
marcochimenti82-gif Jan 4, 2026
f87647a
Update .env.example
marcochimenti82-gif Jan 4, 2026
e562300
Update README.md
marcochimenti82-gif Jan 4, 2026
98ffe58
Update app.js
marcochimenti82-gif Jan 4, 2026
a0e4720
Update package.json
marcochimenti82-gif Jan 4, 2026
9b1d5f7
Update schema.prisma
marcochimenti82-gif Jan 4, 2026
57d948e
Update seed.js
marcochimenti82-gif Jan 4, 2026
22cc347
Update migrate-if-configured.js
marcochimenti82-gif Jan 4, 2026
eb58da8
Update db.js
marcochimenti82-gif Jan 4, 2026
918b5f6
Update calendar.js
marcochimenti82-gif Jan 4, 2026
7654079
Update twilio.js
marcochimenti82-gif Jan 4, 2026
a34b03a
Update .env.example
marcochimenti82-gif Jan 4, 2026
cb08cd2
Update README.md
marcochimenti82-gif Jan 4, 2026
efbea7d
Update yarn.lock
marcochimenti82-gif Jan 4, 2026
67f1361
Update yarn.lock
marcochimenti82-gif Jan 5, 2026
3995b35
Update app.js
marcochimenti82-gif Jan 5, 2026
5d5ae51
Update package.json
marcochimenti82-gif Jan 5, 2026
0559991
Update schema.prisma
marcochimenti82-gif Jan 5, 2026
bc9ed34
Update seed.js
marcochimenti82-gif Jan 5, 2026
63719b2
Update migrate-if-configured.js
marcochimenti82-gif Jan 5, 2026
dcd215a
Update db.js
marcochimenti82-gif Jan 5, 2026
40d2016
Update calendar.js
marcochimenti82-gif Jan 5, 2026
5b08384
Update twilio.js
marcochimenti82-gif Jan 5, 2026
03aa20f
Update .env.example
marcochimenti82-gif Jan 5, 2026
175dec3
Update README.md
marcochimenti82-gif Jan 5, 2026
c4f9c56
Update app.js
marcochimenti82-gif Jan 5, 2026
d26eb34
Update package.json
marcochimenti82-gif Jan 5, 2026
f04bdb1
Update schema.prisma
marcochimenti82-gif Jan 5, 2026
29bf881
Update seed.js
marcochimenti82-gif Jan 5, 2026
5d61cb6
Update migrate-if-configured.js
marcochimenti82-gif Jan 5, 2026
680ca38
Update db.js
marcochimenti82-gif Jan 5, 2026
c456757
Update calendar.js
marcochimenti82-gif Jan 5, 2026
25e059d
Update twilio.js
marcochimenti82-gif Jan 5, 2026
a14b012
Update .env.example
marcochimenti82-gif Jan 5, 2026
357e19c
Update README.md
marcochimenti82-gif Jan 5, 2026
eb69b1b
Update app.js
marcochimenti82-gif Jan 5, 2026
de2eb9b
Update package.json
marcochimenti82-gif Jan 5, 2026
8e6dcbb
Update schema.prisma
marcochimenti82-gif Jan 5, 2026
17d0a25
Update seed.js
marcochimenti82-gif Jan 5, 2026
95ba89c
Update migrate-if-configured.js
marcochimenti82-gif Jan 5, 2026
8ca8df4
Update db.js
marcochimenti82-gif Jan 5, 2026
2179711
Update calendar.js
marcochimenti82-gif Jan 5, 2026
a82d051
Update twilio.js
marcochimenti82-gif Jan 5, 2026
f51c40e
Update .env.example
marcochimenti82-gif Jan 5, 2026
518db1f
Update README.md
marcochimenti82-gif Jan 5, 2026
2d164e6
Update package.json
marcochimenti82-gif Jan 5, 2026
f35c301
Update app.js
marcochimenti82-gif Jan 5, 2026
550e647
Update calendar.js
marcochimenti82-gif Jan 5, 2026
f728f1b
Create voiceFlow.js
marcochimenti82-gif Jan 5, 2026
33e407f
Update package.json
marcochimenti82-gif Jan 5, 2026
6083456
Update app.js
marcochimenti82-gif Jan 5, 2026
6da1a20
Update calendar.js
marcochimenti82-gif Jan 5, 2026
d0f90fa
Update voiceFlow.js
marcochimenti82-gif Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
PORT=3000
BASE_URL=https://<render-url>
DATABASE_URL=
DEFAULT_EVENT_DURATION_MINUTES=120

GOOGLE_SERVICE_ACCOUNT_JSON_B64=
GOOGLE_CALENDAR_ID=

TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_WHATSAPP_FROM=whatsapp:+1415....

FORWARDING_ENABLED=false
HUMAN_FORWARD_TO=+39....
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# README
# Tuttibrilli AI Backoffice

This is the [Express](https://expressjs.com) [Hello world](https://expressjs.com/en/starter/hello-world.html) example on [Render](https://render.com).
Backend Node.js (Express) pronto per Render con Prisma + PostgreSQL, Twilio Voice/WhatsApp e Google Calendar.

The app in this repo is deployed at [https://express.onrender.com](https://express.onrender.com).
## Requisiti
- Node.js 18+
- PostgreSQL (Render Postgres)

## Deployment

See https://render.com/docs/deploy-node-express-app or follow the steps below:

Create a new web service with the following values:
* Build Command: `yarn`
* Start Command: `node app.js`

That's it! Your web service will be live on your Render URL as soon as the build finishes.
## Setup locale
```bash
npm install
cp .env.example .env
npm start
79 changes: 22 additions & 57 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,26 @@
const express = require("express");
const app = express();
const port = process.env.PORT || 3001;
const express = require('express');
const dotenv = require('dotenv');
const { createCalendarClient, createBookingEvent } = require('./lib/calendar');
const { createVoiceRouter } = require('./lib/voiceFlow');

dotenv.config();

app.get("/", (req, res) => res.type('html').send(html));
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

const server = app.listen(port, () => console.log(`Example app listening on port ${port}!`));
app.get('/healthz', (req, res) => {
res.json({ status: 'ok' });
});

server.keepAliveTimeout = 120 * 1000;
server.headersTimeout = 120 * 1000;
app.use(
createVoiceRouter({
createCalendarClient,
createBookingEvent
})
);

const html = `
<!DOCTYPE html>
<html>
<head>
<title>Hello from Render!</title>
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script>
<script>
setTimeout(() => {
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 },
disableForReducedMotion: true
});
}, 500);
</script>
<style>
@import url("https://p.typekit.net/p.css?s=1&k=vnd5zic&ht=tk&f=39475.39476.39477.39478.39479.39480.39481.39482&a=18673890&app=typekit&e=css");
@font-face {
font-family: "neo-sans";
src: url("https://use.typekit.net/af/00ac0a/00000000000000003b9b2033/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3") format("woff2"), url("https://use.typekit.net/af/00ac0a/00000000000000003b9b2033/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3") format("woff"), url("https://use.typekit.net/af/00ac0a/00000000000000003b9b2033/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3") format("opentype");
font-style: normal;
font-weight: 700;
}
html {
font-family: neo-sans;
font-weight: 700;
font-size: calc(62rem / 16);
}
body {
background: white;
}
section {
border-radius: 1em;
padding: 1em;
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<section>
Hello from Render!
</section>
</body>
</html>
`
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
102 changes: 102 additions & 0 deletions lib/calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const { google } = require('googleapis');

function parseServiceAccountJson() {
const raw = process.env.GOOGLE_SERVICE_ACCOUNT_JSON_B64;
if (!raw) {
throw new Error('GOOGLE_SERVICE_ACCOUNT_JSON_B64 missing');
}
const trimmed = raw.trim();
if (trimmed.startsWith('{')) {
return JSON.parse(trimmed);
}
const payload = trimmed.replace(/\s+/g, '');
const decoded = Buffer.from(payload, 'base64').toString('utf8').trim();
return JSON.parse(decoded);
}

function createCalendarClient() {
if (!process.env.GOOGLE_CALENDAR_ID) {
throw new Error('GOOGLE_CALENDAR_ID missing');
}
const credentials = parseServiceAccountJson();
const auth = new google.auth.GoogleAuth({
credentials,
scopes: ['https://www.googleapis.com/auth/calendar']
});
return google.calendar({ version: 'v3', auth });
}

function assertEventPayload(event) {
if (!event.summary) {
throw new Error('Calendar event missing summary');
}
if (!event.start || !event.start.dateTime || !event.start.timeZone) {
throw new Error('Calendar event missing start dateTime/timeZone');
}
if (!event.end || !event.end.dateTime || !event.end.timeZone) {
throw new Error('Calendar event missing end dateTime/timeZone');
}
if (!event.extendedProperties || !event.extendedProperties.private?.bookingKey) {
throw new Error('Calendar event missing bookingKey');
}
}

async function createBookingEvent(calendar, payload) {
if (!calendar) {
throw new Error('Calendar client missing');
}
const calendarId = process.env.GOOGLE_CALENDAR_ID;
if (!calendarId) {
throw new Error('GOOGLE_CALENDAR_ID missing');
}
if (!payload || !payload.bookingKey) {
throw new Error('Missing bookingKey');
}
if (!payload.summary) {
throw new Error('Missing summary');
}
if (!payload.startDateTimeISO) {
throw new Error('Missing startDateTimeISO');
}

const timeZone = process.env.GOOGLE_CALENDAR_TZ || 'Europe/Rome';
const start = new Date(payload.startDateTimeISO);
if (Number.isNaN(start.getTime())) {
throw new Error('Invalid startDateTimeISO');
}
const durationMinutes = Number.isFinite(payload.durationMinutes)
? payload.durationMinutes
: 120;
const end = new Date(start.getTime() + durationMinutes * 60 * 1000);

const event = {
summary: payload.summary,
description: payload.description || '',
start: {
dateTime: start.toISOString(),
timeZone
},
end: {
dateTime: end.toISOString(),
timeZone
},
extendedProperties: {
private: {
bookingKey: payload.bookingKey
}
}
};

assertEventPayload(event);

const response = await calendar.events.insert({
calendarId,
requestBody: event
});
return response.data;
}

module.exports = {
createCalendarClient,
createBookingEvent
};
43 changes: 43 additions & 0 deletions lib/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { PrismaClient } = require('@prisma/client');

let prismaClient;

function getPrismaClient() {
if (!process.env.DATABASE_URL) {
return null;
}
if (!prismaClient) {
prismaClient = new PrismaClient();
}
return prismaClient;
}

async function ensureDefaultLocale(prisma) {
const existing = await prisma.locale.findFirst();
if (existing) return existing;
return prisma.locale.create({
data: {
name: 'Locale Principale'
}
});
}

async function upsertBusinessDay(prisma, localeId, dateISO) {
return prisma.businessDay.upsert({
where: {
localeId_dateISO: { localeId, dateISO }
},
update: {},
create: {
localeId,
dateISO,
isClosed: false
}
});
}

module.exports = {
getPrismaClient,
ensureDefaultLocale,
upsertBusinessDay
};
64 changes: 64 additions & 0 deletions lib/twilio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const twilio = require('twilio');

function getTwilioClient() {
if (!process.env.TWILIO_ACCOUNT_SID || !process.env.TWILIO_AUTH_TOKEN) {
throw new Error('Twilio credentials missing');
}
return twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
}

function getVoiceFromNumber() {
if (process.env.HUMAN_FORWARD_TO) {
return process.env.HUMAN_FORWARD_TO;
}
if (process.env.TWILIO_WHATSAPP_FROM) {
return process.env.TWILIO_WHATSAPP_FROM.replace('whatsapp:', '');
}
return null;
}

async function sendWhatsAppMessage(to, body) {
if (!process.env.TWILIO_WHATSAPP_FROM) {
throw new Error('TWILIO_WHATSAPP_FROM missing');
}
if (!to) {
throw new Error('Missing destination number');
}
const client = getTwilioClient();
try {
return await client.messages.create({
from: process.env.TWILIO_WHATSAPP_FROM,
to: to.startsWith('whatsapp:') ? to : `whatsapp:${to}`,
body
});
} catch (error) {
console.error('Twilio WhatsApp error:', error.code, error.message);
throw error;
}
}

async function createOutboundCall(to) {
if (!process.env.BASE_URL) {
throw new Error('BASE_URL missing');
}
const from = getVoiceFromNumber();
if (!from) {
throw new Error('Missing outbound caller ID (set HUMAN_FORWARD_TO or TWILIO_WHATSAPP_FROM)');
}
const client = getTwilioClient();
try {
return await client.calls.create({
to,
from,
url: `${process.env.BASE_URL}/voice`
});
} catch (error) {
console.error('Twilio call error:', error.code, error.message);
throw error;
}
}

module.exports = {
sendWhatsAppMessage,
createOutboundCall
};
Loading