import { HttpRequester } from '@portal-hq/utils' import { type GDriveDeleteResponse, type GDriveFile, type GDriveFilesListResponse, type GDriveOptions, type GDriveStorage, } from '../../types' class GDrive implements GDriveStorage { public accessToken?: string public api: HttpRequester public boundary: string constructor({ accessToken = undefined, baseUrl = 'https://www.googleapis.com', }: GDriveOptions) { if (accessToken) { this.accessToken = accessToken } this.api = new HttpRequester({ baseUrl, }) this.boundary = `portal-backup-share` } public async delete(id: string): Promise { if (!this.accessToken) { throw new Error( 'Unable to access Google Drive. User is not authenticated.', ) } await this.api.delete(`/drive/v3/files/${id}`, { headers: { Authorization: `Bearer ${this.accessToken}`, }, }) return true } public async getIdForFilename(filename: string): Promise { if (!this.accessToken) { throw new Error( 'Unable to access Google Drive. User is not authenticated.', ) } const query = encodeURIComponent(`name='${filename}'`) const metadataAndSortingQueryParams = 'orderBy=modifiedTime%20desc&pageSize=1' const result = await this.api.get( `/drive/v3/files?corpora=user&q=${query}&${metadataAndSortingQueryParams}`, { headers: { Accept: 'application/json', Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json', }, }, ) if (result?.files?.[0]) { return result.files[0].id } return null } public async getOrCreateFolder(folder: string): Promise { if (!this.accessToken) { throw new Error( 'Unable to access Google Drive. User is not authenticated.', ) } try { const query = encodeURIComponent(`name='${folder}'`) const existingFolder = await this.api.get( `/drive/v3/files?q=${query}`, { headers: { Accept: 'application/json', Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json', }, }, ) if (existingFolder?.files?.[0]) { return existingFolder.files[0] } else { const createdFolder = await this.createFolder(folder) return createdFolder } } catch (err) { const createdFolder = await this.createFolder(folder) return createdFolder } } public async read(id: string): Promise { if (!this.accessToken) { throw new Error( 'Unable to access Google Drive. User is not authenticated.', ) } const result = await this.api.get( `/drive/v3/files/${id}?alt=media`, { headers: { Accept: 'application/json', Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json', }, }, ) if (!result) { return null } return result || null } public async write( folderName: string, filename: string, content: string, ): Promise { if (!this.accessToken) { throw new Error( 'Unable to access Google Drive. User is not authenticated.', ) } // Delete existing file const existingFileId = await this.getIdForFilename(filename) if (existingFileId) { await this.delete(existingFileId) } const folder = await this.getOrCreateFolder(folderName) if (!folder) { return null } const metadata = { name: filename, parents: [folder.id], } const body = this.buildMultipartFormData({ content, metadata, }) as any const result = await this.api.post( `/upload/drive/v3/files?ignoreDefaultVisibility=true&uploadType=multipart`, { body, headers: { Authorization: `Bearer ${this.accessToken}`, 'Content-Type': `multipart/related; boundary=${this.boundary}`, }, }, ) if (result && result.id) { return result.id } return null } public async validateOperations(testFolder: string): Promise { const testKey = 'portal_test.txt' const testValue = 'test_value' // Write. const fileId = await this.write(testFolder, testKey, testValue) if (!fileId) { throw new Error('Failed to write test data') } // Read. const readValue = await this.read(fileId) if (readValue !== testValue) { throw new Error('Failed to read/write test data') } // Delete. await this.delete(fileId) // Validate delete. try { await this.read(fileId) throw new Error('Failed to delete test data') } catch (error) { // File not found is expected. if ((error as { message: string })?.message?.includes('File not found')) { return true } throw error } } private buildMultipartFormData(data: Record): string { const body = [ `--${this.boundary}\n`, `Content-Type: application/json; charset=UTF-8\n\n`, `${JSON.stringify(data.metadata)}\n`, `--${this.boundary}\n`, `Content-Type: text/plain\n\n`, `${data.content}\n`, `--${this.boundary}--`, ] return body.join('') } private async createFolder(folder: string): Promise { const body = { name: folder, mimeType: 'application/vnd.google-apps.folder', parents: ['root'], } const createdFolder = await this.api.post( `/drive/v3/files?ignoreDefaultVisibility=true`, { body, headers: { Accept: 'application/json', Authorization: `Bearer ${this.accessToken}`, 'Content-Type': 'application/json', 'Content-Length': JSON.stringify(body).length, }, }, ) if (!createdFolder) { throw new Error(`Unable to create folder in Google Drive`) } return createdFolder } } export default GDrive