Gmail working

This commit is contained in:
Roman Krček
2025-06-22 16:55:33 +02:00
parent 620c9c5cb7
commit 14213fe341
3 changed files with 185 additions and 0 deletions

58
src/lib/google.ts Normal file
View File

@@ -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 }
});
}

View File

@@ -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 = `
<script>
localStorage.setItem('gmail_refresh_token', ${JSON.stringify(refreshToken)});
location = '/private/creator';
</script>`;
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 });
};

View File

@@ -0,0 +1,63 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
let authorized = false;
let refreshToken = '';
let to = '';
let subject = '';
let body = '';
onMount(() => {
refreshToken = localStorage.getItem('gmail_refresh_token') ?? '';
authorized = !!refreshToken;
});
/* ⇢ redirects straight to Google via server 302 */
const connect = () => goto('/private/api/gmail?action=auth');
async function sendEmail() {
const r = await fetch('/private/api/gmail', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'send',
to,
subject,
text: body,
refreshToken
})
});
r.ok ? alert('Sent!') : alert(await r.text());
to = subject = body = '';
}
async function disconnect() {
if (!confirm('Disconnect Google account?')) return;
await fetch('/private/api/gmail', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'revoke', refreshToken })
});
localStorage.removeItem('gmail_refresh_token');
refreshToken = '';
authorized = false;
}
</script>
{#if !authorized}
<section class="space-y-4">
<p>You havent connected your Google account yet.</p>
<button class="btn" on:click={connect}>Connect Google</button>
</section>
{:else}
<button class="btn-secondary mb-4" on:click={disconnect}>Disconnect Google</button>
<form class="space-y-4" on:submit|preventDefault={sendEmail}>
<input class="input w-full" type="email" placeholder="recipient@example.com" bind:value={to} required />
<input class="input w-full" placeholder="Subject" bind:value={subject} required />
<textarea class="textarea w-full" rows="6" placeholder="Message" bind:value={body} required />
<button class="btn-primary">Send</button>
</form>
{/if}