diff --git a/packages/builder/src/components/backend/DatasourceNavigator/icons/Firebase.svelte b/packages/builder/src/components/backend/DatasourceNavigator/icons/Firebase.svelte new file mode 100644 index 0000000000..3a776a9217 --- /dev/null +++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/Firebase.svelte @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg b/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg new file mode 100644 index 0000000000..dc569606ad --- /dev/null +++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js b/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js index 350fccf73f..515f20a93b 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js +++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js @@ -12,6 +12,7 @@ import Rest from "./Rest.svelte" import Budibase from "./Budibase.svelte" import Oracle from "./Oracle.svelte" import GoogleSheets from "./GoogleSheets.svelte" +import Firebase from "./Firebase.svelte" export default { BUDIBASE: Budibase, @@ -28,4 +29,5 @@ export default { REST: Rest, ORACLE: Oracle, GOOGLE_SHEETS: GoogleSheets, + FIREBASE: Firebase, } diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 4d6a7e3884..9a623241ca 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -178,6 +178,7 @@ export const IntegrationTypes = { ORACLE: "ORACLE", INTERNAL: "INTERNAL", GOOGLE_SHEETS: "GOOGLE_SHEETS", + FIREBASE: "FIREBASE", } export const IntegrationNames = { @@ -195,6 +196,7 @@ export const IntegrationNames = { [IntegrationTypes.ORACLE]: "Oracle", [IntegrationTypes.INTERNAL]: "Internal", [IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets", + [IntegrationTypes.FIREBASE]: "Firebase", } export const SchemaTypeOptions = [ diff --git a/packages/server/package.json b/packages/server/package.json index 30e21a7170..b80b99ab69 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -77,6 +77,7 @@ "@bull-board/api": "^3.7.0", "@bull-board/koa": "^3.7.0", "@elastic/elasticsearch": "7.10.0", + "@google-cloud/firestore": "^5.0.2", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", "@sentry/node": "^6.0.0", diff --git a/packages/server/src/api/routes/public/index.ts b/packages/server/src/api/routes/public/index.ts index 800eae6101..63a6946606 100644 --- a/packages/server/src/api/routes/public/index.ts +++ b/packages/server/src/api/routes/public/index.ts @@ -29,7 +29,7 @@ function getApiLimitPerSecond(): number { return parseInt(env.API_REQ_LIMIT_PER_SEC) } -if (!env.isTest()) { +/*if (!env.isTest()) { const REDIS_OPTS = getRedisOptions() RateLimit.defaultOptions({ store: new Stores.Redis({ @@ -42,7 +42,7 @@ if (!env.isTest()) { database: 1, }), }) -} +}*/ // rate limiting, allows for 2 requests per second const limiter = RateLimit.middleware({ interval: { sec: 1 }, diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index 6c8c8dc07d..208b2dc0a3 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -48,6 +48,7 @@ export enum SourceNames { REST = "REST", ORACLE = "ORACLE", GOOGLE_SHEETS = "GOOGLE_SHEETS", + FIREBASE = "FIREBASE", } export enum IncludeRelationships { diff --git a/packages/server/src/integrations/firebase.ts b/packages/server/src/integrations/firebase.ts new file mode 100644 index 0000000000..8bcf198b7d --- /dev/null +++ b/packages/server/src/integrations/firebase.ts @@ -0,0 +1,161 @@ +import { + DatasourceFieldTypes, + Integration, + QueryTypes, +} from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" +import { Firestore, WhereFilterOp } from "@google-cloud/firestore" + +module Firebase { + interface FirebaseConfig { + email: string + privateKey: string + projectId: string + } + + const SCHEMA: Integration = { + docs: "https://firebase.google.com/docs/firestore/quickstart", + friendlyName: "Firestore", + description: + "Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.", + datasource: { + email: { + type: DatasourceFieldTypes.STRING, + required: true, + }, + privateKey: { + type: DatasourceFieldTypes.STRING, + required: true, + }, + projectId: { + type: DatasourceFieldTypes.STRING, + required: true, + }, + }, + query: { + create: { + type: QueryTypes.JSON, + }, + read: { + type: QueryTypes.JSON, + }, + update: { + type: QueryTypes.JSON, + }, + delete: { + type: QueryTypes.JSON, + }, + }, + extra: { + collection: { + displayName: "Collection", + type: DatasourceFieldTypes.STRING, + required: true, + }, + filter: { + displayName: "Filter query", + type: DatasourceFieldTypes.LIST, + required: false, + data: { + read: [ + "==", + "<", + "<=", + "==", + "!=", + ">=", + ">", + "array-contains", + "in", + "not-in", + "array-contains-any", + ], + }, + }, + queryValue: { + displayName: "Query value", + type: DatasourceFieldTypes.STRING, + required: false, + }, + }, + } + + class FirebaseIntegration implements IntegrationBase { + private config: FirebaseConfig + private db: Firestore + + constructor(config: FirebaseConfig) { + this.config = config + this.db = new Firestore({ + projectId: config.projectId, + credential: { + clientEmail: config.email, + privateKey: config.privateKey, + }, + }) + } + + async create(query: { json: object; extra: { [key: string]: string } }) { + try { + return await this.db.collection(query.extra.collection).add(query.json) + } catch (err) { + console.error("Error writing to Firestore", err) + throw err + } + } + + async read(query: { + field: string + opStr: WhereFilterOp + value: any + extra: { [key: string]: string } + }) { + try { + const snapshot = await this.db + .collection(query.extra.collection) + .where(query.field, query.opStr, query.value) + .get() + const result: any[] = [] + snapshot.forEach(doc => result.push(doc.data())) + + return result + } catch (err) { + console.error("Error querying Firestore", err) + throw err + } + } + + async update(query: { + id: string + json: object + extra: { [key: string]: string } + }) { + try { + return await this.db + .collection(query.extra.collection) + .doc(query.id) + .update(query.json) + } catch (err) { + console.error("Error writing to mongodb", err) + throw err + } + } + + async delete(query: { id: string; extra: { [key: string]: string } }) { + try { + return await this.db + .collection(query.extra.collection) + .doc(query.id) + .delete() + } catch (err) { + console.error("Error writing to mongodb", err) + throw err + } + } + } + + module.exports = { + schema: SCHEMA, + integration: FirebaseIntegration, + } +} diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts index 00b00c25fb..07f3211fcb 100644 --- a/packages/server/src/integrations/index.ts +++ b/packages/server/src/integrations/index.ts @@ -10,6 +10,7 @@ const mysql = require("./mysql") const arangodb = require("./arangodb") const rest = require("./rest") const googlesheets = require("./googlesheets") +const firebase = require("./firebase") const { SourceNames } = require("../definitions/datasource") const environment = require("../environment") @@ -25,6 +26,7 @@ const DEFINITIONS = { [SourceNames.MYSQL]: mysql.schema, [SourceNames.ARANGODB]: arangodb.schema, [SourceNames.REST]: rest.schema, + [SourceNames.FIREBASE]: firebase.schema, } const INTEGRATIONS = { @@ -39,6 +41,7 @@ const INTEGRATIONS = { [SourceNames.MYSQL]: mysql.integration, [SourceNames.ARANGODB]: arangodb.integration, [SourceNames.REST]: rest.integration, + [SourceNames.FIREBASE]: firebase.integration, } // optionally add oracle integration if the oracle binary can be installed