mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
78 changed files with 3960 additions and 3135 deletions
File diff suppressed because it is too large
@ -1,4 +1,4 @@ |
|||||
const elastic = {} |
const elastic: any = {} |
||||
|
|
||||
elastic.Client = function () { |
elastic.Client = function () { |
||||
this.index = jest.fn().mockResolvedValue({ body: [] }) |
this.index = jest.fn().mockResolvedValue({ body: [] }) |
||||
@ -1,18 +0,0 @@ |
|||||
class Email { |
|
||||
constructor() { |
|
||||
this.apiKey = null |
|
||||
} |
|
||||
|
|
||||
setApiKey(apiKey) { |
|
||||
this.apiKey = apiKey |
|
||||
} |
|
||||
|
|
||||
async send(msg) { |
|
||||
if (msg.to === "invalid@test.com") { |
|
||||
throw "Invalid" |
|
||||
} |
|
||||
return msg |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = new Email() |
|
||||
@ -0,0 +1,22 @@ |
|||||
|
module SendgridMock { |
||||
|
class Email { |
||||
|
constructor() { |
||||
|
// @ts-ignore
|
||||
|
this.apiKey = null |
||||
|
} |
||||
|
|
||||
|
setApiKey(apiKey: any) { |
||||
|
// @ts-ignore
|
||||
|
this.apiKey = apiKey |
||||
|
} |
||||
|
|
||||
|
async send(msg: any) { |
||||
|
if (msg.to === "invalid@test.com") { |
||||
|
throw "Invalid" |
||||
|
} |
||||
|
return msg |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = new Email() |
||||
|
} |
||||
@ -1,5 +0,0 @@ |
|||||
function Airtable() { |
|
||||
this.base = jest.fn() |
|
||||
} |
|
||||
|
|
||||
module.exports = Airtable |
|
||||
@ -0,0 +1,8 @@ |
|||||
|
module AirtableMock { |
||||
|
function Airtable() { |
||||
|
// @ts-ignore
|
||||
|
this.base = jest.fn() |
||||
|
} |
||||
|
|
||||
|
module.exports = Airtable |
||||
|
} |
||||
@ -1,21 +0,0 @@ |
|||||
const arangodb = {} |
|
||||
|
|
||||
arangodb.Database = function () { |
|
||||
this.query = jest.fn(() => ({ |
|
||||
all: jest.fn(), |
|
||||
})) |
|
||||
this.collection = jest.fn(() => "collection") |
|
||||
this.close = jest.fn() |
|
||||
} |
|
||||
|
|
||||
arangodb.aql = (strings, ...args) => { |
|
||||
let str = strings.join("{}") |
|
||||
|
|
||||
for (let arg of args) { |
|
||||
str = str.replace("{}", arg) |
|
||||
} |
|
||||
|
|
||||
return str |
|
||||
} |
|
||||
|
|
||||
module.exports = arangodb |
|
||||
@ -0,0 +1,24 @@ |
|||||
|
module ArangoMock { |
||||
|
const arangodb: any = {} |
||||
|
|
||||
|
arangodb.Database = function () { |
||||
|
this.query = jest.fn(() => ({ |
||||
|
all: jest.fn(), |
||||
|
})) |
||||
|
this.collection = jest.fn(() => "collection") |
||||
|
this.close = jest.fn() |
||||
|
} |
||||
|
|
||||
|
// @ts-ignore
|
||||
|
arangodb.aql = (strings, ...args) => { |
||||
|
let str = strings.join("{}") |
||||
|
|
||||
|
for (let arg of args) { |
||||
|
str = str.replace("{}", arg) |
||||
|
} |
||||
|
|
||||
|
return str |
||||
|
} |
||||
|
|
||||
|
module.exports = arangodb |
||||
|
} |
||||
@ -1,38 +0,0 @@ |
|||||
const aws = {} |
|
||||
|
|
||||
const response = body => () => ({ promise: () => body }) |
|
||||
|
|
||||
function DocumentClient() { |
|
||||
this.put = jest.fn(response({})) |
|
||||
this.query = jest.fn( |
|
||||
response({ |
|
||||
Items: [], |
|
||||
}) |
|
||||
) |
|
||||
this.scan = jest.fn( |
|
||||
response({ |
|
||||
Items: [ |
|
||||
{ |
|
||||
Name: "test", |
|
||||
}, |
|
||||
], |
|
||||
}) |
|
||||
) |
|
||||
this.get = jest.fn(response({})) |
|
||||
this.update = jest.fn(response({})) |
|
||||
this.delete = jest.fn(response({})) |
|
||||
} |
|
||||
|
|
||||
function S3() { |
|
||||
this.listObjects = jest.fn( |
|
||||
response({ |
|
||||
Contents: {}, |
|
||||
}) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
aws.DynamoDB = { DocumentClient } |
|
||||
aws.S3 = S3 |
|
||||
aws.config = { update: jest.fn() } |
|
||||
|
|
||||
module.exports = aws |
|
||||
@ -0,0 +1,47 @@ |
|||||
|
module AwsMock { |
||||
|
const aws: any = {} |
||||
|
|
||||
|
const response = (body: any) => () => ({promise: () => body}) |
||||
|
|
||||
|
function DocumentClient() { |
||||
|
// @ts-ignore
|
||||
|
this.put = jest.fn(response({})) |
||||
|
// @ts-ignore
|
||||
|
this.query = jest.fn( |
||||
|
response({ |
||||
|
Items: [], |
||||
|
}) |
||||
|
) |
||||
|
// @ts-ignore
|
||||
|
this.scan = jest.fn( |
||||
|
response({ |
||||
|
Items: [ |
||||
|
{ |
||||
|
Name: "test", |
||||
|
}, |
||||
|
], |
||||
|
}) |
||||
|
) |
||||
|
// @ts-ignore
|
||||
|
this.get = jest.fn(response({})) |
||||
|
// @ts-ignore
|
||||
|
this.update = jest.fn(response({})) |
||||
|
// @ts-ignore
|
||||
|
this.delete = jest.fn(response({})) |
||||
|
} |
||||
|
|
||||
|
function S3() { |
||||
|
// @ts-ignore
|
||||
|
this.listObjects = jest.fn( |
||||
|
response({ |
||||
|
Contents: {}, |
||||
|
}) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
aws.DynamoDB = {DocumentClient} |
||||
|
aws.S3 = S3 |
||||
|
aws.config = {update: jest.fn()} |
||||
|
|
||||
|
module.exports = aws |
||||
|
} |
||||
@ -1,19 +0,0 @@ |
|||||
const mongodb = {} |
|
||||
|
|
||||
mongodb.MongoClient = function () { |
|
||||
this.connect = jest.fn() |
|
||||
this.close = jest.fn() |
|
||||
this.insertOne = jest.fn() |
|
||||
this.find = jest.fn(() => ({ toArray: () => [] })) |
|
||||
|
|
||||
this.collection = jest.fn(() => ({ |
|
||||
insertOne: this.insertOne, |
|
||||
find: this.find, |
|
||||
})) |
|
||||
|
|
||||
this.db = () => ({ |
|
||||
collection: this.collection, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
module.exports = mongodb |
|
||||
@ -0,0 +1,21 @@ |
|||||
|
module MongoMock { |
||||
|
const mongodb: any = {} |
||||
|
|
||||
|
mongodb.MongoClient = function () { |
||||
|
this.connect = jest.fn() |
||||
|
this.close = jest.fn() |
||||
|
this.insertOne = jest.fn() |
||||
|
this.find = jest.fn(() => ({toArray: () => []})) |
||||
|
|
||||
|
this.collection = jest.fn(() => ({ |
||||
|
insertOne: this.insertOne, |
||||
|
find: this.find, |
||||
|
})) |
||||
|
|
||||
|
this.db = () => ({ |
||||
|
collection: this.collection, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
module.exports = mongodb |
||||
|
} |
||||
@ -1,22 +0,0 @@ |
|||||
const mssql = {} |
|
||||
|
|
||||
mssql.query = jest.fn(() => ({ |
|
||||
recordset: [ |
|
||||
{ |
|
||||
a: "string", |
|
||||
b: 1, |
|
||||
}, |
|
||||
], |
|
||||
})) |
|
||||
|
|
||||
// mssql.connect = jest.fn(() => ({ recordset: [] }))
|
|
||||
|
|
||||
mssql.ConnectionPool = jest.fn(() => ({ |
|
||||
connect: jest.fn(() => ({ |
|
||||
request: jest.fn(() => ({ |
|
||||
query: jest.fn(() => ({})), |
|
||||
})), |
|
||||
})), |
|
||||
})) |
|
||||
|
|
||||
module.exports = mssql |
|
||||
@ -0,0 +1,24 @@ |
|||||
|
module MsSqlMock { |
||||
|
const mssql: any = {} |
||||
|
|
||||
|
mssql.query = jest.fn(() => ({ |
||||
|
recordset: [ |
||||
|
{ |
||||
|
a: "string", |
||||
|
b: 1, |
||||
|
}, |
||||
|
], |
||||
|
})) |
||||
|
|
||||
|
// mssql.connect = jest.fn(() => ({ recordset: [] }))
|
||||
|
|
||||
|
mssql.ConnectionPool = jest.fn(() => ({ |
||||
|
connect: jest.fn(() => ({ |
||||
|
request: jest.fn(() => ({ |
||||
|
query: jest.fn(() => ({})), |
||||
|
})), |
||||
|
})), |
||||
|
})) |
||||
|
|
||||
|
module.exports = mssql |
||||
|
} |
||||
@ -1,12 +0,0 @@ |
|||||
const mysql = {} |
|
||||
|
|
||||
const client = { |
|
||||
connect: jest.fn(), |
|
||||
query: jest.fn((query, bindings, fn) => { |
|
||||
fn(null, []) |
|
||||
}), |
|
||||
} |
|
||||
|
|
||||
mysql.createConnection = jest.fn(() => client) |
|
||||
|
|
||||
module.exports = mysql |
|
||||
@ -0,0 +1,14 @@ |
|||||
|
module MySQLMock { |
||||
|
const mysql: any = {} |
||||
|
|
||||
|
const client = { |
||||
|
connect: jest.fn(), |
||||
|
query: jest.fn((query, bindings, fn) => { |
||||
|
fn(null, []) |
||||
|
}), |
||||
|
} |
||||
|
|
||||
|
mysql.createConnection = jest.fn(() => client) |
||||
|
|
||||
|
module.exports = mysql |
||||
|
} |
||||
@ -1,58 +0,0 @@ |
|||||
const fetch = jest.requireActual("node-fetch") |
|
||||
|
|
||||
module.exports = async (url, opts) => { |
|
||||
function json(body, status = 200) { |
|
||||
return { |
|
||||
status, |
|
||||
headers: { |
|
||||
get: () => { |
|
||||
return ["application/json"] |
|
||||
}, |
|
||||
}, |
|
||||
json: async () => { |
|
||||
return body |
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (url.includes("/api/admin")) { |
|
||||
return json({ |
|
||||
email: "test@test.com", |
|
||||
_id: "us_test@test.com", |
|
||||
status: "active", |
|
||||
}) |
|
||||
} |
|
||||
// mocked data based on url
|
|
||||
else if (url.includes("api/apps")) { |
|
||||
return json({ |
|
||||
app1: { |
|
||||
url: "/app1", |
|
||||
}, |
|
||||
}) |
|
||||
} else if (url.includes("test.com")) { |
|
||||
return json({ |
|
||||
body: opts.body, |
|
||||
url, |
|
||||
method: opts.method, |
|
||||
}) |
|
||||
} else if (url.includes("invalid.com")) { |
|
||||
return json( |
|
||||
{ |
|
||||
invalid: true, |
|
||||
}, |
|
||||
404 |
|
||||
) |
|
||||
} else if (url.includes("_search")) { |
|
||||
return json({ |
|
||||
rows: [ |
|
||||
{ |
|
||||
doc: { |
|
||||
_id: "test", |
|
||||
}, |
|
||||
}, |
|
||||
], |
|
||||
bookmark: "test", |
|
||||
}) |
|
||||
} |
|
||||
return fetch(url, opts) |
|
||||
} |
|
||||
@ -0,0 +1,60 @@ |
|||||
|
module FetchMock { |
||||
|
const fetch = jest.requireActual("node-fetch") |
||||
|
|
||||
|
module.exports = async (url: any, opts: any) => { |
||||
|
function json(body: any, status = 200) { |
||||
|
return { |
||||
|
status, |
||||
|
headers: { |
||||
|
get: () => { |
||||
|
return ["application/json"] |
||||
|
}, |
||||
|
}, |
||||
|
json: async () => { |
||||
|
return body |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (url.includes("/api/admin")) { |
||||
|
return json({ |
||||
|
email: "test@test.com", |
||||
|
_id: "us_test@test.com", |
||||
|
status: "active", |
||||
|
}) |
||||
|
} |
||||
|
// mocked data based on url
|
||||
|
else if (url.includes("api/apps")) { |
||||
|
return json({ |
||||
|
app1: { |
||||
|
url: "/app1", |
||||
|
}, |
||||
|
}) |
||||
|
} else if (url.includes("test.com")) { |
||||
|
return json({ |
||||
|
body: opts.body, |
||||
|
url, |
||||
|
method: opts.method, |
||||
|
}) |
||||
|
} else if (url.includes("invalid.com")) { |
||||
|
return json( |
||||
|
{ |
||||
|
invalid: true, |
||||
|
}, |
||||
|
404 |
||||
|
) |
||||
|
} else if (url.includes("_search")) { |
||||
|
return json({ |
||||
|
rows: [ |
||||
|
{ |
||||
|
doc: { |
||||
|
_id: "test", |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
bookmark: "test", |
||||
|
}) |
||||
|
} |
||||
|
return fetch(url, opts) |
||||
|
} |
||||
|
} |
||||
@ -1,29 +0,0 @@ |
|||||
const pg = {} |
|
||||
|
|
||||
const query = jest.fn(() => ({ |
|
||||
rows: [ |
|
||||
{ |
|
||||
a: "string", |
|
||||
b: 1, |
|
||||
}, |
|
||||
], |
|
||||
})) |
|
||||
|
|
||||
// constructor
|
|
||||
function Client() {} |
|
||||
|
|
||||
Client.prototype.query = query |
|
||||
Client.prototype.connect = jest.fn() |
|
||||
Client.prototype.release = jest.fn() |
|
||||
|
|
||||
function Pool() {} |
|
||||
Pool.prototype.query = query |
|
||||
Pool.prototype.connect = jest.fn(() => { |
|
||||
return new Client() |
|
||||
}) |
|
||||
|
|
||||
pg.Client = Client |
|
||||
pg.Pool = Pool |
|
||||
pg.queryMock = query |
|
||||
|
|
||||
module.exports = pg |
|
||||
@ -0,0 +1,35 @@ |
|||||
|
module PgMock { |
||||
|
const pg: any = {} |
||||
|
|
||||
|
const query = jest.fn(() => ({ |
||||
|
rows: [ |
||||
|
{ |
||||
|
a: "string", |
||||
|
b: 1, |
||||
|
}, |
||||
|
], |
||||
|
})) |
||||
|
|
||||
|
// constructor
|
||||
|
function Client() { |
||||
|
} |
||||
|
|
||||
|
Client.prototype.query = query |
||||
|
Client.prototype.connect = jest.fn() |
||||
|
Client.prototype.release = jest.fn() |
||||
|
|
||||
|
function Pool() { |
||||
|
} |
||||
|
|
||||
|
Pool.prototype.query = query |
||||
|
Pool.prototype.connect = jest.fn(() => { |
||||
|
// @ts-ignore
|
||||
|
return new Client() |
||||
|
}) |
||||
|
|
||||
|
pg.Client = Client |
||||
|
pg.Pool = Pool |
||||
|
pg.queryMock = query |
||||
|
|
||||
|
module.exports = pg |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
export interface Table { |
||||
|
_id: string |
||||
|
_rev?: string |
||||
|
type?: string |
||||
|
views?: {} |
||||
|
name?: string |
||||
|
primary?: string[] |
||||
|
schema: { |
||||
|
[key: string]: { |
||||
|
// TODO: replace with field types enum when done
|
||||
|
type: string |
||||
|
fieldName?: string |
||||
|
name: string |
||||
|
constraints?: { |
||||
|
type?: string |
||||
|
email?: boolean |
||||
|
inclusion?: string[] |
||||
|
length?: { |
||||
|
minimum?: string | number |
||||
|
maximum?: string | number |
||||
|
} |
||||
|
presence?: boolean |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
primaryDisplay?: string |
||||
|
sourceId?: string |
||||
|
} |
||||
@ -1,15 +0,0 @@ |
|||||
exports.QUERY_TYPES = { |
|
||||
SQL: "sql", |
|
||||
JSON: "json", |
|
||||
FIELDS: "fields", |
|
||||
} |
|
||||
|
|
||||
exports.FIELD_TYPES = { |
|
||||
STRING: "string", |
|
||||
BOOLEAN: "boolean", |
|
||||
NUMBER: "number", |
|
||||
PASSWORD: "password", |
|
||||
LIST: "list", |
|
||||
OBJECT: "object", |
|
||||
JSON: "json", |
|
||||
} |
|
||||
@ -1,130 +0,0 @@ |
|||||
const Airtable = require("airtable") |
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://airtable.com/api", |
|
||||
description: |
|
||||
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.", |
|
||||
friendlyName: "Airtable", |
|
||||
datasource: { |
|
||||
apiKey: { |
|
||||
type: FIELD_TYPES.PASSWORD, |
|
||||
default: "enter api key", |
|
||||
required: true, |
|
||||
}, |
|
||||
base: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "mybase", |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
read: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
view: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
numRecords: { |
|
||||
type: FIELD_TYPES.NUMBER, |
|
||||
default: 10, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
update: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
id: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
delete: { |
|
||||
type: QUERY_TYPES.JSON, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class AirtableIntegration { |
|
||||
constructor(config) { |
|
||||
this.config = config |
|
||||
this.client = new Airtable(config).base(config.base) |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
const { table, json } = query |
|
||||
|
|
||||
try { |
|
||||
const records = await this.client(table).create([ |
|
||||
{ |
|
||||
fields: json, |
|
||||
}, |
|
||||
]) |
|
||||
return records |
|
||||
} catch (err) { |
|
||||
console.error("Error writing to airtable", err) |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
try { |
|
||||
const records = await this.client(query.table) |
|
||||
.select({ maxRecords: query.numRecords || 10, view: query.view }) |
|
||||
.firstPage() |
|
||||
return records.map(({ fields }) => fields) |
|
||||
} catch (err) { |
|
||||
console.error("Error writing to airtable", err) |
|
||||
return [] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async update(query) { |
|
||||
const { table, id, json } = query |
|
||||
|
|
||||
try { |
|
||||
const records = await this.client(table).update([ |
|
||||
{ |
|
||||
id, |
|
||||
fields: json, |
|
||||
}, |
|
||||
]) |
|
||||
return records |
|
||||
} catch (err) { |
|
||||
console.error("Error writing to airtable", err) |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async delete(query) { |
|
||||
try { |
|
||||
const records = await this.client(query.table).destroy(query.ids) |
|
||||
return records |
|
||||
} catch (err) { |
|
||||
console.error("Error writing to airtable", err) |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: AirtableIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,143 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
} from "./base/definitions" |
||||
|
|
||||
|
module AirtableModule { |
||||
|
const Airtable = require("airtable") |
||||
|
|
||||
|
interface AirtableConfig { |
||||
|
apiKey: string |
||||
|
base: string |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://airtable.com/api", |
||||
|
description: |
||||
|
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.", |
||||
|
friendlyName: "Airtable", |
||||
|
datasource: { |
||||
|
apiKey: { |
||||
|
type: DatasourceFieldTypes.PASSWORD, |
||||
|
default: "enter api key", |
||||
|
required: true, |
||||
|
}, |
||||
|
base: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "mybase", |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
view: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
numRecords: { |
||||
|
type: DatasourceFieldTypes.NUMBER, |
||||
|
default: 10, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
update: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
id: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
delete: { |
||||
|
type: QueryTypes.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class AirtableIntegration { |
||||
|
private config: AirtableConfig |
||||
|
private client: any |
||||
|
|
||||
|
constructor(config: AirtableConfig) { |
||||
|
this.config = config |
||||
|
this.client = new Airtable(config).base(config.base) |
||||
|
} |
||||
|
|
||||
|
async create(query: { table: any; json: any }) { |
||||
|
const { table, json } = query |
||||
|
|
||||
|
try { |
||||
|
return await this.client(table).create([ |
||||
|
{ |
||||
|
fields: json, |
||||
|
}, |
||||
|
]) |
||||
|
} catch (err) { |
||||
|
console.error("Error writing to airtable", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async read(query: { table: any; numRecords: any; view: any }) { |
||||
|
try { |
||||
|
const records = await this.client(query.table) |
||||
|
.select({ maxRecords: query.numRecords || 10, view: query.view }) |
||||
|
.firstPage() |
||||
|
// @ts-ignore
|
||||
|
return records.map(({ fields }) => fields) |
||||
|
} catch (err) { |
||||
|
console.error("Error writing to airtable", err) |
||||
|
return [] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async update(query: { table: any; id: any; json: any }) { |
||||
|
const { table, id, json } = query |
||||
|
|
||||
|
try { |
||||
|
return await this.client(table).update([ |
||||
|
{ |
||||
|
id, |
||||
|
fields: json, |
||||
|
}, |
||||
|
]) |
||||
|
} catch (err) { |
||||
|
console.error("Error writing to airtable", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async delete(query: { table: any; ids: any }) { |
||||
|
try { |
||||
|
return await this.client(query.table).destroy(query.ids) |
||||
|
} catch (err) { |
||||
|
console.error("Error writing to airtable", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: AirtableIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,86 +0,0 @@ |
|||||
const { Database, aql } = require("arangojs") |
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://github.com/arangodb/arangojs", |
|
||||
friendlyName: "ArangoDB", |
|
||||
description: |
|
||||
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ", |
|
||||
datasource: { |
|
||||
url: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "http://localhost:8529", |
|
||||
required: true, |
|
||||
}, |
|
||||
username: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "root", |
|
||||
required: true, |
|
||||
}, |
|
||||
password: { |
|
||||
type: FIELD_TYPES.PASSWORD, |
|
||||
required: true, |
|
||||
}, |
|
||||
databaseName: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "_system", |
|
||||
required: true, |
|
||||
}, |
|
||||
collection: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
read: { |
|
||||
type: QUERY_TYPES.SQL, |
|
||||
}, |
|
||||
create: { |
|
||||
type: QUERY_TYPES.JSON, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class ArangoDBIntegration { |
|
||||
constructor(config) { |
|
||||
config.auth = { |
|
||||
username: config.username, |
|
||||
password: config.password, |
|
||||
} |
|
||||
|
|
||||
this.config = config |
|
||||
this.client = new Database(config) |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
try { |
|
||||
const result = await this.client.query(query.sql) |
|
||||
return result.all() |
|
||||
} catch (err) { |
|
||||
console.error("Error querying arangodb", err.message) |
|
||||
throw err |
|
||||
} finally { |
|
||||
this.client.close() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
const clc = this.client.collection(this.config.collection) |
|
||||
try { |
|
||||
const result = await this.client.query( |
|
||||
aql`INSERT ${query.json} INTO ${clc} RETURN NEW` |
|
||||
) |
|
||||
return result.all() |
|
||||
} catch (err) { |
|
||||
console.error("Error querying arangodb", err.message) |
|
||||
throw err |
|
||||
} finally { |
|
||||
this.client.close() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: ArangoDBIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,106 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
} from "./base/definitions" |
||||
|
|
||||
|
module ArangoModule { |
||||
|
const { Database, aql } = require("arangojs") |
||||
|
|
||||
|
interface ArangodbConfig { |
||||
|
url: string |
||||
|
username: string |
||||
|
password: string |
||||
|
databaseName: string |
||||
|
collection: string |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://github.com/arangodb/arangojs", |
||||
|
friendlyName: "ArangoDB", |
||||
|
description: |
||||
|
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ", |
||||
|
datasource: { |
||||
|
url: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "http://localhost:8529", |
||||
|
required: true, |
||||
|
}, |
||||
|
username: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "root", |
||||
|
required: true, |
||||
|
}, |
||||
|
password: { |
||||
|
type: DatasourceFieldTypes.PASSWORD, |
||||
|
required: true, |
||||
|
}, |
||||
|
databaseName: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "_system", |
||||
|
required: true, |
||||
|
}, |
||||
|
collection: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
read: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
create: { |
||||
|
type: QueryTypes.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class ArangoDBIntegration { |
||||
|
private config: ArangodbConfig |
||||
|
private client: any |
||||
|
|
||||
|
constructor(config: ArangodbConfig) { |
||||
|
const newConfig = { |
||||
|
auth: { |
||||
|
username: config.username, |
||||
|
password: config.password, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
this.config = config |
||||
|
this.client = new Database(newConfig) |
||||
|
} |
||||
|
|
||||
|
async read(query: { sql: any }) { |
||||
|
try { |
||||
|
const result = await this.client.query(query.sql) |
||||
|
return result.all() |
||||
|
} catch (err) { |
||||
|
console.error("Error querying arangodb", err.message) |
||||
|
throw err |
||||
|
} finally { |
||||
|
this.client.close() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async create(query: { json: any }) { |
||||
|
const clc = this.client.collection(this.config.collection) |
||||
|
try { |
||||
|
const result = await this.client.query( |
||||
|
aql`INSERT ${query.json} INTO ${clc} RETURN NEW` |
||||
|
) |
||||
|
return result.all() |
||||
|
} catch (err) { |
||||
|
console.error("Error querying arangodb", err.message) |
||||
|
throw err |
||||
|
} finally { |
||||
|
this.client.close() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: ArangoDBIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,11 +0,0 @@ |
|||||
exports.Operation = { |
|
||||
CREATE: "CREATE", |
|
||||
READ: "READ", |
|
||||
UPDATE: "UPDATE", |
|
||||
DELETE: "DELETE", |
|
||||
} |
|
||||
|
|
||||
exports.SortDirection = { |
|
||||
ASCENDING: "ASCENDING", |
|
||||
DESCENDING: "DESCENDING", |
|
||||
} |
|
||||
@ -0,0 +1,109 @@ |
|||||
|
export enum Operation { |
||||
|
CREATE = "CREATE", |
||||
|
READ = "READ", |
||||
|
UPDATE = "UPDATE", |
||||
|
DELETE = "DELETE", |
||||
|
} |
||||
|
|
||||
|
export enum SortDirection { |
||||
|
ASCENDING = "ASCENDING", |
||||
|
DESCENDING = "DESCENDING", |
||||
|
} |
||||
|
|
||||
|
export enum QueryTypes { |
||||
|
SQL = "sql", |
||||
|
JSON = "json", |
||||
|
FIELDS = "fields", |
||||
|
} |
||||
|
|
||||
|
export enum DatasourceFieldTypes { |
||||
|
STRING = "string", |
||||
|
BOOLEAN = "boolean", |
||||
|
NUMBER = "number", |
||||
|
PASSWORD = "password", |
||||
|
LIST = "list", |
||||
|
OBJECT = "object", |
||||
|
JSON = "json", |
||||
|
} |
||||
|
|
||||
|
export interface QueryDefinition { |
||||
|
type: QueryTypes |
||||
|
displayName?: string |
||||
|
readable?: boolean |
||||
|
customisable?: boolean |
||||
|
fields?: object |
||||
|
urlDisplay?: boolean |
||||
|
} |
||||
|
|
||||
|
export interface Integration { |
||||
|
docs: string |
||||
|
plus?: boolean |
||||
|
description: string |
||||
|
friendlyName: string |
||||
|
datasource: {} |
||||
|
query: { |
||||
|
[key: string]: QueryDefinition |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export interface SearchFilters { |
||||
|
allOr: boolean |
||||
|
string?: { |
||||
|
[key: string]: string |
||||
|
} |
||||
|
fuzzy?: { |
||||
|
[key: string]: string |
||||
|
} |
||||
|
range?: { |
||||
|
[key: string]: { |
||||
|
high: number | string |
||||
|
low: number | string |
||||
|
} |
||||
|
} |
||||
|
equal?: { |
||||
|
[key: string]: any |
||||
|
} |
||||
|
notEqual?: { |
||||
|
[key: string]: any |
||||
|
} |
||||
|
empty?: { |
||||
|
[key: string]: any |
||||
|
} |
||||
|
notEmpty?: { |
||||
|
[key: string]: any |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export interface QueryJson { |
||||
|
endpoint: { |
||||
|
datasourceId: string |
||||
|
entityId: string |
||||
|
operation: Operation |
||||
|
} |
||||
|
resource: { |
||||
|
fields: string[] |
||||
|
} |
||||
|
filters?: SearchFilters |
||||
|
sort?: { |
||||
|
[key: string]: SortDirection |
||||
|
} |
||||
|
paginate?: { |
||||
|
limit: number |
||||
|
page: string | number |
||||
|
} |
||||
|
body?: object |
||||
|
extra: { |
||||
|
idFilter?: SearchFilters |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export interface SqlQuery { |
||||
|
sql: string |
||||
|
bindings?: { |
||||
|
[key: string]: any |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export interface QueryOptions { |
||||
|
disableReturning?: boolean |
||||
|
} |
||||
@ -1,95 +0,0 @@ |
|||||
const PouchDB = require("pouchdb") |
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://docs.couchdb.org/en/stable/", |
|
||||
friendlyName: "CouchDB", |
|
||||
description: |
|
||||
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.", |
|
||||
datasource: { |
|
||||
url: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
default: "http://localhost:5984", |
|
||||
}, |
|
||||
database: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: QUERY_TYPES.JSON, |
|
||||
}, |
|
||||
read: { |
|
||||
type: QUERY_TYPES.JSON, |
|
||||
}, |
|
||||
update: { |
|
||||
type: QUERY_TYPES.JSON, |
|
||||
}, |
|
||||
delete: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
fields: { |
|
||||
id: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class CouchDBIntegration { |
|
||||
constructor(config) { |
|
||||
this.config = config |
|
||||
this.client = new PouchDB(`${config.url}/${config.database}`) |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
try { |
|
||||
const result = await this.client.post(query.json) |
|
||||
return result |
|
||||
} catch (err) { |
|
||||
console.error("Error writing to couchDB", err) |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
try { |
|
||||
const result = await this.client.allDocs({ |
|
||||
include_docs: true, |
|
||||
...query.json, |
|
||||
}) |
|
||||
return result.rows.map(row => row.doc) |
|
||||
} catch (err) { |
|
||||
console.error("Error querying couchDB", err) |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async update(query) { |
|
||||
try { |
|
||||
const result = await this.client.put(query.json) |
|
||||
return result |
|
||||
} catch (err) { |
|
||||
console.error("Error updating couchDB document", err) |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async delete(query) { |
|
||||
try { |
|
||||
const result = await this.client.remove(query.id) |
|
||||
return result |
|
||||
} catch (err) { |
|
||||
console.error("Error deleting couchDB document", err) |
|
||||
throw err |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: CouchDBIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,107 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
} from "./base/definitions" |
||||
|
|
||||
|
module CouchDBModule { |
||||
|
const PouchDB = require("pouchdb") |
||||
|
|
||||
|
interface CouchDBConfig { |
||||
|
url: string |
||||
|
database: string |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://docs.couchdb.org/en/stable/", |
||||
|
friendlyName: "CouchDB", |
||||
|
description: |
||||
|
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.", |
||||
|
datasource: { |
||||
|
url: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
default: "http://localhost:5984", |
||||
|
}, |
||||
|
database: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.JSON, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.JSON, |
||||
|
}, |
||||
|
update: { |
||||
|
type: QueryTypes.JSON, |
||||
|
}, |
||||
|
delete: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
fields: { |
||||
|
id: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class CouchDBIntegration { |
||||
|
private config: CouchDBConfig |
||||
|
private client: any |
||||
|
|
||||
|
constructor(config: CouchDBConfig) { |
||||
|
this.config = config |
||||
|
this.client = new PouchDB(`${config.url}/${config.database}`) |
||||
|
} |
||||
|
|
||||
|
async create(query: { json: object }) { |
||||
|
try { |
||||
|
return this.client.post(query.json) |
||||
|
} catch (err) { |
||||
|
console.error("Error writing to couchDB", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async read(query: { json: object }) { |
||||
|
try { |
||||
|
const result = await this.client.allDocs({ |
||||
|
include_docs: true, |
||||
|
...query.json, |
||||
|
}) |
||||
|
return result.rows.map((row: { doc: object }) => row.doc) |
||||
|
} catch (err) { |
||||
|
console.error("Error querying couchDB", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async update(query: { json: object }) { |
||||
|
try { |
||||
|
return this.client.put(query.json) |
||||
|
} catch (err) { |
||||
|
console.error("Error updating couchDB document", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async delete(query: { id: string }) { |
||||
|
try { |
||||
|
return await this.client.remove(query.id) |
||||
|
} catch (err) { |
||||
|
console.error("Error deleting couchDB document", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: CouchDBIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,200 +0,0 @@ |
|||||
const AWS = require("aws-sdk") |
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|
||||
const { AWS_REGION } = require("../db/dynamoClient") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://github.com/dabit3/dynamodb-documentclient-cheat-sheet", |
|
||||
description: |
|
||||
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.", |
|
||||
friendlyName: "DynamoDB", |
|
||||
datasource: { |
|
||||
region: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
default: "us-east-1", |
|
||||
}, |
|
||||
accessKeyId: { |
|
||||
type: FIELD_TYPES.PASSWORD, |
|
||||
required: true, |
|
||||
}, |
|
||||
secretAccessKey: { |
|
||||
type: FIELD_TYPES.PASSWORD, |
|
||||
required: true, |
|
||||
}, |
|
||||
endpoint: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: false, |
|
||||
default: "https://dynamodb.us-east-1.amazonaws.com", |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
read: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
readable: true, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
index: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
scan: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
readable: true, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
index: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
get: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
readable: true, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
update: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
delete: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
table: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class DynamoDBIntegration { |
|
||||
constructor(config) { |
|
||||
this.config = config |
|
||||
this.connect() |
|
||||
let options = { |
|
||||
correctClockSkew: true, |
|
||||
} |
|
||||
if (config.endpoint) { |
|
||||
options.endpoint = config.endpoint |
|
||||
} |
|
||||
this.client = new AWS.DynamoDB.DocumentClient({ |
|
||||
correctClockSkew: true, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
end() { |
|
||||
this.disconnect() |
|
||||
} |
|
||||
|
|
||||
connect() { |
|
||||
AWS.config.update(this.config) |
|
||||
} |
|
||||
|
|
||||
disconnect() { |
|
||||
AWS.config.update({ |
|
||||
secretAccessKey: undefined, |
|
||||
accessKeyId: undefined, |
|
||||
region: AWS_REGION, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
const params = { |
|
||||
TableName: query.table, |
|
||||
...query.json, |
|
||||
} |
|
||||
return this.client.put(params).promise() |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
const params = { |
|
||||
TableName: query.table, |
|
||||
...query.json, |
|
||||
} |
|
||||
if (query.index) { |
|
||||
params.IndexName = query.index |
|
||||
} |
|
||||
const response = await this.client.query(params).promise() |
|
||||
if (response.Items) { |
|
||||
return response.Items |
|
||||
} |
|
||||
return response |
|
||||
} |
|
||||
|
|
||||
async scan(query) { |
|
||||
const params = { |
|
||||
TableName: query.table, |
|
||||
...query.json, |
|
||||
} |
|
||||
if (query.index) { |
|
||||
params.IndexName = query.index |
|
||||
} |
|
||||
const response = await this.client.scan(params).promise() |
|
||||
if (response.Items) { |
|
||||
return response.Items |
|
||||
} |
|
||||
return response |
|
||||
} |
|
||||
|
|
||||
async get(query) { |
|
||||
const params = { |
|
||||
TableName: query.table, |
|
||||
...query.json, |
|
||||
} |
|
||||
return this.client.get(params).promise() |
|
||||
} |
|
||||
|
|
||||
async update(query) { |
|
||||
const params = { |
|
||||
TableName: query.table, |
|
||||
...query.json, |
|
||||
} |
|
||||
return this.client.update(params).promise() |
|
||||
} |
|
||||
|
|
||||
async delete(query) { |
|
||||
const params = { |
|
||||
TableName: query.table, |
|
||||
...query.json, |
|
||||
} |
|
||||
return this.client.delete(params).promise() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: DynamoDBIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,211 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
} from "./base/definitions" |
||||
|
|
||||
|
module DynamoModule { |
||||
|
const AWS = require("aws-sdk") |
||||
|
const { AWS_REGION } = require("../db/dynamoClient") |
||||
|
|
||||
|
interface DynamoDBConfig { |
||||
|
region: string |
||||
|
accessKeyId: string |
||||
|
secretAccessKey: string |
||||
|
endpoint: string |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://github.com/dabit3/dynamodb-documentclient-cheat-sheet", |
||||
|
description: |
||||
|
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.", |
||||
|
friendlyName: "DynamoDB", |
||||
|
datasource: { |
||||
|
region: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
default: "us-east-1", |
||||
|
}, |
||||
|
accessKeyId: { |
||||
|
type: DatasourceFieldTypes.PASSWORD, |
||||
|
required: true, |
||||
|
}, |
||||
|
secretAccessKey: { |
||||
|
type: DatasourceFieldTypes.PASSWORD, |
||||
|
required: true, |
||||
|
}, |
||||
|
endpoint: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: false, |
||||
|
default: "https://dynamodb.us-east-1.amazonaws.com", |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
readable: true, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
index: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
scan: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
readable: true, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
index: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
get: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
readable: true, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
update: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
delete: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
table: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class DynamoDBIntegration { |
||||
|
private config: DynamoDBConfig |
||||
|
private client: any |
||||
|
|
||||
|
constructor(config: DynamoDBConfig) { |
||||
|
this.config = config |
||||
|
this.connect() |
||||
|
let options = { |
||||
|
correctClockSkew: true, |
||||
|
endpoint: config.endpoint ? config.endpoint : undefined, |
||||
|
} |
||||
|
this.client = new AWS.DynamoDB.DocumentClient(options) |
||||
|
} |
||||
|
|
||||
|
end() { |
||||
|
this.disconnect() |
||||
|
} |
||||
|
|
||||
|
connect() { |
||||
|
AWS.config.update(this.config) |
||||
|
} |
||||
|
|
||||
|
disconnect() { |
||||
|
AWS.config.update({ |
||||
|
secretAccessKey: undefined, |
||||
|
accessKeyId: undefined, |
||||
|
region: AWS_REGION, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async create(query: { table: string; json: object }) { |
||||
|
const params = { |
||||
|
TableName: query.table, |
||||
|
...query.json, |
||||
|
} |
||||
|
return this.client.put(params).promise() |
||||
|
} |
||||
|
|
||||
|
async read(query: { table: string; json: object; index: null | string }) { |
||||
|
const params = { |
||||
|
TableName: query.table, |
||||
|
IndexName: query.index ? query.index : undefined, |
||||
|
...query.json, |
||||
|
} |
||||
|
if (query.index) { |
||||
|
const response = await this.client.query(params).promise() |
||||
|
if (response.Items) { |
||||
|
return response.Items |
||||
|
} |
||||
|
return response |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async scan(query: { table: string; json: object; index: null | string }) { |
||||
|
const params = { |
||||
|
TableName: query.table, |
||||
|
IndexName: query.index ? query.index : undefined, |
||||
|
...query.json, |
||||
|
} |
||||
|
const response = await this.client.scan(params).promise() |
||||
|
if (response.Items) { |
||||
|
return response.Items |
||||
|
} |
||||
|
return response |
||||
|
} |
||||
|
|
||||
|
async get(query: { table: string; json: object }) { |
||||
|
const params = { |
||||
|
TableName: query.table, |
||||
|
...query.json, |
||||
|
} |
||||
|
return this.client.get(params).promise() |
||||
|
} |
||||
|
|
||||
|
async update(query: { table: string; json: object }) { |
||||
|
const params = { |
||||
|
TableName: query.table, |
||||
|
...query.json, |
||||
|
} |
||||
|
return this.client.update(params).promise() |
||||
|
} |
||||
|
|
||||
|
async delete(query: { table: string; json: object }) { |
||||
|
const params = { |
||||
|
TableName: query.table, |
||||
|
...query.json, |
||||
|
} |
||||
|
return this.client.delete(params).promise() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: DynamoDBIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,139 +0,0 @@ |
|||||
const { Client } = require("@elastic/elasticsearch") |
|
||||
const { QUERY_TYPES, FIELD_TYPES } = require("./Integration") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html", |
|
||||
description: |
|
||||
"Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.", |
|
||||
friendlyName: "ElasticSearch", |
|
||||
datasource: { |
|
||||
url: { |
|
||||
type: "string", |
|
||||
required: true, |
|
||||
default: "http://localhost:9200", |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
index: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
read: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
index: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
update: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
customisable: true, |
|
||||
fields: { |
|
||||
id: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
index: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
delete: { |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
fields: { |
|
||||
index: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
id: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class ElasticSearchIntegration { |
|
||||
constructor(config) { |
|
||||
this.config = config |
|
||||
this.client = new Client({ node: config.url }) |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
const { index, json } = query |
|
||||
|
|
||||
try { |
|
||||
const result = await this.client.index({ |
|
||||
index, |
|
||||
body: json, |
|
||||
}) |
|
||||
return result.body |
|
||||
} catch (err) { |
|
||||
console.error("Error writing to elasticsearch", err) |
|
||||
throw err |
|
||||
} finally { |
|
||||
await this.client.close() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
const { index, json } = query |
|
||||
try { |
|
||||
const result = await this.client.search({ |
|
||||
index: index, |
|
||||
body: json, |
|
||||
}) |
|
||||
return result.body.hits.hits.map(({ _source }) => _source) |
|
||||
} catch (err) { |
|
||||
console.error("Error querying elasticsearch", err) |
|
||||
throw err |
|
||||
} finally { |
|
||||
await this.client.close() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async update(query) { |
|
||||
const { id, index, json } = query |
|
||||
try { |
|
||||
const result = await this.client.update({ |
|
||||
id, |
|
||||
index, |
|
||||
body: json, |
|
||||
}) |
|
||||
return result.body |
|
||||
} catch (err) { |
|
||||
console.error("Error querying elasticsearch", err) |
|
||||
throw err |
|
||||
} finally { |
|
||||
await this.client.close() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async delete(query) { |
|
||||
try { |
|
||||
const result = await this.client.delete(query) |
|
||||
return result.body |
|
||||
} catch (err) { |
|
||||
console.error("Error deleting from elasticsearch", err) |
|
||||
throw err |
|
||||
} finally { |
|
||||
await this.client.close() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: ElasticSearchIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,153 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
} from "./base/definitions" |
||||
|
|
||||
|
module ElasticsearchModule { |
||||
|
const { Client } = require("@elastic/elasticsearch") |
||||
|
|
||||
|
interface ElasticsearchConfig { |
||||
|
url: string |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html", |
||||
|
description: |
||||
|
"Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.", |
||||
|
friendlyName: "ElasticSearch", |
||||
|
datasource: { |
||||
|
url: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
default: "http://localhost:9200", |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
index: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
index: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
update: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
customisable: true, |
||||
|
fields: { |
||||
|
id: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
index: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
delete: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
fields: { |
||||
|
index: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
id: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class ElasticSearchIntegration { |
||||
|
private config: ElasticsearchConfig |
||||
|
private client: any |
||||
|
|
||||
|
constructor(config: ElasticsearchConfig) { |
||||
|
this.config = config |
||||
|
this.client = new Client({ node: config.url }) |
||||
|
} |
||||
|
|
||||
|
async create(query: { index: string; json: object }) { |
||||
|
const { index, json } = query |
||||
|
|
||||
|
try { |
||||
|
const result = await this.client.index({ |
||||
|
index, |
||||
|
body: json, |
||||
|
}) |
||||
|
return result.body |
||||
|
} catch (err) { |
||||
|
console.error("Error writing to elasticsearch", err) |
||||
|
throw err |
||||
|
} finally { |
||||
|
await this.client.close() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async read(query: { index: string; json: object }) { |
||||
|
const { index, json } = query |
||||
|
try { |
||||
|
const result = await this.client.search({ |
||||
|
index: index, |
||||
|
body: json, |
||||
|
}) |
||||
|
return result.body.hits.hits.map(({ _source }: any) => _source) |
||||
|
} catch (err) { |
||||
|
console.error("Error querying elasticsearch", err) |
||||
|
throw err |
||||
|
} finally { |
||||
|
await this.client.close() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async update(query: { id: string; index: string; json: object }) { |
||||
|
const { id, index, json } = query |
||||
|
try { |
||||
|
const result = await this.client.update({ |
||||
|
id, |
||||
|
index, |
||||
|
body: json, |
||||
|
}) |
||||
|
return result.body |
||||
|
} catch (err) { |
||||
|
console.error("Error querying elasticsearch", err) |
||||
|
throw err |
||||
|
} finally { |
||||
|
await this.client.close() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async delete(query: object) { |
||||
|
try { |
||||
|
const result = await this.client.delete(query) |
||||
|
return result.body |
||||
|
} catch (err) { |
||||
|
console.error("Error deleting from elasticsearch", err) |
||||
|
throw err |
||||
|
} finally { |
||||
|
await this.client.close() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: ElasticSearchIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,123 +0,0 @@ |
|||||
const sqlServer = require("mssql") |
|
||||
const { FIELD_TYPES } = require("./Integration") |
|
||||
const Sql = require("./base/sql") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://github.com/tediousjs/node-mssql", |
|
||||
description: |
|
||||
"Microsoft SQL Server is a relational database management system developed by Microsoft. ", |
|
||||
friendlyName: "MS SQL Server", |
|
||||
datasource: { |
|
||||
user: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
default: "localhost", |
|
||||
}, |
|
||||
password: { |
|
||||
type: FIELD_TYPES.PASSWORD, |
|
||||
required: true, |
|
||||
}, |
|
||||
server: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "localhost", |
|
||||
}, |
|
||||
port: { |
|
||||
type: FIELD_TYPES.NUMBER, |
|
||||
required: false, |
|
||||
default: 1433, |
|
||||
}, |
|
||||
database: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "root", |
|
||||
}, |
|
||||
encrypt: { |
|
||||
type: FIELD_TYPES.BOOLEAN, |
|
||||
default: true, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
read: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
update: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
delete: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
async function internalQuery(client, query) { |
|
||||
const sql = typeof query === "string" ? query : query.sql |
|
||||
const bindings = typeof query === "string" ? {} : query.bindings |
|
||||
try { |
|
||||
return await client.query(sql, bindings) |
|
||||
} catch (err) { |
|
||||
throw new Error(err) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class SqlServerIntegration extends Sql { |
|
||||
static pool |
|
||||
|
|
||||
constructor(config) { |
|
||||
super("mssql") |
|
||||
this.config = config |
|
||||
this.config.options = { |
|
||||
encrypt: this.config.encrypt, |
|
||||
} |
|
||||
delete this.config.encrypt |
|
||||
if (!this.pool) { |
|
||||
this.pool = new sqlServer.ConnectionPool(this.config) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async connect() { |
|
||||
try { |
|
||||
const client = await this.pool.connect() |
|
||||
this.client = client.request() |
|
||||
} catch (err) { |
|
||||
throw new Error(err) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
await this.connect() |
|
||||
const response = await internalQuery(this.client, query) |
|
||||
return response.recordset |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
await this.connect() |
|
||||
const response = await internalQuery(this.client, query) |
|
||||
return response.recordset || [{ created: true }] |
|
||||
} |
|
||||
|
|
||||
async update(query) { |
|
||||
await this.connect() |
|
||||
const response = await internalQuery(this.client, query) |
|
||||
return response.recordset || [{ updated: true }] |
|
||||
} |
|
||||
|
|
||||
async delete(query) { |
|
||||
await this.connect() |
|
||||
const response = await internalQuery(this.client, query) |
|
||||
return response.recordset || [{ deleted: true }] |
|
||||
} |
|
||||
|
|
||||
async query(json) { |
|
||||
const operation = this._operation(json).toLowerCase() |
|
||||
const input = this._query(json) |
|
||||
const response = await internalQuery(this.client, input) |
|
||||
return response.recordset ? response.recordset : [{ [operation]: true }] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: SqlServerIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,144 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
QueryJson, |
||||
|
SqlQuery, |
||||
|
} from "./base/definitions" |
||||
|
import { getSqlQuery } from "./utils" |
||||
|
|
||||
|
module MSSQLModule { |
||||
|
const sqlServer = require("mssql") |
||||
|
const Sql = require("./base/sql") |
||||
|
|
||||
|
interface MSSQLConfig { |
||||
|
user: string |
||||
|
password: string |
||||
|
server: string |
||||
|
port: number |
||||
|
database: string |
||||
|
encrypt?: boolean |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://github.com/tediousjs/node-mssql", |
||||
|
description: |
||||
|
"Microsoft SQL Server is a relational database management system developed by Microsoft. ", |
||||
|
friendlyName: "MS SQL Server", |
||||
|
datasource: { |
||||
|
user: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
default: "localhost", |
||||
|
}, |
||||
|
password: { |
||||
|
type: DatasourceFieldTypes.PASSWORD, |
||||
|
required: true, |
||||
|
}, |
||||
|
server: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "localhost", |
||||
|
}, |
||||
|
port: { |
||||
|
type: DatasourceFieldTypes.NUMBER, |
||||
|
required: false, |
||||
|
default: 1433, |
||||
|
}, |
||||
|
database: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "root", |
||||
|
}, |
||||
|
encrypt: { |
||||
|
type: DatasourceFieldTypes.BOOLEAN, |
||||
|
default: true, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
update: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
delete: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
async function internalQuery(client: any, query: SqlQuery) { |
||||
|
try { |
||||
|
return await client.query(query.sql, query.bindings || {}) |
||||
|
} catch (err) { |
||||
|
throw new Error(err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class SqlServerIntegration extends Sql { |
||||
|
private readonly config: MSSQLConfig |
||||
|
static pool: any |
||||
|
|
||||
|
constructor(config: MSSQLConfig) { |
||||
|
super("mssql") |
||||
|
this.config = config |
||||
|
const clientCfg = { |
||||
|
...this.config, |
||||
|
options: { |
||||
|
encrypt: this.config.encrypt, |
||||
|
}, |
||||
|
} |
||||
|
delete clientCfg.encrypt |
||||
|
if (!this.pool) { |
||||
|
this.pool = new sqlServer.ConnectionPool(clientCfg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async connect() { |
||||
|
try { |
||||
|
const client = await this.pool.connect() |
||||
|
this.client = client.request() |
||||
|
} catch (err) { |
||||
|
throw new Error(err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async read(query: SqlQuery | string) { |
||||
|
await this.connect() |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.recordset |
||||
|
} |
||||
|
|
||||
|
async create(query: SqlQuery | string) { |
||||
|
await this.connect() |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.recordset || [{ created: true }] |
||||
|
} |
||||
|
|
||||
|
async update(query: SqlQuery | string) { |
||||
|
await this.connect() |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.recordset || [{ updated: true }] |
||||
|
} |
||||
|
|
||||
|
async delete(query: SqlQuery | string) { |
||||
|
await this.connect() |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.recordset || [{ deleted: true }] |
||||
|
} |
||||
|
|
||||
|
async query(json: QueryJson) { |
||||
|
const operation = this._operation(json).toLowerCase() |
||||
|
const input = this._query(json) |
||||
|
const response = await internalQuery(this.client, input) |
||||
|
return response.recordset ? response.recordset : [{ [operation]: true }] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: SqlServerIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,78 +0,0 @@ |
|||||
const { MongoClient } = require("mongodb") |
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://github.com/mongodb/node-mongodb-native", |
|
||||
friendlyName: "MongoDB", |
|
||||
description: |
|
||||
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.", |
|
||||
datasource: { |
|
||||
connectionString: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
default: "mongodb://localhost:27017", |
|
||||
}, |
|
||||
db: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
collection: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: QUERY_TYPES.JSON, |
|
||||
}, |
|
||||
read: { |
|
||||
type: QUERY_TYPES.JSON, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class MongoIntegration { |
|
||||
constructor(config) { |
|
||||
this.config = config |
|
||||
this.client = new MongoClient(config.connectionString) |
|
||||
} |
|
||||
|
|
||||
async connect() { |
|
||||
return this.client.connect() |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
try { |
|
||||
await this.connect() |
|
||||
const db = this.client.db(this.config.db) |
|
||||
const collection = db.collection(this.config.collection) |
|
||||
const result = await collection.insertOne(query.json) |
|
||||
return result |
|
||||
} catch (err) { |
|
||||
console.error("Error writing to mongodb", err) |
|
||||
throw err |
|
||||
} finally { |
|
||||
await this.client.close() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
try { |
|
||||
await this.connect() |
|
||||
const db = this.client.db(this.config.db) |
|
||||
const collection = db.collection(this.config.collection) |
|
||||
const result = await collection.find(query.json).toArray() |
|
||||
return result |
|
||||
} catch (err) { |
|
||||
console.error("Error querying mongodb", err) |
|
||||
throw err |
|
||||
} finally { |
|
||||
await this.client.close() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: MongoIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,92 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
} from "./base/definitions" |
||||
|
|
||||
|
module MongoDBModule { |
||||
|
const { MongoClient } = require("mongodb") |
||||
|
|
||||
|
interface MongoDBConfig { |
||||
|
connectionString: string |
||||
|
db: string |
||||
|
collection: string |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://github.com/mongodb/node-mongodb-native", |
||||
|
friendlyName: "MongoDB", |
||||
|
description: |
||||
|
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.", |
||||
|
datasource: { |
||||
|
connectionString: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
default: "mongodb://localhost:27017", |
||||
|
}, |
||||
|
db: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
collection: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.JSON, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class MongoIntegration { |
||||
|
private config: MongoDBConfig |
||||
|
private client: any |
||||
|
|
||||
|
constructor(config: MongoDBConfig) { |
||||
|
this.config = config |
||||
|
this.client = new MongoClient(config.connectionString) |
||||
|
} |
||||
|
|
||||
|
async connect() { |
||||
|
return this.client.connect() |
||||
|
} |
||||
|
|
||||
|
async create(query: { json: object }) { |
||||
|
try { |
||||
|
await this.connect() |
||||
|
const db = this.client.db(this.config.db) |
||||
|
const collection = db.collection(this.config.collection) |
||||
|
return collection.insertOne(query.json) |
||||
|
} catch (err) { |
||||
|
console.error("Error writing to mongodb", err) |
||||
|
throw err |
||||
|
} finally { |
||||
|
await this.client.close() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async read(query: { json: object }) { |
||||
|
try { |
||||
|
await this.connect() |
||||
|
const db = this.client.db(this.config.db) |
||||
|
const collection = db.collection(this.config.collection) |
||||
|
return collection.find(query.json).toArray() |
||||
|
} catch (err) { |
||||
|
console.error("Error querying mongodb", err) |
||||
|
throw err |
||||
|
} finally { |
||||
|
await this.client.close() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: MongoIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,225 +0,0 @@ |
|||||
const mysql = require("mysql") |
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|
||||
const Sql = require("./base/sql") |
|
||||
const { buildExternalTableId, convertType } = require("./utils") |
|
||||
const { FieldTypes } = require("../constants") |
|
||||
const { Operation } = require("./base/constants") |
|
||||
|
|
||||
const TYPE_MAP = { |
|
||||
text: FieldTypes.LONGFORM, |
|
||||
blob: FieldTypes.LONGFORM, |
|
||||
enum: FieldTypes.STRING, |
|
||||
varchar: FieldTypes.STRING, |
|
||||
int: FieldTypes.NUMBER, |
|
||||
numeric: FieldTypes.NUMBER, |
|
||||
bigint: FieldTypes.NUMBER, |
|
||||
mediumint: FieldTypes.NUMBER, |
|
||||
decimal: FieldTypes.NUMBER, |
|
||||
dec: FieldTypes.NUMBER, |
|
||||
double: FieldTypes.NUMBER, |
|
||||
real: FieldTypes.NUMBER, |
|
||||
fixed: FieldTypes.NUMBER, |
|
||||
smallint: FieldTypes.NUMBER, |
|
||||
timestamp: FieldTypes.DATETIME, |
|
||||
date: FieldTypes.DATETIME, |
|
||||
datetime: FieldTypes.DATETIME, |
|
||||
time: FieldTypes.DATETIME, |
|
||||
tinyint: FieldTypes.BOOLEAN, |
|
||||
json: FIELD_TYPES.JSON, |
|
||||
} |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://github.com/mysqljs/mysql", |
|
||||
plus: true, |
|
||||
friendlyName: "MySQL", |
|
||||
description: |
|
||||
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ", |
|
||||
datasource: { |
|
||||
host: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "localhost", |
|
||||
required: true, |
|
||||
}, |
|
||||
port: { |
|
||||
type: FIELD_TYPES.NUMBER, |
|
||||
default: 3306, |
|
||||
required: false, |
|
||||
}, |
|
||||
user: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "root", |
|
||||
required: true, |
|
||||
}, |
|
||||
password: { |
|
||||
type: FIELD_TYPES.PASSWORD, |
|
||||
default: "root", |
|
||||
required: true, |
|
||||
}, |
|
||||
database: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
required: true, |
|
||||
}, |
|
||||
ssl: { |
|
||||
type: FIELD_TYPES.OBJECT, |
|
||||
required: false, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: QUERY_TYPES.SQL, |
|
||||
}, |
|
||||
read: { |
|
||||
type: QUERY_TYPES.SQL, |
|
||||
}, |
|
||||
update: { |
|
||||
type: QUERY_TYPES.SQL, |
|
||||
}, |
|
||||
delete: { |
|
||||
type: QUERY_TYPES.SQL, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
function internalQuery(client, query, connect = true) { |
|
||||
const sql = typeof query === "string" ? query : query.sql |
|
||||
const bindings = typeof query === "string" ? {} : query.bindings |
|
||||
// Node MySQL is callback based, so we must wrap our call in a promise
|
|
||||
return new Promise((resolve, reject) => { |
|
||||
if (connect) { |
|
||||
client.connect() |
|
||||
} |
|
||||
return client.query(sql, bindings, (error, results) => { |
|
||||
if (error) { |
|
||||
reject(error) |
|
||||
} else { |
|
||||
resolve(results) |
|
||||
} |
|
||||
if (connect) { |
|
||||
client.end() |
|
||||
} |
|
||||
}) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
class MySQLIntegration extends Sql { |
|
||||
constructor(config) { |
|
||||
super("mysql") |
|
||||
this.config = config |
|
||||
if (config.ssl && Object.keys(config.ssl).length === 0) { |
|
||||
delete config.ssl |
|
||||
} |
|
||||
this.client = mysql.createConnection(config) |
|
||||
} |
|
||||
|
|
||||
async buildSchema(datasourceId) { |
|
||||
const tables = {} |
|
||||
const database = this.config.database |
|
||||
this.client.connect() |
|
||||
|
|
||||
// get the tables first
|
|
||||
const tablesResp = await internalQuery(this.client, "SHOW TABLES;", false) |
|
||||
const tableNames = tablesResp.map(obj => obj[`Tables_in_${database}`]) |
|
||||
for (let tableName of tableNames) { |
|
||||
const primaryKeys = [] |
|
||||
const schema = {} |
|
||||
const descResp = await internalQuery( |
|
||||
this.client, |
|
||||
`DESCRIBE ${tableName};`, |
|
||||
false |
|
||||
) |
|
||||
for (let column of descResp) { |
|
||||
const columnName = column.Field |
|
||||
if (column.Key === "PRI") { |
|
||||
primaryKeys.push(columnName) |
|
||||
} |
|
||||
const constraints = {} |
|
||||
if (column.Null !== "YES") { |
|
||||
constraints.required = true |
|
||||
} |
|
||||
schema[columnName] = { |
|
||||
name: columnName, |
|
||||
type: convertType(column.Type, TYPE_MAP), |
|
||||
constraints, |
|
||||
} |
|
||||
} |
|
||||
// for now just default to first column
|
|
||||
if (primaryKeys.length === 0) { |
|
||||
primaryKeys.push(descResp[0].Field) |
|
||||
} |
|
||||
if (!tables[tableName]) { |
|
||||
tables[tableName] = { |
|
||||
_id: buildExternalTableId(datasourceId, tableName), |
|
||||
primary: primaryKeys, |
|
||||
name: tableName, |
|
||||
schema, |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.client.end() |
|
||||
this.tables = tables |
|
||||
} |
|
||||
|
|
||||
async create(query) { |
|
||||
const results = await internalQuery(this.client, query) |
|
||||
return results.length ? results : [{ created: true }] |
|
||||
} |
|
||||
|
|
||||
read(query) { |
|
||||
return internalQuery(this.client, query) |
|
||||
} |
|
||||
|
|
||||
async update(query) { |
|
||||
const results = await internalQuery(this.client, query) |
|
||||
return results.length ? results : [{ updated: true }] |
|
||||
} |
|
||||
|
|
||||
async delete(query) { |
|
||||
const results = await internalQuery(this.client, query) |
|
||||
return results.length ? results : [{ deleted: true }] |
|
||||
} |
|
||||
|
|
||||
async getReturningRow(json) { |
|
||||
if (!json.extra.idFilter) { |
|
||||
return {} |
|
||||
} |
|
||||
const input = this._query({ |
|
||||
endpoint: { |
|
||||
...json.endpoint, |
|
||||
operation: Operation.READ, |
|
||||
}, |
|
||||
fields: [], |
|
||||
filters: json.extra.idFilter, |
|
||||
paginate: { |
|
||||
limit: 1, |
|
||||
}, |
|
||||
}) |
|
||||
return internalQuery(this.client, input, false) |
|
||||
} |
|
||||
|
|
||||
async query(json) { |
|
||||
const operation = this._operation(json) |
|
||||
this.client.connect() |
|
||||
const input = this._query(json, { disableReturning: true }) |
|
||||
let row |
|
||||
// need to manage returning, a feature mySQL can't do
|
|
||||
if (operation === Operation.DELETE) { |
|
||||
row = this.getReturningRow(json) |
|
||||
} |
|
||||
const results = await internalQuery(this.client, input, false) |
|
||||
// same as delete, manage returning
|
|
||||
if (operation === Operation.CREATE || operation === Operation.UPDATE) { |
|
||||
row = this.getReturningRow(json) |
|
||||
} |
|
||||
this.client.end() |
|
||||
if (operation !== Operation.READ) { |
|
||||
return row |
|
||||
} |
|
||||
return results.length ? results : [{ [operation.toLowerCase()]: true }] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: MySQLIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,258 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
Operation, |
||||
|
QueryJson, |
||||
|
SqlQuery, |
||||
|
} from "./base/definitions" |
||||
|
import { getSqlQuery } from "./utils" |
||||
|
|
||||
|
module MySQLModule { |
||||
|
const mysql = require("mysql") |
||||
|
const Sql = require("./base/sql") |
||||
|
const { buildExternalTableId, convertType } = require("./utils") |
||||
|
const { FieldTypes } = require("../constants") |
||||
|
|
||||
|
interface MySQLConfig { |
||||
|
host: string |
||||
|
port: number |
||||
|
user: string |
||||
|
password: string |
||||
|
database: string |
||||
|
ssl?: object |
||||
|
} |
||||
|
|
||||
|
const TYPE_MAP = { |
||||
|
text: FieldTypes.LONGFORM, |
||||
|
blob: FieldTypes.LONGFORM, |
||||
|
enum: FieldTypes.STRING, |
||||
|
varchar: FieldTypes.STRING, |
||||
|
int: FieldTypes.NUMBER, |
||||
|
numeric: FieldTypes.NUMBER, |
||||
|
bigint: FieldTypes.NUMBER, |
||||
|
mediumint: FieldTypes.NUMBER, |
||||
|
decimal: FieldTypes.NUMBER, |
||||
|
dec: FieldTypes.NUMBER, |
||||
|
double: FieldTypes.NUMBER, |
||||
|
real: FieldTypes.NUMBER, |
||||
|
fixed: FieldTypes.NUMBER, |
||||
|
smallint: FieldTypes.NUMBER, |
||||
|
timestamp: FieldTypes.DATETIME, |
||||
|
date: FieldTypes.DATETIME, |
||||
|
datetime: FieldTypes.DATETIME, |
||||
|
time: FieldTypes.DATETIME, |
||||
|
tinyint: FieldTypes.BOOLEAN, |
||||
|
json: DatasourceFieldTypes.JSON, |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://github.com/mysqljs/mysql", |
||||
|
plus: true, |
||||
|
friendlyName: "MySQL", |
||||
|
description: |
||||
|
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ", |
||||
|
datasource: { |
||||
|
host: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "localhost", |
||||
|
required: true, |
||||
|
}, |
||||
|
port: { |
||||
|
type: DatasourceFieldTypes.NUMBER, |
||||
|
default: 3306, |
||||
|
required: false, |
||||
|
}, |
||||
|
user: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "root", |
||||
|
required: true, |
||||
|
}, |
||||
|
password: { |
||||
|
type: DatasourceFieldTypes.PASSWORD, |
||||
|
default: "root", |
||||
|
required: true, |
||||
|
}, |
||||
|
database: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
required: true, |
||||
|
}, |
||||
|
ssl: { |
||||
|
type: DatasourceFieldTypes.OBJECT, |
||||
|
required: false, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
update: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
delete: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
function internalQuery( |
||||
|
client: any, |
||||
|
query: SqlQuery, |
||||
|
connect: boolean = true |
||||
|
): Promise<any[]> { |
||||
|
// Node MySQL is callback based, so we must wrap our call in a promise
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
if (connect) { |
||||
|
client.connect() |
||||
|
} |
||||
|
return client.query( |
||||
|
query.sql, |
||||
|
query.bindings || {}, |
||||
|
(error: any, results: object[]) => { |
||||
|
if (error) { |
||||
|
reject(error) |
||||
|
} else { |
||||
|
resolve(results) |
||||
|
} |
||||
|
if (connect) { |
||||
|
client.end() |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
class MySQLIntegration extends Sql { |
||||
|
private config: MySQLConfig |
||||
|
private readonly client: any |
||||
|
|
||||
|
constructor(config: MySQLConfig) { |
||||
|
super("mysql") |
||||
|
this.config = config |
||||
|
if (config.ssl && Object.keys(config.ssl).length === 0) { |
||||
|
delete config.ssl |
||||
|
} |
||||
|
this.client = mysql.createConnection(config) |
||||
|
} |
||||
|
|
||||
|
async buildSchema(datasourceId: string) { |
||||
|
const tables: any = {} |
||||
|
const database = this.config.database |
||||
|
this.client.connect() |
||||
|
|
||||
|
// get the tables first
|
||||
|
const tablesResp = await internalQuery( |
||||
|
this.client, |
||||
|
{ sql: "SHOW TABLES;" }, |
||||
|
false |
||||
|
) |
||||
|
const tableNames = tablesResp.map( |
||||
|
(obj: any) => obj[`Tables_in_${database}`] |
||||
|
) |
||||
|
for (let tableName of tableNames) { |
||||
|
const primaryKeys = [] |
||||
|
const schema: any = {} |
||||
|
const descResp = await internalQuery( |
||||
|
this.client, |
||||
|
{ sql: `DESCRIBE ${tableName};` }, |
||||
|
false |
||||
|
) |
||||
|
for (let column of descResp) { |
||||
|
const columnName = column.Field |
||||
|
if (column.Key === "PRI") { |
||||
|
primaryKeys.push(columnName) |
||||
|
} |
||||
|
const constraints = { |
||||
|
required: column.Null !== "YES", |
||||
|
} |
||||
|
schema[columnName] = { |
||||
|
name: columnName, |
||||
|
type: convertType(column.Type, TYPE_MAP), |
||||
|
constraints, |
||||
|
} |
||||
|
} |
||||
|
// for now just default to first column
|
||||
|
if (primaryKeys.length === 0) { |
||||
|
primaryKeys.push(descResp[0].Field) |
||||
|
} |
||||
|
if (!tables[tableName]) { |
||||
|
tables[tableName] = { |
||||
|
_id: buildExternalTableId(datasourceId, tableName), |
||||
|
primary: primaryKeys, |
||||
|
name: tableName, |
||||
|
schema, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.client.end() |
||||
|
this.tables = tables |
||||
|
} |
||||
|
|
||||
|
async create(query: SqlQuery | string) { |
||||
|
const results = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return results.length ? results : [{ created: true }] |
||||
|
} |
||||
|
|
||||
|
read(query: SqlQuery | string) { |
||||
|
return internalQuery(this.client, getSqlQuery(query)) |
||||
|
} |
||||
|
|
||||
|
async update(query: SqlQuery | string) { |
||||
|
const results = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return results.length ? results : [{ updated: true }] |
||||
|
} |
||||
|
|
||||
|
async delete(query: SqlQuery | string) { |
||||
|
const results = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return results.length ? results : [{ deleted: true }] |
||||
|
} |
||||
|
|
||||
|
async getReturningRow(json: QueryJson) { |
||||
|
if (!json.extra.idFilter) { |
||||
|
return {} |
||||
|
} |
||||
|
const input = this._query({ |
||||
|
endpoint: { |
||||
|
...json.endpoint, |
||||
|
operation: Operation.READ, |
||||
|
}, |
||||
|
fields: [], |
||||
|
filters: json.extra.idFilter, |
||||
|
paginate: { |
||||
|
limit: 1, |
||||
|
}, |
||||
|
}) |
||||
|
return internalQuery(this.client, input, false) |
||||
|
} |
||||
|
|
||||
|
async query(json: QueryJson) { |
||||
|
const operation = this._operation(json) |
||||
|
this.client.connect() |
||||
|
const input = this._query(json, { disableReturning: true }) |
||||
|
let row |
||||
|
// need to manage returning, a feature mySQL can't do
|
||||
|
if (operation === "awdawd") { |
||||
|
row = this.getReturningRow(json) |
||||
|
} |
||||
|
const results = await internalQuery(this.client, input, false) |
||||
|
// same as delete, manage returning
|
||||
|
if (operation === Operation.CREATE || operation === Operation.UPDATE) { |
||||
|
row = this.getReturningRow(json) |
||||
|
} |
||||
|
this.client.end() |
||||
|
if (operation !== Operation.READ) { |
||||
|
return row |
||||
|
} |
||||
|
return results.length ? results : [{ [operation.toLowerCase()]: true }] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: MySQLIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,191 +0,0 @@ |
|||||
const { Pool } = require("pg") |
|
||||
const { FIELD_TYPES } = require("./Integration") |
|
||||
const Sql = require("./base/sql") |
|
||||
const { FieldTypes } = require("../constants") |
|
||||
const { buildExternalTableId, convertType } = require("./utils") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://node-postgres.com", |
|
||||
plus: true, |
|
||||
friendlyName: "PostgreSQL", |
|
||||
description: |
|
||||
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.", |
|
||||
datasource: { |
|
||||
host: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "localhost", |
|
||||
required: true, |
|
||||
}, |
|
||||
port: { |
|
||||
type: FIELD_TYPES.NUMBER, |
|
||||
required: true, |
|
||||
default: 5432, |
|
||||
}, |
|
||||
database: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "postgres", |
|
||||
required: true, |
|
||||
}, |
|
||||
user: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "root", |
|
||||
required: true, |
|
||||
}, |
|
||||
password: { |
|
||||
type: FIELD_TYPES.PASSWORD, |
|
||||
default: "root", |
|
||||
required: true, |
|
||||
}, |
|
||||
ssl: { |
|
||||
type: FIELD_TYPES.BOOLEAN, |
|
||||
default: false, |
|
||||
required: false, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
read: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
update: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
delete: { |
|
||||
type: "sql", |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
const TYPE_MAP = { |
|
||||
text: FieldTypes.LONGFORM, |
|
||||
varchar: FieldTypes.STRING, |
|
||||
integer: FieldTypes.NUMBER, |
|
||||
bigint: FieldTypes.NUMBER, |
|
||||
decimal: FieldTypes.NUMBER, |
|
||||
smallint: FieldTypes.NUMBER, |
|
||||
timestamp: FieldTypes.DATETIME, |
|
||||
time: FieldTypes.DATETIME, |
|
||||
boolean: FieldTypes.BOOLEAN, |
|
||||
json: FIELD_TYPES.JSON, |
|
||||
} |
|
||||
|
|
||||
async function internalQuery(client, query) { |
|
||||
const sql = typeof query === "string" ? query : query.sql |
|
||||
const bindings = typeof query === "string" ? {} : query.bindings |
|
||||
try { |
|
||||
return await client.query(sql, bindings) |
|
||||
} catch (err) { |
|
||||
throw new Error(err) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class PostgresIntegration extends Sql { |
|
||||
static pool |
|
||||
|
|
||||
COLUMNS_SQL = |
|
||||
"select * from information_schema.columns where table_schema = 'public'" |
|
||||
|
|
||||
PRIMARY_KEYS_SQL = ` |
|
||||
select tc.table_schema, tc.table_name, kc.column_name as primary_key |
|
||||
from information_schema.table_constraints tc |
|
||||
join |
|
||||
information_schema.key_column_usage kc on kc.table_name = tc.table_name |
|
||||
and kc.table_schema = tc.table_schema |
|
||||
and kc.constraint_name = tc.constraint_name |
|
||||
where tc.constraint_type = 'PRIMARY KEY'; |
|
||||
` |
|
||||
|
|
||||
constructor(config) { |
|
||||
super("pg") |
|
||||
this.config = config |
|
||||
if (this.config.ssl) { |
|
||||
this.config.ssl = { |
|
||||
rejectUnauthorized: true, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!this.pool) { |
|
||||
this.pool = new Pool(this.config) |
|
||||
} |
|
||||
|
|
||||
this.client = this.pool |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Fetches the tables from the postgres table and assigns them to the datasource. |
|
||||
* @param {*} datasourceId - datasourceId to fetch |
|
||||
*/ |
|
||||
async buildSchema(datasourceId) { |
|
||||
let tableKeys = {} |
|
||||
try { |
|
||||
const primaryKeysResponse = await this.client.query(this.PRIMARY_KEYS_SQL) |
|
||||
for (let table of primaryKeysResponse.rows) { |
|
||||
const tableName = table.table_name |
|
||||
if (!tableKeys[tableName]) { |
|
||||
tableKeys[tableName] = [] |
|
||||
} |
|
||||
tableKeys[tableName].push(table.column_name || table.primary_key) |
|
||||
} |
|
||||
} catch (err) { |
|
||||
tableKeys = {} |
|
||||
} |
|
||||
|
|
||||
const columnsResponse = await this.client.query(this.COLUMNS_SQL) |
|
||||
const tables = {} |
|
||||
|
|
||||
for (let column of columnsResponse.rows) { |
|
||||
const tableName = column.table_name |
|
||||
const columnName = column.column_name |
|
||||
|
|
||||
// table key doesn't exist yet
|
|
||||
if (!tables[tableName]) { |
|
||||
tables[tableName] = { |
|
||||
_id: buildExternalTableId(datasourceId, tableName), |
|
||||
primary: tableKeys[tableName] || ["id"], |
|
||||
name: tableName, |
|
||||
schema: {}, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
tables[tableName].schema[columnName] = { |
|
||||
name: columnName, |
|
||||
type: convertType(column.data_type, TYPE_MAP), |
|
||||
} |
|
||||
} |
|
||||
this.tables = tables |
|
||||
} |
|
||||
|
|
||||
async create(sql) { |
|
||||
const response = await internalQuery(this.client, sql) |
|
||||
return response.rows.length ? response.rows : [{ created: true }] |
|
||||
} |
|
||||
|
|
||||
async read(sql) { |
|
||||
const response = await internalQuery(this.client, sql) |
|
||||
return response.rows |
|
||||
} |
|
||||
|
|
||||
async update(sql) { |
|
||||
const response = await internalQuery(this.client, sql) |
|
||||
return response.rows.length ? response.rows : [{ updated: true }] |
|
||||
} |
|
||||
|
|
||||
async delete(sql) { |
|
||||
const response = await internalQuery(this.client, sql) |
|
||||
return response.rows.length ? response.rows : [{ deleted: true }] |
|
||||
} |
|
||||
|
|
||||
async query(json) { |
|
||||
const operation = this._operation(json).toLowerCase() |
|
||||
const input = this._query(json) |
|
||||
const response = await internalQuery(this.client, input) |
|
||||
return response.rows.length ? response.rows : [{ [operation]: true }] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: PostgresIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,213 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
QueryJson, |
||||
|
SqlQuery, |
||||
|
} from "./base/definitions" |
||||
|
import { Table } from "../constants/definitions" |
||||
|
import { getSqlQuery } from "./utils" |
||||
|
|
||||
|
module PostgresModule { |
||||
|
const { Pool } = require("pg") |
||||
|
const Sql = require("./base/sql") |
||||
|
const { FieldTypes } = require("../constants") |
||||
|
const { buildExternalTableId, convertType } = require("./utils") |
||||
|
|
||||
|
interface PostgresConfig { |
||||
|
host: string |
||||
|
port: number |
||||
|
database: string |
||||
|
user: string |
||||
|
password: string |
||||
|
ssl?: boolean |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://node-postgres.com", |
||||
|
plus: true, |
||||
|
friendlyName: "PostgreSQL", |
||||
|
description: |
||||
|
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.", |
||||
|
datasource: { |
||||
|
host: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "localhost", |
||||
|
required: true, |
||||
|
}, |
||||
|
port: { |
||||
|
type: DatasourceFieldTypes.NUMBER, |
||||
|
required: true, |
||||
|
default: 5432, |
||||
|
}, |
||||
|
database: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "postgres", |
||||
|
required: true, |
||||
|
}, |
||||
|
user: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "root", |
||||
|
required: true, |
||||
|
}, |
||||
|
password: { |
||||
|
type: DatasourceFieldTypes.PASSWORD, |
||||
|
default: "root", |
||||
|
required: true, |
||||
|
}, |
||||
|
ssl: { |
||||
|
type: DatasourceFieldTypes.BOOLEAN, |
||||
|
default: false, |
||||
|
required: false, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
read: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
update: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
delete: { |
||||
|
type: QueryTypes.SQL, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
const TYPE_MAP = { |
||||
|
text: FieldTypes.LONGFORM, |
||||
|
varchar: FieldTypes.STRING, |
||||
|
integer: FieldTypes.NUMBER, |
||||
|
bigint: FieldTypes.NUMBER, |
||||
|
decimal: FieldTypes.NUMBER, |
||||
|
smallint: FieldTypes.NUMBER, |
||||
|
timestamp: FieldTypes.DATETIME, |
||||
|
time: FieldTypes.DATETIME, |
||||
|
boolean: FieldTypes.BOOLEAN, |
||||
|
json: FieldTypes.JSON, |
||||
|
} |
||||
|
|
||||
|
async function internalQuery(client: any, query: SqlQuery) { |
||||
|
try { |
||||
|
return await client.query(query.sql, query.bindings || {}) |
||||
|
} catch (err) { |
||||
|
throw new Error(err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class PostgresIntegration extends Sql { |
||||
|
static pool: any |
||||
|
private readonly client: any |
||||
|
private readonly config: PostgresConfig |
||||
|
|
||||
|
COLUMNS_SQL = |
||||
|
"select * from information_schema.columns where table_schema = 'public'" |
||||
|
|
||||
|
PRIMARY_KEYS_SQL = ` |
||||
|
select tc.table_schema, tc.table_name, kc.column_name as primary_key |
||||
|
from information_schema.table_constraints tc |
||||
|
join |
||||
|
information_schema.key_column_usage kc on kc.table_name = tc.table_name |
||||
|
and kc.table_schema = tc.table_schema |
||||
|
and kc.constraint_name = tc.constraint_name |
||||
|
where tc.constraint_type = 'PRIMARY KEY'; |
||||
|
` |
||||
|
|
||||
|
constructor(config: PostgresConfig) { |
||||
|
super("pg") |
||||
|
this.config = config |
||||
|
|
||||
|
let newConfig = { |
||||
|
...this.config, |
||||
|
ssl: this.config.ssl ? { rejectUnauthorized: true } : undefined, |
||||
|
} |
||||
|
if (!this.pool) { |
||||
|
this.pool = new Pool(newConfig) |
||||
|
} |
||||
|
|
||||
|
this.client = this.pool |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Fetches the tables from the postgres table and assigns them to the datasource. |
||||
|
* @param {*} datasourceId - datasourceId to fetch |
||||
|
*/ |
||||
|
async buildSchema(datasourceId: string) { |
||||
|
let tableKeys: { [key: string]: string[] } = {} |
||||
|
try { |
||||
|
const primaryKeysResponse = await this.client.query( |
||||
|
this.PRIMARY_KEYS_SQL |
||||
|
) |
||||
|
for (let table of primaryKeysResponse.rows) { |
||||
|
const tableName = table.table_name |
||||
|
if (!tableKeys[tableName]) { |
||||
|
tableKeys[tableName] = [] |
||||
|
} |
||||
|
tableKeys[tableName].push(table.column_name || table.primary_key) |
||||
|
} |
||||
|
} catch (err) { |
||||
|
tableKeys = {} |
||||
|
} |
||||
|
|
||||
|
const columnsResponse = await this.client.query(this.COLUMNS_SQL) |
||||
|
const tables: { [key: string]: Table } = {} |
||||
|
|
||||
|
for (let column of columnsResponse.rows) { |
||||
|
const tableName: string = column.table_name |
||||
|
const columnName: string = column.column_name |
||||
|
|
||||
|
// table key doesn't exist yet
|
||||
|
if (!tables[tableName] || !tables[tableName].schema) { |
||||
|
tables[tableName] = { |
||||
|
_id: buildExternalTableId(datasourceId, tableName), |
||||
|
primary: tableKeys[tableName] || ["id"], |
||||
|
name: tableName, |
||||
|
schema: {}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const type: string = convertType(column.data_type, TYPE_MAP) |
||||
|
tables[tableName].schema[columnName] = { |
||||
|
name: columnName, |
||||
|
type, |
||||
|
} |
||||
|
} |
||||
|
this.tables = tables |
||||
|
} |
||||
|
|
||||
|
async create(query: SqlQuery | string) { |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.rows.length ? response.rows : [{ created: true }] |
||||
|
} |
||||
|
|
||||
|
async read(query: SqlQuery | string) { |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.rows |
||||
|
} |
||||
|
|
||||
|
async update(query: SqlQuery | string) { |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.rows.length ? response.rows : [{ updated: true }] |
||||
|
} |
||||
|
|
||||
|
async delete(query: SqlQuery | string) { |
||||
|
const response = await internalQuery(this.client, getSqlQuery(query)) |
||||
|
return response.rows.length ? response.rows : [{ deleted: true }] |
||||
|
} |
||||
|
|
||||
|
async query(json: QueryJson) { |
||||
|
const operation = this._operation(json).toLowerCase() |
||||
|
const input = this._query(json) |
||||
|
const response = await internalQuery(this.client, input) |
||||
|
return response.rows.length ? response.rows : [{ [operation]: true }] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: PostgresIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,178 +0,0 @@ |
|||||
const fetch = require("node-fetch") |
|
||||
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://github.com/node-fetch/node-fetch", |
|
||||
description: |
|
||||
"Representational state transfer (REST) is a de-facto standard for a software architecture for interactive applications that typically use multiple Web services. ", |
|
||||
friendlyName: "REST API", |
|
||||
datasource: { |
|
||||
url: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
default: "localhost", |
|
||||
required: true, |
|
||||
}, |
|
||||
defaultHeaders: { |
|
||||
type: FIELD_TYPES.OBJECT, |
|
||||
required: false, |
|
||||
default: {}, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
create: { |
|
||||
readable: true, |
|
||||
displayName: "POST", |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
urlDisplay: true, |
|
||||
fields: { |
|
||||
path: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
queryString: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
headers: { |
|
||||
type: FIELD_TYPES.OBJECT, |
|
||||
}, |
|
||||
requestBody: { |
|
||||
type: FIELD_TYPES.JSON, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
read: { |
|
||||
displayName: "GET", |
|
||||
readable: true, |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
urlDisplay: true, |
|
||||
fields: { |
|
||||
path: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
queryString: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
headers: { |
|
||||
type: FIELD_TYPES.OBJECT, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
update: { |
|
||||
displayName: "PUT", |
|
||||
readable: true, |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
urlDisplay: true, |
|
||||
fields: { |
|
||||
path: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
queryString: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
headers: { |
|
||||
type: FIELD_TYPES.OBJECT, |
|
||||
}, |
|
||||
requestBody: { |
|
||||
type: FIELD_TYPES.JSON, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
delete: { |
|
||||
displayName: "DELETE", |
|
||||
type: QUERY_TYPES.FIELDS, |
|
||||
urlDisplay: true, |
|
||||
fields: { |
|
||||
path: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
queryString: { |
|
||||
type: FIELD_TYPES.STRING, |
|
||||
}, |
|
||||
headers: { |
|
||||
type: FIELD_TYPES.OBJECT, |
|
||||
}, |
|
||||
requestBody: { |
|
||||
type: FIELD_TYPES.JSON, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class RestIntegration { |
|
||||
constructor(config) { |
|
||||
this.config = config |
|
||||
} |
|
||||
|
|
||||
async parseResponse(response) { |
|
||||
switch (this.headers.Accept) { |
|
||||
case "application/json": |
|
||||
return await response.json() |
|
||||
case "text/html": |
|
||||
return await response.text() |
|
||||
default: |
|
||||
return await response.json() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async create({ path = "", queryString = "", headers = {}, json }) { |
|
||||
this.headers = { |
|
||||
...this.config.defaultHeaders, |
|
||||
...headers, |
|
||||
} |
|
||||
|
|
||||
const response = await fetch(this.config.url + path + queryString, { |
|
||||
method: "POST", |
|
||||
headers: this.headers, |
|
||||
body: JSON.stringify(json), |
|
||||
}) |
|
||||
|
|
||||
return await this.parseResponse(response) |
|
||||
} |
|
||||
|
|
||||
async read({ path = "", queryString = "", headers = {} }) { |
|
||||
this.headers = { |
|
||||
...this.config.defaultHeaders, |
|
||||
...headers, |
|
||||
} |
|
||||
|
|
||||
const response = await fetch(this.config.url + path + queryString, { |
|
||||
headers: this.headers, |
|
||||
}) |
|
||||
|
|
||||
return await this.parseResponse(response) |
|
||||
} |
|
||||
|
|
||||
async update({ path = "", queryString = "", headers = {}, json }) { |
|
||||
this.headers = { |
|
||||
...this.config.defaultHeaders, |
|
||||
...headers, |
|
||||
} |
|
||||
|
|
||||
const response = await fetch(this.config.url + path + queryString, { |
|
||||
method: "POST", |
|
||||
headers: this.headers, |
|
||||
body: JSON.stringify(json), |
|
||||
}) |
|
||||
|
|
||||
return await this.parseResponse(response) |
|
||||
} |
|
||||
|
|
||||
async delete({ path = "", queryString = "", headers = {} }) { |
|
||||
this.headers = { |
|
||||
...this.config.defaultHeaders, |
|
||||
...headers, |
|
||||
} |
|
||||
|
|
||||
const response = await fetch(this.config.url + path + queryString, { |
|
||||
method: "DELETE", |
|
||||
headers: this.headers, |
|
||||
}) |
|
||||
|
|
||||
return await this.parseResponse(response) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: RestIntegration, |
|
||||
} |
|
||||
@ -0,0 +1,197 @@ |
|||||
|
import { |
||||
|
Integration, |
||||
|
DatasourceFieldTypes, |
||||
|
QueryTypes, |
||||
|
} from "./base/definitions" |
||||
|
|
||||
|
module RestModule { |
||||
|
const fetch = require("node-fetch") |
||||
|
|
||||
|
interface RestConfig { |
||||
|
url: string |
||||
|
defaultHeaders: { |
||||
|
[key: string]: any |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://github.com/node-fetch/node-fetch", |
||||
|
description: |
||||
|
"Representational state transfer (REST) is a de-facto standard for a software architecture for interactive applications that typically use multiple Web services. ", |
||||
|
friendlyName: "REST API", |
||||
|
datasource: { |
||||
|
url: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
default: "localhost", |
||||
|
required: true, |
||||
|
}, |
||||
|
defaultHeaders: { |
||||
|
type: DatasourceFieldTypes.OBJECT, |
||||
|
required: false, |
||||
|
default: {}, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
create: { |
||||
|
readable: true, |
||||
|
displayName: "POST", |
||||
|
type: QueryTypes.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: DatasourceFieldTypes.OBJECT, |
||||
|
}, |
||||
|
requestBody: { |
||||
|
type: DatasourceFieldTypes.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
read: { |
||||
|
displayName: "GET", |
||||
|
readable: true, |
||||
|
type: QueryTypes.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: DatasourceFieldTypes.OBJECT, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
update: { |
||||
|
displayName: "PUT", |
||||
|
readable: true, |
||||
|
type: QueryTypes.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: DatasourceFieldTypes.OBJECT, |
||||
|
}, |
||||
|
requestBody: { |
||||
|
type: DatasourceFieldTypes.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
delete: { |
||||
|
displayName: "DELETE", |
||||
|
type: QueryTypes.FIELDS, |
||||
|
urlDisplay: true, |
||||
|
fields: { |
||||
|
path: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
queryString: { |
||||
|
type: DatasourceFieldTypes.STRING, |
||||
|
}, |
||||
|
headers: { |
||||
|
type: DatasourceFieldTypes.OBJECT, |
||||
|
}, |
||||
|
requestBody: { |
||||
|
type: DatasourceFieldTypes.JSON, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class RestIntegration { |
||||
|
private config: RestConfig |
||||
|
private headers: { |
||||
|
[key: string]: string |
||||
|
} = {} |
||||
|
|
||||
|
constructor(config: RestConfig) { |
||||
|
this.config = config |
||||
|
} |
||||
|
|
||||
|
async parseResponse(response: any) { |
||||
|
switch (this.headers.Accept) { |
||||
|
case "application/json": |
||||
|
return await response.json() |
||||
|
case "text/html": |
||||
|
return await response.text() |
||||
|
default: |
||||
|
return await response.json() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async create({ path = "", queryString = "", headers = {}, json = {} }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
method: "POST", |
||||
|
headers: this.headers, |
||||
|
body: JSON.stringify(json), |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
|
||||
|
async read({ path = "", queryString = "", headers = {} }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
headers: this.headers, |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
|
||||
|
async update({ path = "", queryString = "", headers = {}, json = {} }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
method: "POST", |
||||
|
headers: this.headers, |
||||
|
body: JSON.stringify(json), |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
|
||||
|
async delete({ path = "", queryString = "", headers = {} }) { |
||||
|
this.headers = { |
||||
|
...this.config.defaultHeaders, |
||||
|
...headers, |
||||
|
} |
||||
|
|
||||
|
const response = await fetch(this.config.url + path + queryString, { |
||||
|
method: "DELETE", |
||||
|
headers: this.headers, |
||||
|
}) |
||||
|
|
||||
|
return await this.parseResponse(response) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: RestIntegration, |
||||
|
} |
||||
|
} |
||||
@ -1,60 +0,0 @@ |
|||||
const AWS = require("aws-sdk") |
|
||||
|
|
||||
const SCHEMA = { |
|
||||
docs: "https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html", |
|
||||
description: |
|
||||
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.", |
|
||||
friendlyName: "Amazon S3", |
|
||||
datasource: { |
|
||||
region: { |
|
||||
type: "string", |
|
||||
required: true, |
|
||||
default: "us-east-1", |
|
||||
}, |
|
||||
accessKeyId: { |
|
||||
type: "password", |
|
||||
required: true, |
|
||||
}, |
|
||||
secretAccessKey: { |
|
||||
type: "password", |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
query: { |
|
||||
read: { |
|
||||
type: "fields", |
|
||||
fields: { |
|
||||
bucket: { |
|
||||
type: "string", |
|
||||
required: true, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
class S3Integration { |
|
||||
constructor(config) { |
|
||||
this.config = config |
|
||||
this.connect() |
|
||||
this.client = new AWS.S3() |
|
||||
} |
|
||||
|
|
||||
async connect() { |
|
||||
AWS.config.update(this.config) |
|
||||
} |
|
||||
|
|
||||
async read(query) { |
|
||||
const response = await this.client |
|
||||
.listObjects({ |
|
||||
Bucket: query.bucket, |
|
||||
}) |
|
||||
.promise() |
|
||||
return response.Contents |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = { |
|
||||
schema: SCHEMA, |
|
||||
integration: S3Integration, |
|
||||
} |
|
||||
@ -0,0 +1,74 @@ |
|||||
|
import { Integration, QueryTypes } from "./base/definitions" |
||||
|
|
||||
|
module S3Module { |
||||
|
const AWS = require("aws-sdk") |
||||
|
|
||||
|
interface S3Config { |
||||
|
region: string |
||||
|
accessKeyId: string |
||||
|
secretAccessKey: string |
||||
|
} |
||||
|
|
||||
|
const SCHEMA: Integration = { |
||||
|
docs: "https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html", |
||||
|
description: |
||||
|
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.", |
||||
|
friendlyName: "Amazon S3", |
||||
|
datasource: { |
||||
|
region: { |
||||
|
type: "string", |
||||
|
required: true, |
||||
|
default: "us-east-1", |
||||
|
}, |
||||
|
accessKeyId: { |
||||
|
type: "password", |
||||
|
required: true, |
||||
|
}, |
||||
|
secretAccessKey: { |
||||
|
type: "password", |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
query: { |
||||
|
read: { |
||||
|
type: QueryTypes.FIELDS, |
||||
|
fields: { |
||||
|
bucket: { |
||||
|
type: "string", |
||||
|
required: true, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
class S3Integration { |
||||
|
private readonly config: S3Config |
||||
|
private client: any |
||||
|
private connectionPromise: Promise<any> |
||||
|
|
||||
|
constructor(config: S3Config) { |
||||
|
this.config = config |
||||
|
this.connectionPromise = this.connect() |
||||
|
this.client = new AWS.S3() |
||||
|
} |
||||
|
|
||||
|
async connect() { |
||||
|
AWS.config.update(this.config) |
||||
|
} |
||||
|
|
||||
|
async read(query: { bucket: string }) { |
||||
|
const response = await this.client |
||||
|
.listObjects({ |
||||
|
Bucket: query.bucket, |
||||
|
}) |
||||
|
.promise() |
||||
|
return response.Contents |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
schema: SCHEMA, |
||||
|
integration: S3Integration, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"target": "es5", |
||||
|
"module": "commonjs", |
||||
|
"lib": ["es6"], |
||||
|
"allowJs": true, |
||||
|
"outDir": "dist", |
||||
|
"strict": true, |
||||
|
"noImplicitAny": true, |
||||
|
"esModuleInterop": true, |
||||
|
"resolveJsonModule": true |
||||
|
}, |
||||
|
"include": [ |
||||
|
"./src/**/*" |
||||
|
], |
||||
|
"exclude": [ |
||||
|
"node_modules", |
||||
|
"**/*.json", |
||||
|
"**/*.spec.ts", |
||||
|
"**/*.spec.js" |
||||
|
] |
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue