supabase-local #15

Merged
erman merged 20 commits from supabase-local into main 2025-07-08 17:34:14 +02:00
32 changed files with 257 additions and 594 deletions
Showing only changes of commit ed317feae7 - Show all commits

View File

@@ -92,6 +92,11 @@ onsubmit|preventDefault={handleSubmit} is depracated, do not use it!
Loading session using page.server.ts is not needed as the session is already available in the locals object.
IMPORTANT: Always make sure that the client-side module are not importing secrets
or are running any sensritive code that could expose secrets to the client.
If any requests are needed to check sensitive infomration, create an api route and
fetch data from there instead of directly in the client-side module.
The database schema in supabase is as follows:
-- WARNING: This schema is for context only and is not meant to be run.
-- Table order and constraints may not be valid for execution.

View File

@@ -1,80 +0,0 @@
import { google } from 'googleapis';
import quotedPrintable from 'quoted-printable';
import { getAuthenticatedClient } from './google-server.js';
export function createEmailTemplate(text: string): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap');
</style>
</head>
<body style="font-family: 'Lato', sans-serif; background-color: #f9f9f9; padding: 20px; margin: 0;">
<div style="max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
<p style="white-space: pre-line;font-size: 16px; line-height: 1.5; color: #333;">${text}</p>
<img src="cid:qrCode1" alt="QR Code" style="display: block; margin: 20px auto; max-width: 50%; min-width: 200px; height: auto;" />
<div style="width: 100%; display: flex; flex-direction: row; justify-content: space-between">
<div style="height: 4px; width: 20%; background: #00aeef;"></div>
<div style="height: 4px; width: 20%; background: #ec008c;"></div>
<div style="height: 4px; width: 20%; background: #7ac143;"></div>
<div style="height: 4px; width: 20%; background: #f47b20;"></div>
<div style="height: 4px; width: 20%; background: #2e3192;"></div>
</div>
<div style="font-size: 12px; color: #999; padding-top: 0px; margin-top: 10px; line-height: 1.5; ">
<p>This email has been generated with the help of ScanWave</p>
</div>
</div>
</body>
</html>`;
}
export async function sendGmail(
refreshToken: string,
{ to, subject, text, qr_code }: { to: string; subject: string; text: string; qr_code: string }
) {
const oauth = getAuthenticatedClient(refreshToken);
const gmail = google.gmail({ version: 'v1', auth: oauth });
const message_html = createEmailTemplate(text);
const boundary = 'BOUNDARY';
const nl = '\r\n';
// Convert HTML to a Buffer, then to latin1 string for quotedPrintable.encode
const htmlBuffer = Buffer.from(message_html, 'utf8');
const htmlLatin1 = htmlBuffer.toString('latin1');
const htmlQP = quotedPrintable.encode(htmlLatin1);
const qrLines = qr_code.replace(/.{1,76}/g, '$&' + nl);
const rawParts = [
'MIME-Version: 1.0',
`To: ${to}`,
`Subject: ${subject}`,
`Content-Type: multipart/related; boundary="${boundary}"`,
'',
`--${boundary}`,
'Content-Type: text/html; charset="UTF-8"',
'Content-Transfer-Encoding: quoted-printable',
'',
htmlQP,
'',
`--${boundary}`,
'Content-Type: image/png',
'Content-Transfer-Encoding: base64',
'Content-ID: <qrCode1>',
'Content-Disposition: inline; filename="qr.png"',
'',
qrLines,
'',
`--${boundary}--`,
''
];
const rawMessage = rawParts.join(nl);
const raw = Buffer.from(rawMessage).toString('base64url');
await gmail.users.messages.send({
userId: 'me',
requestBody: { raw }
});
}

View File

@@ -1,38 +0,0 @@
import { google } from 'googleapis';
import { env } from '$env/dynamic/private';
export const scopes = [
'https://www.googleapis.com/auth/gmail.send',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/spreadsheets.readonly'
];
export function getOAuthClient() {
return new google.auth.OAuth2(
env.GOOGLE_CLIENT_ID,
env.GOOGLE_CLIENT_SECRET,
env.GOOGLE_REDIRECT_URI
);
}
export function createAuthUrl() {
return getOAuthClient().generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: scopes,
redirect_uri: env.GOOGLE_REDIRECT_URI
});
}
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 function getAuthenticatedClient(refreshToken: string) {
const oauth = getOAuthClient();
oauth.setCredentials({ refresh_token: refreshToken });
return oauth;
}

View File

@@ -1,95 +0,0 @@
import { browser } from '$app/environment';
// Client-side only functions
export const scopes = [
'https://www.googleapis.com/auth/gmail.send',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/spreadsheets.readonly'
];
// Client-side functions for browser environment
export async function initGoogleAuth(): Promise<void> {
if (!browser) return;
// Google Auth initialization is handled by the OAuth flow
// No initialization needed for our server-side approach
}
export function getAuthUrl(): string {
if (!browser) return '';
// This should be obtained from the server
return '/auth/google';
}
export async function isTokenValid(accessToken: string): Promise<boolean> {
if (!browser) return false;
try {
const response = await fetch(`https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${accessToken}`);
const data = await response.json();
if (response.ok && data.expires_in && data.expires_in > 0) {
return true;
}
return false;
} catch (error) {
console.error('Error validating token:', error);
return false;
}
}
export async function refreshAccessToken(refreshToken: string): Promise<string | null> {
try {
const response = await fetch('/private/api/google/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken })
});
if (response.ok) {
const data = await response.json();
return data.accessToken;
}
return null;
} catch (error) {
console.error('Error refreshing token:', error);
return null;
}
}
export async function getUserInfo(accessToken: string): Promise<{ email: string; name: string; picture: string } | null> {
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<boolean> {
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;
}
}

View File

@@ -26,6 +26,7 @@ export function getOAuthClient() {
* @returns Auth URL for Google OAuth
*/
export function createAuthUrl() {
console.warn("CREATE AUTH URL");
return getOAuthClient().generateAuthUrl({
access_type: 'offline',
prompt: 'consent',

View File

@@ -1,8 +1,13 @@
/**
* Client-side Google API integration module
* Google API integration module
*
* This module provides utilities for interacting with Google APIs from the client-side.
* This module provides utilities for interacting with Google APIs:
* - Authentication (server and client-side)
* - Sheets API
*/
// Re-export auth utilities
export * from './auth/client.js';
// Google service modules
export * as googleAuthClient from './auth/client.ts';
export * as googleSheetsClient from './sheets/client.ts';

View File

@@ -1,5 +0,0 @@
// Re-export client-side auth utilities
export * from '../auth/client.js';
// Re-export types
export * from './types.js';

View File

@@ -1,14 +0,0 @@
/**
* Client-side type definitions for Google API integration
*/
export interface GoogleSheet {
id: string;
name: string;
modifiedTime: string;
webViewLink: string;
}
export interface SheetData {
values: string[][];
}

View File

@@ -1,90 +0,0 @@
import { google } from 'googleapis';
import quotedPrintable from 'quoted-printable';
import { getAuthenticatedClient } from '../auth/server.js';
/**
* Create an HTML email template
* @param text - Email body text
* @returns HTML email template
*/
export function createEmailTemplate(text: string): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap');
</style>
</head>
<body style="font-family: 'Lato', sans-serif; background-color: #f9f9f9; padding: 20px; margin: 0;">
<div style="max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
<p style="white-space: pre-line;font-size: 16px; line-height: 1.5; color: #333;">${text}</p>
<img src="cid:qrCode1" alt="QR Code" style="display: block; margin: 20px auto; max-width: 50%; min-width: 200px; height: auto;" />
<div style="width: 100%; display: flex; flex-direction: row; justify-content: space-between">
<div style="height: 4px; width: 20%; background: #00aeef;"></div>
<div style="height: 4px; width: 20%; background: #ec008c;"></div>
<div style="height: 4px; width: 20%; background: #7ac143;"></div>
<div style="height: 4px; width: 20%; background: #f47b20;"></div>
<div style="height: 4px; width: 20%; background: #2e3192;"></div>
</div>
<div style="font-size: 12px; color: #999; padding-top: 0px; margin-top: 10px; line-height: 1.5; ">
<p>This email has been generated with the help of ScanWave</p>
</div>
</div>
</body>
</html>`;
}
/**
* Send an email through Gmail
* @param refreshToken - Google refresh token
* @param params - Email parameters (to, subject, text, qr_code)
*/
export async function sendGmail(
refreshToken: string,
{ to, subject, text, qr_code }: { to: string; subject: string; text: string; qr_code: string }
) {
const oauth = getAuthenticatedClient(refreshToken);
const gmail = google.gmail({ version: 'v1', auth: oauth });
const message_html = createEmailTemplate(text);
const boundary = 'BOUNDARY';
const nl = '\r\n';
// Convert HTML to a Buffer, then to latin1 string for quotedPrintable.encode
const htmlBuffer = Buffer.from(message_html, 'utf8');
const htmlLatin1 = htmlBuffer.toString('latin1');
const htmlQP = quotedPrintable.encode(htmlLatin1);
const qrLines = qr_code.replace(/.{1,76}/g, '$&' + nl);
const rawParts = [
'MIME-Version: 1.0',
`To: ${to}`,
`Subject: ${subject}`,
`Content-Type: multipart/related; boundary="${boundary}"`,
'',
`--${boundary}`,
'Content-Type: text/html; charset="UTF-8"',
'Content-Transfer-Encoding: quoted-printable',
'',
htmlQP,
'',
`--${boundary}`,
'Content-Type: image/png',
'Content-Transfer-Encoding: base64',
'Content-ID: <qrCode1>',
'Content-Disposition: inline; filename="qr.png"',
'',
qrLines,
'',
`--${boundary}--`,
''
];
const rawMessage = rawParts.join(nl);
const raw = Buffer.from(rawMessage).toString('base64url');
await gmail.users.messages.send({
userId: 'me',
requestBody: { raw }
});
}

