// 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