From 878198fabde848e327c60b5a3e08a91df49edc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Wed, 2 Jul 2025 23:33:35 +0200 Subject: [PATCH] API reformatting --- src/lib/google.ts | 37 ++++++++++++++- src/routes/private/api/google/README.md | 47 +++++++++++++++++++ .../api/google/auth/refresh/+server.ts | 30 ++++++++++++ .../private/api/google/auth/revoke/+server.ts | 31 ++++++++++++ .../api/google/auth/userinfo/+server.ts | 33 +++++++++++++ .../google/sheets/[sheetId]/data/+server.ts | 22 +++++++++ .../api/google/sheets/recent/+server.ts | 20 ++++++++ .../private/events/event/new/+page.svelte | 33 +++++-------- 8 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 src/routes/private/api/google/README.md create mode 100644 src/routes/private/api/google/auth/refresh/+server.ts create mode 100644 src/routes/private/api/google/auth/revoke/+server.ts create mode 100644 src/routes/private/api/google/auth/userinfo/+server.ts create mode 100644 src/routes/private/api/google/sheets/[sheetId]/data/+server.ts create mode 100644 src/routes/private/api/google/sheets/recent/+server.ts diff --git a/src/lib/google.ts b/src/lib/google.ts index 718199b..4977884 100644 --- a/src/lib/google.ts +++ b/src/lib/google.ts @@ -40,7 +40,7 @@ export async function isTokenValid(accessToken: string): Promise { export async function refreshAccessToken(refreshToken: string): Promise { try { - const response = await fetch('/api/auth/refresh', { + const response = await fetch('/private/api/google/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -58,3 +58,38 @@ export async function refreshAccessToken(refreshToken: string): Promise { + try { + const response = await fetch('/private/api/google/auth/userinfo', { + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); + + if (response.ok) { + return await response.json(); + } + return null; + } catch (error) { + console.error('Error fetching user info:', error); + return null; + } +} + +export async function revokeToken(accessToken: string): Promise { + try { + const response = await fetch('/private/api/google/auth/revoke', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ accessToken }) + }); + + return response.ok; + } catch (error) { + console.error('Error revoking token:', error); + return false; + } +} diff --git a/src/routes/private/api/google/README.md b/src/routes/private/api/google/README.md new file mode 100644 index 0000000..6d1f46b --- /dev/null +++ b/src/routes/private/api/google/README.md @@ -0,0 +1,47 @@ +# Google API Integration + +This directory contains unified endpoints for Google API integration, all protected under the `/private` route to ensure only authenticated users can access them. + +## Auth Endpoints + +### `/private/api/google/auth/refresh` + +- **Method**: POST +- **Purpose**: Refreshes an access token using a refresh token +- **Body**: `{ "refreshToken": "your-refresh-token" }` +- **Response**: `{ "accessToken": "new-access-token", "expiresIn": 3600 }` + +### `/private/api/google/auth/userinfo` + +- **Method**: GET +- **Purpose**: Gets information about the authenticated user +- **Headers**: Authorization: Bearer `access_token` +- **Response**: `{ "email": "user@example.com", "name": "User Name", "picture": "profile-pic-url" }` + +### `/private/api/google/auth/revoke` + +- **Method**: POST +- **Purpose**: Revokes an access token +- **Body**: `{ "accessToken": "token-to-revoke" }` +- **Response**: `{ "success": true }` + +## Sheets Endpoints + +### `/private/api/google/sheets/recent` + +- **Method**: GET +- **Purpose**: Gets a list of recent spreadsheets +- **Headers**: Authorization: Bearer `refresh_token` +- **Response**: Array of spreadsheet objects + +### `/private/api/google/sheets/[sheetId]/data` + +- **Method**: GET +- **Purpose**: Gets data from a specific spreadsheet +- **Headers**: Authorization: Bearer `refresh_token` +- **URL Parameters**: sheetId - The ID of the spreadsheet +- **Response**: Spreadsheet data including values array + +## Client Usage + +Use the utility functions in `$lib/google.ts` to interact with these endpoints. diff --git a/src/routes/private/api/google/auth/refresh/+server.ts b/src/routes/private/api/google/auth/refresh/+server.ts new file mode 100644 index 0000000..e6561ee --- /dev/null +++ b/src/routes/private/api/google/auth/refresh/+server.ts @@ -0,0 +1,30 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { getOAuthClient } from '$lib/google-server.js'; + +export const POST: RequestHandler = async ({ request }) => { + try { + const { refreshToken } = await request.json(); + + if (!refreshToken) { + return json({ error: 'Refresh token is required' }, { status: 400 }); + } + + const oauth = getOAuthClient(); + oauth.setCredentials({ refresh_token: refreshToken }); + + const { credentials } = await oauth.refreshAccessToken(); + + if (!credentials.access_token) { + return json({ error: 'Failed to refresh token' }, { status: 500 }); + } + + return json({ + accessToken: credentials.access_token, + expiresIn: credentials.expiry_date + }); + } catch (error) { + console.error('Error refreshing access token:', error); + return json({ error: 'Failed to refresh access token' }, { status: 500 }); + } +}; diff --git a/src/routes/private/api/google/auth/revoke/+server.ts b/src/routes/private/api/google/auth/revoke/+server.ts new file mode 100644 index 0000000..b01adee --- /dev/null +++ b/src/routes/private/api/google/auth/revoke/+server.ts @@ -0,0 +1,31 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; + +export const POST: RequestHandler = async ({ request }) => { + try { + const { accessToken } = await request.json(); + + if (!accessToken) { + return json({ error: 'Access token is required' }, { status: 400 }); + } + + // Call Google's token revocation endpoint + const response = await fetch(`https://accounts.google.com/o/oauth2/revoke?token=${accessToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + if (response.ok) { + return json({ success: true }); + } else { + const error = await response.text(); + console.error('Error revoking token:', error); + return json({ error: 'Failed to revoke token' }, { status: 500 }); + } + } catch (error) { + console.error('Error revoking access token:', error); + return json({ error: 'Failed to revoke access token' }, { status: 500 }); + } +}; diff --git a/src/routes/private/api/google/auth/userinfo/+server.ts b/src/routes/private/api/google/auth/userinfo/+server.ts new file mode 100644 index 0000000..cb98710 --- /dev/null +++ b/src/routes/private/api/google/auth/userinfo/+server.ts @@ -0,0 +1,33 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { getOAuthClient } from '$lib/google-server.js'; +import { google } from 'googleapis'; + +export const GET: RequestHandler = async ({ request }) => { + try { + const authHeader = request.headers.get('authorization'); + + if (!authHeader?.startsWith('Bearer ')) { + return json({ error: 'Missing or invalid authorization header' }, { status: 401 }); + } + + const accessToken = authHeader.slice(7); + + // Create OAuth client with the token + const oauth = getOAuthClient(); + oauth.setCredentials({ access_token: accessToken }); + + // Call the userinfo endpoint to get user details + const oauth2 = google.oauth2({ version: 'v2', auth: oauth }); + const userInfo = await oauth2.userinfo.get(); + + return json({ + email: userInfo.data.email, + name: userInfo.data.name, + picture: userInfo.data.picture + }); + } catch (error) { + console.error('Error fetching user info:', error); + return json({ error: 'Failed to fetch user info' }, { status: 500 }); + } +}; diff --git a/src/routes/private/api/google/sheets/[sheetId]/data/+server.ts b/src/routes/private/api/google/sheets/[sheetId]/data/+server.ts new file mode 100644 index 0000000..263e099 --- /dev/null +++ b/src/routes/private/api/google/sheets/[sheetId]/data/+server.ts @@ -0,0 +1,22 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { getSpreadsheetData } from '$lib/sheets.js'; + +export const GET: RequestHandler = async ({ params, request }) => { + try { + const { sheetId } = params; + const authHeader = request.headers.get('authorization'); + + if (!authHeader?.startsWith('Bearer ')) { + return json({ error: 'Missing or invalid authorization header' }, { status: 401 }); + } + + const refreshToken = authHeader.slice(7); + const sheetData = await getSpreadsheetData(refreshToken, sheetId, 'A1:Z10'); + + return json(sheetData); + } catch (error) { + console.error('Error fetching spreadsheet data:', error); + return json({ error: 'Failed to fetch spreadsheet data' }, { status: 500 }); + } +}; diff --git a/src/routes/private/api/google/sheets/recent/+server.ts b/src/routes/private/api/google/sheets/recent/+server.ts new file mode 100644 index 0000000..d9b94a2 --- /dev/null +++ b/src/routes/private/api/google/sheets/recent/+server.ts @@ -0,0 +1,20 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { getRecentSpreadsheets } from '$lib/sheets.js'; + +export const GET: RequestHandler = async ({ request }) => { + try { + const authHeader = request.headers.get('authorization'); + if (!authHeader?.startsWith('Bearer ')) { + return json({ error: 'Missing or invalid authorization header' }, { status: 401 }); + } + + const refreshToken = authHeader.slice(7); + const sheets = await getRecentSpreadsheets(refreshToken, 20); + + return json(sheets); + } catch (error) { + console.error('Error fetching recent spreadsheets:', error); + return json({ error: 'Failed to fetch spreadsheets' }, { status: 500 }); + } +}; diff --git a/src/routes/private/events/event/new/+page.svelte b/src/routes/private/events/event/new/+page.svelte index 5f20ee6..b3868bb 100644 --- a/src/routes/private/events/event/new/+page.svelte +++ b/src/routes/private/events/event/new/+page.svelte @@ -1,8 +1,8 @@