@accounter/server
Version:
Accounter GraphQL server
150 lines • 6.49 kB
JavaScript
var GoogleDriveProvider_1;
import { __decorate, __metadata, __param } from "tslib";
import { Inject, Injectable, Scope } from 'graphql-modules';
import { ENVIRONMENT } from '../../../shared/tokens.js';
import { fileSchema, folderContentSchema, } from './types/folder-content.js';
let GoogleDriveProvider = GoogleDriveProvider_1 = class GoogleDriveProvider {
env;
apiKey;
constructor(env) {
this.env = env;
this.apiKey = this.env.googleDrive?.driveApiKey ?? '';
}
async fetchFolderContent(folderUrl) {
const mainUrl = folderUrl.split('?')[0];
const folderId = mainUrl.split('folders/')[1];
if (!folderId) {
throw new Error('Invalid Google Drive URL');
}
const url = new URL(`https://www.googleapis.com/drive/v3/files?q='${folderId}'+in+parents&key=${this.apiKey}`);
const res = await fetch(url).catch(err => {
const message = `Failed fetching data from Google Drive for URL="${folderUrl}"`;
console.error(`${message}: ${err}`);
throw new Error(message);
});
const jsonResponse = await res.json().catch(err => {
const message = `Failed parsing response from Google Drive`;
console.error(`${message}: ${err}`);
throw new Error(message);
});
const parsedResponse = folderContentSchema.safeParse(jsonResponse);
if (!parsedResponse.success) {
const message = `Failed to parse response from Google Drive`;
console.error(message, parsedResponse.error);
throw new Error(message);
}
return parsedResponse.data;
}
async fetchFile(fileInfo) {
const fileId = fileInfo.id;
const url = new URL(`https://drive.google.com/uc?export=download&id=${fileId}`);
// fetch file
const response = await fetch(url).catch(err => {
const message = `Failed fetching file from Google Drive for file="${fileInfo.name}"`;
console.error(`${message}: ${err}`);
throw new Error(message);
});
const buffer = await response.arrayBuffer().catch(err => {
const message = `Failed parsing file from Google Drive for file="${fileInfo.name}"`;
console.error(`${message}: ${err}`);
throw new Error(message);
});
const file = new File([buffer], fileInfo.name, { type: fileInfo.mimeType });
return file;
}
isRelevantFileType(originalMimeType) {
const mimeType = originalMimeType.toLowerCase();
return mimeType === 'application/pdf' || mimeType.startsWith('image/');
}
/**
* Whether a URL points at a single Google Drive *file* (as opposed to a
* folder, which {@link fetchFilesFromSharedFolder} handles).
*/
static isFileUrl(rawUrl) {
return GoogleDriveProvider_1.extractFileId(rawUrl) !== null;
}
/**
* Pull the file id out of the several share-link shapes Drive hands out:
* `/file/d/<id>/view`, `/open?id=<id>`, `/uc?id=<id>`.
*/
static extractFileId(rawUrl) {
let url;
try {
url = new URL(rawUrl);
}
catch {
return null;
}
const host = url.hostname.toLowerCase();
if (host !== 'drive.google.com' && host !== 'docs.google.com') {
return null;
}
const pathMatch = /\/(?:file|document|spreadsheets|presentation)\/d\/([^/?#]+)/.exec(url.pathname);
if (pathMatch) {
return pathMatch[1];
}
const idParam = url.searchParams.get('id');
return idParam && idParam.length > 0 ? idParam : null;
}
async fetchFileMetadata(fileId) {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?fields=id,name,mimeType,kind&key=${this.apiKey}`);
const res = await fetch(url).catch(err => {
const message = `Failed fetching file metadata from Google Drive for id="${fileId}"`;
console.error(`${message}: ${err}`);
throw new Error(message);
});
if (!res.ok) {
// Almost always "not shared with the API key" rather than "missing".
throw new Error(`Google Drive returned HTTP ${res.status} for file id="${fileId}". Make sure the file is shared with anyone holding the link.`);
}
const parsed = fileSchema.safeParse(await res.json().catch(() => null));
if (!parsed.success) {
throw new Error(`Failed to parse Google Drive metadata for file id="${fileId}"`);
}
return parsed.data;
}
/**
* Fetch a single file from a Drive share link.
*
* A share link is not a download link — `/file/d/<id>/view` answers with an
* HTML page — so the id is resolved through the Drive API for its real name
* and MIME type before the bytes are pulled.
*/
async fetchFileFromUrl(fileUrl) {
const fileId = GoogleDriveProvider_1.extractFileId(fileUrl);
if (!fileId) {
throw new Error(`Not a Google Drive file URL: "${fileUrl}"`);
}
const metadata = await this.fetchFileMetadata(fileId);
if (!this.isRelevantFileType(metadata.mimeType)) {
throw new Error(`Unsupported Google Drive file type "${metadata.mimeType}" for file="${metadata.name}" — expected a PDF or an image`);
}
return this.fetchFile(metadata);
}
async fetchFilesFromSharedFolder(folderUrl) {
try {
const folderData = await this.fetchFolderContent(folderUrl);
const relevantFiles = folderData.files.filter(file => this.isRelevantFileType(file.mimeType));
if (!relevantFiles.length) {
return [];
}
const files = await Promise.all(relevantFiles.map(fileInfo => this.fetchFile(fileInfo)));
return files;
}
catch (e) {
const message = `Failed fetching files from Google Drive`;
console.error(`${message}: ${e}`);
throw new Error(message, { cause: e });
}
}
};
GoogleDriveProvider = GoogleDriveProvider_1 = __decorate([
Injectable({
scope: Scope.Singleton,
global: true,
}),
__param(0, Inject(ENVIRONMENT)),
__metadata("design:paramtypes", [Object])
], GoogleDriveProvider);
export { GoogleDriveProvider };
//# sourceMappingURL=google-drive.provider.js.map