Google Calendar API - knpy/yuka-app GitHub Wiki
- Google Cloud Platform アカウント
- OAuth 2.0 クライアントID作成済み
- プロジェクトが作成済み
-
Google Cloud Console にアクセス
-
プロジェクト選択
- 対象のプロジェクトを選択
-
API ライブラリ
- 左メニュー「APIs & Services」→「Library」
- 「Google Calendar API」を検索
- 「ENABLE」をクリック
OAuth consent screen で以下のスコープを追加:
https://www.googleapis.com/auth/calendar.readonly
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
npm install googleapis google-auth-library
// pages/api/calendar/events.ts
import { NextRequest, NextResponse } from 'next/server';
import { google } from 'googleapis';
import { getServerSession } from 'next-auth';
import { authOptions } from '../auth/[...nextauth]';
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.accessToken) {
return NextResponse.json(
{ error: 'Not authenticated' },
{ status: 401 }
);
}
// OAuth2クライアント設定
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET
);
oauth2Client.setCredentials({
access_token: session.accessToken
});
// Calendar API インスタンス
const calendar = google.calendar({
version: 'v3',
auth: oauth2Client
});
// 今日の日付範囲
const today = new Date();
const startOfDay = new Date(today);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(today);
endOfDay.setHours(23, 59, 59, 999);
// イベント取得
const response = await calendar.events.list({
calendarId: 'primary',
timeMin: startOfDay.toISOString(),
timeMax: endOfDay.toISOString(),
singleEvents: true,
orderBy: 'startTime',
});
return NextResponse.json({
events: response.data.items || [],
totalEvents: response.data.items?.length || 0,
date: today.toISOString().split('T')[0]
});
} catch (error) {
console.error('Calendar API Error:', error);
return NextResponse.json(
{ error: 'Failed to fetch calendar events' },
{ status: 500 }
);
}
}
// components/calendar/GoogleCalendar.tsx
import React from 'react';
import { useSession } from 'next-auth/react';
interface CalendarEvent {
id: string;
summary: string;
start: {
dateTime?: string;
date?: string;
};
end: {
dateTime?: string;
date?: string;
};
location?: string;
description?: string;
}
export default function GoogleCalendar() {
const { data: session } = useSession();
const [events, setEvents] = React.useState<CalendarEvent[]>([]);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const fetchEvents = React.useCallback(async () => {
if (!session?.accessToken) return;
setLoading(true);
setError(null);
try {
const response = await fetch('/api/calendar/events', {
headers: {
'Authorization': `Bearer ${session.accessToken}`
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
setEvents(data.events);
} catch (err) {
setError(err instanceof Error ? err.message : '不明なエラー');
} finally {
setLoading(false);
}
}, [session?.accessToken]);
React.useEffect(() => {
if (session) {
fetchEvents();
}
}, [session, fetchEvents]);
const formatTime = (dateTime?: string, date?: string) => {
if (dateTime) {
return new Date(dateTime).toLocaleTimeString('ja-JP', {
hour: '2-digit',
minute: '2-digit'
});
}
return '終日';
};
if (loading) return <div>読み込み中...</div>;
if (error) return <div>エラー: {error}</div>;
return (
<div className="space-y-4">
<h2 className="text-xl font-bold">今日の予定</h2>
{events.length === 0 ? (
<p>今日の予定はありません</p>
) : (
<div className="space-y-2">
{events.map((event) => (
<div key={event.id} className="border p-3 rounded">
<h3 className="font-semibold">{event.summary}</h3>
<p className="text-sm text-gray-600">
{formatTime(event.start.dateTime, event.start.date)} -
{formatTime(event.end.dateTime, event.end.date)}
</p>
{event.location && (
<p className="text-sm text-gray-500">📍 {event.location}</p>
)}
{event.description && (
<p className="text-sm mt-2">{event.description}</p>
)}
</div>
))}
</div>
)}
<button
onClick={fetchEvents}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
再読み込み
</button>
</div>
);
}
.env.local
に以下を設定:
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
NEXTAUTH_SECRET=your_nextauth_secret
NEXTAUTH_URL=http://localhost:3000
-
403 Forbidden
- Calendar APIが有効になっているか確認
- 正しいスコープが設定されているか確認
-
401 Unauthorized
- アクセストークンが有効か確認
- OAuth認証が完了しているか確認
-
429 Too Many Requests
- API制限に達している
- リクエスト頻度を調整
// API呼び出し時のデバッグ
console.log('Access Token:', session?.accessToken ? 'Present' : 'Missing');
console.log('API Response Status:', response.status);
console.log('Events Count:', data.events?.length || 0);
- クエリ/日: 1,000,000
- クエリ/分/ユーザー: 300
- クエリ/秒/ユーザー: 10
- 必要最小限のフィールドのみ取得
- キャッシュの活用
- バッチリクエストの使用
- サーバーサイドでのみトークン使用
- 適切な有効期限設定
- リフレッシュトークンによる自動更新
- 個人情報の適切な取り扱い
- 最小権限の原則
- ログ出力時の機密情報除外