diff --git a/package.json b/package.json index 8b5c263..4c27c9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nest-google-sheet-connector", - "version": "1.1.3", + "version": "2.0.0", "description": "A Google Sheet integration for NestJs", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/google-sheet-connector.service.ts b/src/google-sheet-connector.service.ts index bef2c95..839029b 100644 --- a/src/google-sheet-connector.service.ts +++ b/src/google-sheet-connector.service.ts @@ -8,11 +8,99 @@ import {GaxiosPromise} from "googleapis-common"; export class GoogleSheetConnectorService{ private readonly _jwtClient: JWT; + private _loadedSpreadsheet: sheets_v4.Schema$Spreadsheet; + private loadedSheet: sheets_v4.Schema$Sheet; constructor(private _authService: GoogleAuthService) { this._jwtClient = this._authService.getClient(); } + getLoadedSheet(): sheets_v4.Schema$Sheet { + return this.loadedSheet; + } + + async loadSheet(spreadsheetId: string, index: number): Promise { + if (!this._loadedSpreadsheet || this._loadedSpreadsheet.spreadsheetId !== spreadsheetId) { + await this.loadSpreadSheet(spreadsheetId); + } + + this.loadedSheet = await this.getSheetByLoadedSpreadSheetIndex(index); + return this.loadedSheet; + } + + getLoadedSpreadSheet(): sheets_v4.Schema$Spreadsheet { + return this._loadedSpreadsheet; + } + + async loadSpreadSheet(spreadsheetId: string): Promise { + const sheets = this.getGoogleSheetConnect() + const res = await sheets.spreadsheets.get({ + spreadsheetId: spreadsheetId, + includeGridData: true, + }); + + this._loadedSpreadsheet = res.data; + + return res.data; + } + + /** + * Read a range of cells from a spreadsheet + * + * @param range + * + * @returns {Promise} + */ + async readRangeFromLoadedSheet(range: string): Promise { + return this.readRangeFromLoadedSpreadSheet(this.getLoadedSheet().properties.title, range); + } + + /** + * Read a range of cells from a spreadsheet + * + * @param sheet + * @param range + * + * @returns {Promise} + */ + async readRangeFromLoadedSpreadSheet(sheet, range: string): Promise { + + const spreadsheet = this.getLoadedSpreadSheet(); + const sheetIndex = spreadsheet.sheets.findIndex((s) => s.properties.title === sheet); + const sheetData = spreadsheet.sheets[sheetIndex].data[0].rowData; + const rangeData = range.split('!')[1]; + return this.getRangeData(sheetData, rangeData); + } + + private getCharNumber(char: string): number { + let result = 0; + for (let i = 0; i < char.length; i++) { + result = result * 26 + char.charCodeAt(i) - 64; + } + return result; + } + + private getRangeCell(range: string): { row: number, column: number } { + const row = parseInt(range.match(/\d+/)[0]) - 1; + const column = this.getCharNumber(range.match(/[A-Z]+/)[0]) - 1; + return {row, column}; + } + private getRangeData(sheetData: sheets_v4.Schema$RowData[], range: string): any[][] { + const rangeData = range.split(':'); + const start = this.getRangeCell(rangeData[0]); + const end = this.getRangeCell(rangeData[1]); + const data = []; + for (let i = start.row; i <= end.row; i++) { + const row = []; + for (let j = start.column; j <= end.column; j++) { + const value = sheetData[i].values[j].formattedValue; + row.push(value); + } + data.push(row); + } + return data; + } + /** * Read a range of cells from a spreadsheet * @@ -131,6 +219,13 @@ export class GoogleSheetConnectorService{ }); } + private async getSheetByLoadedSpreadSheetIndex(index: number): Promise { + return this._loadedSpreadsheet.sheets[index]; + } + + private async getSheetBySpreadSheetIndex(spreedsheatId: string, index: number): Promise { + return this.readAllSheet(spreedsheatId).then((sheets) => sheets[index]); + } /** * Create a new spreadsheet * @@ -152,6 +247,145 @@ export class GoogleSheetConnectorService{ return res.data.spreadsheetId; } + private async getSheetMergeMetadata( + sheet: sheets_v4.Schema$Sheet, + ) { + // Find all rows groups locations from merged cells + return sheet.merges.map((merge) => ({ + sheetId: merge.sheetId, + start: merge.startRowIndex, + end: merge.endRowIndex, + })); + } + + /** + * Insert empty dimension + * + * @param spreadsheetId + * @param sheetId + * @param dimension + * @param startIndex + * @param endIndex + */ + private async insertDimension( + spreadsheetId: string, + sheetId: number, + dimension: string, + startIndex: number, + endIndex: number, + ) { + // Initialize request parameters. + const request: sheets_v4.Schema$Request = { + insertDimension: { + range: { + sheetId, + dimension, + startIndex, + endIndex, + }, + inheritFromBefore: false, + }, + }; + + // Update spreadsheet. + return this.getGoogleSheetConnect() + .spreadsheets.batchUpdate({ + spreadsheetId: spreadsheetId, + requestBody: { + requests: [request], + }, + }); + } + + /** + * Insert empty row + * + * @param spreadsheetId, + * @param sheetId + * @param startIndex + * @param endIndex + */ + private async insertRow( + spreadsheetId: string, + sheetId: number, + startIndex: number, + endIndex: number, + ) { + return this.insertDimension(spreadsheetId, sheetId, 'ROWS', startIndex, endIndex); + } + + /** + * Merge cells in sheet + * + * @param spreadsheetId + * @param sheetId + * @param startRowIndex + * @param endRowIndex + * @param startColumnIndex + * @param endColumnIndex + */ + private async mergeCells( + spreadsheetId: string, + sheetId: number, + startRowIndex: number, + endRowIndex: number, + startColumnIndex: number, + endColumnIndex: number, + ) { + // Initialize request parameters. + const request: sheets_v4.Schema$Request = { + mergeCells: { + range: { + sheetId: sheetId, + startRowIndex: startRowIndex, + endRowIndex: endRowIndex, + startColumnIndex: startColumnIndex, + endColumnIndex: endColumnIndex, + }, + }, + }; + + return await this.getGoogleSheetConnect() + .spreadsheets.batchUpdate({ + spreadsheetId: spreadsheetId, + requestBody: { + requests: [request], + }, + }); + } + + async appendRow(spreedsheatId, sheet, rows) { + // Initialize request parameters + const request = { + spreadsheetId: spreedsheatId, + range: `${sheet.properties.title}`, + resource: { + values: rows, + }, + valueInputOption: 'USER_ENTERED', + insertDataOption: 'INSERT_ROWS', + }; + + return await this.getGoogleSheetConnect().spreadsheets.values.append(request); + } + + /** + * Get column value from a spreadsheet + * @param spreedsheatId + * @param sheet + * @param columnKey + * @private + */ + private async getColumnValues(spreedsheatId, sheet, columnKey: string): Promise { + const values = await this.getGoogleSheetConnect() + .spreadsheets.values.batchGet({ + spreadsheetId: spreedsheatId, + ranges: [`${sheet.properties.title}!${columnKey}`], + }); + + return values.data.valueRanges[0].values.map((value: string[]) => value[0]); + } + /** * Get Google Sheet Connection *