View File

@@ -0,0 +1,88 @@
import { google } from 'googleapis';
import quotedPrintable from 'quoted-printable';
import { getAuthenticatedClient } from '../auth/server.js';
/**
* Create an HTML email template
* @param text - Email body text
* @returns HTML email template
*/
export function createEmailTemplate(text: string): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap');
</style>
</head>
<body style="font-family: 'Lato', sans-serif; background-color: #f9f9f9; padding: 20px; margin: 0;">
<div style="max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 8px;">
<p style="white-space: pre-line; font-size: 16px; color: #333;">${text}</p>
<img src="cid:qrCode1" alt="QR Code" style="display: block; margin: 20px auto; max-width: 50%; height: auto;" />
</div>
</body>
</html>`;
}
/**
* Send an email through Gmail
* @param refreshToken - Google refresh token
* @param params - Email parameters (to, subject, text, qr_code)
*/
export async function sendGmail(
refreshToken: string,
{
to,
subject,
text,
qr_code
}: {
to: string;
subject: string;
text: string;
qr_code: string;
}
) {
const oauth = getAuthenticatedClient(refreshToken);
const gmail = google.gmail({ version: 'v1', auth: oauth });
const message_html = createEmailTemplate(text);
const boundary = 'BOUNDARY';
const nl = '\r\n';
const htmlBuffer = Buffer.from(message_html, 'utf8');
const htmlLatin1 = htmlBuffer.toString('latin1');
const htmlQP = quotedPrintable.encode(htmlLatin1);
const qrLines = qr_code.replace(/.{1,76}/g, '$&' + nl);
const rawParts = [
'MIME-Version: 1.0',
`To: ${to}`,
`Subject: ${subject}`,
`Content-Type: multipart/related; boundary="${boundary}"`,
'--' + boundary,
'Content-Type: text/html; charset="UTF-8"',
'Content-Transfer-Encoding: quoted-printable',
'',
htmlQP,
'',
'--' + boundary,
'Content-Type: image/png',
'Content-Transfer-Encoding: base64',
'Content-ID: <qrCode1>',
'Content-Disposition: inline; filename="qr.png"',
'',
qrLines,
'',
'--' + boundary + '--',
''
];
const rawMessage = rawParts.join(nl);
const raw = Buffer.from(rawMessage).toString('base64url');
await gmail.users.messages.send({
userId: 'me',
requestBody: { raw }
});
}

View File

@@ -1,9 +0,0 @@
/**
* Google API integration module
*
* This module provides utilities for interacting with Google APIs.
* NOTE: This is a client-side module. For server-side code, import from '$lib/google/server.js'
*/
// Re-export client-side auth utilities
export * from './auth/client.js';

View File

@@ -1,14 +1,15 @@
/**
* Server-side Google API integration module
* Google API integration module
*
* This module provides utilities for interacting with Google APIs from the server-side.
* This module provides utilities for interacting with Google APIs:
* - Authentication (server and client-side)
* - Sheets API
* - Gmail API
*/
// Re-export server-side auth utilities
export * from './auth/server.js';
// Google service modules
export * as googleAuthServer from './auth/server.ts';
// Re-export sheets utilities
export * from './sheets/index.js';
export * as googleSheetsServer from './sheets/server.ts';
// Re-export Gmail utilities
export * from './gmail/index.js';
export * as googleGmailServer from './gmail/server.ts';

View File

@@ -0,0 +1,23 @@
// Client-side Sheets functions (use fetch to call protected API endpoints)
/**
* Fetch recent spreadsheets via protected endpoint
*/
export async function getRecentSpreadsheetsClient(refreshToken: string, limit: number = 10) {
const response = await fetch(`/private/api/google/sheets/recent?limit=${limit}`, {
headers: { Authorization: `Bearer ${refreshToken}` }
});
if (!response.ok) throw new Error('Failed to fetch recent sheets');
return await response.json();
}
/**
* Fetch spreadsheet data via protected endpoint
*/
export async function getSpreadsheetDataClient(refreshToken: string, sheetId: string, range: string = 'A1:Z10') {
const response = await fetch(`/private/api/google/sheets/${sheetId}/data?range=${encodeURIComponent(range)}`, {
headers: { Authorization: `Bearer ${refreshToken}` }
});
if (!response.ok) throw new Error('Failed to fetch spreadsheet data');
return await response.json();
}

View File

@@ -1,77 +0,0 @@
import { google } from 'googleapis';
import { getAuthenticatedClient } from '../auth/server.js';
export interface GoogleSheet {
id: string;
name: string;
modifiedTime: string;
webViewLink: string;
}
export interface SheetData {
values: string[][];
}
/**
* Get a list of recent Google Sheets
* @param refreshToken - Google refresh token
* @param limit - Maximum number of sheets to return
* @returns List of Google Sheets
*/
export async function getRecentSpreadsheets(refreshToken: string, limit: number = 10): Promise<GoogleSheet[]> {
const oauth = getAuthenticatedClient(refreshToken);
const drive = google.drive({ version: 'v3', auth: oauth });
const response = await drive.files.list({
q: "mimeType='application/vnd.google-apps.spreadsheet'",
orderBy: 'modifiedTime desc',
pageSize: limit,
fields: 'files(id,name,modifiedTime,webViewLink)'
});
return response.data.files?.map(file => ({
id: file.id!,
name: file.name!,
modifiedTime: file.modifiedTime!,
webViewLink: file.webViewLink!
})) || [];
}
/**
* Get data from a Google Sheet
* @param refreshToken - Google refresh token
* @param spreadsheetId - ID of the spreadsheet
* @param range - Cell range to retrieve (default: A1:Z10)
* @returns Sheet data as a 2D array
*/
export async function getSpreadsheetData(refreshToken: string, spreadsheetId: string, range: string = 'A1:Z10'): Promise<SheetData> {
const oauth = getAuthenticatedClient(refreshToken);
const sheets = google.sheets({ version: 'v4', auth: oauth });
const response = await sheets.spreadsheets.values.get({
spreadsheetId,
range
});
return {
values: response.data.values || []
};
}
/**
* Get metadata about a Google Sheet
* @param refreshToken - Google refresh token
* @param spreadsheetId - ID of the spreadsheet
* @returns Spreadsheet metadata
*/
export async function getSpreadsheetInfo(refreshToken: string, spreadsheetId: string) {
const oauth = getAuthenticatedClient(refreshToken);
const sheets = google.sheets({ version: 'v4', auth: oauth });
const response = await sheets.spreadsheets.get({
spreadsheetId,
fields: 'properties.title,sheets.properties(title,sheetId)'
});
return response.data;
}

View File

@@ -0,0 +1,89 @@
import { google } from 'googleapis';
import { getAuthenticatedClient } from '../auth/server.js';
export interface GoogleSheet {
id: string;
name: string;
modifiedTime: string;
webViewLink: string;
}
export interface SheetData {
values: string[][];
}
/**
* Get a list of recent Google Sheets
* @param refreshToken - Google refresh token
* @param limit - Maximum number of sheets to return
* @returns List of Google Sheets
*/
export async function getRecentSpreadsheets(
refreshToken: string,
limit: number = 10
): Promise<GoogleSheet[]> {
const oauth = getAuthenticatedClient(refreshToken);
const drive = google.drive({ version: 'v3', auth: oauth });
const response = await drive.files.list({
q: "mimeType='application/vnd.google-apps.spreadsheet'",
orderBy: 'modifiedTime desc',
pageSize: limit,
fields: 'files(id,name,modifiedTime,webViewLink)'
});
return (
response.data.files?.map(file => ({
id: file.id!, // eslint-disable-line @typescript-eslint/no-non-null-assertion
name: file.name!,
modifiedTime: file.modifiedTime!,
webViewLink: file.webViewLink!
})) || []
);
}
/**
* Get data from a Google Sheet
* @param refreshToken - Google refresh token
* @param spreadsheetId - ID of the spreadsheet
* @param range - Cell range to retrieve (default: A1:Z10)
* @returns Sheet data as a 2D array
*/
export async function getSpreadsheetData(
refreshToken: string,
spreadsheetId: string,
range: string = 'A1:Z10'
): Promise<SheetData> {
const oauth = getAuthenticatedClient(refreshToken);
const sheets = google.sheets({ version: 'v4', auth: oauth });
const response = await sheets.spreadsheets.values.get({
spreadsheetId,
range
});
return {
values: response.data.values || []
};
}
/**
* Get metadata about a Google Sheet
* @param refreshToken - Google refresh token
* @param spreadsheetId - ID of the spreadsheet
* @returns Spreadsheet metadata
*/
export async function getSpreadsheetInfo(
refreshToken: string,
spreadsheetId: string
) {
const oauth = getAuthenticatedClient(refreshToken);
const sheets = google.sheets({ version: 'v4', auth: oauth });
const response = await sheets.spreadsheets.get({
spreadsheetId,
fields: 'properties.title,sheets.properties(title,sheetId)'
});
return response.data;
}

View File

@@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@@ -1,58 +0,0 @@
import { google } from 'googleapis';
import { getAuthenticatedClient } from './google-server.js';
export interface GoogleSheet {
id: string;
name: string;
modifiedTime: string;
webViewLink: string;
}
export interface SheetData {
values: string[][];
}
export async function getRecentSpreadsheets(refreshToken: string, limit: number = 10): Promise<GoogleSheet[]> {
const oauth = getAuthenticatedClient(refreshToken);
const drive = google.drive({ version: 'v3', auth: oauth });
const response = await drive.files.list({
q: "mimeType='application/vnd.google-apps.spreadsheet'",
orderBy: 'modifiedTime desc',
pageSize: limit,
fields: 'files(id,name,modifiedTime,webViewLink)'
});
return response.data.files?.map(file => ({
id: file.id!,
name: file.name!,
modifiedTime: file.modifiedTime!,
webViewLink: file.webViewLink!
})) || [];
}
export async function getSpreadsheetData(refreshToken: string, spreadsheetId: string, range: string = 'A1:Z10'): Promise<SheetData> {
const oauth = getAuthenticatedClient(refreshToken);
const sheets = google.sheets({ version: 'v4', auth: oauth });
const response = await sheets.spreadsheets.values.get({
spreadsheetId,
range
});
return {
values: response.data.values || []
};
}
export async function getSpreadsheetInfo(refreshToken: string, spreadsheetId: string) {
const oauth = getAuthenticatedClient(refreshToken);
const sheets = google.sheets({ version: 'v4', auth: oauth });
const response = await sheets.spreadsheets.get({
spreadsheetId,
fields: 'properties.title,sheets.properties(title,sheetId)'
});
return response.data;
}

View File

@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { authServer } from '$lib/google/index.js';
import { getOAuthClient } from '$lib/google/auth/server.js';
export const POST: RequestHandler = async ({ request }) => {
try {
@@ -10,7 +10,7 @@ export const POST: RequestHandler = async ({ request }) => {
return json({ error: 'Refresh token is required' }, { status: 400 });
}
const oauth = authServer.getOAuthClient();
const oauth = getOAuthClient();
oauth.setCredentials({ refresh_token: refreshToken });
const { credentials } = await oauth.refreshAccessToken();

View File

@@ -1,21 +0,0 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ locals }) => {
try {
const { data: events, error } = await locals.supabase
.from('events')
.select('*')
.order('date', { ascending: false });
if (error) {
console.error('Error fetching events:', error);
return json({ error: error.message }, { status: 500 });
}
return json({ events });
} catch (err) {
console.error('Error in events API:', err);
return json({ error: 'Internal server error' }, { status: 500 });
}
};

View File

@@ -1,8 +1,8 @@
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { authServer } from '$lib/google/index.js';
import { createAuthUrl } from '$lib/google/auth/server.js';
export const GET: RequestHandler = () => {
const authUrl = authServer.createAuthUrl();
const authUrl = createAuthUrl();
throw redirect(302, authUrl);
};

View File

@@ -1,6 +1,6 @@
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { authServer } from '$lib/google/index.js';
import { googleAuthServer } from '$lib/google/server.ts';
export const GET: RequestHandler = async ({ url }) => {
try {
@@ -17,7 +17,7 @@ export const GET: RequestHandler = async ({ url }) => {
}
// Exchange code for tokens
const oauth = authServer.getOAuthClient();
const oauth = googleAuthServer.getOAuthClient();
const { tokens } = await oauth.getToken(code);
if (!tokens.refresh_token || !tokens.access_token) {

View File

@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getOAuthClient } from '$lib/google/server.js';
import { googleAuthServer } from '$lib/google/server.ts';
export const POST: RequestHandler = async ({ request }) => {
try {
@@ -10,7 +10,7 @@ export const POST: RequestHandler = async ({ request }) => {
return json({ error: 'Refresh token is required' }, { status: 400 });
}
const oauth = getOAuthClient();
const oauth = googleAuthServer.getOAuthClient();
oauth.setCredentials({ refresh_token: refreshToken });
const { credentials } = await oauth.refreshAccessToken();

View File

@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getOAuthClient } from '$lib/google/server.js';
import { googleAuthServer } from '$lib/google/server.ts';
import { google } from 'googleapis';
export const GET: RequestHandler = async ({ request }) => {
@@ -14,7 +14,7 @@ export const GET: RequestHandler = async ({ request }) => {
const accessToken = authHeader.slice(7);
// Create OAuth client with the token
const oauth = getOAuthClient();
const oauth = googleAuthServer.getOAuthClient();
oauth.setCredentials({ access_token: accessToken });
// Call the userinfo endpoint to get user details

View File

@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getSpreadsheetData } from '$lib/google/server.js';
import { sheets } from '$lib/google/index.js';
export const GET: RequestHandler = async ({ params, request }) => {
try {
@@ -12,7 +12,7 @@ export const GET: RequestHandler = async ({ params, request }) => {
}
const refreshToken = authHeader.slice(7);
const sheetData = await getSpreadsheetData(refreshToken, sheetId, 'A1:Z10');
const sheetData = await sheets.getSpreadsheetData(refreshToken, sheetId, 'A1:Z10');
return json(sheetData);
} catch (error) {

View File

@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getRecentSpreadsheets } from '$lib/google/server.js';
import { sheets } from '$lib/google/index.js';
export const GET: RequestHandler = async ({ request }) => {
try {
@@ -10,7 +10,7 @@ export const GET: RequestHandler = async ({ request }) => {
}
const refreshToken = authHeader.slice(7);
const spreadsheets = await getRecentSpreadsheets(refreshToken, 20);
const spreadsheets = await sheets.getRecentSpreadsheets(refreshToken, 20);
return json(spreadsheets);
} catch (error) {

View File

@@ -1,85 +1,24 @@
<script lang="ts">
// Get the supabase client
let { data } = $props();
// Create reactive states for events and loading
let events = $state([]);
let loading = $state(true);
let error = $state(null);
// Load events when component mounts
$effect(() => {
loadEvents();
});
async function loadEvents() {
loading = true;
error = null;
try {
const { data: eventsData, error: supabaseError } = await data.supabase
.from('events')
.select('*')
.order('date', { ascending: false });
if (supabaseError) throw supabaseError;
events = eventsData || [];
} catch (err) {
console.error('Error fetching events:', err);
error = 'Failed to load events';
} finally {
loading = false;
}
}
export let data;
</script>
<h1 class="text-2xl font-bold mb-4 mt-2 text-center">All Events</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-2xl mx-auto">
{#if loading}
<!-- Loading placeholders -->
{#each Array(4) as _}
<div class="border border-gray-300 rounded bg-white p-4 animate-pulse">
<div class="flex flex-col gap-1">
<div class="h-5 bg-gray-200 rounded w-3/4 mb-2"></div>
<div class="h-4 bg-gray-100 rounded w-1/2"></div>
</div>
{#each data.events as event}
<a
href={`/private/events/event?id=${event.id}`}
class="block border border-gray-300 rounded bg-white p-4 shadow-none transition cursor-pointer hover:border-blue-500 group"
>
<div class="flex flex-col gap-1">
<span class="font-semibold text-lg text-black-700 group-hover:underline">{event.name}</span>
<span class="text-gray-500 text-sm">{event.date}</span>
</div>
{/each}
{:else if error}
<!-- Error state -->
<div class="col-span-full text-center p-4 text-red-600">
{error}
<button
onclick={loadEvents}
class="ml-2 text-blue-600 underline"
>
Try again
</button>
</div>
{:else if events.length === 0}
<!-- Empty state -->
<div class="col-span-full text-center p-4 text-gray-500">
No events found
</div>
{:else}
<!-- Events list -->
{#each events as event}
<a
href={`/private/events/event?id=${event.id}`}
class="block border border-gray-300 rounded bg-white p-4 shadow-none transition cursor-pointer hover:border-blue-500 group"
>
<div class="flex flex-col gap-1">
<span class="font-semibold text-lg text-black-700 group-hover:underline">{event.name}</span>
<span class="text-gray-500 text-sm">{event.date}</span>
</div>
</a>
{/each}
{/if}
</a>
{/each}
</div>
<a
href="/private/events/event/new"
href="/private/creator"
class="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-full shadow-none border border-gray-300"
>
New Event

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { isTokenValid, refreshAccessToken, getUserInfo, revokeToken } from '$lib/google/client.js';
import type { GoogleSheet } from '$lib/google/client/types.js';
import { isTokenValid, getUserInfo, revokeToken } from '$lib/google/auth/client.js';
import type { GoogleSheet } from '$lib/google/sheets/client.js';
import { goto } from '$app/navigation';
// Import Components

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { GoogleSheet } from '$lib/google/client/types.js';
import type { GoogleSheet } from '$lib/google/sheets';
// Props
let { sheetsData, errors, loadRecentSheets, selectSheet, toggleSheetList } = $props<{

View File

@@ -2,8 +2,8 @@
import QRScanner from './QRScanner.svelte';
import TicketDisplay from './TicketDisplay.svelte';
import type { TicketData } from '$lib/types';
import { ScanState, defaultTicketData } from '$lib/types';
import type { TicketData } from '$lib/types/types';
import { ScanState, defaultTicketData } from '$lib/types/types';
let { data } = $props();
let scanned_id = $state<string>("");

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { TicketData } from '$lib/types';
import { ScanState } from '$lib/types';
import type { TicketData } from '$lib/types/types';
import { ScanState } from '$lib/types/types';
let { ticket_data, scan_state }: { ticket_data: TicketData; scan_state: ScanState } = $props();