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..583b90f5c3
--- /dev/null
+++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg
@@ -0,0 +1,67 @@
+
+
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 484ffef85f..df5b4262eb 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/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts
index 2e2ad25f58..88115237a0 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..503dae5c95
--- /dev/null
+++ b/packages/server/src/integrations/firebase.ts
@@ -0,0 +1,205 @@
+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
+ serviceAccount?: 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,
+ },
+ serviceAccount: {
+ type: DatasourceFieldTypes.JSON,
+ required: false,
+ },
+ },
+ 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,
+ },
+ filterField: {
+ displayName: "Filter field",
+ type: DatasourceFieldTypes.STRING,
+ required: false,
+ },
+ filter: {
+ displayName: "Filter comparison",
+ type: DatasourceFieldTypes.LIST,
+ required: false,
+ data: {
+ read: [
+ "==",
+ "<",
+ "<=",
+ "==",
+ "!=",
+ ">=",
+ ">",
+ "array-contains",
+ "in",
+ "not-in",
+ "array-contains-any",
+ ],
+ },
+ },
+ filterValue: {
+ displayName: "Filter value",
+ type: DatasourceFieldTypes.STRING,
+ required: false,
+ },
+ },
+ }
+
+ class FirebaseIntegration implements IntegrationBase {
+ private config: FirebaseConfig
+ private db: Firestore
+
+ constructor(config: FirebaseConfig) {
+ this.config = config
+ if (config.serviceAccount) {
+ const serviceAccount = JSON.parse(config.serviceAccount)
+ this.db = new Firestore({
+ projectId: serviceAccount.project_id,
+ credentials: {
+ client_email: serviceAccount.client_email,
+ private_key: serviceAccount.private_key,
+ },
+ })
+ } else {
+ this.db = new Firestore({
+ projectId: config.projectId,
+ credentials: {
+ client_email: config.email,
+ private_key: config.privateKey,
+ },
+ })
+ }
+ }
+
+ async create(query: { json: object; extra: { [key: string]: string } }) {
+ try {
+ const documentReference = this.db
+ .collection(query.extra.collection)
+ .doc()
+ await documentReference.set({ ...query.json, id: documentReference.id })
+ const snapshot = await documentReference.get()
+ return snapshot.data()
+ } catch (err) {
+ console.error("Error writing to Firestore", err)
+ throw err
+ }
+ }
+
+ async read(query: { json: object; extra: { [key: string]: string } }) {
+ try {
+ let snapshot
+ const collectionRef = this.db.collection(query.extra.collection)
+ if (
+ query.extra.filterField &&
+ query.extra.filter &&
+ query.extra.filterValue
+ ) {
+ snapshot = await collectionRef
+ .where(
+ query.extra.filterField,
+ query.extra.filter as WhereFilterOp,
+ query.extra.filterValue
+ )
+ .get()
+ } else {
+ snapshot = await collectionRef.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: {
+ json: Record
+ extra: { [key: string]: string }
+ }) {
+ try {
+ await this.db
+ .collection(query.extra.collection)
+ .doc(query.json.id)
+ .update(query.json)
+
+ return (
+ await this.db
+ .collection(query.extra.collection)
+ .doc(query.json.id)
+ .get()
+ ).data()
+ } catch (err) {
+ console.error("Error writing to firebase", err)
+ throw err
+ }
+ }
+
+ async delete(query: {
+ json: { id: string }
+ extra: { [key: string]: string }
+ }) {
+ try {
+ await this.db
+ .collection(query.extra.collection)
+ .doc(query.json.id)
+ .delete()
+ return true
+ } 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