Merge branch 'master' into feature/signature-field-and-component
This commit is contained in:
commit
c45ae63742
9 changed files with 95 additions and 37 deletions
|
@ -1,4 +1,6 @@
|
||||||
import { IdentityContext, Snippet, VM } from "@budibase/types"
|
import { IdentityContext, Snippet, VM } from "@budibase/types"
|
||||||
|
import { OAuth2Client } from "google-auth-library"
|
||||||
|
import { GoogleSpreadsheet } from "google-spreadsheet"
|
||||||
|
|
||||||
// keep this out of Budibase types, don't want to expose context info
|
// keep this out of Budibase types, don't want to expose context info
|
||||||
export type ContextMap = {
|
export type ContextMap = {
|
||||||
|
@ -12,4 +14,8 @@ export type ContextMap = {
|
||||||
vm?: VM
|
vm?: VM
|
||||||
cleanup?: (() => void | Promise<void>)[]
|
cleanup?: (() => void | Promise<void>)[]
|
||||||
snippets?: Snippet[]
|
snippets?: Snippet[]
|
||||||
|
googleSheets?: {
|
||||||
|
oauthClient: OAuth2Client
|
||||||
|
clients: Record<string, GoogleSpreadsheet>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,8 +86,9 @@ export const createValidatedConfigStore = (integration, config) => {
|
||||||
([$configStore, $errorsStore, $selectedValidatorsStore]) => {
|
([$configStore, $errorsStore, $selectedValidatorsStore]) => {
|
||||||
const validatedConfig = []
|
const validatedConfig = []
|
||||||
|
|
||||||
|
const allowedRestKeys = ["rejectUnauthorized", "downloadImages"]
|
||||||
Object.entries(integration.datasource).forEach(([key, properties]) => {
|
Object.entries(integration.datasource).forEach(([key, properties]) => {
|
||||||
if (integration.name === "REST" && key !== "rejectUnauthorized") {
|
if (integration.name === "REST" && !allowedRestKeys.includes(key)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
"dotenv": "8.2.0",
|
"dotenv": "8.2.0",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"global-agent": "3.0.0",
|
"global-agent": "3.0.0",
|
||||||
"google-spreadsheet": "4.1.2",
|
"google-spreadsheet": "npm:@budibase/google-spreadsheet@4.1.2",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
"isolated-vm": "^4.7.2",
|
"isolated-vm": "^4.7.2",
|
||||||
"jimp": "0.22.10",
|
"jimp": "0.22.10",
|
||||||
|
|
|
@ -229,32 +229,53 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
|
|
||||||
private async connect() {
|
private async connect() {
|
||||||
try {
|
try {
|
||||||
await setupCreationAuth(this.config)
|
const bbCtx = context.getCurrentContext()
|
||||||
|
let oauthClient = bbCtx?.googleSheets?.oauthClient
|
||||||
|
|
||||||
// Initialise oAuth client
|
if (!oauthClient) {
|
||||||
const googleConfig = await configs.getGoogleDatasourceConfig()
|
await setupCreationAuth(this.config)
|
||||||
if (!googleConfig) {
|
|
||||||
throw new HTTPError("Google config not found", 400)
|
// Initialise oAuth client
|
||||||
|
const googleConfig = await configs.getGoogleDatasourceConfig()
|
||||||
|
if (!googleConfig) {
|
||||||
|
throw new HTTPError("Google config not found", 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
if (bbCtx && !bbCtx.googleSheets) {
|
||||||
|
bbCtx.googleSheets = {
|
||||||
|
oauthClient,
|
||||||
|
clients: {},
|
||||||
|
}
|
||||||
|
bbCtx.cleanup = bbCtx.cleanup || []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const oauthClient = new OAuth2Client({
|
let client = bbCtx?.googleSheets?.clients[this.spreadsheetId]
|
||||||
clientId: googleConfig.clientID,
|
if (!client) {
|
||||||
clientSecret: googleConfig.clientSecret,
|
client = new GoogleSpreadsheet(this.spreadsheetId, oauthClient)
|
||||||
})
|
await client.loadInfo()
|
||||||
|
|
||||||
const tokenResponse = await this.fetchAccessToken({
|
if (bbCtx?.googleSheets?.clients) {
|
||||||
client_id: googleConfig.clientID,
|
bbCtx.googleSheets.clients[this.spreadsheetId] = client
|
||||||
client_secret: googleConfig.clientSecret,
|
}
|
||||||
refresh_token: this.config.auth.refreshToken,
|
}
|
||||||
})
|
|
||||||
|
|
||||||
oauthClient.setCredentials({
|
this.client = client
|
||||||
refresh_token: this.config.auth.refreshToken,
|
|
||||||
access_token: tokenResponse.access_token,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.client = new GoogleSpreadsheet(this.spreadsheetId, oauthClient)
|
|
||||||
await this.client.loadInfo()
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// this happens for xlsx imports
|
// this happens for xlsx imports
|
||||||
if (err.message?.includes("operation is not supported")) {
|
if (err.message?.includes("operation is not supported")) {
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import {
|
import {
|
||||||
Integration,
|
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
QueryType,
|
HttpMethod,
|
||||||
PaginationConfig,
|
Integration,
|
||||||
IntegrationBase,
|
IntegrationBase,
|
||||||
|
PaginationConfig,
|
||||||
PaginationValues,
|
PaginationValues,
|
||||||
RestQueryFields as RestQuery,
|
QueryType,
|
||||||
RestConfig,
|
|
||||||
RestAuthType,
|
RestAuthType,
|
||||||
RestBasicAuthConfig,
|
RestBasicAuthConfig,
|
||||||
RestBearerAuthConfig,
|
RestBearerAuthConfig,
|
||||||
HttpMethod,
|
RestConfig,
|
||||||
|
RestQueryFields as RestQuery,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import get from "lodash/get"
|
import get from "lodash/get"
|
||||||
import * as https from "https"
|
import * as https from "https"
|
||||||
import qs from "querystring"
|
import qs from "querystring"
|
||||||
import fetch from "node-fetch"
|
|
||||||
import type { Response } from "node-fetch"
|
import type { Response } from "node-fetch"
|
||||||
|
import fetch from "node-fetch"
|
||||||
import { formatBytes } from "../utilities"
|
import { formatBytes } from "../utilities"
|
||||||
import { performance } from "perf_hooks"
|
import { performance } from "perf_hooks"
|
||||||
import FormData from "form-data"
|
import FormData from "form-data"
|
||||||
|
@ -87,6 +87,12 @@ const SCHEMA: Integration = {
|
||||||
default: true,
|
default: true,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
downloadImages: {
|
||||||
|
display: "Download images",
|
||||||
|
type: DatasourceFieldType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
|
@ -139,7 +145,8 @@ class RestIntegration implements IntegrationBase {
|
||||||
filename: string | undefined
|
filename: string | undefined
|
||||||
|
|
||||||
const { contentType, contentDisposition } = getAttachmentHeaders(
|
const { contentType, contentDisposition } = getAttachmentHeaders(
|
||||||
response.headers
|
response.headers,
|
||||||
|
{ downloadImages: this.config.downloadImages }
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
contentDisposition.includes("filename") ||
|
contentDisposition.includes("filename") ||
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { getAttachmentHeaders } from "../utils/restUtils"
|
import { getAttachmentHeaders } from "../utils/restUtils"
|
||||||
import type { Headers } from "node-fetch"
|
import type { Headers } from "node-fetch"
|
||||||
|
|
||||||
function headers(dispositionValue: string) {
|
function headers(dispositionValue: string, contentType?: string) {
|
||||||
return {
|
return {
|
||||||
get: (name: string) => {
|
get: (name: string) => {
|
||||||
if (name.toLowerCase() === "content-disposition") {
|
if (name.toLowerCase() === "content-disposition") {
|
||||||
return dispositionValue
|
return dispositionValue
|
||||||
} else {
|
} else {
|
||||||
return "application/pdf"
|
return contentType || "application/pdf"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set: () => {},
|
set: () => {},
|
||||||
|
@ -35,4 +35,14 @@ describe("getAttachmentHeaders", () => {
|
||||||
)
|
)
|
||||||
expect(contentDisposition).toBe(`inline; filename="report.pdf"`)
|
expect(contentDisposition).toBe(`inline; filename="report.pdf"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should handle an image", () => {
|
||||||
|
const { contentDisposition } = getAttachmentHeaders(
|
||||||
|
headers("", "image/png"),
|
||||||
|
{
|
||||||
|
downloadImages: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(contentDisposition).toBe(`attachment; filename="image.png"`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import type { Headers } from "node-fetch"
|
import type { Headers } from "node-fetch"
|
||||||
|
|
||||||
export function getAttachmentHeaders(headers: Headers) {
|
export function getAttachmentHeaders(
|
||||||
|
headers: Headers,
|
||||||
|
opts?: { downloadImages?: boolean }
|
||||||
|
) {
|
||||||
const contentType = headers.get("content-type") || ""
|
const contentType = headers.get("content-type") || ""
|
||||||
let contentDisposition = headers.get("content-disposition") || ""
|
let contentDisposition = headers.get("content-disposition") || ""
|
||||||
|
|
||||||
|
@ -23,6 +26,15 @@ export function getAttachmentHeaders(headers: Headers) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// for images which don't supply a content disposition, make one up, as binary
|
||||||
|
// data for images in REST responses isn't really useful, we should always download them
|
||||||
|
else if (opts?.downloadImages && contentType.startsWith("image/")) {
|
||||||
|
const format = contentType.split("/")[1]
|
||||||
|
return {
|
||||||
|
contentDisposition: `attachment; filename="image.${format}"`,
|
||||||
|
contentType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { contentDisposition, contentType }
|
return { contentDisposition, contentType }
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ export interface DynamicVariable {
|
||||||
export interface RestConfig {
|
export interface RestConfig {
|
||||||
url: string
|
url: string
|
||||||
rejectUnauthorized: boolean
|
rejectUnauthorized: boolean
|
||||||
|
downloadImages?: boolean
|
||||||
defaultHeaders: {
|
defaultHeaders: {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
|
@ -11540,10 +11540,10 @@ google-p12-pem@^4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-forge "^1.3.1"
|
node-forge "^1.3.1"
|
||||||
|
|
||||||
google-spreadsheet@4.1.2:
|
"google-spreadsheet@npm:@budibase/google-spreadsheet@4.1.2":
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/google-spreadsheet/-/google-spreadsheet-4.1.2.tgz#92e30fdba7e0d78c55d50731528df7835d58bfee"
|
resolved "https://registry.yarnpkg.com/@budibase/google-spreadsheet/-/google-spreadsheet-4.1.2.tgz#90548ccba2284b3042b08d2974ef3caeaf772ad9"
|
||||||
integrity sha512-HFBweDAkOcyC2qO9kmWESKbNuOcn+R7UzZN/tj5LLNxVv8FHmg113u0Ow+yaKwwIOt/NnDtPLuptAhaxTs0FYw==
|
integrity sha512-dxoY3rQGGnuNeZiXhNc9oYPduzU8xnIjWujFwNvaRRv3zWeUV7mj6HE2o/OJOeekPGt7o44B+w6DfkiaoteZgg==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^1.4.0"
|
axios "^1.4.0"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
|
|
Loading…
Reference in a new issue