add postgres integration to save progress across devices

This commit is contained in:
2025-08-28 01:21:28 -07:00
parent fc88ee9a97
commit b8cef4d7b3
13 changed files with 1138 additions and 307 deletions

View File

@@ -23,12 +23,15 @@
let tokenClient: any;
let accessToken = '';
let gapiLoaded = false;
let googleLoaded = false;
let readerFolderId = '';
let volumeDataId = '';
let profilesId = '';
let loadingMessage = '';
let errorMessage = '';
let completed = 0;
let totalSize = 0;
@@ -36,6 +39,11 @@
function xhrDownloadFileId(fileId: string) {
return new Promise<Blob>((resolve, reject) => {
if (!gapiLoaded || !gapi.auth.getToken()) {
reject(new Error('Not authenticated'));
return;
}
const { access_token } = gapi.auth.getToken();
const xhr = new XMLHttpRequest();
@@ -88,50 +96,61 @@
accessToken = resp?.access_token;
loadingMessage = 'Connecting to drive';
const { result: readerFolderRes } = await gapi.client.drive.files.list({
q: `mimeType='application/vnd.google-apps.folder' and name='${READER_FOLDER}'`,
fields: 'files(id)'
});
if (readerFolderRes.files?.length === 0) {
const { result: createReaderFolderRes } = await gapi.client.drive.files.create({
resource: { mimeType: FOLDER_MIME_TYPE, name: READER_FOLDER },
fields: 'id'
try {
const { result: readerFolderRes } = await gapi.client.drive.files.list({
q: `mimeType='application/vnd.google-apps.folder' and name='${READER_FOLDER}'`,
fields: 'files(id)'
});
readerFolderId = createReaderFolderRes.id || '';
} else {
const id = readerFolderRes.files?.[0]?.id || '';
if (readerFolderRes.files?.length === 0) {
const { result: createReaderFolderRes } = await gapi.client.drive.files.create({
resource: { mimeType: FOLDER_MIME_TYPE, name: READER_FOLDER },
fields: 'id'
});
readerFolderId = id || '';
}
readerFolderId = createReaderFolderRes.id || '';
} else {
const id = readerFolderRes.files?.[0]?.id || '';
readerFolderId = id || '';
}
const { result: volumeDataRes } = await gapi.client.drive.files.list({
q: `'${readerFolderId}' in parents and name='${VOLUME_DATA_FILE}'`,
fields: 'files(id, name)'
});
const { result: volumeDataRes } = await gapi.client.drive.files.list({
q: `'${readerFolderId}' in parents and name='${VOLUME_DATA_FILE}'`,
fields: 'files(id, name)'
});
if (volumeDataRes.files?.length !== 0) {
volumeDataId = volumeDataRes.files?.[0].id || '';
}
if (volumeDataRes.files?.length !== 0) {
volumeDataId = volumeDataRes.files?.[0].id || '';
}
const { result: profilesRes } = await gapi.client.drive.files.list({
q: `'${readerFolderId}' in parents and name='${PROFILES_FILE}'`,
fields: 'files(id, name)'
});
const { result: profilesRes } = await gapi.client.drive.files.list({
q: `'${readerFolderId}' in parents and name='${PROFILES_FILE}'`,
fields: 'files(id, name)'
});
if (profilesRes.files?.length !== 0) {
profilesId = profilesRes.files?.[0].id || '';
}
if (profilesRes.files?.length !== 0) {
profilesId = profilesRes.files?.[0].id || '';
}
loadingMessage = '';
loadingMessage = '';
errorMessage = '';
if (accessToken) {
showSnackbar('Connected to Google Drive');
if (accessToken) {
showSnackbar('Connected to Google Drive');
}
} catch (error) {
console.error('Error connecting to Drive:', error);
errorMessage = 'Failed to connect to Google Drive. Please check your credentials.';
loadingMessage = '';
}
}
function signIn() {
if (!gapiLoaded || !googleLoaded) {
errorMessage = 'Google APIs not loaded yet. Please wait and try again.';
return;
}
if (gapi.client.getToken() === null) {
tokenClient.requestAccessToken({ prompt: 'consent' });
} else {
@@ -140,23 +159,50 @@
}
onMount(() => {
gapi.load('client', async () => {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC]
// Check if environment variables are set
if (!CLIENT_ID || !API_KEY) {
errorMessage = 'Google Drive integration not configured. Please set VITE_GDRIVE_CLIENT_ID and VITE_GDRIVE_API_KEY environment variables.';
return;
}
// Load Google APIs
if (typeof gapi !== 'undefined') {
gapiLoaded = true;
gapi.load('client', async () => {
try {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC]
});
} catch (error) {
console.error('Error initializing gapi client:', error);
errorMessage = 'Failed to initialize Google Drive client.';
}
});
});
gapi.load('picker', () => {});
gapi.load('picker', () => {});
} else {
errorMessage = 'Google APIs not loaded. Please check your internet connection.';
}
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: connectDrive
});
if (typeof google !== 'undefined') {
googleLoaded = true;
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: connectDrive
});
} else {
errorMessage = 'Google OAuth not loaded. Please check your internet connection.';
}
});
function createPicker() {
if (!googleLoaded || !accessToken) {
showSnackbar('Not connected to Google Drive');
return;
}
const docsView = new google.picker.DocsView(google.picker.ViewId.DOCS)
.setMimeTypes('application/zip,application/x-zip-compressed')
.setMode(google.picker.DocsViewMode.LIST)
@@ -293,6 +339,12 @@
</svelte:head>
<div class="p-2 h-[90svh]">
{#if errorMessage}
<div class="bg-red-900/20 border border-red-700 rounded-lg p-4 mb-4">
<p class="text-red-400">{errorMessage}</p>
</div>
{/if}
{#if loadingMessage || completed > 0}
<Loader>
{#if completed > 0}
@@ -352,12 +404,16 @@
<button
class="w-full border rounded-lg border-slate-600 p-10 border-opacity-50 hover:bg-slate-800 max-w-3xl"
on:click={signIn}
disabled={!gapiLoaded || !googleLoaded}
>
<div class="flex sm:flex-row flex-col gap-2 items-center justify-center">
<GoogleSolid size="lg" />
<h2 class="text-lg">Connect to Google Drive</h2>
</div>
</button>
{#if !gapiLoaded || !googleLoaded}
<p class="text-sm text-gray-400 mt-2">Loading Google APIs...</p>
{/if}
</div>
{/if}
</div>