2021-11-25 18:12:12 +01:00
|
|
|
import {
|
2023-05-16 11:44:58 +02:00
|
|
|
ConnectionInfo,
|
2023-05-31 14:29:45 +02:00
|
|
|
Datasource,
|
2023-05-16 13:37:30 +02:00
|
|
|
DatasourceFeature,
|
2022-08-11 14:50:05 +02:00
|
|
|
DatasourceFieldType,
|
2022-09-07 13:40:45 +02:00
|
|
|
DatasourcePlus,
|
2023-03-13 19:04:29 +01:00
|
|
|
FieldType,
|
2021-11-25 18:12:12 +01:00
|
|
|
Integration,
|
2023-03-13 18:21:04 +01:00
|
|
|
Operation,
|
2023-03-09 09:50:26 +01:00
|
|
|
PaginationJson,
|
2022-09-07 13:40:45 +02:00
|
|
|
QueryJson,
|
2022-08-11 14:50:05 +02:00
|
|
|
QueryType,
|
2023-03-13 19:04:29 +01:00
|
|
|
Row,
|
2023-03-09 09:50:26 +01:00
|
|
|
SearchFilters,
|
|
|
|
SortJson,
|
2023-06-20 18:07:12 +02:00
|
|
|
ExternalTable,
|
2023-03-13 19:04:29 +01:00
|
|
|
TableRequest,
|
2022-08-11 12:48:58 +02:00
|
|
|
} from "@budibase/types"
|
2022-01-06 09:08:54 +01:00
|
|
|
import { OAuth2Client } from "google-auth-library"
|
2023-03-13 19:04:29 +01:00
|
|
|
import { buildExternalTableId, finaliseExternalTables } from "./utils"
|
2023-05-09 13:05:12 +02:00
|
|
|
import { GoogleSpreadsheet, GoogleSpreadsheetRow } from "google-spreadsheet"
|
2023-02-27 17:25:26 +01:00
|
|
|
import fetch from "node-fetch"
|
2023-05-31 14:29:45 +02:00
|
|
|
import { cache, configs, context, HTTPError } from "@budibase/backend-core"
|
2023-06-07 13:29:36 +02:00
|
|
|
import { dataFilters, utils } from "@budibase/shared-core"
|
2023-03-14 13:11:01 +01:00
|
|
|
import { GOOGLE_SHEETS_PRIMARY_KEY } from "../constants"
|
2023-05-31 14:29:45 +02:00
|
|
|
import sdk from "../sdk"
|
2022-08-12 18:03:06 +02:00
|
|
|
|
|
|
|
interface GoogleSheetsConfig {
|
|
|
|
spreadsheetId: string
|
|
|
|
auth: OAuthClientConfig
|
2023-05-31 14:29:45 +02:00
|
|
|
continueSetupId?: string
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2022-01-06 09:08:54 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
interface OAuthClientConfig {
|
|
|
|
appId: string
|
|
|
|
accessToken: string
|
|
|
|
refreshToken: string
|
|
|
|
}
|
2021-11-25 18:12:12 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
interface AuthTokenRequest {
|
|
|
|
client_id: string
|
|
|
|
client_secret: string
|
|
|
|
refresh_token: string
|
|
|
|
}
|
2022-04-25 00:32:47 +02:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
interface AuthTokenResponse {
|
|
|
|
access_token: string
|
|
|
|
}
|
2022-04-25 00:32:47 +02:00
|
|
|
|
2023-03-13 19:04:29 +01:00
|
|
|
const ALLOWED_TYPES = [
|
|
|
|
FieldType.STRING,
|
|
|
|
FieldType.FORMULA,
|
|
|
|
FieldType.NUMBER,
|
|
|
|
FieldType.LONGFORM,
|
|
|
|
FieldType.DATETIME,
|
|
|
|
FieldType.OPTIONS,
|
|
|
|
FieldType.BOOLEAN,
|
|
|
|
FieldType.BARCODEQR,
|
|
|
|
]
|
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
const SCHEMA: Integration = {
|
|
|
|
plus: true,
|
|
|
|
auth: {
|
|
|
|
type: "google",
|
|
|
|
},
|
|
|
|
relationships: false,
|
|
|
|
docs: "https://developers.google.com/sheets/api/quickstart/nodejs",
|
|
|
|
description:
|
2023-05-22 15:28:18 +02:00
|
|
|
"Create and collaborate on online spreadsheets in real-time and from any device.",
|
2022-08-12 18:03:06 +02:00
|
|
|
friendlyName: "Google Sheets",
|
|
|
|
type: "Spreadsheet",
|
2023-05-24 10:50:51 +02:00
|
|
|
features: {
|
|
|
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
|
|
|
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
|
|
|
|
},
|
2022-08-12 18:03:06 +02:00
|
|
|
datasource: {
|
|
|
|
spreadsheetId: {
|
2023-05-31 12:26:01 +02:00
|
|
|
display: "Spreadsheet URL",
|
2022-08-12 18:03:06 +02:00
|
|
|
type: DatasourceFieldType.STRING,
|
|
|
|
required: true,
|
2022-01-06 09:08:54 +01:00
|
|
|
},
|
2022-08-12 18:03:06 +02:00
|
|
|
},
|
|
|
|
query: {
|
|
|
|
create: {
|
|
|
|
type: QueryType.FIELDS,
|
|
|
|
fields: {
|
|
|
|
sheet: {
|
|
|
|
type: DatasourceFieldType.STRING,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
row: {
|
|
|
|
type: QueryType.JSON,
|
|
|
|
required: true,
|
|
|
|
},
|
2021-11-25 18:12:12 +01:00
|
|
|
},
|
|
|
|
},
|
2022-08-12 18:03:06 +02:00
|
|
|
read: {
|
|
|
|
type: QueryType.FIELDS,
|
|
|
|
fields: {
|
|
|
|
sheet: {
|
|
|
|
type: DatasourceFieldType.STRING,
|
|
|
|
required: true,
|
2021-11-25 18:12:12 +01:00
|
|
|
},
|
|
|
|
},
|
2022-08-12 18:03:06 +02:00
|
|
|
},
|
|
|
|
update: {
|
|
|
|
type: QueryType.FIELDS,
|
|
|
|
fields: {
|
|
|
|
sheet: {
|
|
|
|
type: DatasourceFieldType.STRING,
|
|
|
|
required: true,
|
2021-11-25 18:12:12 +01:00
|
|
|
},
|
2022-08-12 18:03:06 +02:00
|
|
|
rowIndex: {
|
|
|
|
type: DatasourceFieldType.STRING,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
row: {
|
|
|
|
type: QueryType.JSON,
|
|
|
|
required: true,
|
2021-11-25 18:12:12 +01:00
|
|
|
},
|
|
|
|
},
|
2022-08-12 18:03:06 +02:00
|
|
|
},
|
|
|
|
delete: {
|
|
|
|
type: QueryType.FIELDS,
|
|
|
|
fields: {
|
|
|
|
sheet: {
|
|
|
|
type: DatasourceFieldType.STRING,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
rowIndex: {
|
|
|
|
type: DatasourceFieldType.NUMBER,
|
|
|
|
required: true,
|
2021-11-25 18:12:12 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-08-12 18:03:06 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
class GoogleSheetsIntegration implements DatasourcePlus {
|
|
|
|
private readonly config: GoogleSheetsConfig
|
2023-02-27 17:25:26 +01:00
|
|
|
private client: GoogleSpreadsheet
|
2023-06-20 18:07:12 +02:00
|
|
|
public tables: Record<string, ExternalTable> = {}
|
2022-08-12 18:03:06 +02:00
|
|
|
public schemaErrors: Record<string, string> = {}
|
|
|
|
|
|
|
|
constructor(config: GoogleSheetsConfig) {
|
|
|
|
this.config = config
|
|
|
|
const spreadsheetId = this.cleanSpreadsheetUrl(this.config.spreadsheetId)
|
|
|
|
this.client = new GoogleSpreadsheet(spreadsheetId)
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
|
|
|
|
2023-05-16 11:44:58 +02:00
|
|
|
async testConnection(): Promise<ConnectionInfo> {
|
2023-05-16 10:02:11 +02:00
|
|
|
try {
|
|
|
|
await this.connect()
|
2023-05-16 11:44:58 +02:00
|
|
|
return { connected: true }
|
2023-05-16 10:02:11 +02:00
|
|
|
} catch (e: any) {
|
2023-05-16 11:44:58 +02:00
|
|
|
return {
|
|
|
|
connected: false,
|
|
|
|
error: e.message as string,
|
|
|
|
}
|
2023-05-16 10:02:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
getBindingIdentifier() {
|
|
|
|
return ""
|
|
|
|
}
|
2021-11-25 18:12:12 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
getStringConcat(parts: string[]) {
|
|
|
|
return ""
|
|
|
|
}
|
2022-01-15 19:28:04 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
/**
|
|
|
|
* Pull the spreadsheet ID out from a valid google sheets URL
|
|
|
|
* @param spreadsheetId - the URL or standard spreadsheetId of the google sheet
|
|
|
|
* @returns spreadsheet Id of the google sheet
|
|
|
|
*/
|
|
|
|
cleanSpreadsheetUrl(spreadsheetId: string) {
|
|
|
|
if (!spreadsheetId) {
|
|
|
|
throw new Error(
|
|
|
|
"You must set a spreadsheet ID in your configuration to fetch tables."
|
|
|
|
)
|
2022-03-02 23:45:10 +01:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
const parts = spreadsheetId.split("/")
|
|
|
|
return parts.length > 5 ? parts[5] : spreadsheetId
|
|
|
|
}
|
2022-03-02 23:45:10 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async fetchAccessToken(
|
|
|
|
payload: AuthTokenRequest
|
|
|
|
): Promise<AuthTokenResponse> {
|
|
|
|
const response = await fetch("https://www.googleapis.com/oauth2/v4/token", {
|
|
|
|
method: "POST",
|
|
|
|
body: JSON.stringify({
|
|
|
|
...payload,
|
|
|
|
grant_type: "refresh_token",
|
|
|
|
}),
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
},
|
|
|
|
})
|
2022-03-11 01:19:26 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
const json = await response.json()
|
2021-11-25 18:12:12 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
if (response.status !== 200) {
|
|
|
|
throw new Error(
|
|
|
|
`Error authenticating with google sheets. ${json.error_description}`
|
2022-04-25 00:32:47 +02:00
|
|
|
)
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2022-04-25 00:32:47 +02:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
return json
|
|
|
|
}
|
2022-06-05 17:43:04 +02:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async connect() {
|
|
|
|
try {
|
2023-06-06 13:27:49 +02:00
|
|
|
await setupCreationAuth(this.config)
|
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
// Initialise oAuth client
|
2023-02-28 11:36:00 +01:00
|
|
|
let googleConfig = await configs.getGoogleDatasourceConfig()
|
2022-08-12 18:03:06 +02:00
|
|
|
if (!googleConfig) {
|
2023-02-23 14:41:35 +01:00
|
|
|
throw new HTTPError("Google config not found", 400)
|
2022-04-25 00:32:47 +02:00
|
|
|
}
|
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
const oauthClient = new OAuth2Client({
|
|
|
|
clientId: googleConfig.clientID,
|
|
|
|
clientSecret: googleConfig.clientSecret,
|
|
|
|
})
|
|
|
|
|
|
|
|
const tokenResponse = await this.fetchAccessToken({
|
|
|
|
client_id: googleConfig.clientID,
|
|
|
|
client_secret: googleConfig.clientSecret,
|
|
|
|
refresh_token: this.config.auth.refreshToken,
|
|
|
|
})
|
|
|
|
|
|
|
|
oauthClient.setCredentials({
|
|
|
|
refresh_token: this.config.auth.refreshToken,
|
|
|
|
access_token: tokenResponse.access_token,
|
|
|
|
})
|
|
|
|
|
|
|
|
this.client.useOAuth2Client(oauthClient)
|
|
|
|
await this.client.loadInfo()
|
2023-03-10 19:46:48 +01:00
|
|
|
} catch (err: any) {
|
|
|
|
// this happens for xlsx imports
|
|
|
|
if (err.message?.includes("operation is not supported")) {
|
|
|
|
err.message =
|
|
|
|
"This operation is not supported - XLSX sheets must be converted."
|
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
console.error("Error connecting to google sheets", err)
|
|
|
|
throw err
|
2022-04-25 00:32:47 +02:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2022-04-25 00:32:47 +02:00
|
|
|
|
2023-05-23 09:55:46 +02:00
|
|
|
async getTableNames(): Promise<string[]> {
|
|
|
|
await this.connect()
|
|
|
|
const sheets = this.client.sheetsByIndex
|
|
|
|
return sheets.map(s => s.title)
|
2023-05-22 14:57:56 +02:00
|
|
|
}
|
|
|
|
|
2023-06-20 18:07:12 +02:00
|
|
|
getTableSchema(
|
|
|
|
title: string,
|
|
|
|
headerValues: string[],
|
|
|
|
datasourceId: string,
|
|
|
|
id?: string
|
|
|
|
) {
|
2023-03-13 17:21:22 +01:00
|
|
|
// base table
|
2023-06-20 18:07:12 +02:00
|
|
|
const table: ExternalTable = {
|
2023-03-13 17:21:22 +01:00
|
|
|
name: title,
|
2023-03-14 13:11:01 +01:00
|
|
|
primary: [GOOGLE_SHEETS_PRIMARY_KEY],
|
2023-03-13 17:21:22 +01:00
|
|
|
schema: {},
|
2023-06-20 18:07:12 +02:00
|
|
|
sourceId: datasourceId,
|
2023-03-13 17:21:22 +01:00
|
|
|
}
|
|
|
|
if (id) {
|
|
|
|
table._id = id
|
|
|
|
}
|
|
|
|
// build schema from headers
|
|
|
|
for (let header of headerValues) {
|
|
|
|
table.schema[header] = {
|
|
|
|
name: header,
|
2023-03-13 19:04:29 +01:00
|
|
|
type: FieldType.STRING,
|
2023-03-13 17:21:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
2023-06-20 18:07:12 +02:00
|
|
|
async buildSchema(
|
|
|
|
datasourceId: string,
|
|
|
|
entities: Record<string, ExternalTable>
|
|
|
|
) {
|
2023-06-16 11:13:43 +02:00
|
|
|
// not fully configured yet
|
|
|
|
if (!this.config.auth) {
|
|
|
|
return
|
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
await this.connect()
|
2023-02-27 17:25:26 +01:00
|
|
|
const sheets = this.client.sheetsByIndex
|
2023-06-20 18:07:12 +02:00
|
|
|
const tables: Record<string, ExternalTable> = {}
|
2023-06-07 13:29:36 +02:00
|
|
|
await utils.parallelForeach(
|
|
|
|
sheets,
|
|
|
|
async sheet => {
|
|
|
|
// must fetch rows to determine schema
|
2023-06-20 19:09:23 +02:00
|
|
|
await sheet.getRows()
|
2022-03-28 17:44:33 +02:00
|
|
|
|
2023-06-07 13:29:36 +02:00
|
|
|
const id = buildExternalTableId(datasourceId, sheet.title)
|
|
|
|
tables[sheet.title] = this.getTableSchema(
|
|
|
|
sheet.title,
|
|
|
|
sheet.headerValues,
|
2023-06-20 19:09:23 +02:00
|
|
|
datasourceId,
|
2023-06-07 13:29:36 +02:00
|
|
|
id
|
|
|
|
)
|
|
|
|
},
|
|
|
|
10
|
|
|
|
)
|
2023-03-13 19:04:29 +01:00
|
|
|
const final = finaliseExternalTables(tables, entities)
|
|
|
|
this.tables = final.tables
|
|
|
|
this.schemaErrors = final.errors
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2021-11-25 20:12:32 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async query(json: QueryJson) {
|
|
|
|
const sheet = json.endpoint.entityId
|
2023-03-13 18:21:04 +01:00
|
|
|
switch (json.endpoint.operation) {
|
|
|
|
case Operation.CREATE:
|
|
|
|
return this.create({ sheet, row: json.body as Row })
|
|
|
|
case Operation.BULK_CREATE:
|
|
|
|
return this.createBulk({ sheet, rows: json.body as Row[] })
|
|
|
|
case Operation.READ:
|
|
|
|
return this.read({ ...json, sheet })
|
|
|
|
case Operation.UPDATE:
|
|
|
|
return this.update({
|
2022-08-12 18:03:06 +02:00
|
|
|
// exclude the header row and zero index
|
|
|
|
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
|
|
sheet,
|
|
|
|
row: json.body,
|
2023-03-13 18:21:04 +01:00
|
|
|
})
|
|
|
|
case Operation.DELETE:
|
|
|
|
return this.delete({
|
2022-08-12 18:03:06 +02:00
|
|
|
// exclude the header row and zero index
|
|
|
|
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
|
|
sheet,
|
2023-03-13 18:21:04 +01:00
|
|
|
})
|
|
|
|
case Operation.CREATE_TABLE:
|
|
|
|
return this.createTable(json?.table?.name)
|
|
|
|
case Operation.UPDATE_TABLE:
|
2023-03-13 19:04:29 +01:00
|
|
|
return this.updateTable(json.table!)
|
2023-03-13 18:21:04 +01:00
|
|
|
case Operation.DELETE_TABLE:
|
|
|
|
return this.deleteTable(json?.table?.name)
|
|
|
|
default:
|
|
|
|
throw new Error(
|
|
|
|
`GSheets integration does not support "${json.endpoint.operation}".`
|
|
|
|
)
|
2021-11-25 20:12:32 +01:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2022-01-18 17:15:29 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
buildRowObject(headers: string[], values: string[], rowNumber: number) {
|
|
|
|
const rowObject: { rowNumber: number; [key: string]: any } = { rowNumber }
|
|
|
|
for (let i = 0; i < headers.length; i++) {
|
|
|
|
rowObject._id = rowNumber
|
|
|
|
rowObject[headers[i]] = values[i]
|
2021-11-25 20:12:32 +01:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
return rowObject
|
|
|
|
}
|
2021-11-25 20:12:32 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async createTable(name?: string) {
|
2023-03-13 19:10:35 +01:00
|
|
|
if (!name) {
|
|
|
|
throw new Error("Must provide name for new sheet.")
|
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
try {
|
|
|
|
await this.connect()
|
2023-03-13 19:10:35 +01:00
|
|
|
return await this.client.addSheet({ title: name, headerValues: [name] })
|
2022-08-12 18:03:06 +02:00
|
|
|
} catch (err) {
|
|
|
|
console.error("Error creating new table in google sheets", err)
|
|
|
|
throw err
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2021-11-25 18:12:12 +01:00
|
|
|
|
2023-03-13 19:04:29 +01:00
|
|
|
async updateTable(table: TableRequest) {
|
|
|
|
await this.connect()
|
|
|
|
const sheet = this.client.sheetsByTitle[table.name]
|
|
|
|
await sheet.loadHeaderRow()
|
|
|
|
|
|
|
|
if (table._rename) {
|
|
|
|
const headers = []
|
|
|
|
for (let header of sheet.headerValues) {
|
|
|
|
if (header === table._rename.old) {
|
|
|
|
headers.push(table._rename.updated)
|
|
|
|
} else {
|
|
|
|
headers.push(header)
|
2022-01-18 17:15:29 +01:00
|
|
|
}
|
2023-03-13 19:04:29 +01:00
|
|
|
}
|
|
|
|
try {
|
2022-08-12 18:03:06 +02:00
|
|
|
await sheet.setHeaderRow(headers)
|
2023-03-13 19:04:29 +01:00
|
|
|
} catch (err) {
|
|
|
|
console.error("Error updating column name in google sheets", err)
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const updatedHeaderValues = [...sheet.headerValues]
|
|
|
|
|
|
|
|
// add new column - doesn't currently exist
|
|
|
|
for (let [key, column] of Object.entries(table.schema)) {
|
|
|
|
if (!ALLOWED_TYPES.includes(column.type)) {
|
|
|
|
throw new Error(
|
|
|
|
`Column type: ${column.type} not allowed for GSheets integration.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
!sheet.headerValues.includes(key) &&
|
|
|
|
column.type !== FieldType.FORMULA
|
|
|
|
) {
|
|
|
|
updatedHeaderValues.push(key)
|
2023-03-13 17:21:22 +01:00
|
|
|
}
|
2023-03-13 19:04:29 +01:00
|
|
|
}
|
2023-02-27 13:33:19 +01:00
|
|
|
|
2023-03-13 19:04:29 +01:00
|
|
|
// clear out deleted columns
|
|
|
|
for (let key of sheet.headerValues) {
|
|
|
|
if (!Object.keys(table.schema).includes(key)) {
|
|
|
|
const idx = updatedHeaderValues.indexOf(key)
|
|
|
|
updatedHeaderValues.splice(idx, 1)
|
2023-02-27 13:33:19 +01:00
|
|
|
}
|
2023-03-13 19:04:29 +01:00
|
|
|
}
|
2023-02-27 13:33:19 +01:00
|
|
|
|
2023-03-13 19:04:29 +01:00
|
|
|
try {
|
2023-02-27 13:33:19 +01:00
|
|
|
await sheet.setHeaderRow(updatedHeaderValues)
|
2023-03-13 19:04:29 +01:00
|
|
|
} catch (err) {
|
|
|
|
console.error("Error updating table in google sheets", err)
|
|
|
|
throw err
|
2022-01-18 17:15:29 +01:00
|
|
|
}
|
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2022-01-18 17:15:29 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async deleteTable(sheet: any) {
|
|
|
|
try {
|
|
|
|
await this.connect()
|
2023-02-27 17:25:26 +01:00
|
|
|
const sheetToDelete = this.client.sheetsByTitle[sheet]
|
2022-08-12 18:03:06 +02:00
|
|
|
return await sheetToDelete.delete()
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Error deleting table in google sheets", err)
|
|
|
|
throw err
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2021-11-25 18:12:12 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async create(query: { sheet: string; row: any }) {
|
|
|
|
try {
|
|
|
|
await this.connect()
|
2023-02-27 17:25:26 +01:00
|
|
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
2022-08-12 18:03:06 +02:00
|
|
|
const rowToInsert =
|
|
|
|
typeof query.row === "string" ? JSON.parse(query.row) : query.row
|
|
|
|
const row = await sheet.addRow(rowToInsert)
|
|
|
|
return [
|
|
|
|
this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
|
|
|
|
]
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Error writing to google sheets", err)
|
|
|
|
throw err
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2021-11-25 18:12:12 +01:00
|
|
|
|
2023-03-13 18:21:04 +01:00
|
|
|
async createBulk(query: { sheet: string; rows: any[] }) {
|
|
|
|
try {
|
|
|
|
await this.connect()
|
|
|
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
|
|
|
let rowsToInsert = []
|
|
|
|
for (let row of query.rows) {
|
|
|
|
rowsToInsert.push(typeof row === "string" ? JSON.parse(row) : row)
|
|
|
|
}
|
|
|
|
const rows = await sheet.addRows(rowsToInsert)
|
|
|
|
return rows.map(row =>
|
|
|
|
this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber)
|
|
|
|
)
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Error bulk writing to google sheets", err)
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-09 09:50:26 +01:00
|
|
|
async read(query: {
|
|
|
|
sheet: string
|
|
|
|
filters?: SearchFilters
|
|
|
|
sort?: SortJson
|
|
|
|
paginate?: PaginationJson
|
|
|
|
}) {
|
2022-08-12 18:03:06 +02:00
|
|
|
try {
|
|
|
|
await this.connect()
|
2023-06-05 11:03:47 +02:00
|
|
|
const hasFilters = dataFilters.hasFilters(query.filters)
|
|
|
|
const limit = query.paginate?.limit || 100
|
|
|
|
const page: number =
|
|
|
|
typeof query.paginate?.page === "number"
|
|
|
|
? query.paginate.page
|
|
|
|
: parseInt(query.paginate?.page || "1")
|
|
|
|
const offset = (page - 1) * limit
|
2023-02-27 17:25:26 +01:00
|
|
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
2023-05-09 13:05:12 +02:00
|
|
|
let rows: GoogleSpreadsheetRow[] = []
|
2023-06-05 11:03:47 +02:00
|
|
|
if (query.paginate && !hasFilters) {
|
2023-05-09 13:05:12 +02:00
|
|
|
rows = await sheet.getRows({
|
|
|
|
limit,
|
2023-06-05 11:03:47 +02:00
|
|
|
offset,
|
2023-05-09 13:05:12 +02:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
rows = await sheet.getRows()
|
|
|
|
}
|
2023-06-02 19:25:35 +02:00
|
|
|
// this is a special case - need to handle the _id, it doesn't exist
|
|
|
|
// we cannot edit the returned structure from google, it does not have
|
|
|
|
// setter functions and is immutable, easier to update the filters
|
|
|
|
// to look for the _rowNumber property rather than rowNumber
|
|
|
|
if (query.filters?.equal) {
|
|
|
|
const idFilterKeys = Object.keys(query.filters.equal).filter(filter =>
|
|
|
|
filter.includes(GOOGLE_SHEETS_PRIMARY_KEY)
|
|
|
|
)
|
|
|
|
for (let idFilterKey of idFilterKeys) {
|
|
|
|
const id = query.filters.equal[idFilterKey]
|
|
|
|
delete query.filters.equal[idFilterKey]
|
|
|
|
query.filters.equal[`_${GOOGLE_SHEETS_PRIMARY_KEY}`] = id
|
|
|
|
}
|
|
|
|
}
|
2023-06-05 11:03:47 +02:00
|
|
|
let filtered = dataFilters.runLuceneQuery(rows, query.filters)
|
|
|
|
if (hasFilters && query.paginate) {
|
|
|
|
filtered = filtered.slice(offset, offset + limit)
|
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
const headerValues = sheet.headerValues
|
2023-03-09 09:50:26 +01:00
|
|
|
let response = []
|
|
|
|
for (let row of filtered) {
|
2022-08-12 18:03:06 +02:00
|
|
|
response.push(
|
|
|
|
this.buildRowObject(headerValues, row._rawData, row._rowNumber)
|
|
|
|
)
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
2023-03-09 09:50:26 +01:00
|
|
|
|
|
|
|
if (query.sort) {
|
|
|
|
if (Object.keys(query.sort).length !== 1) {
|
|
|
|
console.warn("Googlesheets does not support multiple sorting", {
|
|
|
|
sortInfo: query.sort,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
const [sortField, sortInfo] = Object.entries(query.sort)[0]
|
|
|
|
response = dataFilters.luceneSort(
|
|
|
|
response,
|
|
|
|
sortField,
|
|
|
|
sortInfo.direction,
|
|
|
|
sortInfo.type
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
return response
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Error reading from google sheets", err)
|
|
|
|
throw err
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
}
|
2021-11-25 18:12:12 +01:00
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async update(query: { sheet: string; rowIndex: number; row: any }) {
|
|
|
|
try {
|
2021-11-25 18:12:12 +01:00
|
|
|
await this.connect()
|
2023-02-27 17:25:26 +01:00
|
|
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
2021-11-25 18:12:12 +01:00
|
|
|
const rows = await sheet.getRows()
|
|
|
|
const row = rows[query.rowIndex]
|
|
|
|
if (row) {
|
2022-09-07 13:40:45 +02:00
|
|
|
const updateValues =
|
|
|
|
typeof query.row === "string" ? JSON.parse(query.row) : query.row
|
2022-08-12 18:03:06 +02:00
|
|
|
for (let key in updateValues) {
|
|
|
|
row[key] = updateValues[key]
|
|
|
|
}
|
|
|
|
await row.save()
|
|
|
|
return [
|
|
|
|
this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
|
|
|
|
]
|
2021-11-25 18:12:12 +01:00
|
|
|
} else {
|
|
|
|
throw new Error("Row does not exist.")
|
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
} catch (err) {
|
|
|
|
console.error("Error reading from google sheets", err)
|
|
|
|
throw err
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
async delete(query: { sheet: string; rowIndex: number }) {
|
|
|
|
await this.connect()
|
2023-02-27 17:25:26 +01:00
|
|
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
2022-08-12 18:03:06 +02:00
|
|
|
const rows = await sheet.getRows()
|
|
|
|
const row = rows[query.rowIndex]
|
|
|
|
if (row) {
|
|
|
|
await row.delete()
|
2023-06-02 19:25:35 +02:00
|
|
|
return [
|
|
|
|
{
|
|
|
|
deleted: query.rowIndex,
|
|
|
|
[GOOGLE_SHEETS_PRIMARY_KEY]: query.rowIndex,
|
|
|
|
},
|
|
|
|
]
|
2022-08-12 18:03:06 +02:00
|
|
|
} else {
|
|
|
|
throw new Error("Row does not exist.")
|
|
|
|
}
|
2021-11-25 18:12:12 +01:00
|
|
|
}
|
|
|
|
}
|
2022-08-12 18:03:06 +02:00
|
|
|
|
2023-05-31 14:29:45 +02:00
|
|
|
export async function setupCreationAuth(datasouce: GoogleSheetsConfig) {
|
|
|
|
if (datasouce.continueSetupId) {
|
|
|
|
const appId = context.getAppId()
|
|
|
|
const tokens = await cache.get(
|
|
|
|
`datasource:creation:${appId}:google:${datasouce.continueSetupId}`
|
|
|
|
)
|
|
|
|
|
|
|
|
datasouce.auth = tokens.tokens
|
|
|
|
delete datasouce.continueSetupId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-12 18:03:06 +02:00
|
|
|
export default {
|
|
|
|
schema: SCHEMA,
|
|
|
|
integration: GoogleSheetsIntegration,
|
|
|
|
}
|