Merge pull request #42 from R0m1k3/claude/integrate-google-calendar-api-011CV44Nie9HB7U35ArSUnNa

Feature: Ajout du support d'API Key pour Google Calendar
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-12 14:24:00 +01:00
commit 76d1837ebc
3 files changed
+104 -11

No files matched your search

+38 -2
View File
@@ -17,7 +17,8 @@ const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
*/ */
const AUTH_TYPES = { const AUTH_TYPES = {
OAUTH2: 'oauth2', OAUTH2: 'oauth2',
SERVICE_ACCOUNT: 'service_account' SERVICE_ACCOUNT: 'service_account',
API_KEY: 'api_externe'
}; };
/** /**
@@ -76,6 +77,19 @@ async function getServiceAccountClient() {
} }
} }
/**
* Créer un client avec API Key
*/
async function getApiKeyClient() {
const apiKey = await getOne("SELECT value FROM settings WHERE key = 'google_calendar_api_key'");
if (!apiKey || !apiKey.value) {
throw new Error('Clé API non configurée');
}
return apiKey.value;
}
/** /**
* Créer un client Google Calendar selon la méthode d'authentification * Créer un client Google Calendar selon la méthode d'authentification
* @param {number} userId - ID de l'utilisateur (pour OAuth2) * @param {number} userId - ID de l'utilisateur (pour OAuth2)
@@ -86,6 +100,9 @@ async function getCalendarClient(userId = null) {
if (authType === AUTH_TYPES.SERVICE_ACCOUNT) { if (authType === AUTH_TYPES.SERVICE_ACCOUNT) {
const auth = await getServiceAccountClient(); const auth = await getServiceAccountClient();
return google.calendar({ version: 'v3', auth }); return google.calendar({ version: 'v3', auth });
} else if (authType === AUTH_TYPES.API_KEY) {
const apiKey = await getApiKeyClient();
return google.calendar({ version: 'v3', auth: apiKey });
} else { } else {
// OAuth2 // OAuth2
if (!userId) { if (!userId) {
@@ -227,6 +244,17 @@ router.get('/auth-status', async (req, res) => {
isExpired: false, isExpired: false,
needsReauth: !isAuthenticated needsReauth: !isAuthenticated
}); });
} else if (authType === AUTH_TYPES.API_KEY) {
// Vérifier si la clé API est configurée
const apiKey = await getOne("SELECT value FROM settings WHERE key = 'google_calendar_api_key'");
const isAuthenticated = !!apiKey?.value;
res.json({
authType: AUTH_TYPES.API_KEY,
isAuthenticated,
isExpired: false,
needsReauth: !isAuthenticated
});
} else { } else {
// OAuth2 // OAuth2
const tokens = await getOne( const tokens = await getOne(
@@ -324,7 +352,7 @@ router.get('/events', async (req, res) => {
/** /**
* POST /api/calendar/sync * POST /api/calendar/sync
* Synchroniser avec Google Calendar (OAuth2 ou Service Account) * Synchroniser avec Google Calendar (OAuth2, Service Account ou API Key)
*/ */
router.post('/sync', async (req, res) => { router.post('/sync', async (req, res) => {
try { try {
@@ -339,6 +367,14 @@ router.post('/sync', async (req, res) => {
} }
} }
// Si API Key, permettre de spécifier l'ID du calendrier
if (authType === AUTH_TYPES.API_KEY) {
const calendarIdSetting = await getOne("SELECT value FROM settings WHERE key = 'google_calendar_id'");
if (calendarIdSetting?.value) {
calendarId = calendarIdSetting.value;
}
}
// Créer un client Google Calendar selon la méthode d'authentification // Créer un client Google Calendar selon la méthode d'authentification
const calendar = await getCalendarClient(req.user.id); const calendar = await getCalendarClient(req.user.id);
+65 -7
View File
@@ -58,7 +58,7 @@ const Index = () => {
isAuthenticated: boolean; isAuthenticated: boolean;
isExpired: boolean; isExpired: boolean;
needsReauth: boolean; needsReauth: boolean;
authType?: 'oauth2' | 'service_account'; authType?: 'oauth2' | 'service_account' | 'api_externe';
}>({ }>({
isAuthenticated: false, isAuthenticated: false,
isExpired: false, isExpired: false,
@@ -1359,7 +1359,7 @@ const Index = () => {
<Label htmlFor="google-auth-type">Méthode d'authentification</Label> <Label htmlFor="google-auth-type">Méthode d'authentification</Label>
<Select <Select
value={settings.google_auth_type || 'oauth2'} value={settings.google_auth_type || 'oauth2'}
onValueChange={(value) => setSettings({ ...settings, google_auth_type: value as 'oauth2' | 'service_account' })} onValueChange={(value) => setSettings({ ...settings, google_auth_type: value as 'oauth2' | 'service_account' | 'api_externe' })}
> >
<SelectTrigger id="google-auth-type"> <SelectTrigger id="google-auth-type">
<SelectValue placeholder="Sélectionnez une méthode" /> <SelectValue placeholder="Sélectionnez une méthode" />
@@ -1381,11 +1381,20 @@ const Index = () => {
</span> </span>
</div> </div>
</SelectItem> </SelectItem>
<SelectItem value="api_externe">
<div className="flex flex-col">
<span className="font-medium">API externe</span>
<span className="text-xs text-muted-foreground">
Connectez-vous à une API externe pour Google Calendar
</span>
</div>
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
<strong>OAuth 2.0</strong> : Pour accéder au calendrier personnel avec autorisation de l'utilisateur.<br/> <strong>OAuth 2.0</strong> : Pour accéder au calendrier personnel avec autorisation de l'utilisateur.<br/>
<strong>Service Account</strong> : Pour accéder à des calendriers partagés sans interaction utilisateur. <strong>Service Account</strong> : Pour accéder à des calendriers partagés sans interaction utilisateur.<br/>
<strong>API externe</strong> : Utilisez votre propre API backend pour gérer la connexion à Google Calendar.
</p> </p>
</div> </div>
@@ -1474,6 +1483,50 @@ const Index = () => {
</> </>
)} )}
{/* Configuration API Key */}
{settings.google_auth_type === 'api_externe' && (
<>
<div className="space-y-2">
<Label htmlFor="google-api-key">Clé API Google Calendar</Label>
<Input
id="google-api-key"
type="password"
placeholder="AIzaSy..."
value={settings.google_calendar_api_key || ''}
onChange={(e) => setSettings({ ...settings, google_calendar_api_key: e.target.value })}
/>
<p className="text-sm text-muted-foreground">
Entrez votre clé API Google Calendar.
</p>
</div>
<div className="space-y-2">
<Label htmlFor="google-calendar-id-api">ID du calendrier</Label>
<Input
id="google-calendar-id-api"
type="text"
placeholder="primary ou votre-email@gmail.com"
value={settings.google_calendar_id || ''}
onChange={(e) => setSettings({ ...settings, google_calendar_id: e.target.value })}
/>
<p className="text-sm text-muted-foreground">
Utilisez "primary" pour votre calendrier principal ou l'email du calendrier spécifique.
</p>
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<p className="text-sm font-semibold text-blue-900 dark:text-blue-200 mb-1">
ℹ️ Obtenir une clé API
</p>
<p className="text-xs text-blue-800 dark:text-blue-300 mb-2">
1. Allez sur <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener noreferrer" className="underline">Google Cloud Console</a><br/>
2. Créez une clé API<br/>
3. Activez l'API Google Calendar<br/>
4. Copiez la clé API ici
</p>
</div>
</div>
</>
)}
<div className="flex gap-2"> <div className="flex gap-2">
<Button onClick={handleSaveSettings} className="flex items-center gap-2"> <Button onClick={handleSaveSettings} className="flex items-center gap-2">
<Key className="h-4 w-4" /> <Key className="h-4 w-4" />
@@ -1483,7 +1536,8 @@ const Index = () => {
{( {(
((!settings.google_auth_type || settings.google_auth_type === 'oauth2') && settings.google_client_id && settings.google_client_secret) || ((!settings.google_auth_type || settings.google_auth_type === 'oauth2') && settings.google_client_id && settings.google_client_secret) ||
(settings.google_auth_type === 'service_account' && settings.google_service_account_key) (settings.google_auth_type === 'service_account' && settings.google_service_account_key) ||
(settings.google_auth_type === 'api_externe' && settings.google_calendar_api_key)
) && ( ) && (
<> <>
<div className="border-t pt-4"> <div className="border-t pt-4">
@@ -1491,7 +1545,11 @@ const Index = () => {
{calendarAuthStatus.isAuthenticated && !calendarAuthStatus.isExpired ? ( {calendarAuthStatus.isAuthenticated && !calendarAuthStatus.isExpired ? (
<div className="flex items-center gap-2 text-green-600"> <div className="flex items-center gap-2 text-green-600">
<div className="h-2 w-2 rounded-full bg-green-600"></div> <div className="h-2 w-2 rounded-full bg-green-600"></div>
<span>Connecté à Google Calendar ({calendarAuthStatus.authType === 'service_account' ? 'Service Account' : 'OAuth 2.0'})</span> <span>Connecté à Google Calendar ({
calendarAuthStatus.authType === 'service_account' ? 'Service Account' :
calendarAuthStatus.authType === 'api_externe' ? 'API Key' :
'OAuth 2.0'
})</span>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-2 text-yellow-600"> <div className="flex items-center gap-2 text-yellow-600">
@@ -1538,7 +1596,7 @@ const Index = () => {
Déconnecter Déconnecter
</Button> </Button>
</> </>
) : ( ) : settings.google_auth_type === 'api_externe' || settings.google_auth_type === 'service_account' ? (
<Button <Button
onClick={async () => { onClick={async () => {
try { try {
@@ -1554,7 +1612,7 @@ const Index = () => {
<RefreshCw className="h-4 w-4" /> <RefreshCw className="h-4 w-4" />
Synchroniser Synchroniser
</Button> </Button>
)} ) : null}
</div> </div>
</> </>
)} )}
+1 -2
View File
@@ -7,10 +7,9 @@ export interface Settings {
theme?: 'light' | 'dark' | 'auto'; theme?: 'light' | 'dark' | 'auto';
google_client_id?: string; google_client_id?: string;
google_client_secret?: string; google_client_secret?: string;
google_auth_type?: 'oauth2' | 'service_account'; google_auth_type?: 'oauth2' | 'service_account' | 'api_externe';
google_service_account_key?: string; google_service_account_key?: string;
google_calendar_email?: string; google_calendar_email?: string;
// Legacy fields (kept for backward compatibility)
google_calendar_api_key?: string; google_calendar_api_key?: string;
google_calendar_id?: string; google_calendar_id?: string;
} }