From 14213fe3413e849a8a96de4ee1eaa221e24424eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sun, 22 Jun 2025 16:55:33 +0200 Subject: [PATCH] Gmail working --- src/lib/google.ts | 58 ++++++++++++++++++++++ src/routes/private/api/gmail/+server.ts | 64 +++++++++++++++++++++++++ src/routes/private/creator/+page.svelte | 63 ++++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 src/lib/google.ts create mode 100644 src/routes/private/api/gmail/+server.ts create mode 100644 src/routes/private/creator/+page.svelte diff --git a/src/lib/google.ts b/src/lib/google.ts new file mode 100644 index 0000000..8048080 --- /dev/null +++ b/src/lib/google.ts @@ -0,0 +1,58 @@ +import { google } from 'googleapis'; +import { env } from '$env/dynamic/private'; + +const { + GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET +} = env; + +/* NEW REDIRECT — must match Google Cloud OAuth settings */ +const REDIRECT_URI = 'http://localhost:5173/private/api/gmail'; + +export const scopes = ['https://www.googleapis.com/auth/gmail.send']; + +export function getOAuthClient() { + return new google.auth.OAuth2( + GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET, + REDIRECT_URI + ); +} + +export function createAuthUrl() { + return getOAuthClient().generateAuthUrl({ + access_type: 'offline', + prompt: 'consent', + scope: scopes + }); +} + +export async function exchangeCodeForTokens(code: string) { + const { tokens } = await getOAuthClient().getToken(code); + if (!tokens.refresh_token) throw new Error('No refresh_token returned'); + return tokens.refresh_token; +} + +export async function sendGmail( + refreshToken: string, + { to, subject, text }: { to: string; subject: string; text: string } +) { + const oauth = getOAuthClient(); + oauth.setCredentials({ refresh_token: refreshToken }); + + const gmail = google.gmail({ version: 'v1', auth: oauth }); + const raw = Buffer + .from( + [`To: ${to}`, + 'Content-Type: text/plain; charset="UTF-8"', + 'Content-Transfer-Encoding: 7bit', + `Subject: ${subject}`, + '', + text].join('\n')) + .toString('base64url'); + + await gmail.users.messages.send({ + userId: 'me', + requestBody: { raw } + }); +} diff --git a/src/routes/private/api/gmail/+server.ts b/src/routes/private/api/gmail/+server.ts new file mode 100644 index 0000000..aa76b34 --- /dev/null +++ b/src/routes/private/api/gmail/+server.ts @@ -0,0 +1,64 @@ +import type { RequestHandler } from './$types'; +import { json, redirect } from '@sveltejs/kit'; +import { + createAuthUrl, + exchangeCodeForTokens, + sendGmail, + getOAuthClient +} from '$lib/google'; + +/* ───────────── GET ───────────── */ +export const GET: RequestHandler = async ({ url }) => { + /* 1. /private/api/gmail?action=auth → 302 to Google */ + if (url.searchParams.get('action') === 'auth') { + throw redirect(302, createAuthUrl()); + } + + /* 2. Google callback /private/api/gmail?code=XXXX */ + const code = url.searchParams.get('code'); + if (code) { + try { + const refreshToken = await exchangeCodeForTokens(code); + + const html = ` + `; + return new Response(html, { headers: { 'Content-Type': 'text/html' } }); + } catch (err) { + return new Response((err as Error).message, { status: 500 }); + } + } + + return new Response('Bad request', { status: 400 }); +}; + +/* ───────────── POST ───────────── */ +export const POST: RequestHandler = async ({ request }) => { + const { action, refreshToken, to, subject, text } = await request.json(); + + /* send e-mail */ + if (action === 'send') { + if (!refreshToken) return new Response('Missing token', { status: 401 }); + try { + await sendGmail(refreshToken, { to, subject, text }); + return json({ ok: true }); + } catch (err) { + return new Response((err as Error).message, { status: 500 }); + } + } + + /* revoke token */ + if (action === 'revoke') { + if (!refreshToken) return new Response('Missing token', { status: 401 }); + try { + await getOAuthClient().revokeToken(refreshToken); + return json({ ok: true }); + } catch (err) { + return new Response((err as Error).message, { status: 500 }); + } + } + + return new Response('Bad request', { status: 400 }); +}; diff --git a/src/routes/private/creator/+page.svelte b/src/routes/private/creator/+page.svelte new file mode 100644 index 0000000..42a156a --- /dev/null +++ b/src/routes/private/creator/+page.svelte @@ -0,0 +1,63 @@ + + +{#if !authorized} +
+

You haven’t connected your Google account yet.

+ +
+{:else} + + +
+ + +