export const dynamic = 'force-dynamic' import { NextResponse } from 'next/server' import { google } from 'googleapis' import * as fs from 'fs' import * as path from 'path' function getAuth(readonly = true) { const scopes = readonly ? ['https://www.googleapis.com/auth/calendar.readonly'] : ['https://www.googleapis.com/auth/calendar'] const saJson = process.env.GOOGLE_SA_JSON if (saJson) { const sa = JSON.parse(saJson) return new google.auth.GoogleAuth({ credentials: sa, scopes, }) } const saPath = path.join(process.cwd(), 'google-sa.json') if (fs.existsSync(saPath)) { return new google.auth.GoogleAuth({ keyFile: saPath, scopes, }) } return null } export async function GET(req: Request) { const { searchParams } = new URL(req.url) const range = searchParams.get('range') || 'today' const auth = getAuth(true) if (!auth) { return NextResponse.json({ events: [], error: 'not_configured' }) } const daniilCalendarId = process.env.DANIIL_CALENDAR_ID || 'daniilklimov25@gmail.com' const svetaCalendarId = process.env.SVETA_CALENDAR_ID || '' const now = new Date() let timeMin: string let timeMax: string // Moscow boundaries (UTC+3, no DST). 00:00 MSK = 21:00 UTC previous day. const MSK_OFFSET_MS = 3 * 3600 * 1000 const mskNow = new Date(now.getTime() + MSK_OFFSET_MS) const my = mskNow.getUTCFullYear() const mm = mskNow.getUTCMonth() const md = mskNow.getUTCDate() const mskMidnight = (y: number, m: number, d: number) => new Date(Date.UTC(y, m, d) - MSK_OFFSET_MS).toISOString() if (range === 'today') { timeMin = mskMidnight(my, mm, md) timeMax = mskMidnight(my, mm, md + 1) } else if (range === 'week') { timeMin = mskMidnight(my, mm, md) timeMax = mskMidnight(my, mm, md + 7) } else { // month — support year/month query params const targetYear = parseInt(searchParams.get('year') || String(now.getFullYear())) const targetMonth = parseInt(searchParams.get('month') || String(now.getMonth())) timeMin = new Date(targetYear, targetMonth, 1).toISOString() timeMax = new Date(targetYear, targetMonth + 1, 0, 23, 59).toISOString() } const calendarClient = google.calendar({ version: 'v3', auth: auth as any }) const calendars = [ { id: daniilCalendarId, owner: 'daniil', color: '#6366f1', name: 'Даниил' }, ...(svetaCalendarId ? [{ id: svetaCalendarId, owner: 'sveta', color: '#ec4899', name: 'Света' }] : []) ] const results = await Promise.allSettled( calendars.map(cal => calendarClient.events.list({ calendarId: cal.id, timeMin, timeMax, singleEvents: true, orderBy: 'startTime', maxResults: 100, }).then(r => ({ ...cal, events: r.data.items || [] })) ) ) const allEvents = results .filter(r => r.status === 'fulfilled') .flatMap(r => { const val = (r as PromiseFulfilledResult).value return val.events.map((e: any) => ({ id: e.id, title: e.summary || '(без названия)', start: e.start?.dateTime || e.start?.date, end: e.end?.dateTime || e.end?.date, allDay: !e.start?.dateTime, description: e.description || null, location: e.location || null, owner: val.owner, ownerName: val.name, color: val.color, })) }) .sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()) const errors = results .filter(r => r.status === 'rejected') .map(r => (r as PromiseRejectedResult).reason?.message || 'unknown') return NextResponse.json({ events: allEvents, errors: errors.length ? errors : undefined, fetchedAt: new Date().toISOString() }) } export async function POST(req: Request) { const body = await req.json() const { title, date, startTime, endTime, allDay, owner } = body const auth = getAuth(false) if (!auth) return NextResponse.json({ error: 'not_configured' }, { status: 500 }) const calendarClient = google.calendar({ version: 'v3', auth: auth as any }) const daniilCalendarId = process.env.DANIIL_CALENDAR_ID || 'daniilklimov25@gmail.com' const svetaCalendarId = process.env.SVETA_CALENDAR_ID || '' const calendars: Record = { daniil: { id: daniilCalendarId, name: 'Даниил', color: '#6366f1' }, ...(svetaCalendarId ? { sveta: { id: svetaCalendarId, name: 'Света', color: '#ec4899' } } : {}), } const selectedOwner = owner && calendars[owner] ? owner : 'daniil' const cal = calendars[selectedOwner] let start: any, end: any if (allDay) { start = { date } end = { date } } else { start = { dateTime: `${date}T${startTime}:00`, timeZone: 'Europe/Moscow' } end = { dateTime: `${date}T${endTime}:00`, timeZone: 'Europe/Moscow' } } try { const res = await calendarClient.events.insert({ calendarId: cal.id, requestBody: { summary: title, start, end }, }) const e = res.data return NextResponse.json({ event: { id: e.id, title: e.summary || title, start: e.start?.dateTime || e.start?.date, end: e.end?.dateTime || e.end?.date, allDay: !e.start?.dateTime, owner: selectedOwner, ownerName: cal.name, color: cal.color, } }) } catch (err: any) { return NextResponse.json({ error: err.message || 'Failed to create event' }, { status: 500 }) } } export async function PUT(req: Request) { const body = await req.json() const { eventId, title, date, startTime, endTime, allDay, calendarId } = body if (!eventId) { return NextResponse.json({ error: 'eventId is required' }, { status: 400 }) } const auth = getAuth(false) if (!auth) return NextResponse.json({ error: 'not_configured' }, { status: 500 }) const targetCalendarId = calendarId || process.env.DANIIL_CALENDAR_ID || 'daniilklimov25@gmail.com' const calendarClient = google.calendar({ version: 'v3', auth: auth as any }) let start: any, end: any if (allDay) { start = { date } end = { date } } else { start = { dateTime: `${date}T${startTime}:00`, timeZone: 'Europe/Moscow' } end = { dateTime: `${date}T${endTime}:00`, timeZone: 'Europe/Moscow' } } try { const res = await calendarClient.events.patch({ calendarId: targetCalendarId, eventId, requestBody: { summary: title, start, end, }, }) const e = res.data return NextResponse.json({ event: { id: e.id, title: e.summary || title, start: e.start?.dateTime || e.start?.date, end: e.end?.dateTime || e.end?.date, allDay: !e.start?.dateTime, } }) } catch (err: any) { return NextResponse.json({ error: err.message || 'Failed to update event' }, { status: 500 }) } } export async function DELETE(req: Request) { const { searchParams } = new URL(req.url) const eventId = searchParams.get('eventId') const calendarId = searchParams.get('calendarId') if (!eventId) { return NextResponse.json({ error: 'eventId is required' }, { status: 400 }) } const auth = getAuth(false) if (!auth) { return NextResponse.json({ error: 'not_configured' }, { status: 500 }) } const targetCalendarId = calendarId || process.env.DANIIL_CALENDAR_ID || 'daniilklimov25@gmail.com' const calendarClient = google.calendar({ version: 'v3', auth: auth as any }) try { await calendarClient.events.delete({ calendarId: targetCalendarId, eventId, }) return NextResponse.json({ success: true }) } catch (err: any) { return NextResponse.json({ error: err.message || 'Failed to delete event' }, { status: 500 }) } }