// npm install googleapis
import { google } from 'googleapis';
import express from 'express';
const app = express();
app.use(express.json());
// TODO: lade deine Credentials aus Datei/Env
const auth = new google.auth.GoogleAuth({
keyFile: 'service-account.json', // oder OAuth, wenn du das nutzt
scopes: ['https://www.googleapis.com/auth/calendar.freebusy'],
});
app.get('/api/free-slots', async (req, res) => {
try {
const client = await auth.getClient();
const calendar = google.calendar({ version: 'v3', auth: client });
// Zeitraum definieren, z.B. heute + 7 Tage
const now = new Date();
const timeMin = now.toISOString();
const timeMax = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString();
const response = await calendar.freebusy.query({
requestBody: {
timeMin,
timeMax,
timeZone: 'Europe/Zurich',
items: [
{ id: 'primary' }, // dein Hauptkalender, oder deine@adresse.ch
],
},
});
const busy = response.data.calendars.primary.busy || [];
// Hier: aus busy-Blöcken freie Slots berechnen
const slots = calculateFreeSlots(busy, {
dayStart: 9, // 09:00
dayEnd: 17, // 17:00
slotMinutes: 30, // 30-Minuten Slots
timeMin,
timeMax,
});
res.json({ slots });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Fehler beim Lesen des Kalenders' });
}
});
function calculateFreeSlots(busyBlocks, options) {
const { dayStart, dayEnd, slotMinutes, timeMin, timeMax } = options;
const slots = [];
const start = new Date(timeMin);
const end = new Date(timeMax);
// gehe Tag für Tag durch
for (
let day = new Date(start.getFullYear(), start.getMonth(), start.getDate());
day < end;
day.setDate(day.getDate() + 1)
) {
// Wochenenden überspringen
const weekday = day.getDay(); // 0 So, 6 Sa
if (weekday === 0 || weekday === 6) continue;
for (let hour = dayStart; hour < dayEnd; hour++) {
for (let minute = 0; minute < 60; minute += slotMinutes) {
const slotStart = new Date(day);
slotStart.setHours(hour, minute, 0, 0);
const slotEnd = new Date(slotStart.getTime() + slotMinutes * 60000);
// Grenze beachten
if (slotStart < start || slotEnd > end) continue;
// Prüfen, ob Slot mit einem Busy-Block kollidiert
const overlapsBusy = busyBlocks.some((b) => {
const bStart = new Date(b.start);
const bEnd = new Date(b.end);
return slotStart < bEnd && slotEnd > bStart;
});
if (!overlapsBusy) {
slots.push({
start: slotStart.toISOString(),
end: slotEnd.toISOString(),
});
}
}
}
}
return slots;
}
app.listen(3000, () => console.log('Server läuft auf Port 3000'));
Kontaktiere uns
Standort
Sandbüelstrasse 6
8604 Volketswil